Changelog
AI assistant
Build this with Claude Code, Cursor or Copilot
Copy a Tessero-tuned prompt for Changelog, grounded in 16 real API signatures , into your IDE's AI. No chatbot, just exact context.
All notable changes are recorded here. The format follows
Keep a Changelog, and the project uses
Semantic Versioning (breaking changes are allowed in 0.x
minor releases and are always listed; see docs/operations/upgrading.md).
[Unreleased]
Added
-
Opt-in GPU-matching floating-point profile (
csrc/src/fpenv.c, declared intessero.h; featurefp_flush_profile). NVIDIA GPUs flush subnormal values to zero and expose no preserve mode, and NaN results can carry different bit patterns on different processors.tsr_fpenv_flush_enter/tsr_fpenv_flush_leaveswitch the calling thread to flush-to-zero for a scope and restore its previous state exactly;tsr_flush_subnormal_f32andtsr_canon_nan_f32normalise arrays in place with integer bit operations;tsr_nan_first_f32finds the first NaN, with f64 twins of the three array helpers. The flush scope is for f32 work only: NVIDIA GPUs keep f64 subnormals, so f64 inside a scope would diverge. Additive only: no existing function, default, golden or self-check changes, and nothing runs unless called. -
Reproducible-f32 transcendental primitives (
tsr_repro_rsqrt_f32,tsr_repro_exp2_f32,tsr_repro_exp_f32,tsr_repro_erf_f32;csrc/src/repro_f32.c, declared incsrc/include/tessero.h). A new, original contribution, NOT part of the ported NumPy/SciPy architecture. NumPy/SciPy transcendentals are accuracy-first and defer to the systemlibm/vendor math, which is not bit-reproducible across hardware, drivers, or SIMD width. These are reproducible by construction - built only from correctly-rounded f32 operations, giving one defined f32 bit-value on every SIMD width, thread count, and CPU ISA (and, via a mirrored GPU shader, the GPU). Proven bit-identical across scalar/AVX2/AVX-512/NEON/SVE, 1 vs 4 threads, and x86 == ARM (csrc/repro/ARCH_MATRIX.md); four frozen goldens + canonical spec incsrc/repro/GOLDENS.md. Minimax coefficients are from Cephes (public domain) and Abramowitz & Stegun 7.1.26; the reproducible construction and the resulting bit-identity are the contribution. The ~1,845 parity-gated NumPy/SciPy functions are unaffected. -
Reproducible
recipandtanhprimitives (tsr_repro_recip_f32,tsr_repro_tanh_f32), with goldensf7f9ec19and5b43f3d6incsrc/repro/GOLDENS.md. -
Reproducible composites (
tsr_repro_{sigmoid,gelu_tanh,softmax,rmsnorm,layernorm}_f32): one definition of each composition, built on the primitives. Goldens confirmed on two independent hosts, each scalar == AVX2 == 4-thread. -
Reproducible backward composites (gradients of
gelu_tanh,erf,sigmoid,tanh,rmsnorm,layernorm, and the normgammagradient) in the single headercsrc/include/tessero_repro_f32.h. Goldens recorded incsrc/repro/GOLDENS.md, approved on two hosts. -
Talario's GPU kernels mirror these definitions and match them bit for bit on twelve NVIDIA GPU models across four generations, Ampere to Blackwell (attested at 1024 x 1024 by Talario); other GPUs are not yet attested.
-
NumPy/SciPy parity program (
prompts/numpy-scipy-parity-program.md, ADR 0011, ADR 0012). Coverage is measured, not asserted.tools/parity/census.pylists every public symbol of NumPy 2.4.4 and SciPy 1.17.1, andtools/parity/report.pyclassifies each one from the code and the recorded fixture runs (docs/project/numpy-scipy-coverage.md).- Kernel function registry (ADR 0011). One C table of functions (ufunc, gufunc, distribution, routine,
random) interpreted at run time by both bindings. The extension registers
Tessero\Ext\Special,Stats,NpandRandom\Generatorfrom it at MINIT. The FFI façadesTessero\Special,Stats,Npand theGeneratormethods are generated from it (tools/parity/gen-facades.php). - scipy.special: every real-valued ufunc, from the code SciPy itself runs (xsf, Boost.Math and SciPy's
glue, vendored with their licences; see
THIRD_PARTY_NOTICES.md). - scipy.stats: the continuous and discrete distributions, on a port of
rv_continuous/rv_discrete(csrc/cxx/stats_dist.*: the public-method semantics and the generic QUADPACK/brentq/bisection fallbacks, with SciPy's QUADPACK and brentq vendored). Also descriptive statistics, correlation and hypothesis tests. A new classTessero\Stats\Distribution(andTessero\Ext\Distribution) providespdf...isf,stats,moment,entropy,intervalandrvs. - NumPy statistics, sets and searching (
Tessero\Np,Tessero\Ext\Np): median, quantile/percentile (all methods), nan-reductions, histogram/bincount/digitize, cov/corrcoef, unique and set operations, partition, lexsort, searchsorted, correlate/convolve. - NumPy array construction and manipulation (
Tessero\Np,Tessero\Ext\Np, ADR 0013): the split and stack families,concatenate,tile/repeat/resize,flip/roll/rot90, axis moves, broadcasting,append/insert/delete,diag/tri*/*_indices,*_like,take/compress/nonzero, index conversions,meshgrid,select/choose,pad(all modes). Also the numeric routines:outer/inner/kron/cross/tensordot,diff/gradient/trapezoid/unwrap,interp,logspace/geomspace/vander, thepoly*functions, the window functions,i0/sinc,nan_to_num, and the in-place writersput,putmask,place,copyto,fill_diagonalandput_along_axis. That is 118 functions, verified by NumPy fixtures on both backends. Integer results are exact above 2^53, includingunique, the set operations andquantile's discrete methods, which now keep the input's integer or bool dtype as NumPy does. - numpy.random.Generator distributions from NumPy's own
distributions.con the kernel PCG64. Streams are bit-identical to NumPy on both backends. The extension gains a statefulTessero\Ext\Random\Generator(defaultRng,integers,permutation,shuffle,getState/setState). - Fixtures generated by NumPy/SciPy for every registry function, replayed on both backends by
tools/parity/run-tests.php. They are generated in a pinned numerical environment (tools/parity/gen-fixtures.sh, ADR 0012), and every file records that environment. - Deviation rules (
docs/project/reference-deviations.md, with counts generated from the fixtures). These cover inputs where SciPy crashes or never returns, rows where SciPy's own value is undefined (found with AddressSanitizer or by history dependence), SciPy exceptions on invalid parameters where its contract says NaN, and a vectorised result that disagrees with SciPy's element-by-element one. tools/parity/claims-check.phpfails the build on coverage language or percentages that the generated pages do not back. CI runs the fixtures on both backends.- Coverage of real-world usage (
docs/project/usage-weighted-coverage.md).tools/parity/usage.pycounts how often the library code of 18 widely used scientific Python packages calls each NumPy and SciPy function, andreport.pyweights the verified coverage by those counts and lists the most-called functions that are not yet verified on both backends.
- Kernel function registry (ADR 0011). One C table of functions (ufunc, gufunc, distribution, routine,
random) interpreted at run time by both bindings. The extension registers
-
Beta tracking.
- The NumPy/SciPy coverage inventory
(
docs/project/numpy-scipy-coverage.md, 169 rows) is generated from the public API bytools/api-dump.phpandtools/gap_inventory.py, so it cannot claim a function that does not exist. - A gap analysis interprets the inventory.
- An open-issue list: 50 issues with acceptance criteria, kept in
.github/beta-issues.json.tools/beta-issues.phprenders it and imports it into GitHub withgh. - A live-testing checklist for Windows, macOS, arm64/musl, FPM, Laravel and long soaks.
- The NumPy/SciPy coverage inventory
(
-
Backend parity (ADR 0010,
docs/architecture/adr/0010-shared-kernels-and-vector-math.md):- The loop ufuncs moved from the extension into libtessero (
csrc/src/ufunc.c,tsr_ufunc_*). The FFI package gainsTessero\Math, with the same 74 functions, signatures,out:semantics and results asTessero\Ext\Math. - Memory mapping and
.npyheaders moved into libtessero (csrc/src/mmap.c:tsr_mmap_plan/open/flush/close,tsr_npy_header,tsr_npy_write_header). - The FFI package gains
NDArray::memmap(),openMemmap(),load($path, $mmapMode),save(),flush(),isMemmap(),isReadonly(),isWritable(),filename(), andTessero::info()['memory_mapped']. It checks open_basedir and refuses stream wrappers, like the extension. - The extension gains
.npyI/O:NDArray::load($path, ?$mmapMode),$a->save($path)(byte-identical tonumpy.save) andNDArray::openMemmap(), with Fortran-order files mapped with Fortran strides. - The extension reduces over a list of axes with
keepdims:sum prod min max any all mean var std, andargmin/argmaxwithkeepdims. Results are bit-identical to the FFI package. - Laravel:
Tessero::memmap(),load()andopenMemmap()on either backend.
- The loop ufuncs moved from the extension into libtessero (
-
Hardening:
- libFuzzer targets for the slice parser, the
.npyheader parser, memory-map planning and the JSON writer (csrc/fuzz/,make -C csrc fuzz), with seed corpora and oracles. - A standalone mutation driver for machines without libFuzzer
(
make fuzz-standalone). - Property-based tests (
tests/Unit/PropertyTest.php). - A cross-backend byte-parity test (
tests/Ext/BackendParityTest.php). - A soak harness (
tools/soak.php). - A nightly workflow: 30 min of fuzzing per target, 5.5 h soaks per backend, property tests ×50.
docs/operations/soak-testing.md, the memory-safety review, a Safeguards table in the memory model, and crash-debugging steps (core dumps, gdb, ASan builds of both bindings) in troubleshooting.tests/Unit/MemorySafetyTest.phpandext/tests/014-memory-safety.phpt.
- libFuzzer targets for the slice parser, the
-
Performance:
- Vectorised
exp/logfor float64 and float32 (csrc/src/vmath.h: branch-free, fdlibm algorithms, < 1 ulp).expon 1M values went from 9.3 ms to 1.2 ms. - Real-input FFT (
tsr_rfft,tsr_irfft: half-length transform);Fft::rfftandirfftuse it. sortuses an MSD + LSD hybrid radix sort with parallel buckets: 1M float64 went from 53 ms to 30 ms.
- Vectorised
-
Bench cases
log 1Mandrfft 2^20 real..npyfixtures written by NumPy (tests/fixtures/generate_npy.py). -
Error code -9
TSR_EIOfor operating-system I/O errors.
Earlier in this cycle
- Universal functions
Tessero\Ext\Math: 74 element-wise functions with NumPy broadcasting, type resolution andout:arrays.- Kernel family: bit-identical to the
NDArraymethods. - Loop family:
cbrt exp2 trunc arcsinh arccosh arctanh degrees radians erf erfc gamma lgamma signbit fmax fmin copysign nextafter logaddexp logaddexp2 fmod. - Checked against NumPy/SciPy (64 cases).
- Kernel family: bit-identical to the
- Memory-mapped arrays
NDArray::memmap($file, $mode, $shape, $dtype, $offset): the modes of numpy.memmap, shape inference,flush()/flush(sync: true), and the mapping released after the last view. - ADR-0009, guide pages Universal functions and Memory-mapped arrays.
Security
From the memory-safety review (docs/project/memory-safety-review.md):
- Element-count overflow (both backends, and C reshape). A shape whose
element count wraps around 64 bits could describe a small allocation as a
huge array. Such shapes could come from
fromBytes, fromunserialize()of a cached or queued payload, fromzeros/empty, or fromreshape. Negative dimensions with a positive product were also accepted when unserializing. All sizes are now overflow-checked before allocating. - FFI
Generator::shuffleon a read-only memory map wrote throughPROT_READpages and crashed the process. It now throws. - Preload header in the shared temp directory was written under a predictable name and reused if present. It is now created exclusively (0600) and deleted after loading.
- Library version check. The FFI package refuses a libtessero binary from a different version instead of risking mismatched layouts.
- Released memory is never passed to C. This covers
flush()after a mapping is closed and LAPACK pointers during PHP's shutdown destructor pass.
Fixed
-
Registry routine errors now use Tessero's exception classes. On both backends, an index error from a registry routine is
IndexError(IndexExceptionin the extension), and a dtype or type error isDTypeError(DTypeException); these used to be PHP'sValueError. FFI messages name the method in camelCase, as the extension's do (Np::putAlongAxis). A float given where an integer is required is a type error, as in NumPy. -
Np::unique, the set operations,quantileand 0-d integer results lost precision above 2^53. They are exact now. -
Np::searchsortedaccepted a sorted array that is not 1-D. It now raises, as NumPy does. -
Np::bincountrejected a PHP list of floats. It now accepts one and converts it withint(), as NumPy does for a Python list. -
Reference values depended on the generating machine. On AVX-512 CPUs, NumPy's float64
exp/log/power/expm1/log1p/sinh/arcsin/cbrtdiffer from the C library by an ulp on some inputs. All fixtures, including the olderparity.jsonandufunc.json, are now generated with those dispatch targets disabled (ADR 0012). Hand-set tolerances that only absorbed the difference were removed: random draws are compared exactly, andcov/corrcoef/correlate/convolveuse the default tolerance. -
Math::cbrtreturns the C library's value, as NumPy does outside AVX-512 (cbrt(27) = 3.0000000000000004). The Newton correction that matched AVX-512 NumPy was removed. -
Memory safety of the new numerical code (an independent verification and a sanitizer fuzz pass over all 539 registry functions, about 370 000 calls under AddressSanitizer and UBSan):
- Negative trimming proportions read out of bounds, and could crash PHP (
trim_mean,trimboth,trim1, andlevene/flignerwithcenter='trimmed'). lmomentwith a huge order read out of bounds.cumulative_sum/cumulative_prodwithinclude_initialother than 0/1 returned uninitialised memory.bincountwith ±inf indexed memory wildly.- C++ exceptions (allocation failures, oversized requests) terminated the PHP process instead of raising. Every C++ entry point is now guarded, including callbacks from the vendored QUADPACK and brentq.
- Parameters that size an array now raise
MemoryErrorbeyond the memory budget, or the machine's RAM when none is set. - NaN integer parameters, oversized axis and bin values, and integer overflow in
fisher_exactandcramervonmises_2sampare now handled; every float→int conversion is defined. tests/Unit/AdversarialInputTest.phppins every case on both backends.
- Negative trimming proportions read out of bounds, and could crash PHP (
-
Where SciPy itself crashes, Tessero returns NaN; where SciPy raises, Tessero raises. Mathieu functions for |q| > 2·10⁷ or m > 3·10⁸;
hyp2f1for |c| ≥ 2⁵³ on its recurrence;hyp1f1with |b| ≥ 2⁵² on its integer-b path (these three crash SciPy).levy_stablewith α = 1 far in the tail raises (SciPy:RecursionError). A Boost overflow raises (SciPy:OverflowError), for example inncf.pdfandncf.isf. -
Integer inputs follow NumPy's int64 arithmetic.
ptp,nansum,nanprod,nanmin,nanmax, the cumulative functions,partition,correlateandconvolveare exact above 2⁵³ and wrap on overflow as NumPy does.correlate/convolvereturn NumPy's integer dtype. -
NumPy input validation:
bincountrejects float arrays and a non-integerminlength,ptprejects booleans, andaveragechecks weight shapes (_weights_are_valid). -
Vendored SciPy code on inputs where SciPy itself fails (
THIRD_PARTY_NOTICES.md). The spheroidal wave functions' work arrays are sized for their loops (SciPy overruns them).qstarno longer reads before its array for m = 0. cdflib's non-central t sum (nctdtridf,nctdtrinc) stops only when it provably cannot terminate, instead of looping forever. -
sort()/argsort()ignored kernel errors (both bindings). When the memory budget ran out while the kernel allocated sort scratch space, the result came back unsorted (or with zeroed indices) instead of throwing. Both bindings now check every return code and throwMemoryErrororMemoryException. The FFI sparse constructors and products (tsr_dense_to_csr,tsr_csr_matvec,tsr_csr_matmat) are now checked too. Regression tests are inMemorySafetyTestand014-memory-safety.phpt(finding F-9 in the memory-safety review). -
Slice parser, found by fuzzing:
- A blank item (
"0, ,1") read past its comma, an out-of-bounds read in C. Blanks are now bounded, and"1 , 2"(blanks around commas) parses as it should; it was wrongly rejected before. - Steps near 2^62 overflowed the element-count and stride arithmetic, in C and in the FFI package's PHP slicer.
- A blank item (
-
FFI
NDArraybinary and unary operations without:wrote straight intoouteven whenoutpartially overlapped an input (a shifted view of the same buffer), so they read already-overwritten values. They now go through a temporary, as the extension does. -
cloneof an FFI array now copies the data (it shared the buffer), matching the extension. Writing a clone no longer changes the original or a memory map. -
Multi-axis
mean/var/stdin the FFI package now merge the axes in ascending order whatever order they are listed in, so results do not depend on the listing order. -
mean,varandstdcompute exactly as NumPy does (two-pass pairwise) on both backends;cbrtcorrects glibc's 1-ulp error on exact cubes.
Verification record (reference machine: Linux x86-64, 2 cores, AVX-512, PHP 8.4.21, GCC 13, NumPy 2.4.4)
- C: 270 checks, optimised, ASan + UBSan, Clang without OpenMP.
- Fuzzing: about 13 million mutated inputs across the four targets under ASan + UBSan (standalone driver), clean after the two slice-parser fixes. libFuzzer itself could not be installed here (package downloads blocked); CI runs it.
- FFI suites: 706 tests in all three JIT modes (JIT off, function, tracing).
- Extension: 14 phpt files and 505 parity/ufunc cases, also under ASan + UBSan. Unit, extension and backend-parity suites together: 1 214 pass, 26 FFI-only skipped. Preload end-to-end passes.
- Laravel bridge: 5 tests on both backends. Documentation examples: 59 checks with both backends.
- Soak (
tools/soak.php): 30 minutes per backend, running concurrently.- Extension, JIT off: 785 608 iterations.
- FFI package, tracing JIT: 475 522 iterations.
- Result: native and mapped bytes back at 0 after every iteration; heap slope 0; RSS slope +13.7 / -5.2 KiB/min (thresholds 16 / 64); no descriptor growth. PASS.
- An earlier 11-minute run of both backends was also clean.
- Not run: the Windows memory-map path (
csrc/src/mmap.c, Win32 API, never compiled), the CI matrix, 24-72 h soaks.
[0.2.0] - 2026-09-26
Added
- Native extension
ext-tessero(Tessero\Ext\NDArray,Engine,Operand):- Arrays: operators
+ - * / ** %, slicing via$a['…'], boolean masks and integer-list indexing,foreach(native iterator),count(),clone,==. - Serialisation:
json_encode/toJson()(byte-identical tojson_encodewithJSON_PRESERVE_ZERO_FRACTION),serialize(). - Maths: reductions along one axis,
matmul,sort,where. Engine:linprog,milp, MDP solvers, FFT and NumPy-identicalrandom/normal/integers.- INI settings
tessero.threads,tessero.memory_budget,tessero.epsilon, re-applied at every request start. - Kernel symbols are kept private (visibility + linker version script), so the extension coexists with the FFI library.
- Arrays: operators
- Linear and mixed-integer programming in C:
Optimize\LinearProgramming::linprog/milpwith SciPy conventions (bounds, status codes, marginals),LinprogResult,MilpResult::gap(). - Markov decision processes in C:
Mdp\MarkovDecisionProcesswithfromDense,fromSparse,fromTransitions,valueIteration,policyIteration(exact or modified),evaluate,qValues,finiteHorizon,simulate. - OpenMP threads for large element-wise kernels and MDP sweeps
(
Tessero::setThreads,Engine::setMaxThreads). Results are identical for any thread count. tsr_arraymetadata in the ABI with a C slice parser, broadcasting helpers and a JSON writer.NDArray::sliceNative(),toJson(),meta(),__serialize/__unserialize.tsr_reduce_mid(fast reductions along non-last axes),tsr_cumulative,tsr_pcg64_shuffle, error codes -6, -7, -8.- Laravel package
tessero/laravel: service provider,Tesserofacade,AsNDArray/TensorCastEloquent casts (JSON or exact binary),NumericArrayvalidation rule,response()->ndarray(),tessero:doctor, settings re-applied per Octane request/task and queued job. tessero info --json.tessero doctorreports the kernel, SIMD, OpenMP and the extension.- Documentation site (MkDocs): user guide, operations manual (deployment,
configuration, memory and threads, monitoring, troubleshooting, runbooks,
security, upgrading, Docker), architecture (kernel, memory model, bindings,
numerical methods, solvers, testing, 8 ADRs), generated API reference, C ABI
and error codes.
tests/docs/examples.phpchecks the numbers in the guides. - Project files:
CONTRIBUTING.md,GOVERNANCE.md,CODE_OF_CONDUCT.md,SECURITY.md,RELEASING.md,MAINTAINERS.md. - Reference Docker image (
docker/Dockerfile). - Benchmarks for
linprogand MDP solvers.
Changed
NDArrayextendsTessero\Internal\OperandBase, which isTessero\Ext\Operandwhen the extension is loaded, so FFI arrays gain operators.NDArrayconstructor-promoted properties are no longerreadonly(needed by__unserialize).- LP/MILP workspaces are allocated with
tsr_allocand count against the memory budget. - MILP reports status 1 (limit) instead of pruning a subtree whose relaxation hit the iteration limit or numerical trouble, and stops with an error on out-of-memory instead of pruning.
Fixed
- Signed integer overflow in int cumulative products, sums, products and int64 matmul is now defined two's-complement wrap-around (was UB, flagged by UBSan).
- Nelder-Mead produced wrong iterates under
opcache.jit=function(PHP JIT miscompile; the loop counter now lives in a property; seedocs/project/upstream-bugs.md, issue 4). - Stacked
Linalg::det/slogdetcould return a wrong value underopcache.jit=tracingafter a long warm-up (PHP JIT defect, not yet isolated). LAPACK pivots are now copied out ofCDatain one call (issue 5).
Security
- LP/MILP reject NaN/±INF in
c,A,b(and NaN bounds) instead of returning undefined results. - MDP constructors and extension solvers reject non-finite rewards and
probabilities and malformed CSR input (
indptrnot starting at 0, decreasing, or pointing past the data), which could previously read out of bounds in the extension.
Removed
- The operator-only extension
tessero_ops(replaced byext-tessero).
Verification record
Run on the reference machine (Linux x86-64, 2 cores, AVX-512, GCC, PHP 8.4.21 NTS, NumPy 2.4.4, SciPy 1.17.1):
- C: 173 checks, optimised and under ASan + UBSan.
- FFI package: unit, numerics, solvers, parity (258 cases) and fuzz (300)
suites, with the JIT off,
functionandtracing. - Extension: 10 phpt files and the extension parity suite (439 run, 26 FFI-only skipped), also under ASan + UBSan.
- Laravel bridge tests on both backends. Documentation examples. FPM preload e2e.
- Not run here (no network access to GitHub/Packagist, no container daemon): the CI matrix on other platforms and PHP versions, the service provider inside a real Laravel application, and the Docker image build.
[0.1.0] - 2026-09-26
Added
libtesseroC kernel: allocator with budget, element-wise ops for 7 dtypes with SIMD dispatch, pairwise reductions, radix sort, mixed-radix + Bluestein FFT, PCG64/SeedSequence/ziggurat RNG (NumPy-identical), CSR with CG/BiCGSTAB.- FFI package:
NDArray(views, slicing, broadcasting, NEP 50 promotion),Linalg(LAPACKE),Fft,Random\Generator,Sparse\CsrMatrix,Optimize(BFGS, L-BFGS-B, Nelder-Mead, brentq, newton, root, least squares, curve_fit),Io\Npy. - FPM preload support,
tessero doctor, parity and fuzz suites, benchmarks, CI configuration. - Optional
tessero_opsextension for operators.