pycodemath 0.2.0__tar.gz → 0.3.0__tar.gz

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.
Files changed (58) hide show
  1. {pycodemath-0.2.0 → pycodemath-0.3.0}/PKG-INFO +69 -7
  2. {pycodemath-0.2.0 → pycodemath-0.3.0}/README.md +66 -5
  3. pycodemath-0.3.0/pycodemath/__init__.py +171 -0
  4. {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath/__main__.py +10 -10
  5. {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath/bench/benchmark.py +61 -61
  6. pycodemath-0.3.0/pycodemath/cli/mcp_server.py +416 -0
  7. pycodemath-0.3.0/pycodemath/cli/repl.py +994 -0
  8. {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath/codegen/emit.py +7 -7
  9. {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath/codegen/pipeline.py +139 -139
  10. pycodemath-0.3.0/pycodemath/core/budget.py +571 -0
  11. pycodemath-0.3.0/pycodemath/core/errors.py +337 -0
  12. {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath/core/ir.py +60 -14
  13. pycodemath-0.3.0/pycodemath/core/result.py +317 -0
  14. {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath/engine/linalg.py +23 -6
  15. pycodemath-0.3.0/pycodemath/engine/numerics.py +2370 -0
  16. {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath/engine/ode.py +61 -41
  17. {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath/engine/symbolic.py +99 -18
  18. {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath/frontend/parser.py +107 -12
  19. pycodemath-0.3.0/pycodemath/py.typed +0 -0
  20. {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath.egg-info/PKG-INFO +69 -7
  21. {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath.egg-info/SOURCES.txt +10 -1
  22. {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath.egg-info/requires.txt +1 -1
  23. {pycodemath-0.2.0 → pycodemath-0.3.0}/pyproject.toml +17 -2
  24. pycodemath-0.3.0/tests/test_budget.py +508 -0
  25. {pycodemath-0.2.0 → pycodemath-0.3.0}/tests/test_cli.py +271 -265
  26. {pycodemath-0.2.0 → pycodemath-0.3.0}/tests/test_codegen.py +51 -51
  27. {pycodemath-0.2.0 → pycodemath-0.3.0}/tests/test_core.py +76 -47
  28. {pycodemath-0.2.0 → pycodemath-0.3.0}/tests/test_coverage_r2.py +5 -5
  29. pycodemath-0.3.0/tests/test_errors.py +362 -0
  30. pycodemath-0.3.0/tests/test_linalg.py +53 -0
  31. pycodemath-0.3.0/tests/test_mcp.py +621 -0
  32. pycodemath-0.3.0/tests/test_numerics.py +858 -0
  33. {pycodemath-0.2.0 → pycodemath-0.3.0}/tests/test_ode.py +194 -194
  34. pycodemath-0.3.0/tests/test_parser.py +178 -0
  35. pycodemath-0.3.0/tests/test_quadrature.py +572 -0
  36. pycodemath-0.3.0/tests/test_result.py +919 -0
  37. pycodemath-0.3.0/tests/test_symbolic.py +147 -0
  38. pycodemath-0.3.0/tests/test_trailing_options.py +356 -0
  39. pycodemath-0.2.0/pycodemath/__init__.py +0 -91
  40. pycodemath-0.2.0/pycodemath/cli/mcp_server.py +0 -82
  41. pycodemath-0.2.0/pycodemath/cli/repl.py +0 -620
  42. pycodemath-0.2.0/pycodemath/core/errors.py +0 -15
  43. pycodemath-0.2.0/pycodemath/engine/numerics.py +0 -973
  44. pycodemath-0.2.0/tests/test_linalg.py +0 -53
  45. pycodemath-0.2.0/tests/test_numerics.py +0 -505
  46. pycodemath-0.2.0/tests/test_parser.py +0 -160
  47. pycodemath-0.2.0/tests/test_symbolic.py +0 -87
  48. {pycodemath-0.2.0 → pycodemath-0.3.0}/LICENSE +0 -0
  49. {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath/bench/__init__.py +0 -0
  50. {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath/cli/__init__.py +0 -0
  51. {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath/codegen/__init__.py +0 -0
  52. {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath/core/__init__.py +0 -0
  53. {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath/engine/__init__.py +0 -0
  54. {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath/frontend/__init__.py +0 -0
  55. {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath.egg-info/dependency_links.txt +0 -0
  56. {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath.egg-info/entry_points.txt +0 -0
  57. {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath.egg-info/top_level.txt +0 -0
  58. {pycodemath-0.2.0 → pycodemath-0.3.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pycodemath
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Token-efficient exact math for AI agents: symbolic engine, ODE suite, optimizer and an optimized-code generator (SymPy + NumPy).
5
5
  Author: cybersora
6
6
  License: MIT
@@ -11,6 +11,7 @@ Classifier: Programming Language :: Python :: 3.12
11
11
  Classifier: Intended Audience :: Developers
12
12
  Classifier: Intended Audience :: Science/Research
13
13
  Classifier: Topic :: Scientific/Engineering :: Mathematics
14
+ Classifier: Typing :: Typed
14
15
  Requires-Python: >=3.11
15
16
  Description-Content-Type: text/markdown
16
17
  License-File: LICENSE
@@ -21,7 +22,7 @@ Provides-Extra: dev
21
22
  Requires-Dist: pytest>=7; extra == "dev"
22
23
  Requires-Dist: mypy>=1.8; extra == "dev"
23
24
  Provides-Extra: mcp
24
- Requires-Dist: mcp>=1.0; extra == "mcp"
25
+ Requires-Dist: mcp<2.0,>=1.0; extra == "mcp"
25
26
  Dynamic: license-file
26
27
 
27
28
  <p align="center">
@@ -36,13 +37,18 @@ Dynamic: license-file
36
37
  </p>
37
38
 
38
39
  <p align="center">
40
+ <a href="https://pypi.org/project/pycodemath/"><img alt="PyPI" src="https://img.shields.io/pypi/v/pycodemath.svg?color=e11d33"></a>
39
41
  <a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-e11d33.svg"></a>
40
42
  <img alt="Python 3.11+" src="https://img.shields.io/badge/python-3.11%2B-3776ab.svg">
41
- <img alt="230 tests passing" src="https://img.shields.io/badge/tests-230%20passing-2ea043.svg">
43
+ <img alt="668 tests passing" src="https://img.shields.io/badge/tests-668%20passing-2ea043.svg">
42
44
  <img alt="mypy: clean" src="https://img.shields.io/badge/mypy-clean-2ea043.svg">
43
45
  <a href="https://github.com/cybersora9/pycodemath/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/cybersora9/pycodemath/actions/workflows/ci.yml/badge.svg"></a>
44
46
  </p>
45
47
 
48
+ <p align="center">
49
+ <img alt="pycodemath demo — one command in, one exact result out" src="assets/demo.svg" width="760">
50
+ </p>
51
+
46
52
  Write math in a few characters, get an exact answer or standalone, optimized
47
53
  Python/NumPy code back — instead of asking a language model to "do arithmetic
48
54
  in its head" or to hand-write numerical loops.
@@ -64,9 +70,14 @@ text → [parser] → IR (expression tree) → [engine] evaluate / simplify
64
70
  ## Install
65
71
 
66
72
  ```bash
67
- pip install -e . # core: sympy + numpy
68
- pip install -e .[mcp] # + MCP server for AI agents
69
- pip install -e .[dev] # + pytest
73
+ pip install pycodemath # core: sympy + numpy
74
+ pip install pycodemath[mcp] # + MCP server for AI agents
75
+ ```
76
+
77
+ From a clone, for development (editable install):
78
+
79
+ ```bash
80
+ pip install -e .[dev] # editable + pytest + mypy
70
81
  ```
71
82
 
72
83
  Requires Python ≥ 3.11.
@@ -308,6 +319,53 @@ ev = solve_ode_events(parse("cos(t)"), "t", 0.0, (0.0, 10.0), parse("y"), rtol=1
308
319
  ev.event_times # -> [3.141593, 6.283185, 9.424778] (π, 2π, 3π)
309
320
  ```
310
321
 
322
+ ## Structured results: the evidence behind the answer
323
+
324
+ Every solver in this package now has a `full_result=True` form that returns
325
+ evidence instead of a bare number — a frozen `SolveResult` (value,
326
+ iterations, residual, converged, status) for `root_find` / `root_find_nd` /
327
+ `minimize` / `minimize_nd`, and a `QuadratureResult` (adds `error_estimate`)
328
+ for `integrate_num`. Default calls are unchanged, bit-identical to before.
329
+
330
+ ```pycon
331
+ >>> from pycodemath import root_find, minimize, integrate_num, parse
332
+ >>> root_find(parse("x^2 - 2"), "x", 1.0, full_result=True)
333
+ SolveResult(value=1.4142135623746899, iterations=5, residual=4.510614104447086e-12, converged=True, status='converged')
334
+
335
+ >>> minimize(parse("-x^2"), "x", 1.0, full_result=True)
336
+ SolveResult(value=1085298990978.309, iterations=152, residual=2170597981956.618, converged=False, status='diverged')
337
+
338
+ >>> integrate_num(parse("sqrt(x)"), "x", 0.0, 1.0, tol=1e-10, full_result=True)
339
+ QuadratureResult(value=0.6666666666666469, error_estimate=2.4271240969151142e-11, evaluations=1005, refinements=201, converged=True, status='converged')
340
+ ```
341
+
342
+ `converged` is the only field worth branching on, and it is corroborated, not
343
+ just "the tolerance test fired": a vanishing gradient at a saddle or a
344
+ maximum reports `status='not_a_minimum'` rather than a false convergence.
345
+
346
+ The MCP server carries the same structure over the wire as `structuredContent`
347
+ (`text` / `solve` / `quadrature` / `error` fields), not just prose — a client
348
+ validates it against the declared schema instead of parsing text.
349
+
350
+ **Trailing options** put those same knobs on the REPL/one-shot grammar:
351
+
352
+ ```console
353
+ $ python -m pycodemath "min x^4 for x at 1 tol 1e-5"
354
+ 0.029229144526165384
355
+
356
+ $ python -m pycodemath "nintegrate sqrt(x) dx from 0 to 1 tol 1e-10"
357
+ 0.6666666666666469
358
+ ```
359
+
360
+ `tol <t>` (on `min` / `min_nd` / `nintegrate`), `max_iter <k>` (on `min` /
361
+ `min_nd`) and `budget <s>` (on the symbolic commands, bounding a call the
362
+ same way `pycodemath.time_budget(seconds)` does in Python) are `key value`
363
+ pairs at the end of a command, in any order.
364
+
365
+ A symbolic call that refuses says which of two things happened:
366
+ `NoClosedFormError` (the engine searched and found nothing — try numerically)
367
+ or `UnsupportedFormError` (no method exists for this shape at all).
368
+
311
369
  ## Design contracts
312
370
 
313
371
  - One IR (`Expr`/`Matrix`) shared by the engine and the generator.
@@ -320,7 +378,11 @@ ev.event_times # -> [3.141593, 6.283185, 9.424778] (π, 2π, 3π)
320
378
  access are rejected outright, and evaluation cost is bounded so a single
321
379
  expression (e.g. `9**9**9`) can't exhaust memory.
322
380
  - Tests measure real numbers first, then assert them with a margin —
323
- **230 tests**, all green, on Ubuntu and Windows (CI + mypy included).
381
+ **668 tests**, all green, on Ubuntu and Windows (CI + mypy included).
382
+ - Every failure is a specific `PycodemathError` subclass (`ParseError`,
383
+ `DomainError`, `DivergenceError`, `StagnationError`, `NonConvergenceError`,
384
+ `NoClosedFormError`, `UnsupportedFormError`, `TimeBudgetError`) — a caller
385
+ can catch the mathematical outcome, not string-match a message.
324
386
 
325
387
  ## License
326
388
 
@@ -10,13 +10,18 @@
10
10
  </p>
11
11
 
12
12
  <p align="center">
13
+ <a href="https://pypi.org/project/pycodemath/"><img alt="PyPI" src="https://img.shields.io/pypi/v/pycodemath.svg?color=e11d33"></a>
13
14
  <a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-e11d33.svg"></a>
14
15
  <img alt="Python 3.11+" src="https://img.shields.io/badge/python-3.11%2B-3776ab.svg">
15
- <img alt="230 tests passing" src="https://img.shields.io/badge/tests-230%20passing-2ea043.svg">
16
+ <img alt="668 tests passing" src="https://img.shields.io/badge/tests-668%20passing-2ea043.svg">
16
17
  <img alt="mypy: clean" src="https://img.shields.io/badge/mypy-clean-2ea043.svg">
17
18
  <a href="https://github.com/cybersora9/pycodemath/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/cybersora9/pycodemath/actions/workflows/ci.yml/badge.svg"></a>
18
19
  </p>
19
20
 
21
+ <p align="center">
22
+ <img alt="pycodemath demo — one command in, one exact result out" src="assets/demo.svg" width="760">
23
+ </p>
24
+
20
25
  Write math in a few characters, get an exact answer or standalone, optimized
21
26
  Python/NumPy code back — instead of asking a language model to "do arithmetic
22
27
  in its head" or to hand-write numerical loops.
@@ -38,9 +43,14 @@ text → [parser] → IR (expression tree) → [engine] evaluate / simplify
38
43
  ## Install
39
44
 
40
45
  ```bash
41
- pip install -e . # core: sympy + numpy
42
- pip install -e .[mcp] # + MCP server for AI agents
43
- pip install -e .[dev] # + pytest
46
+ pip install pycodemath # core: sympy + numpy
47
+ pip install pycodemath[mcp] # + MCP server for AI agents
48
+ ```
49
+
50
+ From a clone, for development (editable install):
51
+
52
+ ```bash
53
+ pip install -e .[dev] # editable + pytest + mypy
44
54
  ```
45
55
 
46
56
  Requires Python ≥ 3.11.
@@ -282,6 +292,53 @@ ev = solve_ode_events(parse("cos(t)"), "t", 0.0, (0.0, 10.0), parse("y"), rtol=1
282
292
  ev.event_times # -> [3.141593, 6.283185, 9.424778] (π, 2π, 3π)
283
293
  ```
284
294
 
295
+ ## Structured results: the evidence behind the answer
296
+
297
+ Every solver in this package now has a `full_result=True` form that returns
298
+ evidence instead of a bare number — a frozen `SolveResult` (value,
299
+ iterations, residual, converged, status) for `root_find` / `root_find_nd` /
300
+ `minimize` / `minimize_nd`, and a `QuadratureResult` (adds `error_estimate`)
301
+ for `integrate_num`. Default calls are unchanged, bit-identical to before.
302
+
303
+ ```pycon
304
+ >>> from pycodemath import root_find, minimize, integrate_num, parse
305
+ >>> root_find(parse("x^2 - 2"), "x", 1.0, full_result=True)
306
+ SolveResult(value=1.4142135623746899, iterations=5, residual=4.510614104447086e-12, converged=True, status='converged')
307
+
308
+ >>> minimize(parse("-x^2"), "x", 1.0, full_result=True)
309
+ SolveResult(value=1085298990978.309, iterations=152, residual=2170597981956.618, converged=False, status='diverged')
310
+
311
+ >>> integrate_num(parse("sqrt(x)"), "x", 0.0, 1.0, tol=1e-10, full_result=True)
312
+ QuadratureResult(value=0.6666666666666469, error_estimate=2.4271240969151142e-11, evaluations=1005, refinements=201, converged=True, status='converged')
313
+ ```
314
+
315
+ `converged` is the only field worth branching on, and it is corroborated, not
316
+ just "the tolerance test fired": a vanishing gradient at a saddle or a
317
+ maximum reports `status='not_a_minimum'` rather than a false convergence.
318
+
319
+ The MCP server carries the same structure over the wire as `structuredContent`
320
+ (`text` / `solve` / `quadrature` / `error` fields), not just prose — a client
321
+ validates it against the declared schema instead of parsing text.
322
+
323
+ **Trailing options** put those same knobs on the REPL/one-shot grammar:
324
+
325
+ ```console
326
+ $ python -m pycodemath "min x^4 for x at 1 tol 1e-5"
327
+ 0.029229144526165384
328
+
329
+ $ python -m pycodemath "nintegrate sqrt(x) dx from 0 to 1 tol 1e-10"
330
+ 0.6666666666666469
331
+ ```
332
+
333
+ `tol <t>` (on `min` / `min_nd` / `nintegrate`), `max_iter <k>` (on `min` /
334
+ `min_nd`) and `budget <s>` (on the symbolic commands, bounding a call the
335
+ same way `pycodemath.time_budget(seconds)` does in Python) are `key value`
336
+ pairs at the end of a command, in any order.
337
+
338
+ A symbolic call that refuses says which of two things happened:
339
+ `NoClosedFormError` (the engine searched and found nothing — try numerically)
340
+ or `UnsupportedFormError` (no method exists for this shape at all).
341
+
285
342
  ## Design contracts
286
343
 
287
344
  - One IR (`Expr`/`Matrix`) shared by the engine and the generator.
@@ -294,7 +351,11 @@ ev.event_times # -> [3.141593, 6.283185, 9.424778] (π, 2π, 3π)
294
351
  access are rejected outright, and evaluation cost is bounded so a single
295
352
  expression (e.g. `9**9**9`) can't exhaust memory.
296
353
  - Tests measure real numbers first, then assert them with a margin —
297
- **230 tests**, all green, on Ubuntu and Windows (CI + mypy included).
354
+ **668 tests**, all green, on Ubuntu and Windows (CI + mypy included).
355
+ - Every failure is a specific `PycodemathError` subclass (`ParseError`,
356
+ `DomainError`, `DivergenceError`, `StagnationError`, `NonConvergenceError`,
357
+ `NoClosedFormError`, `UnsupportedFormError`, `TimeBudgetError`) — a caller
358
+ can catch the mathematical outcome, not string-match a message.
298
359
 
299
360
  ## License
300
361
 
@@ -0,0 +1,171 @@
1
+ """Pycodemath — heavy computational math + Python.
2
+
3
+ Top-level public API. Everything a caller needs is importable from here:
4
+
5
+ ``parse`` / ``E`` / ``M`` / ``V``
6
+ text -> the single IR (``Expr`` / ``Matrix``) the engine and the code
7
+ generator share.
8
+ ``symbolic`` / ``linalg`` / ``numerics`` / ``ode``
9
+ the engines. The numerical entry points that carry structured results are
10
+ re-exported by name as well: ``root_find``, ``minimize``, ``root_find_nd``,
11
+ ``minimize_nd``, ``integrate_num``.
12
+ ``PycodemathError`` and its eight subclasses
13
+ what a failure IS, as a type — the thing to branch on instead of reading a
14
+ message. ``ParseError`` / ``DomainError`` are refusals of the INPUT;
15
+ ``DivergenceError`` / ``StagnationError`` / ``NonConvergenceError`` /
16
+ ``NotAMinimumError`` / ``BudgetExhaustedError`` are outcomes of a RUN; and
17
+ ``TimeBudgetError`` (module 9) is the run that had no outcome at all because
18
+ the wall clock ran out first.
19
+ ``time_budget`` / ``DEFAULT_TIME_BUDGET``
20
+ how long the symbolic engine may spend before it refuses. Every symbolic entry
21
+ point is bounded — ``integrate 1/(x^5+x+1) dx`` used to hang forever — and
22
+ ``with time_budget(seconds):`` is how a caller says in advance how long it is
23
+ willing to wait.
24
+ ``SolveResult`` / ``QuadratureResult`` / ``SolveStatus`` / ``SOLVE_STATUSES``
25
+ what a run LEARNED, returned by the calls above under ``full_result=True``.
26
+ ``converged`` is the only field to branch on — and since module 8 it answers
27
+ the question that was ASKED (a ROOT, a MINIMUM), not merely "did an exit test
28
+ fire". The package ships ``py.typed``,
29
+ so the overloads that make ``full_result`` change the return TYPE are visible
30
+ to an installed caller's type checker — no cast anywhere.
31
+
32
+ The README documents each of these with runnable examples.
33
+ """
34
+
35
+ from __future__ import annotations
36
+
37
+ from .codegen.pipeline import (
38
+ CodeArtifact,
39
+ generate,
40
+ generate_ode,
41
+ generate_ode_adaptive,
42
+ generate_ode_dense,
43
+ generate_system,
44
+ )
45
+ from .core.budget import DEFAULT_TIME_BUDGET, time_budget
46
+ from .core.errors import (
47
+ NUMERIC_ROUTES,
48
+ ROUTES,
49
+ BudgetExhaustedError,
50
+ DivergenceError,
51
+ DomainError,
52
+ NoClosedFormError,
53
+ NonConvergenceError,
54
+ NotAMinimumError,
55
+ ParseError,
56
+ PycodemathError,
57
+ StagnationError,
58
+ TimeBudgetError,
59
+ UnsupportedFormError,
60
+ )
61
+ from .core.ir import E, Expr, M, Matrix, V, symbols
62
+ from .core.result import (
63
+ SOLVE_STATUSES,
64
+ QuadratureResult,
65
+ SolveResult,
66
+ SolveStatus,
67
+ )
68
+ from .engine import linalg, numerics, ode, symbolic
69
+ from .engine.numerics import (
70
+ gradient,
71
+ hessian,
72
+ integrate_num,
73
+ jacobian,
74
+ minimize,
75
+ minimize_nd,
76
+ root_find,
77
+ root_find_nd,
78
+ )
79
+ from .engine.ode import (
80
+ DenseSolution,
81
+ DenseSystemSolution,
82
+ EventSolution,
83
+ EventSystemSolution,
84
+ dsolve,
85
+ solve_ode_adaptive,
86
+ solve_ode_dense,
87
+ solve_ode_events,
88
+ solve_ode_num,
89
+ solve_ode_stiff,
90
+ solve_ode_stiff_adaptive,
91
+ solve_ode_system_dense,
92
+ solve_ode_system_events,
93
+ solve_ode_system_num,
94
+ solve_ode_system_stiff,
95
+ solve_ode_system_stiff_adaptive,
96
+ )
97
+ from .engine.symbolic import limit, series, summation
98
+ from .frontend.parser import parse
99
+
100
+ __version__ = "0.3.0"
101
+
102
+ __all__ = [
103
+ "E",
104
+ "Expr",
105
+ "M",
106
+ "V",
107
+ "Matrix",
108
+ "symbols",
109
+ "parse",
110
+ "PycodemathError",
111
+ "ParseError",
112
+ "DomainError",
113
+ "DivergenceError",
114
+ "StagnationError",
115
+ "NonConvergenceError",
116
+ "NotAMinimumError",
117
+ "BudgetExhaustedError",
118
+ "TimeBudgetError",
119
+ "NoClosedFormError",
120
+ "UnsupportedFormError",
121
+ "ROUTES",
122
+ "NUMERIC_ROUTES",
123
+ "time_budget",
124
+ "DEFAULT_TIME_BUDGET",
125
+ "SolveResult",
126
+ "QuadratureResult",
127
+ "SolveStatus",
128
+ "SOLVE_STATUSES",
129
+ "symbolic",
130
+ "linalg",
131
+ "numerics",
132
+ "ode",
133
+ "generate",
134
+ "generate_system",
135
+ "generate_ode",
136
+ "generate_ode_adaptive",
137
+ "generate_ode_dense",
138
+ "CodeArtifact",
139
+ "limit",
140
+ "series",
141
+ "summation",
142
+ "gradient",
143
+ "jacobian",
144
+ "hessian",
145
+ # The five functions that RETURN the types just above. Before module 7 only the
146
+ # ``_nd`` pair was exported, so ``from pycodemath import SolveResult`` worked
147
+ # while ``from pycodemath import root_find`` — the very call that produces one —
148
+ # did not. Same module, same contract, same @overload: the split was an accident.
149
+ "root_find",
150
+ "minimize",
151
+ "root_find_nd",
152
+ "minimize_nd",
153
+ "integrate_num",
154
+ "dsolve",
155
+ "solve_ode_num",
156
+ "solve_ode_stiff",
157
+ "solve_ode_stiff_adaptive",
158
+ "solve_ode_adaptive",
159
+ "solve_ode_dense",
160
+ "DenseSolution",
161
+ "solve_ode_events",
162
+ "EventSolution",
163
+ "solve_ode_system_num",
164
+ "solve_ode_system_stiff",
165
+ "solve_ode_system_stiff_adaptive",
166
+ "solve_ode_system_dense",
167
+ "DenseSystemSolution",
168
+ "solve_ode_system_events",
169
+ "EventSystemSolution",
170
+ "__version__",
171
+ ]
@@ -1,10 +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()
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()
@@ -1,61 +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()
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()