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 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()