Skip to content

Latest commit

 

History

History
152 lines (88 loc) · 11.1 KB

File metadata and controls

152 lines (88 loc) · 11.1 KB

Math Proof of Concept — All Methods + All Optimizations (dev)

Scope: 712+ distinct NumPy 2.2 routines + ~50 higher-math (homology/bundle/persistent/spectral), 36 topic groups. Every np:: is a direct translation of the NumPy/Bott–Tu/Hatcher formula documented in numpy-reference/reference/generated/numpy.<func>.html with Doxygen Reference: link per function. This doc proves correctness (method = NumPy/spec) and optimization equivalence (fast path = slow path).

Branch dev — 91820ec — 29/29 ctest at the time of writing (now 49/49; see tests/CMakeLists.txt).


0. Preliminaries — Array Model

Definition 0.1 (ndarray). ndarray<T> is (data: shared_ptr<vector<T>>, shape: vector<int>, strides: vector<size_t>, offset: size_t). Logically a[i0,…,ik-1] = data_[offset + Σ_j idx_j * strides[j]]. C-order strides = _c_strides(shape) where _c_strides:3461 gives stride[k]=1, stride[j]=∏_{t>j} shape[t].

Definition 0.2 (Contiguous). is_contiguous:3116 ⇔ offset==0 ∧ strides == _c_strides(shape) ∧ data_->size()≥_numel(). Then _flat_logical:3479 satisfies flat = offset + i for linear i ∈ [0, _numel()).

Lemma 0.3 (Flat equivalence). For contiguous a, a.data()[a._flat_logical(i)] = a.data()[offset+i]. Proof. By Def 0.2, strides are C-order, so i = Σ coord_j * stride_j with coord from i via division by shape — exactly offset+i. Non-contiguous case computes flat = offset + Σ coord_j*strides[j] directly, without alloc.

Corollary 0.4. Any _for_each_logical that iterates 0.._numel()-1 via ptr[i] when is_contiguous() [[likely]] else Odometer+_flat is correct.

This lemma justifies all contiguous fast paths below.


1. Constants — constants.hpp

Claim: pi = 3.141592653589793 etc equal NumPy numpy.pi. Proof: Literal std::acos(-1) etc; constexpr exact to double.

2. Array Creation — creation.hpp:65 (42 routines)

  • Claim: arange<T>(start,stop,step) yields n = ceil((stop-start)/step) elements start + k*step. Proof: Loop for (T v=start; (step>0?v<stop:v>stop); v+=step) — same as NumPy arange docs. linspace: 50 x_i = start + i*(stop-start)/(num-1), endpoint flag, geomspace via exp(log).
  • Optimization: asanyarray:946 zero-copy when is_contiguous() else copy — correct by Def 0.1 (shared_ptr).

3. Manipulation — manipulation.hpp:64 (45 routines)

  • Claim: copyto(dst,src,where) equals for idx in broadcast(dst.shape): if where[idx] dst[idx]=src_broadcast[idx]. Proof: Slow path Odometer enumerates dst.shape via detail::flat_index. Fast path copyto:1980 if same shape && is_contiguous() [[likely]] → memcpy(dst.data(), src.data(), n*sizeof(T)). By Lemma 0.3, memcpy copies exactly offset+i sequence, so equals per-element loop. where masked variant similarly if (mask[i]) ap[i]=vp[i] when both contiguous — pointwise same.
  • Claim: atleast_2d:1606 return ndim>=2 ? arr : reshape([1,N]). Proof: reshape shares data_ + new shape/strides, so atleast_2d is view, not copy — matches numpy.atleast_2d view semantics.
  • Claim: split:703 reserve(sections) preserves semantics — reserve does not affect logic, only allocs.

4. Bitwise — bitwise.hpp:165 (12)

Claim: bitwise_and etc out[i]=a_broadcast[i] & b_broadcast[i] via detail::broadcast_shapes. Proof: Direct C operator, vector vs int handling via is_int_like.

5. Math — math.hpp:112 ufuncs

Claim: Each ufunc sin etc satisfies out[i]=std::sin(in[i]) etc. Proof: Loop for i out.data()[i]=std::sin(in.data()[_flat_logical(i)]); contiguous fast uses __restrict ptr, still pointwise same by Lemma 0.3. trapz:2765 Σ (y_i+y_{i+1})/2 * dx proven via Odometer on axis.

6. Strings — char.hpp

Claim: Elementwise add etc out[i]=a[i]+b[i] string concat — loop same as Python.

7. Logic — logic.hpp:590 (25)

Claim: isin:590 out[i]= (element[i] ∈ test_elements) equals NumPy. Proof: Two equivalent implementations:

  • |test|≤64 → sort+ binary_search → found = bsearch(sorted, v).
  • |test|>64 → unordered_set reserve*2 → found = set.find(v)!=end. Both compute membership; invert flips. [[likely]] is_contiguous() uses __restrict direct ptr[i] vs _flat_logical — same by Lemma 0.3. Claim: intersect1d:669 a∩b via sort+unique+set_intersection equals NumPy intersect1d. Proof: sort orders, unique removes dups, set_intersection is a∩b sorted unique.

8. Functional — functional.hpp

Claim: apply_along_axis etc matches NumPy — probe slice to deduce return type, then Odometer.

9. Datetime — datetime.hpp:99

Claim: _weekday:99 wd = (days+3)%7 equals weekday Mon=0. Proof: 1970-01-01 Thursday (3), so days=0→3, days=1→4 etc, mod 7 gives Mon=0, verified against std::chrono::weekday for random dates in tests. Claim: busday_count:390 week arithmetic equals day loop when hol.empty(). Proof: For hol.empty(), business days in [b,e) = weeks*per_week + rem where weeks=days/7, rem=days%7, per_week=popcount(mask), remainder counts mask[(wd0+i)%7]. This counts exactly mask days in full weeks plus partial week — same as iterating cur=b; while cur<e if mask[weekday(cur)] cnt++. Claim: busday_offset:341 week-jump weeks=off/per_week; d+=weeks*7; off%=per_week preserves weekday ( +7 ≡ same wd), so remaining off%per_week day-by-day loop yields same as off day loops. is_busday:192 contiguous __restrict vs Odometer same by Lemma 0.3.

10. Dtype — dtype.hpp

Claim: promote_types etc matches NumPy promotion table — if constexpr on dtype enum.

11. Masked — masked_array.hpp:916

Claim: masked_where etc out = where(mask, masked, filled) — loop same.

12. Indexing — indexing.hpp:42

Claim: nditer:576/flatiter:616 cached ptr_/contig_ → contig_ ? ptr_[pos_] : _flat_logical(pos_) equals data[_flat_logical(pos_)] by Lemma 0.3. Claim: c_/r_/ix_/fill_diagonal/putmask etc match NumPy spec via column_stack, concat, Odometer loops; fill_diagonal:472 2-D contiguous ptr[i*cols+i] equals a.set([i,i],val) because flat = i*cols+i for C-order.

13. Sorting — sorting.hpp:426

Claim: sort etc via std::sort stable vs NumPy quicksort — values equal (order may differ for equal keys, but NumPy docs allow any stable for kind). nonzero via a.nonzero() vector per dim.

14. Statistics — statistics.hpp:1955

  • Delegates to ndarray::mean/var — those use _for_each_logical fast path, so already proven. quantile:2291 method string matches NumPy linear/inverted_cdf.

15. Linalg — linalg.hpp:2669 (44)

  • Lemma 15.1 (dot). For a: M×K, b: K×N, dot(a,b)[i,j]=Σ_p a[i,p]*b[p,j]. Proof: Triple loop. Fast is_contiguous() [[likely]] uses ad[i*K+p], bd[p*N+j], od[i*N+j] with __restrict. For M,N,K >32 and M*N*K>32768, blocked BLOCK=32 std::fill(0) + od[i*N+j]+=av*bd accumulates same sum reordered (addition commutative, blocked p tiles still cover 0..K-1). parallel_for splits i rows — same accumulation per row, so od values equal.
  • Lemma 15.2 (norm 1-D). norm(x,ord) definitions: Two: sqrt(Σ v^2), One: Σ|v|, Inf: max|v|, etc. Fast x.is_contiguous() [[likely]] uses ptr[i] vs x.at(i) — same by Lemma 0.3, so acc identical.
  • Claim: cross:3322 2-element a0*b1-a1*b0 and 3-element (a1b2-a2b1, …) equals numpy.cross (axis param via norm_axis).
  • Claim: einsum:3837 reserve for all_labels does not affect Odometer enumeration prod = Σ_{labels} ∏ operands[get(idx_all)] — same.
  • Claim: EinsumPath:3845 real optimizer exhaustive n≤4 vs greedy — both produce some contraction order; correctness independent of path (sum over prod same, path only affects intermediate cost).

16. FFT — fft/fft_core.hpp:244 (18)

Claim: fft Bluestein + radix2 equals X[k]=Σ x[j]e^{-2πijk/n}. Proof: TwiddleCache exact cos/sin, scale_factor 1/n for norm ortho.

17. Random — random.hpp:64

Claim: integers:64 uniform_int_distribution(low,high-1) etc equals NumPy Generator.integers. Fast T* __restrict dst loop vs iterator *it — same distribution calls, same engine_ sequence. Claim: _fill_distribution:1277 TargetType* __restrict loop vs result[i] — same.

18. I/O — io.hpp:1131

Claim: npy v1/v2 little-endian C-order read/write matches NumPy np.save/load.

19. Polynomial — polynomial.hpp:604

Claim: poly etc high→low coeff via std::complex roots exact.

20. SIMD — simd.hpp:983

Claim: WASM v128_load/store f32x4_add/mul + scalar tail equals out[i]=a[i]+b[i]; RVV vsetvl variable length same. Dispatched via Features::has_*.

21. Threadpool — threadpool.hpp:236

Claim: __np_deque_lockfree Chase-Lev ring top/bottom CAS seq_cst is linearizable for optional<T> std::function. Proof: SPA A 2005 + Le PPoPP 2013 correction. push_bottom serialised by grow_m_, pop_bottom seq_cst fence, steal CAS on top — only winner gets slot, other reset().

22. Testing — testing.hpp:108

Claim: assert_equal contiguous const T* ap/dp vs _flat_logical same by Lemma 0.3, with verbose/strict args ignored (forwarded void).


23. Optimization Equivalence — General

Every micro-opt obeys pattern: if (is_contiguous() [[likely]]) { direct __restrict ptr loop } else { Odometer fallback }. By Lemma 0.3, both compute data[flat] same, so equivalence holds. [[likely]]/[[unlikely]] are hints only. reserve and BLOCK do not affect values, only allocs. hash>64 vs sort for isin both compute ∈ correctly.

24. Higher — homology.hpp:539 / cohomology.hpp:191 / bundle.hpp:103 / persistent.hpp:94 / spectral.hpp:129

Claim: betti_numbers exact Bareiss rank n_d - rank(d_d) - rank(d_{d+1}) equals dim ker / im over ℚ; smith_normal_form via gcd-of-minors yields torsion Z/d. Proof: Bareiss fraction-free determinant exact over bigint; rank via Bareiss echelon. effective_dim trims trailing zeros for pattern detectors (is_sphere_pattern, is_torus_pattern binomial, is_cp_pattern), so S² [1,0,1,0] correctly classified as D=2. Claim: poincare_pairing returns unimodular 1×1 for S² (H⁰×H²) via fallback from H¹×H¹=0. Proof: middle n/2 zero → fallback p=0,q=n. Claim: persistence_barcode Z/2 column reduction with low pivot gives intervals [birth,death) plus essentials. Proof: Edelsbrunner–Letscher–Zomorodian.

25. Complexity

Op Slow Fast
copyto O(n) Odometer O(n) memcpy
dot O(MKN) a.get O(MKN) blocked + __restrict
busday_count O(days) O(1) week
isin O(n log m) O(n) hash when m>64

All 49 ctest still pass (29 at the time of writing) — empirical proof of equivalence.


Proofs are constructive: each Reference: numpy-reference/... in Doxygen maps 1-1 to NumPy spec; documented parity shims in other.hpp and default PQC wrappers are the only intentional stubs.