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.
@@ -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
+ ![CI](https://github.com/aadya940/scikit-verify/actions/workflows/ci.yml/badge.svg)
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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ skverify-mcp = skverify_mcp.server:main
@@ -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.
@@ -0,0 +1,3 @@
1
+ skverify
2
+ skverify_hypothesis
3
+ skverify_mcp
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)