scikit-verify 0.1.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.
- scikit_verify-0.1.0.dist-info/METADATA +145 -0
- scikit_verify-0.1.0.dist-info/RECORD +35 -0
- scikit_verify-0.1.0.dist-info/WHEEL +5 -0
- scikit_verify-0.1.0.dist-info/entry_points.txt +2 -0
- scikit_verify-0.1.0.dist-info/licenses/LICENSE +28 -0
- scikit_verify-0.1.0.dist-info/top_level.txt +3 -0
- skverify/__init__.py +73 -0
- skverify/api.py +488 -0
- skverify/atoms.py +448 -0
- skverify/checks.py +189 -0
- skverify/coercion.py +223 -0
- skverify/contracts.py +346 -0
- skverify/derivation.py +393 -0
- skverify/dialect.py +118 -0
- skverify/helpers.py +72 -0
- skverify/instrument/__init__.py +44 -0
- skverify/instrument/registries.py +109 -0
- skverify/instrument/rewriter.py +325 -0
- skverify/instrument/runtime.py +561 -0
- skverify/instrument/triage.py +437 -0
- skverify/instrument/twins.py +469 -0
- skverify/maps/__init__.py +0 -0
- skverify/maps/numpy.py +1587 -0
- skverify/maps/special.py +81 -0
- skverify/pair.py +2211 -0
- skverify/recurrence.py +668 -0
- skverify/registry.py +2 -0
- skverify/session.py +91 -0
- skverify/sets.py +151 -0
- skverify_hypothesis/README.md +50 -0
- skverify_hypothesis/__init__.py +3 -0
- skverify_hypothesis/skverify_hypothesis.py +239 -0
- skverify_mcp/README.md +24 -0
- skverify_mcp/__init__.py +3 -0
- skverify_mcp/server.py +124 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: scikit-verify
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Lift NumPy/SciPy code to the mathematics it implements, and verify it.
|
|
5
|
+
Author: Aadya Chinubhai
|
|
6
|
+
License: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, https://github.com/aadya940/scikit-verify
|
|
8
|
+
Project-URL: Issues, https://github.com/aadya940/scikit-verify/issues
|
|
9
|
+
Keywords: verification,symbolic,sympy,numpy,scipy,numerical-analysis,scientific-computing
|
|
10
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
21
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: numpy>=1.26
|
|
26
|
+
Requires-Dist: sympy>=1.12
|
|
27
|
+
Provides-Extra: mcp
|
|
28
|
+
Requires-Dist: mcp<2,>=1.2; extra == "mcp"
|
|
29
|
+
Provides-Extra: hypothesis
|
|
30
|
+
Requires-Dist: hypothesis>=6; extra == "hypothesis"
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
33
|
+
Requires-Dist: hypothesis>=6; extra == "dev"
|
|
34
|
+
Requires-Dist: ruff; extra == "dev"
|
|
35
|
+
Requires-Dist: scipy>=1.12; extra == "dev"
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
<p align="center">
|
|
39
|
+
<img src="doc/logos/scikit-verify-lockup.svg" alt="scikit-verify" width="380">
|
|
40
|
+
</p>
|
|
41
|
+
|
|
42
|
+
<p align="center">Translate Python and NumPy programs to symbolic mathematics</p>
|
|
43
|
+
|
|
44
|
+

|
|
45
|
+
|
|
46
|
+
* [Source code](https://github.com/aadya940/scikit-verify)
|
|
47
|
+
* [Coverage](doc/coverage.md)
|
|
48
|
+
* [License](https://github.com/aadya940/scikit-verify/blob/master/LICENSE)
|
|
49
|
+
* [skverify-mcp](skverify-mcp/) - MCP for mathematical feedback for coding agents
|
|
50
|
+
* [skverify-hypothesis](skverify-hypothesis/) - find every branch, boundary and edge case of your function with Hypothesis
|
|
51
|
+
|
|
52
|
+
scikit-verify is a tracer for numerical Python. It runs your NumPy
|
|
53
|
+
function once and returns the formula it computed, as an ordinary SymPy
|
|
54
|
+
expression you can read, simplify, compare against a paper, or evaluate
|
|
55
|
+
at any other input. Your code is not modified or annotated. For example:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
import numpy as np
|
|
59
|
+
from skverify import to_sympy
|
|
60
|
+
|
|
61
|
+
def weighted_rms(x, w):
|
|
62
|
+
return np.sqrt(np.sum(w * x**2) / np.sum(w))
|
|
63
|
+
|
|
64
|
+
out = to_sympy(weighted_rms, np.array([1.0, 2.0, 3.0]), np.array([0.5, 0.3, 0.2]))
|
|
65
|
+
|
|
66
|
+
out.formula
|
|
67
|
+
# sqrt(Sum(w[j]*x[j]**2, (j, 0, 2))/Sum(w[j], (j, 0, 2)))
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Every formula comes as a certificate: the expression, plus the
|
|
71
|
+
assumptions it was derived under. When code branches on your data, the
|
|
72
|
+
branch taken becomes a stated hypothesis instead of a hidden one:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
out = to_sympy(np.median, np.array([3.0, 1.0, 4.0, 1.5]))
|
|
76
|
+
print(out.pretty())
|
|
77
|
+
|
|
78
|
+
# formula = a[0]/2 + a[3]/2
|
|
79
|
+
# assumes[0] = a[0] <= a[2]
|
|
80
|
+
# assumes[1] = a[1] <= a[3]
|
|
81
|
+
# assumes[2] = a[3] <= a[0]
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The contract is exact-or-refuse. If an operation has no faithful
|
|
85
|
+
symbolic form, scikit-verify raises instead of guessing:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
to_sympy(lambda a: a.astype(int).mean(), np.array([1.4, 2.6]))
|
|
89
|
+
# NotImplementedError: astype to non-float would change the math
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
This works on real library code, not just kernels: scikit-learn metrics
|
|
93
|
+
come back as their defining formulas (precision as its ratio of counting
|
|
94
|
+
sums), fitted estimators as their closed forms, iterative solvers as
|
|
95
|
+
held recurrences, and compiled routines (LAPACK, FFT, Cython) as named
|
|
96
|
+
terms that are checked against their defining equations on every call:
|
|
97
|
+
svd against U diag(S) Vh = A, fft against the DFT sum itself.
|
|
98
|
+
|
|
99
|
+
Randomness stays honest too. A draw like ``rng.normal(0, s)`` enters
|
|
100
|
+
the formula as a random variable with that distribution, so
|
|
101
|
+
``sympy.stats.E`` and ``variance`` of the result compute in closed
|
|
102
|
+
form, while the concrete run keeps the exact numbers drawn.
|
|
103
|
+
|
|
104
|
+
Tested against numpy, scipy, scikit-learn, statsmodels, cvxpy and
|
|
105
|
+
random research code from GitHub; the boards in [coverage](coverage/)
|
|
106
|
+
regenerate every number.
|
|
107
|
+
|
|
108
|
+
## Installation
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
pip install scikit-verify
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Requires Python >= 3.11, `numpy`, and `sympy`. The import name is
|
|
115
|
+
`skverify`. The companion layers install as extras:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
pip install "scikit-verify[mcp]" # MCP server for coding agents
|
|
119
|
+
pip install "scikit-verify[hypothesis]" # testing helpers
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Pre-alpha; the API may change. Iterative solvers at real sizes can be
|
|
123
|
+
slow to trace (minutes, not wrong); the boards in coverage/ carry
|
|
124
|
+
timings.
|
|
125
|
+
|
|
126
|
+
## Lineage
|
|
127
|
+
|
|
128
|
+
The ideas here are old and good. Pairing a concrete execution with a
|
|
129
|
+
symbolic one is King's symbolic execution (CACM 1976), run in the
|
|
130
|
+
concolic style of Cadar and Sen. Checking a compiled routine's answer
|
|
131
|
+
against its defining equation, instead of trusting its name, is
|
|
132
|
+
Blum and Kannan's result checking (1989). Folding a long trace back
|
|
133
|
+
into its loop structure follows Larus's whole-program paths (PLDI
|
|
134
|
+
1999), with templates recovered by Plotkin's anti-unification (1970).
|
|
135
|
+
The stance that code verification means checking code against the
|
|
136
|
+
mathematics it claims to implement is Oberkampf and Roy's (2010).
|
|
137
|
+
Verified lifting of stencils to summaries was developed by Kamil et
|
|
138
|
+
al. (PLDI 2016) for performance; scikit-verify lifts for correctness.
|
|
139
|
+
Converting NumPy to SymPy was wished for in
|
|
140
|
+
[sympy#2810](https://github.com/sympy/sympy/issues/2810) (2014).
|
|
141
|
+
|
|
142
|
+
## License
|
|
143
|
+
|
|
144
|
+
BSD-3-Clause. scikit-verify is an independent project and is not affiliated
|
|
145
|
+
with the SciPy developers.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
scikit_verify-0.1.0.dist-info/licenses/LICENSE,sha256=PuQljKseyk67tsP3H2NWZde8x0cqWpDFbqR9A2u1LmE,1502
|
|
2
|
+
skverify/__init__.py,sha256=3KIYG8yyWOGZSr4m0M0cFCl97wMT2KEYYlQ8Yp3_Xhk,2773
|
|
3
|
+
skverify/api.py,sha256=wVxXr4ZdV6d5NxMvLL2qBYoXegKVxXmvShq8ZYHwOVk,19920
|
|
4
|
+
skverify/atoms.py,sha256=SbWrvuQ_96pQvsoB2OeSltF9IZoOmYQHUXxg5z0zPc8,16146
|
|
5
|
+
skverify/checks.py,sha256=PI1MaDcq9N-m8jGypYuHGc-baHn6vpZ7eRBF6p-PLsM,6640
|
|
6
|
+
skverify/coercion.py,sha256=7QNQh5FP2A90hpuezNsQksyWkvyO8cxRgB7yH2LOBKI,7736
|
|
7
|
+
skverify/contracts.py,sha256=9xZ5KdeWis67RxWpqITeCNMYorpYjOVWcPTsBLMEJPY,11330
|
|
8
|
+
skverify/derivation.py,sha256=9TjsziXvLTtNYaKcBWbzHeChctsTzyK7j8l-0D1sS2Y,14538
|
|
9
|
+
skverify/dialect.py,sha256=jXCGqwCsNGp4wnYYvA-Zg9igVr0GHmxBwUYJ2DAq0H4,3882
|
|
10
|
+
skverify/helpers.py,sha256=1WGhIf1aArFBf7oofkgjMVNGJUwHZgWaVs9OxlpfeLw,2592
|
|
11
|
+
skverify/pair.py,sha256=n8fp0LSyQQUTWNv62jZ5ZLPzFPrAeWa9oETWsbITw3U,88023
|
|
12
|
+
skverify/recurrence.py,sha256=L2E6vOg5TG74FhqtCDNMDxB-AB2cs3uBL2b3GeS2Lp8,26044
|
|
13
|
+
skverify/registry.py,sha256=TK5dhpO_SZsjONTeTys_qX1qHeLhuM752y-zEH5D6P0,37
|
|
14
|
+
skverify/session.py,sha256=UO3qKWdQe2mc3Xq7HPFS90nBlQ0R2qfZEi9_ebp-F1Q,3339
|
|
15
|
+
skverify/sets.py,sha256=7a_TqBfdEOKSXAH_87QDrrQn9ykzcYwA9xCPxv28RLE,4827
|
|
16
|
+
skverify/instrument/__init__.py,sha256=W9_L_p2MlI1f5MSxNU12rDv60lavUzKTrZWhvKdJV3g,1445
|
|
17
|
+
skverify/instrument/registries.py,sha256=RJdGO9iVUaWP7bM94BuKbu61lij-7i2BdlK3zS_VMcc,3094
|
|
18
|
+
skverify/instrument/rewriter.py,sha256=tZN3VbvUaThMfqRd2rM73JZdRdNiurF0G3x_XlrO5QQ,13342
|
|
19
|
+
skverify/instrument/runtime.py,sha256=6swR4saMzIZ6tO3-9rT8W7nScp1RnhTFWFZyl1Xr8-s,19948
|
|
20
|
+
skverify/instrument/triage.py,sha256=WEYCv4zjpeFgLvk-cgOeAf4ZMTlghcZOSDEBExoambE,17916
|
|
21
|
+
skverify/instrument/twins.py,sha256=H_T5AbRMgnN8c8A_6lSiHnsfhm8lgM5STuBfq1RipJY,18938
|
|
22
|
+
skverify/maps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
|
+
skverify/maps/numpy.py,sha256=0EftOMKwSEq-3Du7vkVbs-BVeVwG3Fqxm_ujD0M9FrY,59858
|
|
24
|
+
skverify/maps/special.py,sha256=UZjlSbgBa6rq_ZtxcmpD2hmQ39901poaTOI0C6aqwCo,2972
|
|
25
|
+
skverify_hypothesis/README.md,sha256=KJccL1DsAO4Yl5IsKTBndwHjbMqN0mHyMAuVLPI9Xqs,1443
|
|
26
|
+
skverify_hypothesis/__init__.py,sha256=sKp8v3Ib7tdrY2UCVWfI7NvWT5C-gm3w48a_PVQMZvw,108
|
|
27
|
+
skverify_hypothesis/skverify_hypothesis.py,sha256=aj0L60OX7LDW6CgXG6iyj1RA8uFPJMyYAjKao7XlGYc,8550
|
|
28
|
+
skverify_mcp/README.md,sha256=QSy_BhOiFsVw8NDjiWI61IAjEHyjsBEJkGKtkKou3_s,656
|
|
29
|
+
skverify_mcp/__init__.py,sha256=X_DkAxeoLMiV8MCzKuiVNJNm9zHeeoQmyU9_Ncv732U,99
|
|
30
|
+
skverify_mcp/server.py,sha256=bN9931WFrkTQEtJ147aesOpPa7RFK1kZeiHjjZJ9mbY,4394
|
|
31
|
+
scikit_verify-0.1.0.dist-info/METADATA,sha256=lt9u9igkBZLFmBFlgveLVQzmK1duFcP3xsgXlTP4CB8,5692
|
|
32
|
+
scikit_verify-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
33
|
+
scikit_verify-0.1.0.dist-info/entry_points.txt,sha256=OfM7m0AvFJtCPaxs535y3dgI9fzD_zPRMGzywuKp5vA,58
|
|
34
|
+
scikit_verify-0.1.0.dist-info/top_level.txt,sha256=IGlBt1JgJ4ez8taijPuXPWvTVKwituO0-edDGALIqiA,42
|
|
35
|
+
scikit_verify-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Aadya Chinubhai
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
skverify/__init__.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from skverify.pair import Pair, IDX
|
|
2
|
+
from skverify.maps import numpy as _numpy_map
|
|
3
|
+
from skverify.maps import special as _special_map
|
|
4
|
+
from .api import to_sympy
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _tidy(expr):
|
|
8
|
+
"""Print-tier cleanups that change nothing mathematically:
|
|
9
|
+
collapse sum axes of extent one, and state negated comparisons
|
|
10
|
+
positively (-a/n > -b/n becomes a/n < b/n)."""
|
|
11
|
+
import sympy as _sym
|
|
12
|
+
|
|
13
|
+
def collapse(s):
|
|
14
|
+
fun, limits = s.function, []
|
|
15
|
+
for v, lo, hi in s.limits:
|
|
16
|
+
if lo == hi:
|
|
17
|
+
# the axis has one slot: drop the dummy from indexed
|
|
18
|
+
# terms outright (a dead index position), substitute
|
|
19
|
+
# anywhere it appears in arithmetic
|
|
20
|
+
fun = fun.replace(
|
|
21
|
+
lambda x: isinstance(x, _sym.Indexed) and v in x.indices,
|
|
22
|
+
lambda x: x.base[
|
|
23
|
+
tuple(i for i in x.indices if i != v)
|
|
24
|
+
] if len(x.indices) > 1 else x.base[lo],
|
|
25
|
+
)
|
|
26
|
+
fun = fun.subs(v, lo)
|
|
27
|
+
else:
|
|
28
|
+
limits.append((v, lo, hi))
|
|
29
|
+
if fun.could_extract_minus_sign():
|
|
30
|
+
# a sum of negated terms IS the negated sum: pull the sign
|
|
31
|
+
# out so comparisons can read positively
|
|
32
|
+
inner = _sym.Sum(-fun, *limits) if limits else -fun
|
|
33
|
+
return -inner
|
|
34
|
+
return _sym.Sum(fun, *limits) if limits else fun
|
|
35
|
+
|
|
36
|
+
expr = expr.replace(lambda x: isinstance(x, _sym.Sum), collapse)
|
|
37
|
+
rel_flip = {
|
|
38
|
+
_sym.Lt: _sym.Gt, _sym.Le: _sym.Ge,
|
|
39
|
+
_sym.Gt: _sym.Lt, _sym.Ge: _sym.Le,
|
|
40
|
+
}
|
|
41
|
+
if type(expr) in rel_flip:
|
|
42
|
+
lhs, rhs = expr.lhs, expr.rhs
|
|
43
|
+
if lhs.could_extract_minus_sign() and rhs.could_extract_minus_sign():
|
|
44
|
+
expr = rel_flip[type(expr)](-lhs, -rhs)
|
|
45
|
+
return expr
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def latex(expr, aliases=None):
|
|
49
|
+
"""sympy.latex with readable defaults for certificates: code-ish
|
|
50
|
+
names render upright, sum axes of extent one collapse, negated
|
|
51
|
+
comparisons read positively. Pass a dict as `aliases` to shorten
|
|
52
|
+
long names to T1, T2, ...; the dict fills with alias -> full name
|
|
53
|
+
so you can print a legend."""
|
|
54
|
+
import sympy as _sym
|
|
55
|
+
|
|
56
|
+
if not isinstance(expr, _sym.Basic):
|
|
57
|
+
return str(expr)
|
|
58
|
+
expr = _tidy(expr)
|
|
59
|
+
names = {}
|
|
60
|
+
for s in sorted(expr.atoms(_sym.Symbol), key=str):
|
|
61
|
+
n = str(s)
|
|
62
|
+
if "_" not in n:
|
|
63
|
+
continue
|
|
64
|
+
if aliases is not None and len(n) > 14:
|
|
65
|
+
short = next(
|
|
66
|
+
(k for k, v in aliases.items() if v == n),
|
|
67
|
+
f"T{len(aliases) + 1}",
|
|
68
|
+
)
|
|
69
|
+
aliases[short] = n
|
|
70
|
+
names[s] = r"\mathtt{%s}" % short
|
|
71
|
+
else:
|
|
72
|
+
names[s] = r"\mathtt{%s}" % n.replace("_", r"\_")
|
|
73
|
+
return _sym.latex(expr, symbol_names=names)
|