pycodemath 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pycodemath/__init__.py +91 -0
- pycodemath/__main__.py +10 -0
- pycodemath/bench/__init__.py +0 -0
- pycodemath/bench/benchmark.py +61 -0
- pycodemath/cli/__init__.py +0 -0
- pycodemath/cli/mcp_server.py +82 -0
- pycodemath/cli/repl.py +620 -0
- pycodemath/codegen/__init__.py +0 -0
- pycodemath/codegen/emit.py +554 -0
- pycodemath/codegen/pipeline.py +139 -0
- pycodemath/core/__init__.py +0 -0
- pycodemath/core/errors.py +15 -0
- pycodemath/core/ir.py +307 -0
- pycodemath/engine/__init__.py +0 -0
- pycodemath/engine/linalg.py +197 -0
- pycodemath/engine/numerics.py +973 -0
- pycodemath/engine/ode.py +1279 -0
- pycodemath/engine/symbolic.py +204 -0
- pycodemath/frontend/__init__.py +0 -0
- pycodemath/frontend/parser.py +299 -0
- pycodemath-0.2.0.dist-info/METADATA +327 -0
- pycodemath-0.2.0.dist-info/RECORD +26 -0
- pycodemath-0.2.0.dist-info/WHEEL +5 -0
- pycodemath-0.2.0.dist-info/entry_points.txt +3 -0
- pycodemath-0.2.0.dist-info/licenses/LICENSE +21 -0
- pycodemath-0.2.0.dist-info/top_level.txt +1 -0
pycodemath/__init__.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Pycodemath — heavy computational math + Python.
|
|
2
|
+
|
|
3
|
+
Top-level public API.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from .codegen.pipeline import (
|
|
9
|
+
CodeArtifact,
|
|
10
|
+
generate,
|
|
11
|
+
generate_ode,
|
|
12
|
+
generate_ode_adaptive,
|
|
13
|
+
generate_ode_dense,
|
|
14
|
+
generate_system,
|
|
15
|
+
)
|
|
16
|
+
from .core.ir import E, Expr, M, Matrix, V, symbols
|
|
17
|
+
from .engine import linalg, numerics, ode, symbolic
|
|
18
|
+
from .engine.numerics import (
|
|
19
|
+
gradient,
|
|
20
|
+
hessian,
|
|
21
|
+
jacobian,
|
|
22
|
+
minimize_nd,
|
|
23
|
+
root_find_nd,
|
|
24
|
+
)
|
|
25
|
+
from .engine.ode import (
|
|
26
|
+
DenseSolution,
|
|
27
|
+
DenseSystemSolution,
|
|
28
|
+
EventSolution,
|
|
29
|
+
EventSystemSolution,
|
|
30
|
+
dsolve,
|
|
31
|
+
solve_ode_adaptive,
|
|
32
|
+
solve_ode_dense,
|
|
33
|
+
solve_ode_events,
|
|
34
|
+
solve_ode_num,
|
|
35
|
+
solve_ode_stiff,
|
|
36
|
+
solve_ode_stiff_adaptive,
|
|
37
|
+
solve_ode_system_dense,
|
|
38
|
+
solve_ode_system_events,
|
|
39
|
+
solve_ode_system_num,
|
|
40
|
+
solve_ode_system_stiff,
|
|
41
|
+
solve_ode_system_stiff_adaptive,
|
|
42
|
+
)
|
|
43
|
+
from .engine.symbolic import limit, series, summation
|
|
44
|
+
from .frontend.parser import parse
|
|
45
|
+
|
|
46
|
+
__version__ = "0.2.0"
|
|
47
|
+
|
|
48
|
+
__all__ = [
|
|
49
|
+
"E",
|
|
50
|
+
"Expr",
|
|
51
|
+
"M",
|
|
52
|
+
"V",
|
|
53
|
+
"Matrix",
|
|
54
|
+
"symbols",
|
|
55
|
+
"parse",
|
|
56
|
+
"symbolic",
|
|
57
|
+
"linalg",
|
|
58
|
+
"numerics",
|
|
59
|
+
"ode",
|
|
60
|
+
"generate",
|
|
61
|
+
"generate_system",
|
|
62
|
+
"generate_ode",
|
|
63
|
+
"generate_ode_adaptive",
|
|
64
|
+
"generate_ode_dense",
|
|
65
|
+
"CodeArtifact",
|
|
66
|
+
"limit",
|
|
67
|
+
"series",
|
|
68
|
+
"summation",
|
|
69
|
+
"gradient",
|
|
70
|
+
"jacobian",
|
|
71
|
+
"hessian",
|
|
72
|
+
"root_find_nd",
|
|
73
|
+
"minimize_nd",
|
|
74
|
+
"dsolve",
|
|
75
|
+
"solve_ode_num",
|
|
76
|
+
"solve_ode_stiff",
|
|
77
|
+
"solve_ode_stiff_adaptive",
|
|
78
|
+
"solve_ode_adaptive",
|
|
79
|
+
"solve_ode_dense",
|
|
80
|
+
"DenseSolution",
|
|
81
|
+
"solve_ode_events",
|
|
82
|
+
"EventSolution",
|
|
83
|
+
"solve_ode_system_num",
|
|
84
|
+
"solve_ode_system_stiff",
|
|
85
|
+
"solve_ode_system_stiff_adaptive",
|
|
86
|
+
"solve_ode_system_dense",
|
|
87
|
+
"DenseSystemSolution",
|
|
88
|
+
"solve_ode_system_events",
|
|
89
|
+
"EventSystemSolution",
|
|
90
|
+
"__version__",
|
|
91
|
+
]
|
pycodemath/__main__.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Entry point: ``python -m pycodemath`` — REPL or one-shot from arguments.
|
|
2
|
+
|
|
3
|
+
Forcing UTF-8 for the Windows console is done by ``run()`` (shared with the
|
|
4
|
+
``pycodemath`` console script).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .cli.repl import run
|
|
8
|
+
|
|
9
|
+
if __name__ == "__main__":
|
|
10
|
+
run()
|
|
File without changes
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Benchmark — proof of the thesis: code with CSE beats naive Python.
|
|
2
|
+
|
|
3
|
+
We take an expression with heavily repeating subexpressions, generate two
|
|
4
|
+
versions (with CSE and without), then measure time over a vectorized NumPy
|
|
5
|
+
array. The CSE version computes the repeats once — it should be measurably
|
|
6
|
+
faster.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import sys
|
|
12
|
+
import time
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
|
|
16
|
+
from ..codegen.pipeline import generate
|
|
17
|
+
from ..frontend.parser import parse
|
|
18
|
+
|
|
19
|
+
# Subexpression (sin(x)+cos(x))**? repeated many times — ideal for CSE.
|
|
20
|
+
DEMO = "(sin(x)+cos(x))**2 + (sin(x)+cos(x))**3 + (sin(x)+cos(x))**4 + exp(sin(x)+cos(x))"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _time(func, x, repeats: int = 200) -> float:
|
|
24
|
+
best = float("inf")
|
|
25
|
+
for _ in range(repeats):
|
|
26
|
+
t0 = time.perf_counter()
|
|
27
|
+
func(x)
|
|
28
|
+
best = min(best, time.perf_counter() - t0)
|
|
29
|
+
return best
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def main() -> None:
|
|
33
|
+
for stream in (sys.stdout, sys.stderr):
|
|
34
|
+
if hasattr(stream, "reconfigure"):
|
|
35
|
+
stream.reconfigure(encoding="utf-8")
|
|
36
|
+
expr = parse(DEMO)
|
|
37
|
+
naive = generate(expr, func_name="naive", simplify=False, use_cse=False)
|
|
38
|
+
optimized = generate(expr, func_name="opt", simplify=False, use_cse=True)
|
|
39
|
+
|
|
40
|
+
x = np.linspace(0.1, 10.0, 200_000)
|
|
41
|
+
|
|
42
|
+
# sanity: both give the same result
|
|
43
|
+
assert np.allclose(naive(x), optimized(x)), "results differ!"
|
|
44
|
+
|
|
45
|
+
t_naive = _time(naive.func, x)
|
|
46
|
+
t_opt = _time(optimized.func, x)
|
|
47
|
+
|
|
48
|
+
print("Demo expression:")
|
|
49
|
+
print(f" {DEMO}\n")
|
|
50
|
+
print("Naive code (without CSE):")
|
|
51
|
+
print(" " + naive.source.replace("\n", "\n ") + "\n")
|
|
52
|
+
print("Optimized code (CSE):")
|
|
53
|
+
print(" " + optimized.source.replace("\n", "\n ") + "\n")
|
|
54
|
+
print(f"naive time : {t_naive * 1e3:.3f} ms")
|
|
55
|
+
print(f"CSE time : {t_opt * 1e3:.3f} ms")
|
|
56
|
+
if t_opt > 0:
|
|
57
|
+
print(f"speedup : {t_naive / t_opt:.2f}x")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
if __name__ == "__main__":
|
|
61
|
+
main()
|
|
File without changes
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""MCP server — Pycodemath as a math tool for any agent.
|
|
2
|
+
|
|
3
|
+
Exposes a single ``math_eval`` tool delegating to the REPL dispatcher
|
|
4
|
+
(``repl.handle``): the agent sends a concise command, gets an exact result
|
|
5
|
+
from SymPy/NumPy — instead of computing "in its head" or writing code. This is the
|
|
6
|
+
path that realizes the project goal of "cutting tokens" outside Claude Code (skill) — any
|
|
7
|
+
MCP client can plug in Pycodemath.
|
|
8
|
+
|
|
9
|
+
Launch (stdio transport):
|
|
10
|
+
python -m pycodemath.cli.mcp_server # or the script: pycodemath-mcp
|
|
11
|
+
|
|
12
|
+
The ``mcp`` dependency is optional (extra ``pycodemath[mcp]``).
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from . import repl
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
from mcp.server.fastmcp import FastMCP
|
|
21
|
+
except ImportError: # optional dependency missing — refusal deferred to run()
|
|
22
|
+
# importing the module itself must not blow up with a traceback; a readable
|
|
23
|
+
# message goes to whoever actually tries to start the server
|
|
24
|
+
FastMCP = None # type: ignore[assignment, misc]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def math_eval(command: str) -> str:
|
|
28
|
+
"""Compute math exactly (SymPy/NumPy engine) — one command, one result.
|
|
29
|
+
|
|
30
|
+
Commands (power notation: ^ or **):
|
|
31
|
+
<expression> — simplify (e.g. sin(x)^2 + cos(x)^2)
|
|
32
|
+
diff <expr> d<var> — derivative (e.g. diff sin(x)*x dx)
|
|
33
|
+
integrate <expr> d<var> — symbolic integral
|
|
34
|
+
solve <expr> for <var> — solve expr = 0
|
|
35
|
+
code <expr> — optimized Python/NumPy code (CSE)
|
|
36
|
+
matrix <A> — show the matrix, e.g. matrix [[1,2],[3,4]]
|
|
37
|
+
det/inv/transpose/eig <A> — matrix, e.g. det [[1,2],[3,4]]
|
|
38
|
+
solve_system <A> = <b> — linear system A x = b
|
|
39
|
+
code_system <A> = <b> — NumPy code solving the system
|
|
40
|
+
root <expr> for <var> at <x0> — root f=0 numerically
|
|
41
|
+
nintegrate <expr> d<var> from <a> to <b> — numerical integration
|
|
42
|
+
min <expr> for <var> at <x0> [method newton|bfgs] — 1D minimum (gradient descent by default; newton/bfgs = 2nd order + Armijo)
|
|
43
|
+
grad <expr> for <x,y> — symbolic gradient
|
|
44
|
+
solve_nd <f1>; <f2> for <x,y> at <x0,y0> — nonlinear system (Newton)
|
|
45
|
+
min_nd <expr> for <x,y> at <x0,y0> [method newton|bfgs] — minimum of several variables (bfgs copes e.g. with the Rosenbrock valley)
|
|
46
|
+
dsolve <expr> for y(t) — ODE symbolically: y' = f(t,y)
|
|
47
|
+
ode <expr> for y(t) from <t0> to <t1> at <y0> [steps <n>] — ODE numerically (RK4)
|
|
48
|
+
odestiff <expr> for y(t) from <t0> to <t1> at <y0> [steps <n>] — STIFF ODE (implicit BDF2 + Newton; e.g. odestiff -1000*(y-cos(t)) for y(t) from 0 to 1 at 0)
|
|
49
|
+
odestiff_adaptive <expr> for y(t) from <t0> to <t1> at <y0> [rtol <r>] — STIFF ODE with adaptive step (adaptive BDF2; an order fewer steps than DOPRI5 on a stiff problem)
|
|
50
|
+
ode_adaptive <expr> for y(t) from <t0> to <t1> at <y0> [rtol <r>] — ODE, adaptive step (DOPRI5)
|
|
51
|
+
odedense <expr> for y(t) from <t0> to <t1> at <y0> at t=<point> [rtol <r>] — ODE, read y(t) at any point (dense output)
|
|
52
|
+
odeevents <expr> for y(t) from <t0> to <t1> at <y0> zero <g> [dir <+1|-1>] [rtol <r>] [stop] — ODE + moments g(t,y)=0 (event detection; stop = terminal event, stop integration)
|
|
53
|
+
|
|
54
|
+
Errors are returned as text "error: ..." (specific).
|
|
55
|
+
"""
|
|
56
|
+
try:
|
|
57
|
+
out = repl.handle(command)
|
|
58
|
+
except Exception as exc: # the agent gets text, not a traceback
|
|
59
|
+
return f"error: {exc}"
|
|
60
|
+
return out or "(empty result)"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
server: "FastMCP | None" = None
|
|
64
|
+
if FastMCP is not None:
|
|
65
|
+
server = FastMCP("pycodemath")
|
|
66
|
+
server.tool()(math_eval)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def run() -> None:
|
|
70
|
+
"""Run the server over stdio (the default transport for MCP clients)."""
|
|
71
|
+
if server is None:
|
|
72
|
+
# SystemExit with a string: message on stderr + code 1, no traceback
|
|
73
|
+
# (works the same for the pycodemath-mcp entry point and python -m)
|
|
74
|
+
raise SystemExit(
|
|
75
|
+
"error: the MCP server requires the 'mcp' package "
|
|
76
|
+
"(install: pip install pycodemath[mcp])."
|
|
77
|
+
)
|
|
78
|
+
server.run()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
if __name__ == "__main__":
|
|
82
|
+
run()
|