ulpwise 0.3.0__tar.gz → 0.3.2__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.
- ulpwise-0.3.2/.github/dependabot.yml +23 -0
- {ulpwise-0.3.0 → ulpwise-0.3.2}/.github/workflows/ci.yml +16 -9
- {ulpwise-0.3.0 → ulpwise-0.3.2}/.github/workflows/release.yml +31 -10
- {ulpwise-0.3.0 → ulpwise-0.3.2}/.gitignore +1 -0
- ulpwise-0.3.2/CHANGELOG.md +245 -0
- {ulpwise-0.3.0 → ulpwise-0.3.2}/Cargo.lock +11 -11
- {ulpwise-0.3.0 → ulpwise-0.3.2}/Cargo.toml +1 -1
- {ulpwise-0.3.0 → ulpwise-0.3.2}/PKG-INFO +82 -14
- {ulpwise-0.3.0 → ulpwise-0.3.2}/README.md +80 -13
- {ulpwise-0.3.0 → ulpwise-0.3.2}/pyproject.toml +3 -2
- {ulpwise-0.3.0 → ulpwise-0.3.2}/python/ulpwise/__init__.py +115 -8
- {ulpwise-0.3.0 → ulpwise-0.3.2}/python/ulpwise/__main__.py +1 -1
- ulpwise-0.3.2/python/ulpwise/corpus/cases.json +1108 -0
- {ulpwise-0.3.0 → ulpwise-0.3.2}/python/ulpwise/scan.py +20 -7
- {ulpwise-0.3.0 → ulpwise-0.3.2}/python/ulpwise/survey.py +155 -50
- {ulpwise-0.3.0 → ulpwise-0.3.2}/studies/accuracy-survey-2026-09/README.md +13 -1
- {ulpwise-0.3.0 → ulpwise-0.3.2}/studies/accuracy-survey-2026-09/results.csv +2 -2
- {ulpwise-0.3.0 → ulpwise-0.3.2}/studies/accuracy-survey-2026-09/results.md +2 -2
- ulpwise-0.3.2/studies/accuracy-survey-2026-10-half/README.md +133 -0
- ulpwise-0.3.2/studies/accuracy-survey-2026-10-half/results.csv +91 -0
- ulpwise-0.3.2/studies/accuracy-survey-2026-10-half/results.md +105 -0
- {ulpwise-0.3.0 → ulpwise-0.3.2}/tests/test_core.py +64 -0
- {ulpwise-0.3.0 → ulpwise-0.3.2}/tests/test_corpus.py +23 -4
- {ulpwise-0.3.0 → ulpwise-0.3.2}/tests/test_scan.py +71 -0
- ulpwise-0.3.2/tests/test_survey.py +684 -0
- ulpwise-0.3.0/CHANGELOG.md +0 -80
- ulpwise-0.3.0/python/ulpwise/corpus/cases.json +0 -437
- ulpwise-0.3.0/tests/test_survey.py +0 -53
- {ulpwise-0.3.0 → ulpwise-0.3.2}/CONTRIBUTING.md +0 -0
- {ulpwise-0.3.0 → ulpwise-0.3.2}/LICENSE-APACHE +0 -0
- {ulpwise-0.3.0 → ulpwise-0.3.2}/LICENSE-MIT +0 -0
- {ulpwise-0.3.0 → ulpwise-0.3.2}/examples/sqrt_knife_values.py +0 -0
- {ulpwise-0.3.0 → ulpwise-0.3.2}/examples/torch_sqrt_conformance.py +0 -0
- {ulpwise-0.3.0 → ulpwise-0.3.2}/python/ulpwise/corpus/__init__.py +0 -0
- {ulpwise-0.3.0 → ulpwise-0.3.2}/python/ulpwise/plugin.py +0 -0
- {ulpwise-0.3.0 → ulpwise-0.3.2}/src/edge.rs +0 -0
- {ulpwise-0.3.0 → ulpwise-0.3.2}/src/exact.rs +0 -0
- {ulpwise-0.3.0 → ulpwise-0.3.2}/src/knife.rs +0 -0
- {ulpwise-0.3.0 → ulpwise-0.3.2}/src/lib.rs +0 -0
- {ulpwise-0.3.0 → ulpwise-0.3.2}/src/py.rs +0 -0
- {ulpwise-0.3.0 → ulpwise-0.3.2}/src/ulp.rs +0 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Dependabot keeps Cargo.lock and the workflow actions current through pull requests that CI
|
|
2
|
+
# tests, so a pyo3 patch release (0.29.2 -> 0.29.3 at the time of writing) is picked up without
|
|
3
|
+
# anyone running cargo by hand. Python dependencies stay unpinned in pyproject.toml on purpose:
|
|
4
|
+
# the corpus is meant to run against whatever torch, kornia, timm, peft, torchrl and
|
|
5
|
+
# ultralytics the user has installed.
|
|
6
|
+
version: 2
|
|
7
|
+
updates:
|
|
8
|
+
- package-ecosystem: cargo
|
|
9
|
+
directory: /
|
|
10
|
+
schedule:
|
|
11
|
+
interval: weekly
|
|
12
|
+
day: monday
|
|
13
|
+
open-pull-requests-limit: 3
|
|
14
|
+
commit-message:
|
|
15
|
+
prefix: "deps(cargo)"
|
|
16
|
+
- package-ecosystem: github-actions
|
|
17
|
+
directory: /
|
|
18
|
+
schedule:
|
|
19
|
+
interval: weekly
|
|
20
|
+
day: monday
|
|
21
|
+
open-pull-requests-limit: 3
|
|
22
|
+
commit-message:
|
|
23
|
+
prefix: "deps(actions)"
|
|
@@ -4,6 +4,9 @@ on:
|
|
|
4
4
|
push:
|
|
5
5
|
branches: [main]
|
|
6
6
|
pull_request:
|
|
7
|
+
schedule:
|
|
8
|
+
- cron: "17 6 * * 1" # Monday 06:17 UTC: the corpus against the latest releases, strict
|
|
9
|
+
workflow_dispatch:
|
|
7
10
|
|
|
8
11
|
jobs:
|
|
9
12
|
rust:
|
|
@@ -14,7 +17,7 @@ jobs:
|
|
|
14
17
|
matrix:
|
|
15
18
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
16
19
|
steps:
|
|
17
|
-
- uses: actions/checkout@
|
|
20
|
+
- uses: actions/checkout@v7
|
|
18
21
|
- uses: dtolnay/rust-toolchain@stable
|
|
19
22
|
with:
|
|
20
23
|
components: rustfmt, clippy
|
|
@@ -34,29 +37,33 @@ jobs:
|
|
|
34
37
|
- os: ubuntu-latest
|
|
35
38
|
python: "3.9"
|
|
36
39
|
steps:
|
|
37
|
-
- uses: actions/checkout@
|
|
40
|
+
- uses: actions/checkout@v7
|
|
38
41
|
- uses: dtolnay/rust-toolchain@stable
|
|
39
|
-
- uses: actions/setup-python@
|
|
42
|
+
- uses: actions/setup-python@v7
|
|
40
43
|
with:
|
|
41
44
|
python-version: ${{ matrix.python }}
|
|
42
|
-
- run: pip install pytest numpy packaging
|
|
45
|
+
- run: pip install pytest numpy packaging mpmath
|
|
43
46
|
- run: pip install .
|
|
44
|
-
- run: pytest tests/test_core.py -q -p no:cacheprovider
|
|
47
|
+
- run: pytest tests/test_core.py tests/test_survey.py tests/test_scan.py -q -p no:cacheprovider
|
|
45
48
|
- name: sqrt conformance of numpy on this platform
|
|
46
49
|
run: python examples/torch_sqrt_conformance.py && python examples/sqrt_knife_values.py
|
|
47
50
|
|
|
48
51
|
corpus:
|
|
49
52
|
name: regression corpus (ubuntu, torch cpu)
|
|
50
53
|
runs-on: ubuntu-latest
|
|
54
|
+
env:
|
|
55
|
+
# On the weekly and the manual runs an unexpected pass is a failure: a release fixed a case,
|
|
56
|
+
# update cases.json. Push and pull request runs stay lenient.
|
|
57
|
+
ULPWISE_CORPUS_STRICT: ${{ (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && '1' || '0' }}
|
|
51
58
|
steps:
|
|
52
|
-
- uses: actions/checkout@
|
|
59
|
+
- uses: actions/checkout@v7
|
|
53
60
|
- uses: dtolnay/rust-toolchain@stable
|
|
54
|
-
- uses: actions/setup-python@
|
|
61
|
+
- uses: actions/setup-python@v7
|
|
55
62
|
with:
|
|
56
63
|
python-version: "3.12"
|
|
57
|
-
- run: pip install pytest numpy packaging scipy
|
|
64
|
+
- run: pip install pytest numpy packaging scipy mpmath expecttest
|
|
58
65
|
- run: pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu
|
|
59
|
-
- run: pip install kornia timm peft torchrl
|
|
66
|
+
- run: pip install kornia timm peft torchrl ultralytics
|
|
60
67
|
- run: pip install .
|
|
61
68
|
- run: pytest tests -q -p no:cacheprovider -rxXs
|
|
62
69
|
- name: sqrt conformance of torch and numpy on this platform
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# Builds wheels for every supported platform plus an sdist. On a tag push (v*) it also
|
|
2
2
|
# uploads them to PyPI through trusted publishing (no API token stored anywhere):
|
|
3
3
|
# the PyPI project "ulpwise" must list this repository, this workflow file name and the
|
|
4
|
-
# "pypi" environment as its publisher. A manual run only builds and checks the wheels
|
|
5
|
-
#
|
|
6
|
-
#
|
|
4
|
+
# "pypi" environment as its publisher. A manual run only builds and checks the wheels and
|
|
5
|
+
# gathers them the way the publish job would (the "collect" job), unless its "publish" input
|
|
6
|
+
# is set, in which case it instead uploads, tags the commit and creates the GitHub release
|
|
7
|
+
# with the CHANGELOG section as notes and the wheels attached.
|
|
7
8
|
name: release
|
|
8
9
|
|
|
9
10
|
on:
|
|
@@ -48,8 +49,8 @@ jobs:
|
|
|
48
49
|
target: x64
|
|
49
50
|
smoke: true
|
|
50
51
|
steps:
|
|
51
|
-
- uses: actions/checkout@
|
|
52
|
-
- uses: actions/setup-python@
|
|
52
|
+
- uses: actions/checkout@v7
|
|
53
|
+
- uses: actions/setup-python@v7
|
|
53
54
|
with:
|
|
54
55
|
python-version: "3.12"
|
|
55
56
|
- uses: PyO3/maturin-action@v1
|
|
@@ -66,7 +67,7 @@ jobs:
|
|
|
66
67
|
python -c "import ulpwise; print('ulpwise', ulpwise.__version__)"
|
|
67
68
|
python -m ulpwise ulp 1.0 1.0000001 --dtype f32
|
|
68
69
|
python -m ulpwise midpoint sqrt 0.8528626561164856 --dtype f32
|
|
69
|
-
- uses: actions/upload-artifact@
|
|
70
|
+
- uses: actions/upload-artifact@v7
|
|
70
71
|
with:
|
|
71
72
|
name: wheels-${{ matrix.name }}
|
|
72
73
|
path: dist
|
|
@@ -74,7 +75,7 @@ jobs:
|
|
|
74
75
|
sdist:
|
|
75
76
|
runs-on: ubuntu-latest
|
|
76
77
|
steps:
|
|
77
|
-
- uses: actions/checkout@
|
|
78
|
+
- uses: actions/checkout@v7
|
|
78
79
|
- uses: PyO3/maturin-action@v1
|
|
79
80
|
with:
|
|
80
81
|
command: sdist
|
|
@@ -84,11 +85,31 @@ jobs:
|
|
|
84
85
|
pip install pytest numpy
|
|
85
86
|
pip install dist/*.tar.gz
|
|
86
87
|
pytest tests/test_core.py -q -p no:cacheprovider
|
|
87
|
-
- uses: actions/upload-artifact@
|
|
88
|
+
- uses: actions/upload-artifact@v7
|
|
88
89
|
with:
|
|
89
90
|
name: wheels-sdist
|
|
90
91
|
path: dist
|
|
91
92
|
|
|
93
|
+
# A manual run without "publish" ends here: gather the artifacts exactly the way the publish
|
|
94
|
+
# job does and count them, so a dry run exercises the whole pipeline short of the upload.
|
|
95
|
+
collect:
|
|
96
|
+
if: ${{ !(startsWith(github.ref, 'refs/tags/v') || inputs.publish == true) }}
|
|
97
|
+
needs: [wheels, sdist]
|
|
98
|
+
runs-on: ubuntu-latest
|
|
99
|
+
steps:
|
|
100
|
+
- uses: actions/download-artifact@v8
|
|
101
|
+
with:
|
|
102
|
+
pattern: wheels-*
|
|
103
|
+
merge-multiple: true
|
|
104
|
+
path: dist
|
|
105
|
+
- name: one abi3 wheel per matrix entry plus the sdist
|
|
106
|
+
run: |
|
|
107
|
+
ls -l dist
|
|
108
|
+
wheels=$(ls dist/*.whl | wc -l)
|
|
109
|
+
sdists=$(ls dist/*.tar.gz | wc -l)
|
|
110
|
+
echo "$wheels wheels, $sdists sdist"
|
|
111
|
+
test "$wheels" -eq 5 && test "$sdists" -eq 1
|
|
112
|
+
|
|
92
113
|
publish:
|
|
93
114
|
if: startsWith(github.ref, 'refs/tags/v') || inputs.publish == true
|
|
94
115
|
needs: [wheels, sdist]
|
|
@@ -98,8 +119,8 @@ jobs:
|
|
|
98
119
|
id-token: write
|
|
99
120
|
contents: write
|
|
100
121
|
steps:
|
|
101
|
-
- uses: actions/checkout@
|
|
102
|
-
- uses: actions/download-artifact@
|
|
122
|
+
- uses: actions/checkout@v7
|
|
123
|
+
- uses: actions/download-artifact@v8
|
|
103
124
|
with:
|
|
104
125
|
pattern: wheels-*
|
|
105
126
|
merge-multiple: true
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.3.2 (2026-10-06)
|
|
4
|
+
|
|
5
|
+
- Two regression corpus cases for pytorch #199850: `torch.erf` in bfloat16 and float16 on CPU returns 0 at and
|
|
6
|
+
below 1.8e-7 and loses relative accuracy below 1e-3 (13404 bfloat16 ulps, 5 float16 ulps at worst), found with
|
|
7
|
+
the half precision rows of `ulpwise survey --functions erf --backends torch --dtypes f16,bf16`.
|
|
8
|
+
- Two regression corpus cases for pytorch #199867: `torch.special.logit` in float16 and bfloat16 on CPU rounds `1 - x`
|
|
9
|
+
and `x / (1 - x)` to the input dtype before the log, so logit(0.499756) in float16 is -0.000488 for an exact -0.000977
|
|
10
|
+
(512 ulp, 64 bfloat16 ulp at worst), found with `ulpwise survey --functions logit --backends torch --dtypes f16,bf16`.
|
|
11
|
+
- `studies/accuracy-survey-2026-10-half`: the second accuracy survey, torch 2.14.0+cpu in float16 and bfloat16 for the
|
|
12
|
+
45 functions with half precision CPU kernels. 33 float16 and 34 bfloat16 rows of 45 are correctly rounded; `erf`
|
|
13
|
+
(pytorch #199850) and `logit` (pytorch #199867) lose their digits before the rounding step, `polygamma(2, x)` in
|
|
14
|
+
float16 is off at every half integer in `(-1024, -256)` because the Hurwitz zeta sum accumulates in float for the
|
|
15
|
+
reduced types, and the activation tails and the `rsqrt` and `i0e` vector versus scalar disagreements of September
|
|
16
|
+
show again.
|
|
17
|
+
- `ulpwise survey`: the `polygamma_1` and `polygamma_2` references at a non positive integer are now the signed
|
|
18
|
+
infinity of the pole, `+inf` for an odd order (the limit from both sides) and `-inf` for an even one (the sign of
|
|
19
|
+
`(-1) ** (n + 1) * n! * zeta(n + 1, x)`, which is what scipy, torch and jax return), instead of a domain error
|
|
20
|
+
that expected `nan`. The f16 and bf16 grids reach the integers from 2048 and 256 up, so every negative point
|
|
21
|
+
past there was a pole and counted as a nonfinite mismatch: on torch 2.14 the two functions had 354 of 3546
|
|
22
|
+
float16 points and 492 of 4016 bfloat16 points each, `polygamma_2` has 0 now in every dtype and `polygamma_1`
|
|
23
|
+
keeps 491 bfloat16, 4 float32 and 2 float16 points where torch returns a large finite value instead of `inf`
|
|
24
|
+
at a negative integer (pytorch #198663).
|
|
25
|
+
|
|
26
|
+
## 0.3.1 (2026-10-05)
|
|
27
|
+
|
|
28
|
+
- `ulpwise survey` takes `f16` and `bf16` in `--dtypes` (the default stays `f32,f64`): torch and jax get half
|
|
29
|
+
precision rows, measured in float16 or bfloat16 ulps against the same mpmath reference; numpy has no
|
|
30
|
+
bfloat16 and scipy.special computes a float16 input in float32, so those two have no `bf16` rows and scipy
|
|
31
|
+
has no `f16` rows. A backend without a kernel for a function in a dtype (torch's Bessel and Airy functions,
|
|
32
|
+
`erfcx`, `ndtri`, `log_ndtr` and `zeta` in half precision) is logged and skipped before the reference is
|
|
33
|
+
computed. The torch default tolerances of the two dtypes are `(1.6e-2, 1e-5)` and `(1e-3, 1e-5)`, as in
|
|
34
|
+
`torch.testing`. A log spaced domain wider than the dtype is now clipped to the dtype's finite range before
|
|
35
|
+
the points are spaced: the float16 grid of `sqrt` and `log` over `(1e-300, 1e300)` kept 32 of 600 points and
|
|
36
|
+
`acosh` 24, they have about 570 and 490 now; the float32 grids of the same functions go from about 100 points
|
|
37
|
+
to about 600, the float64 grids are unchanged. The `(0, 1)` and `(-1, 1)` grids of `ndtri`, `logit`, `asin`,
|
|
38
|
+
`acos`, `atanh` and `erfinv` stop at the float below 1 in half precision too, where `1 - 1e-4` and `1 - 1e-3`
|
|
39
|
+
rounded to 1. Two corrections to the ulp metric for every dtype: the threshold from which an exact result
|
|
40
|
+
counts as a correct overflow to infinity was `fmax * (1 + eps / 2)`, about one ulp above the largest finite
|
|
41
|
+
value, and is now `fmax + ulp / 2`, the rounding threshold; and the spacing at the largest finite value was
|
|
42
|
+
numpy's `inf`, which turned every finite result there into a 0 ulp error, it is `ulpwise.spacing` now.
|
|
43
|
+
An unknown dtype raises `ValueError` instead of a `KeyError` from the grid.
|
|
44
|
+
- `next_up`, `next_down`, `spacing`, `neighbours`, `binade_edges` and `all_floats` accept `f16` and `bf16` (and the
|
|
45
|
+
`float16`, `half`, `bfloat16`, `torch.float16`, `torch.bfloat16` aliases) like `ulp_distance` and `special` already
|
|
46
|
+
did; they raised `unsupported dtype` before. The 16 bit versions are pure Python on the bit patterns, with the
|
|
47
|
+
conventions of the Rust ones: the largest float steps to infinity, both zeros to the smallest subnormal, `spacing`
|
|
48
|
+
is the distance from `|x|` to the next float and stays finite at the largest float, `neighbours` returns finite
|
|
49
|
+
values only. A test walks every finite float16 and bfloat16 and checks the six functions against each other, against
|
|
50
|
+
numpy's float16 `nextafter` and `spacing` and against torch's bfloat16 `nextafter`.
|
|
51
|
+
- `ulpwise scan` read the first positional argument of a method call as the value the method is applied
|
|
52
|
+
to, so `log(exp(x).sum(-1))` was not a `logsumexp-by-hand` finding while `log(exp(x).sum())` and
|
|
53
|
+
`log(exp(x).sum(dim=-1))` were. A method call whose first argument is a dimension (an integer, `None`
|
|
54
|
+
or a tuple of them) is now read as applying to its receiver, like the argument-less form.
|
|
55
|
+
- `ulpwise scan` printed a `SyntaxWarning` (a `DeprecationWarning` before Python 3.12) for every invalid
|
|
56
|
+
escape sequence in the files it scanned, four lines on kornia. The parse now ignores those warnings;
|
|
57
|
+
a file that does not parse is skipped as before.
|
|
58
|
+
- `ulpwise scan` reported Windows paths with backslashes (`kornia\geometry\conversions.py`), so the same
|
|
59
|
+
report read differently from the one made on Linux or macOS. Paths are now written with forward
|
|
60
|
+
slashes on every platform; `--run` still imports the module from either form. The CI pytest job on
|
|
61
|
+
ubuntu, macOS and windows runs `tests/test_scan.py` now, and that file also covers the reasons
|
|
62
|
+
`--run` gives for a function it does not run (a class, a constant, an instance method, a builtin
|
|
63
|
+
without a signature, a nested name, a module that fails to import) and the ulp comparison of the
|
|
64
|
+
results across infinities, NaNs and an all zero reference.
|
|
65
|
+
- `ulpwise survey` without scipy installed died at `ndtri` with a `ModuleNotFoundError` and lost every
|
|
66
|
+
row computed before it: the reference started its Newton iteration from `scipy.special.ndtri`. It now
|
|
67
|
+
starts from a bisection on `math.erfc`, returns an exact zero at `p = 0.5` (a Newton step there leaves a
|
|
68
|
+
rounding residual, which the ulp metric would read as 1e16 ulps against the exact 0.0 of torch and scipy)
|
|
69
|
+
and gives the same rows as before on the 600 point float64 and float32 grids. The survey also evaluates
|
|
70
|
+
the reference only when some requested backend implements the function; a numpy only run no longer
|
|
71
|
+
spends its time on references for the torch and scipy only entries. The pytest job on ubuntu, macOS and
|
|
72
|
+
windows (Python 3.12 and 3.9) runs `tests/test_survey.py` now, with numpy as the only backend.
|
|
73
|
+
- `ulpwise survey` printed a numpy `RuntimeWarning` for every backend call that divided by zero or
|
|
74
|
+
overflowed at the edge values of the grid (`reciprocal` and `log` at 0, `reciprocal` at the overflow
|
|
75
|
+
edge), hundreds of lines on a full run. Those points are counted in the `nonfinite` column already, so
|
|
76
|
+
the survey now evaluates the backends under `np.errstate(all="ignore")`, and a test runs `reciprocal` and
|
|
77
|
+
`log` with warnings turned into errors.
|
|
78
|
+
- The README said that two kornia fixes in the corpus were merged but not released; the corpus has 31
|
|
79
|
+
kornia cases now, and the pytorch/rl, peft and pytorch (#198006) fixes are in the same state (kornia
|
|
80
|
+
0.8.3 and 0.9.0rc1, torchrl 0.14.0, peft 0.21.2 and torch 2.14.1 predate them or cherry-pick other
|
|
81
|
+
changes), while the timm and ultralytics fixes are released. The sentence says so now, and
|
|
82
|
+
`tests/test_corpus.py` checks that the README table has one row per case with the merge date from
|
|
83
|
+
`cases.json`, so a case added without its row, or a row with a stale date, fails the suite.
|
|
84
|
+
- `tests/test_survey.py` checks every reference of the survey registry against the libraries it measures:
|
|
85
|
+
each entry is surveyed on a 24 point float64 grid against torch, numpy and scipy and its median error must
|
|
86
|
+
stay under 32 ulp, which a reference that is another function fails by fifteen orders of magnitude (torch's
|
|
87
|
+
`polygamma_1` is the worst true median at 9 ulp), and the piecewise references (`selu`, `elu`, `entr`,
|
|
88
|
+
`gelu_tanh`, `log_ndtr`, `lgamma`, `digamma`, `erfinv`, `ndtri`, `zeta`, `sinc`, `spherical_bessel_j0`)
|
|
89
|
+
are pinned once per branch, since a slip in one branch moves a third of the grid and keeps the median.
|
|
90
|
+
`survey.py` goes from 77 to 94 percent covered.
|
|
91
|
+
- With torch installed but `expecttest` missing, `torch.testing._internal` does not import and
|
|
92
|
+
`ulpwise survey` silently used the dtype default as the `OpInfo` tolerance, so the `op tol` columns
|
|
93
|
+
looked like an override that was never read. The survey now logs one line with the import error and
|
|
94
|
+
the `survey` extra installs `expecttest`. The regression corpus CI job, which has torch but had no
|
|
95
|
+
`expecttest`, failed on the two `op_db` tests for the same reason; it installs `expecttest` now and the
|
|
96
|
+
tests skip with the reason where `op_db` is not importable.
|
|
97
|
+
- `ulpwise survey` looked up torch's `OpInfo` tolerance by op name and took the first `op_db` entry
|
|
98
|
+
with that name. `polygamma` has one entry per order, and the first, `polygamma_n_0`, has no
|
|
99
|
+
override, so the `op fail` column of `polygamma_1` and `polygamma_2` in float32 was computed with
|
|
100
|
+
the dtype default (`rtol 1.3e-6`, `atol 1e-5`) instead of torch's `polygamma_n_1` and `_n_2`
|
|
101
|
+
override (`rtol 0.01`, `atol 1e-4`). `Entry` gains `opinfo_variant`, `torch_opinfo_tolerance`
|
|
102
|
+
takes a `variant` and otherwise prefers the base variant, and the two registry entries name
|
|
103
|
+
theirs. In `studies/accuracy-survey-2026-09` the `polygamma_1` f32 row goes from 86 to 7 inputs
|
|
104
|
+
failing the op's tolerance; the measured errors are unchanged (erratum in its README).
|
|
105
|
+
- `tests/test_survey.py` covers the survey itself now: `survey()` on `sqrt` against numpy and torch
|
|
106
|
+
(numpy within half an ulp, torch under one), an inexact backend, a backend that raises or returns
|
|
107
|
+
the wrong shape, the backend resolution, the `op_db` tolerance lookup including the variant case,
|
|
108
|
+
the csv and markdown writers, `versions()`, `main()` and the `survey` subcommand. CI installs
|
|
109
|
+
`mpmath` for the Python job so these tests run there instead of being skipped.
|
|
110
|
+
- Six more kornia cases, each present on kornia 0.8.3 and fixed on main: the Hessian of `So3.exp`
|
|
111
|
+
at the identity (#4972, nine nan for `-I / 4` and zeros), `sampson_epipolar_distance` of a point on
|
|
112
|
+
its epiline with `squared=False` (#5116, `sqrt(eps)` = 1e-4 for 0) and of the same `F` scaled by
|
|
113
|
+
1e-4 (#5116, a third lower), `RandomHue` on a float64 image (#5131, 8.7e-8 rad past a half turn,
|
|
114
|
+
the float32 rounding error of pi), `MS_SSIMLoss(sigmas=(0.5, 1.3), reduction="none")` (#5143, an
|
|
115
|
+
even 6-pixel window and a `(1, 15, 19)` map for a `(1, 3, 16, 20)` input) and `MS_SSIMLoss` on a
|
|
116
|
+
uint8 pair (#5353, `expected scalar type Byte but found Float`; with `data_range=255` it now scores
|
|
117
|
+
the pair divided by 255 at the default to 1e-6). Six more: `So3.right_jacobian` of a float16
|
|
118
|
+
45 rad rotation (#4967, the identity for a matrix of 0.2 to 0.4), `average_quaternions` with a member
|
|
119
|
+
stored as `3 q` (#4980, 41.8 degrees for the 22.5 degree bisector), `Hyperplane.through` a float16
|
|
120
|
+
triangle with legs of 300 (#5104, the normal `(0, 0, 1)` for `(0, 0, -1)`), `filter2d` with a
|
|
121
|
+
per-sample kernel on a channels-last image (#5301, torch's view error), `lovasz_hinge_loss` of a
|
|
122
|
+
float16 prediction (#5303, a float32 loss) and `get_box_kernel1d` (#5357, a stride-0 view where
|
|
123
|
+
one write zeroed all three taps). The kornia block is 31 cases, all `present` on 0.8.3 and all
|
|
124
|
+
`fixed` on main. One more ultralytics case, `scale_masks` with the dataloader's `ratio_pad` and an
|
|
125
|
+
odd letterbox padding (#26379, a padded row survived the crop and the bottom rows of every mask
|
|
126
|
+
faded to 0) and `verify_image_label` on two triangles that tile one square (#26377, the second
|
|
127
|
+
polygon was dropped as a duplicate of the first because only the class and the box were compared);
|
|
128
|
+
both carry `fixed_in_release` 8.4.165, the first tag with the fixes), and `verify_image_label` on a
|
|
129
|
+
pose row for a detect task (#26357, the row was read as a polygon, its box was out of bounds and
|
|
130
|
+
the image was dropped as corrupt; `fixed_in_release` 8.4.164). The corpus is 50 cases.
|
|
131
|
+
- The manual `workflow_dispatch` run of CI is now strict about the corpus like the Monday run (the
|
|
132
|
+
changelog said so already, the workflow set the variable for `schedule` only).
|
|
133
|
+
- The release workflow's manual dry run (`workflow_dispatch` without `publish`) now ends in a
|
|
134
|
+
`collect` job that downloads the artifacts the way the publish job does and checks that there
|
|
135
|
+
are five wheels and one sdist, so a dry run covers the whole pipeline short of the upload. The
|
|
136
|
+
workflow actions moved to `actions/checkout@v7`, `setup-python@v7`, `upload-artifact@v7` and
|
|
137
|
+
`download-artifact@v8` (the first Dependabot pull requests), and `Cargo.lock` to pyo3 0.29.3.
|
|
138
|
+
- Dependabot (`.github/dependabot.yml`) opens weekly pull requests for Cargo.lock and the
|
|
139
|
+
workflow actions, so pyo3 patch releases land through CI-tested pull requests instead of a
|
|
140
|
+
hand-run `cargo update`; Python dependencies stay unpinned on purpose.
|
|
141
|
+
- Eight more kornia cases, each present on kornia 0.8.3 and fixed on main: `Se2.exp`/`Se2.log` at
|
|
142
|
+
theta = 1e-8 (#4960, the translation came back as (1, 2) from exp and (1e-8, -5e-9) from log),
|
|
143
|
+
`point_line_distance` of a homogeneous point with w = 2 (#4975, 4.0 for 1.5), `Quaternion.__pow__`
|
|
144
|
+
of -1 (#5003, the zero quaternion), `So2` from a `(B, 1)` angle times `(B, 2)` points (#5005,
|
|
145
|
+
`(B, B, 2)`), `solve_cubic` of `1e-30 x^3 + 2x - 6` (#5024, roots `[0, 0, 0]` for 3),
|
|
146
|
+
`RgbToGrayscale` on uint8 (#5111, all zeros), `conv_soft_argmax2d` with a far-away peak (#5134,
|
|
147
|
+
the weak window's coordinates moved from 0.8834 to 1.0) and `get_gaussian_discrete_kernel1d(1,
|
|
148
|
+
sigma)` (#5376, 3 taps). Six more in the same shape: `Se3.exp`'s `d t / d omega` at the identity
|
|
149
|
+
(#4963, nan on 0.8.3, zero on main before the fix), `Quaternion.polar_angle`'s gradient at the
|
|
150
|
+
identity (#4981, nan), `decompose_essential_matrix` of a `(3, 3)` input (#4998, `(1, 3, 3)`),
|
|
151
|
+
`Hyperplane.through`'s gradients for orthogonal equal-length edges (#5058, nan),
|
|
152
|
+
`Vector3.normalized` of a float16 zero vector (#5084, NaN) and `otsu_threshold`'s mask below
|
|
153
|
+
zero (#5182, all False). The whole kornia block of the corpus, 19 cases, reads `present` on 0.8.3;
|
|
154
|
+
the mean_average_precision recall thresholds (#5101) were checked and left out because 0.8.3
|
|
155
|
+
already scores the exact-tenth case right.
|
|
156
|
+
- CI runs the regression corpus every Monday (and on `workflow_dispatch`) with
|
|
157
|
+
`ULPWISE_CORPUS_STRICT=1`, which turns an unexpected pass into a failure: a release fixed a case
|
|
158
|
+
and `fixed_in_release` is stale. Push and pull request runs stay non strict. The corpus job also
|
|
159
|
+
installs `ultralytics`, so the ultralytics case runs there instead of being skipped.
|
|
160
|
+
- Corpus metadata: kornia #4838 is fixed by kornia #5124 (merged 2026-09-30) and kornia #4897 by
|
|
161
|
+
kornia #4941 (merged 2026-09-26); both cases now carry the `pr` and `merged_at`, and stay expected
|
|
162
|
+
failures because no kornia release after 0.9.0rc1 (2026-07-19) exists yet. ultralytics #26330 sets
|
|
163
|
+
`fixed_in_release` to `8.4.164`: the tag `v8.4.164` (2026-09-27, the first release after the merge)
|
|
164
|
+
carries the `use_obb` check in `verify_labels` and `v8.4.163` does not, so the case is a hard
|
|
165
|
+
failure, not an expected one, from 8.4.164 on. peft #3777 keeps `fixed_in_release: null`: the
|
|
166
|
+
`v0.21.1` and `v0.21.2` tags still carry the `weight.size()[2:4]` shortcut in `lora/layer.py`.
|
|
167
|
+
|
|
168
|
+
## 0.3.0 (2026-09-26)
|
|
169
|
+
|
|
170
|
+
- `ulpwise scan`: static scan of a repository (a directory, a GitHub URL or `owner/repo`, cloned
|
|
171
|
+
with depth 1) for the floating point patterns behind the corpus bugs. Twelve rules with severity,
|
|
172
|
+
reason, replacement and upstream example: `exp-of-square`, `sin-of-pi-times`, `softplus-by-hand`,
|
|
173
|
+
`logsumexp-by-hand`, `hypot-by-hand`, `sqrt-of-difference`, `one-minus-cos`, `log1p-by-hand`,
|
|
174
|
+
`expm1-by-hand`, `atan-of-quotient`, `small-angle-division`, `acos-for-angle`. Findings carry
|
|
175
|
+
file, line, function and the source line; the report ends with the functions that do the most
|
|
176
|
+
elementary math. `--report` writes Markdown, `--rules` filters, `--fail-on` gates CI,
|
|
177
|
+
`--include-tests` widens the walk. On kornia main the four `small-angle-division` and
|
|
178
|
+
`one-minus-cos` lines are the ones kornia #4897 fixes.
|
|
179
|
+
- `ulpwise scan --run`: imports the math-heavy module level functions and calls them on the same
|
|
180
|
+
grid in float32 and float64, reporting the largest error in ulps of the largest output and the
|
|
181
|
+
worst elementwise ulp distance with its input. Static methods run, instance methods and other
|
|
182
|
+
skips carry their reason. `--run-limit` bounds it, `--run-installed` imports the installed package
|
|
183
|
+
instead of the scanned tree.
|
|
184
|
+
- `ulp_distance`, `ulp_distances`, `ordered`, `max_ulp` and `assert_max_ulp` take `f16` and
|
|
185
|
+
`bf16`, and `flatten` reads the dtype off float16 and bfloat16 numpy arrays and torch tensors.
|
|
186
|
+
Inputs that are not representable are rounded to nearest even first, so a bfloat16 tensor can be
|
|
187
|
+
measured against a float64 reference; without an explicit dtype the less precise of the two
|
|
188
|
+
inputs decides. Cross-checked against the numpy int16 view for float16 and the torch view for
|
|
189
|
+
bfloat16.
|
|
190
|
+
- `special("f16")` and `special("bf16")`: the same 29 named edge values as f32 and f64, so
|
|
191
|
+
`edge_values`, the `edge_f16` and `edge_bf16` pytest fixtures and `ulpwise special f16` work.
|
|
192
|
+
Every value satisfies the same checks as the f32 and f64 tables, run through numpy for float16
|
|
193
|
+
and torch for bfloat16.
|
|
194
|
+
- `ulpwise corpus`: runs the regression corpus against the installed packages without pytest and
|
|
195
|
+
prints one line per case, present, fixed or skipped, with the installed version and the upstream
|
|
196
|
+
reference. `--repo` filters by repository or case id, `--fail-if-present` makes a present bug exit 1.
|
|
197
|
+
Any exception from a repro counts as present, like the pytest run: several corpus bugs are crashes.
|
|
198
|
+
|
|
199
|
+
## 0.2.0 (2026-09-26)
|
|
200
|
+
|
|
201
|
+
- `ulpwise survey` (`ulpwise.survey`): accuracy survey of 61 elementary and special functions of
|
|
202
|
+
torch, numpy, scipy and jax against a 200 bit mpmath reference, in ulps of the dtype, with the
|
|
203
|
+
worst input per row. For torch it also counts inputs where the vectorized kernel and the scalar
|
|
204
|
+
tail disagree and inputs that would fail the `OpInfo` reference test tolerance, default and per op
|
|
205
|
+
override, read from `op_db`. Writes `results.csv` and `results.md`. Optional extra
|
|
206
|
+
`ulpwise[survey]` pulls in mpmath.
|
|
207
|
+
- `studies/accuracy-survey-2026-09`: the first run and its reading notes.
|
|
208
|
+
- Corpus: `max_ulp` check type, and five open cases with the complete patch attached to the issue:
|
|
209
|
+
pytorch #198448 (`torch.sqrt` float64 rounding), pytorch #198583 (Bessel and Airy `p1evl` leading
|
|
210
|
+
1), kornia #4838 and #4897 (small angle series), torchvision #9676 (rotated box clamp). Open cases
|
|
211
|
+
carry `issue`, `pr: null` and `fixed_in_release: null` and stay expected failures until a release
|
|
212
|
+
contains the fix.
|
|
213
|
+
- Corpus: three more open cases, pytorch #198663 (`polygamma(1, x)`: float64 series truncation and
|
|
214
|
+
float32 reflection argument) and pytorch #198664 (`erfcx` negative branch, `exp` at the rounded
|
|
215
|
+
square), both with the complete patch attached to the issue.
|
|
216
|
+
- Corpus: kornia #4768, `ellipse_to_laf` described the wrong ellipse whenever `b != 0`.
|
|
217
|
+
- Corpus: ultralytics #26330, OBB datasets with plain box labels are rejected at load time; the repro
|
|
218
|
+
writes a one-image dataset to a temporary directory, so it needs ultralytics but no weights.
|
|
219
|
+
|
|
220
|
+
## 0.1.2 (2026-09-24)
|
|
221
|
+
|
|
222
|
+
- `ulpwise` console script, so `uvx ulpwise midpoint sqrt 0.85`, `pipx run ulpwise ...` and a plain
|
|
223
|
+
`ulpwise ...` inside a virtualenv work without `python -m`.
|
|
224
|
+
|
|
225
|
+
## 0.1.1 (2026-09-24)
|
|
226
|
+
|
|
227
|
+
No code changes. The AI disclosure in the README was shortened and the package was republished
|
|
228
|
+
from a repository with a fresh history.
|
|
229
|
+
|
|
230
|
+
## 0.1.0 (2026-09-24)
|
|
231
|
+
|
|
232
|
+
First release.
|
|
233
|
+
|
|
234
|
+
- Rust core: ordered float views and ulp distances (`ulp`), named edge values computed from the
|
|
235
|
+
format (`edge`), exact rounding oracles for `sqrt`, reciprocal and division with correctly rounded
|
|
236
|
+
results that do not depend on the platform libm, f64 referenced midpoint reports for 15 more
|
|
237
|
+
`f32` functions (`exact`), and knife-edge scans (`knife`).
|
|
238
|
+
- Python package built with maturin: `ulp_distance`, `assert_max_ulp` and `max_ulp` for floats,
|
|
239
|
+
lists, numpy arrays and torch tensors, `special`, `neighbours`, `binade_edges`, `all_floats`,
|
|
240
|
+
`midpoint`, `knife_edges`, `sqrt_cr`, a CLI (`python -m ulpwise`) and a pytest plugin
|
|
241
|
+
(`edge_f32` / `edge_f64` parametrization, `assert_max_ulp` fixture).
|
|
242
|
+
- Regression corpus of 11 upstream bugs with runnable repros (kornia, pytorch, pytorch/rl, timm,
|
|
243
|
+
peft), run by `tests/test_corpus.py` against whatever is installed.
|
|
244
|
+
- `examples/torch_sqrt_conformance.py`: measures how often a platform's `torch.sqrt` and
|
|
245
|
+
`numpy.sqrt` disagree with correct rounding at knife-edge inputs.
|
|
@@ -37,9 +37,9 @@ dependencies = [
|
|
|
37
37
|
|
|
38
38
|
[[package]]
|
|
39
39
|
name = "pyo3"
|
|
40
|
-
version = "0.29.
|
|
40
|
+
version = "0.29.3"
|
|
41
41
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
42
|
-
checksum = "
|
|
42
|
+
checksum = "700d18fa267b73b9b521fd7e13580e2f446916f176cacee1ab63fcc8191f1655"
|
|
43
43
|
dependencies = [
|
|
44
44
|
"libc",
|
|
45
45
|
"once_cell",
|
|
@@ -51,18 +51,18 @@ dependencies = [
|
|
|
51
51
|
|
|
52
52
|
[[package]]
|
|
53
53
|
name = "pyo3-build-config"
|
|
54
|
-
version = "0.29.
|
|
54
|
+
version = "0.29.3"
|
|
55
55
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
56
|
-
checksum = "
|
|
56
|
+
checksum = "7b3fc0c4d08f6bb10e71fe39dfb9e2f59c6eb6854e22ec8092f50c69a4499adb"
|
|
57
57
|
dependencies = [
|
|
58
58
|
"target-lexicon",
|
|
59
59
|
]
|
|
60
60
|
|
|
61
61
|
[[package]]
|
|
62
62
|
name = "pyo3-ffi"
|
|
63
|
-
version = "0.29.
|
|
63
|
+
version = "0.29.3"
|
|
64
64
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
65
|
-
checksum = "
|
|
65
|
+
checksum = "dfc0b8e19df29aad7086cf977bb0c2a2f143e30567eb113e9cf72b62ca698330"
|
|
66
66
|
dependencies = [
|
|
67
67
|
"libc",
|
|
68
68
|
"pyo3-build-config",
|
|
@@ -70,9 +70,9 @@ dependencies = [
|
|
|
70
70
|
|
|
71
71
|
[[package]]
|
|
72
72
|
name = "pyo3-macros"
|
|
73
|
-
version = "0.29.
|
|
73
|
+
version = "0.29.3"
|
|
74
74
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
75
|
-
checksum = "
|
|
75
|
+
checksum = "6100e8a4b5eba53afaa5ed078364851a0b2499c553a44c31026b929049b49dc6"
|
|
76
76
|
dependencies = [
|
|
77
77
|
"proc-macro2",
|
|
78
78
|
"pyo3-macros-backend",
|
|
@@ -82,9 +82,9 @@ dependencies = [
|
|
|
82
82
|
|
|
83
83
|
[[package]]
|
|
84
84
|
name = "pyo3-macros-backend"
|
|
85
|
-
version = "0.29.
|
|
85
|
+
version = "0.29.3"
|
|
86
86
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
87
|
-
checksum = "
|
|
87
|
+
checksum = "6143877a16e82b5a727b7127ff4cd86858a24a28d745d72f43e6f227c7b1bdb3"
|
|
88
88
|
dependencies = [
|
|
89
89
|
"heck",
|
|
90
90
|
"proc-macro2",
|
|
@@ -120,7 +120,7 @@ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
|
|
120
120
|
|
|
121
121
|
[[package]]
|
|
122
122
|
name = "ulpwise"
|
|
123
|
-
version = "0.3.
|
|
123
|
+
version = "0.3.2"
|
|
124
124
|
dependencies = [
|
|
125
125
|
"pyo3",
|
|
126
126
|
]
|