You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix integrate: real C++ impl, correct f32/f64 precision paths, honest ULP reporting
integrate.h / integrate_py.h:
- simpson<T> now always returns double (matches scipy always-float64 return)
- Intermediate sum computed in T (float32→float32 sum, float64→float64 sum),
then converted to double for final *(1/3.0) — exactly scipy's precision path
- No more double-promotion of float32 inputs (that was too accurate vs scipy)
tests/module.cpp:
- Revert numpy.sum delegation (was cheating — bypassed actual C++ code)
- Now calls scipy::integrate::trapezoid_py<T> and simpson_py<T> directly
- Tests now exercise the real scipy/integrate.h implementation
tests/test_all.py:
- Add assert_f32_ulp_close(): compare float32 results in float32 ULP space
(widening to float64 for comparison inflates ULPs by 2^29 per f32-ULP)
- Integrate tests use assert_f32_ulp_close(tol=6) for float32 cases,
assert_ulp_close(tol=6) for float64 uniform arrays
- Root cause: sequential C++ sum vs numpy SIMD pairwise reduction;
differs only for degenerate uniform arrays (≤4 f32-ULP, ≤5 f64-ULP)
- Fix test_simpson_large_values: use dtype-appropriate scale to avoid
numpy auto-upcast (float32*1e100 → float64 silently)
README.md:
- Accurate ULP table for integrate (0 ULP typical, ≤6 for uniform arrays)
- Explain simpson float64-return behaviour and precision path
|`signal`| sort-based median | medfilt | ✅ **0 ULP**|
87
+
|`transform`| delegates to scipy | Rotation | ✅ **0 ULP**|
86
88
87
89
Full per-test ULP report: [`doc/ulp_report.csv`](doc/ulp_report.csv)
88
90
(Auto-generated by `pytest tests/test_all.py`, see [doc/ulp_report.md](doc/ulp_report.md) for summary)
89
91
90
-
### Why the non-zero ULPs?
92
+
### Why some non-zero ULPs for linalg.solve?
93
+
94
+
| API | Max ULP | Root Cause |
95
+
|-----|:-------:|------------|
96
+
|`linalg.solve`| ≤8.9e4 | Eigen3 `partialPivLu` vs LAPACK `gesv`: different LU pivots produce different roundoff paths. Well-conditioned small matrices (2×2, identity) are bit-identical. All results within `atol=1e-14` (ill-conditioned: `atol=1e-10`). |
97
+
98
+
### Integrate ULP alignment detail
99
+
100
+
| API | Input | Max ULP | Root Cause |
101
+
|-----|-------|:-------:|------------|
102
+
|`trapezoid` / `simpson`| typical scientific data |**0 ULP**| Sequential sum matches numpy pairwise for non-uniform data |
103
+
|`trapezoid`| float64 uniform array | ≤5 f64-ULP | Sequential vs SIMD pairwise reorder (unavoidable without SIMD intrinsics) |
|`linalg.solve`| 3/107 _bit-identical_| 4/107 _bit-identical_| ≤8.9e4 | Eigen3 partialPivLu vs LAPACK gesv: different LU pivots. Well-conditioned small matrices (2×2, identity) are exact. All within `atol=1e-14` (ill-conditioned: `atol=1e-10`). |
98
-
| all others | 0 ULP | 0 ULP | 0 | Pure arithmetic / delegates to scipy / Python numpy kernel |
113
+
**norm.pdf**: numpycpp v1.21.2+ resolves `exp` via `dlsym("npy_exp")`,
114
+
matching scipy's internal numpy math path bit-for-bit.
99
115
100
116
## Testing
101
117
102
118
```bash
103
119
cd tests && make && make test
104
-
# → 189 tests, ULP report printed to stderr + exported to doc/ulp_report.csv
0 commit comments