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.
- {pycodemath-0.2.0 → pycodemath-0.3.0}/PKG-INFO +69 -7
- {pycodemath-0.2.0 → pycodemath-0.3.0}/README.md +66 -5
- pycodemath-0.3.0/pycodemath/__init__.py +171 -0
- {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath/__main__.py +10 -10
- {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath/bench/benchmark.py +61 -61
- pycodemath-0.3.0/pycodemath/cli/mcp_server.py +416 -0
- pycodemath-0.3.0/pycodemath/cli/repl.py +994 -0
- {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath/codegen/emit.py +7 -7
- {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath/codegen/pipeline.py +139 -139
- pycodemath-0.3.0/pycodemath/core/budget.py +571 -0
- pycodemath-0.3.0/pycodemath/core/errors.py +337 -0
- {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath/core/ir.py +60 -14
- pycodemath-0.3.0/pycodemath/core/result.py +317 -0
- {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath/engine/linalg.py +23 -6
- pycodemath-0.3.0/pycodemath/engine/numerics.py +2370 -0
- {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath/engine/ode.py +61 -41
- {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath/engine/symbolic.py +99 -18
- {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath/frontend/parser.py +107 -12
- pycodemath-0.3.0/pycodemath/py.typed +0 -0
- {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath.egg-info/PKG-INFO +69 -7
- {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath.egg-info/SOURCES.txt +10 -1
- {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath.egg-info/requires.txt +1 -1
- {pycodemath-0.2.0 → pycodemath-0.3.0}/pyproject.toml +17 -2
- pycodemath-0.3.0/tests/test_budget.py +508 -0
- {pycodemath-0.2.0 → pycodemath-0.3.0}/tests/test_cli.py +271 -265
- {pycodemath-0.2.0 → pycodemath-0.3.0}/tests/test_codegen.py +51 -51
- {pycodemath-0.2.0 → pycodemath-0.3.0}/tests/test_core.py +76 -47
- {pycodemath-0.2.0 → pycodemath-0.3.0}/tests/test_coverage_r2.py +5 -5
- pycodemath-0.3.0/tests/test_errors.py +362 -0
- pycodemath-0.3.0/tests/test_linalg.py +53 -0
- pycodemath-0.3.0/tests/test_mcp.py +621 -0
- pycodemath-0.3.0/tests/test_numerics.py +858 -0
- {pycodemath-0.2.0 → pycodemath-0.3.0}/tests/test_ode.py +194 -194
- pycodemath-0.3.0/tests/test_parser.py +178 -0
- pycodemath-0.3.0/tests/test_quadrature.py +572 -0
- pycodemath-0.3.0/tests/test_result.py +919 -0
- pycodemath-0.3.0/tests/test_symbolic.py +147 -0
- pycodemath-0.3.0/tests/test_trailing_options.py +356 -0
- pycodemath-0.2.0/pycodemath/__init__.py +0 -91
- pycodemath-0.2.0/pycodemath/cli/mcp_server.py +0 -82
- pycodemath-0.2.0/pycodemath/cli/repl.py +0 -620
- pycodemath-0.2.0/pycodemath/core/errors.py +0 -15
- pycodemath-0.2.0/pycodemath/engine/numerics.py +0 -973
- pycodemath-0.2.0/tests/test_linalg.py +0 -53
- pycodemath-0.2.0/tests/test_numerics.py +0 -505
- pycodemath-0.2.0/tests/test_parser.py +0 -160
- pycodemath-0.2.0/tests/test_symbolic.py +0 -87
- {pycodemath-0.2.0 → pycodemath-0.3.0}/LICENSE +0 -0
- {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath/bench/__init__.py +0 -0
- {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath/cli/__init__.py +0 -0
- {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath/codegen/__init__.py +0 -0
- {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath/core/__init__.py +0 -0
- {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath/engine/__init__.py +0 -0
- {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath/frontend/__init__.py +0 -0
- {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath.egg-info/dependency_links.txt +0 -0
- {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath.egg-info/entry_points.txt +0 -0
- {pycodemath-0.2.0 → pycodemath-0.3.0}/pycodemath.egg-info/top_level.txt +0 -0
- {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.
|
|
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
|
|
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="
|
|
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
|
|
68
|
-
pip install
|
|
69
|
-
|
|
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
|
-
**
|
|
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="
|
|
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
|
|
42
|
-
pip install
|
|
43
|
-
|
|
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
|
-
**
|
|
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()
|