# Changelog & Release Notes Source: https://numpyts.dev/changelog ## numpy-ts v1.6.0 Compile-time `dtype` typing across the public API. `NDArray` now tracks its dtype through nearly every operation at zero runtime cost, so results report their exact dtype (`add(int32Arr, float32Arr)` → `NDArray<'float64'>`) and element access is typed per dtype (`bigint` for `int64`/`uint64`, `Complex` for complex, `number` otherwise). See [Compile-time dtype tracking](/next/guides/typescript-patterns#compile-time-dtype-tracking). ### Breaking * Floating-point reductions now preserve narrow floats instead of widening to `float64`, matching NumPy. `std`, `var`, `median`, `percentile`, `quantile`, and the `nan*` family return `float16`/`float32` for `float16`/`float32` input; `var`/`std` on `complex64` return `float32`; `round`/`around` upcast `bool → float16`. Add an explicit `.astype('float64')` if you relied on the old output. ### Notes * Validation test suite pinned to NumPy 2.4 (`numpy>=2.4,<2.5`). ## numpy-ts v1.5.0 Performance release. A large batch of element-wise and reduction operations move from JavaScript into hand-written Zig/WASM SIMD kernels, and the project's tooling migrates from npm to pnpm. numpy-ts is now, on average [1.25x faster than native NumPy](/v1.5.x/performance/vs-numpy)! ### Breaking * Dropped support for Node 20, which has reached [end-of-life](https://nodejs.org/en/about/previous-releases). numpy-ts now requires Node 22 or later. ### New WASM SIMD kernels The following operations now run through vectorized WASM kernels (previously JS fallbacks), with integer and complex dtype variants where applicable: * Trigonometric: `sin`, `cos`, `tan` * Hyperbolic: `sinh`, `cosh`, `tanh` * Inverse trig / hyperbolic: `arctan`, `arctan2`, `arcsinh`, `arccosh`, `arctanh` * Exp / log family: `exp`, `exp2`, `expm1`, `log`, `log1p`, `logaddexp`, `logaddexp2` * Other element-wise: `power`, `heaviside`, `modulo`, `sinc` * Signal: `convolve`, `correlate` (relaxed-FMA variants) * Strided reductions: `argmin`, `argmax`, `prod` * Cumulative: `cumsum`, `cumprod` These share new common SIMD utilities for transcendental functions and complex arithmetic, and redundant relaxed-FMA kernel variants were removed. ### Tooling & CI * Migrated the toolchain from npm to pnpm (workspaces, CI, and `pnpm publish` via OIDC Trusted Publishing). Thanks [@cyfung1031](https://github.com/dupontcyborg/numpy-ts/pull/128)! * Fixed missing `DType` type export from `numpy-ts` entrypoint, thanks to [@OSquiddy](https://github.com/dupontcyborg/numpy-ts/pull/133)! * Validation tests moved from a per-test NumPy process to a per-worker NumPy "server" for faster oracle comparisons. * Dependency bumps across dev and bench tooling. ## numpy-ts v1.4.0 NumPy-compatibility release. A wide set of public functions gain the kwargs / overloads they were missing relative to NumPy, and several signatures accept `ArrayLike` so plain `number[]` no longer needs `np.array(...)` wrapping. Small (2-5%) performance regressions due to additional correctness checks which will be optimized in a later release. ### Breaking * `meshgrid` (`numpy-ts/core` only) - default `indexing` is now `'xy'` (NumPy-compatible). Previously `core/meshgrid` had no options and effectively produced `'ij'` shapes. If you import `meshgrid` from `numpy-ts/core` and relied on the old behavior, pass `{ indexing: 'ij' }` explicitly. Importers from `numpy-ts` (the full API) are unaffected - that wrapper already defaulted to `'xy'`. ### New kwargs / overloads * Reductions `where` / `initial` / `dtype` - `sum`, `prod`, `max`/`amax`, `min`/`amin`, `all`, `any` now accept a `ReductionOpts` bag as a 4th argument for masked reductions, seeded accumulation, and dtype-controlled accumulation. * `argmin` / `argmax` - accept `keepdims?: boolean` (NumPy 1.22+). * `ptp` - `axis` widened to `number | number[]`, consistent with the other reductions. * `average` - new `returned?: boolean`. When `true`, returns `[avg, sum_of_weights]` matching `np.average(..., returned=True)`. * `concatenate` / `concat` - `axis` widened to `number | null`. `axis=null` flattens each input and concatenates along axis 0. * `diff` - new `prepend?` and `append?` arguments (`ArrayLike`). * `interp` - new `period?: number` for circular/phase data; `xp` is normalized into `[0, period)` and wraps at the boundary. * `gradient` - per-axis spacing can now be a 1-D coordinate array (non-uniform second-order central-difference), not just a scalar. * `apply_along_axis` - accepts trailing `...args` forwarded to `func1d`, matching `np.apply_along_axis(func1d, axis, arr, *args)`. * `pad` - `pad_width` and `constant_values` accept the full NumPy broadcast forms (scalar, `[n]`, `[before, after]`, per-axis scalars, `[[b, a]]`, per-axis pairs, mixed). New exported types `PadWidthArg` and `PadValueArg`. * `meshgrid` - `MeshgridOptions { indexing?, sparse?, copy? }` bag. `sparse: true` returns open grids; `copy: false` returns broadcast views. * `block` - accepts nested sequences (`NestedNDArrays[]`) with NumPy's innermost-to-outer axis semantics. Flat-list calls behave identically. ### `ArrayLike` widening These now accept plain `number[]`, nested arrays, and scalars in addition to `NDArrayCore`. Behavior for existing NDArray callers is unchanged. * `take` - `indices: ArrayLike` (scalar, 1-D, 2-D, or 3-D). Output preserves the shape of `indices`. * `where` - `x` and `y` are `ArrayLike`. Also fixes a falsy-zero bug: `where(cond, x, 0)` now honors the `0` branch instead of falling through to the indices form. * `select` - `condlist`, `choicelist`, and `defaultVal` widened to `ArrayLike` / `ArrayLike[]`. * `bincount`, `digitize`, `histogram`, `histogram2d`, `histogramdd` - data, weights, and bin arguments are `ArrayLike`. ## numpy-ts v1.3.2 Patch release with a couple of bug fixes and improvements: * Fixed issue with `[Symbol.dispose]` not working properly on Safari & old runtimes * Added `wasmFreeBytes` to public exports to allow memory usage checks ## numpy-ts v1.3.1 Patch release with new WASM kernels, performance improvements, and export fixes. * **New WASM kernels**: `conj`, `deg2rad`, `modf`, `packbits`, `nanquantile`, `argwhere`, and `float32` SVD * Optimized WASM SIMD for `argmin`/`argmax` int16/uint16 paths * Reduced WASM base threshold for earlier WASM dispatch on smaller arrays * **Export fixes**: `configureWasm`, `wasmConfig`, and `hasFloat16` now exported from `core` and `full` entrypoints * Benchmark runner refactored from if-else chain to dictionary lookup ## numpy-ts v1.3.0 **This is the first release where numpy-ts is faster than native NumPy on average** — **1.13x** across 7,200 benchmarks, leading in 12 of 18 categories. See the [Performance Overview](./v1.3.x/performance/overview) for the full breakdown. v1.3.0 gets there by moving array storage into WebAssembly linear memory for true zero-copy WASM kernel execution, ships a new memory-management API, and brings a stack of NumPy compatibility fixes. * **WASM-backed array storage**: Arrays now live directly in a shared WebAssembly memory pool (default 256 MiB). WASM kernels operate on these pointers with **zero copy-in/copy-out overhead**. * Bandwidth-bound operations (`add`, `multiply`, bitwise) see up to **2.6x improvement** * Compute-bound operations (`matmul`, `svd`) see minimal change (already kernel-dominated) * Graceful fallback to JS `TypedArray`s when the pool is full * Comes with new `dispose()` and `configureWasm()` methods for manual memory management and pool configuration * **Browser bundle is ESM**. Load via ` ``` ```html jsdelivr theme={null} ``` ```html unpkg theme={null} ``` ## Choose your entry point numpy-ts ships two entry points. Pick the one that fits your use case: | Entry Point | Import Path | Returns | Bundle Size | Use When | | ----------- | --------------- | ------------- | ------------ | --------------------------------------------------------------------------- | | **Full** | `numpy-ts` | `NDArray` | \~200-300 KB | You want method chaining, the complete API, and file I/O on server runtimes | | **Core** | `numpy-ts/core` | `NDArrayCore` | \~10-40 KB | Bundle size matters; you only need specific functions | See the [Tree-Shaking guide](.//tree-shaking) for a detailed comparison. ### Full library (`numpy-ts`) The default entry point. Functions return `NDArray`, which supports method chaining (`.add()`, `.reshape()`, `.T`, etc.). On server runtimes (Node.js, Bun, Deno), file I/O operations (`loadNpy`, `saveNpy`, `loadNpzFile`, `savez`, `loadtxt`, `savetxt`) are available directly. In browsers, calling file I/O functions throws a clear error. ```typescript theme={null} import * as np from 'numpy-ts'; const a = np.array([1, 2, 3, 4]); const b = a.add(10).reshape([2, 2]).T; // Method chaining // File I/O — works on Node.js, Bun, and Deno const arr = await np.loadNpy('data.npy'); await np.saveNpy('output.npy', arr); ``` Best for applications, scripts, and projects where developer experience matters more than bundle size. ### Tree-shakeable core (`numpy-ts/core`) Functions return `NDArrayCore` (no method chaining). Your bundler (webpack, Vite, esbuild, Rollup) will only include the functions you actually import. ```typescript theme={null} import { array, add, reshape, transpose } from 'numpy-ts/core'; const a = array([1, 2, 3, 4]); const b = transpose(reshape(add(a, 10), [2, 2])); ``` Best for libraries, frontend apps where every kilobyte counts, and when you only need a handful of functions. Importing just `array` and `add` from `numpy-ts/core` results in a bundle around **10 KB** minified. The full entry point is always \~200-300 KB regardless of what you use. ## Next steps Create arrays, run operations, and explore the API in 5 minutes. Coming from Python NumPy? See what is different in TypeScript. Understand how the two entry points affect your bundle size. # Memory Management Source: https://numpyts.dev/v1.5.x/guides/memory-management Starting in `v1.3.0`, numpy-ts arrays are backed directly by WebAssembly linear memory. This eliminates the copy-in / copy-out overhead that previously dominated WASM kernel execution time and unlocks significant speedups for bandwidth-bound operations. For most users this is fully transparent — but a small set of new APIs (`.dispose()`, the `using` keyword, and `configureWasm()`) gives you precise control when you need it. ## WASM-backed array storage All arrays created via `np.zeros()`, `np.ones()`, `np.array()`, `np.arange()`, etc. are now allocated from a shared WebAssembly memory pool (default: **256 MiB**). WASM kernels operate directly on these pointers — there is no copy when an operation runs. **What you get for free:** * Bandwidth-bound operations (add, multiply, bitwise) see up to **2.6x improvement** * Chained operations benefit most: intermediate results stay in WASM memory across kernel calls without round-tripping to JS * Compute-bound operations (matmul, SVD) see minimal change (\~1-2%) — they were already dominated by the kernel itself, not the copy **When the pool is full:** * Allocations transparently fall back to regular JS `TypedArray`s * Operations on JS-backed arrays still work correctly (using the previous copy-in / copy-out path) * No exceptions, no surprises — the only observable effect is reduced throughput for very large workloads ## `.dispose()` — eager cleanup `NDArray` (and the underlying `ArrayStorage`) now expose a `.dispose()` method that immediately frees the WASM memory backing an array. ```ts theme={null} // Default — let GC handle it const result = np.add(a, b); // ... use result ... // memory freed when GC collects `result` // Manual — free immediately const result = np.add(a, b); // ... use result ... result.dispose(); // WASM memory freed now ``` In normal usage you don't need to call this — a `FinalizationRegistry` frees WASM memory when arrays are garbage collected. But manual disposal is useful when: * **Tight loops** create many short-lived intermediate arrays — calling `.dispose()` keeps the pool from filling up faster than GC can drain it * **Benchmarks and performance-critical code** where you want deterministic memory behavior * **Long-running processes** where GC latency would otherwise let memory pressure accumulate You generally **don't** need it for: * Normal application code — GC handles cleanup automatically * Small scripts — the 256 MiB pool won't fill up * Arrays returned to callers — let the caller manage lifetime ### `Symbol.dispose` and the `using` keyword All arrays implement `[Symbol.dispose]`, enabling the `using` keyword for automatic scope-based cleanup: ```ts theme={null} { using result = np.add(a, b); // ... use result ... } // result.dispose() called automatically at end of block // Works in loops too — each iteration auto-disposes for (let i = 0; i < 1000; i++) { using temp = np.multiply(arr, scalar); arr = np.add(temp, bias); // temp is disposed at end of each iteration } ``` **Browser compatibility:** The `using` keyword requires either native runtime support (Node 22+, Chrome 134+, Firefox 132+) or a transpiler that downlevels `using` (TypeScript, esbuild, Babel, SWC). On runtimes without native `Symbol.dispose` (e.g. Safari), numpy-ts installs a polyfill using the same `Symbol.for("Symbol.dispose")` key that these transpilers emit, so transpiled `using` statements work correctly everywhere. You can also call `.dispose()` directly on any runtime. ## `configureWasm()` — pool sizing The WASM memory pool size can be configured at startup via `configureWasm()`. It must be called **before any array operations** — once WASM memory is initialized, it cannot be resized. ```ts theme={null} import { configureWasm } from 'numpy-ts'; // Increase WASM memory to 512 MiB (default: 256 MiB) configureWasm({ maxMemory: 512 * 1024 * 1024 }); // Now use numpy-ts as normal const a = np.zeros([1000, 1000]); ``` **Options:** | Option | Type | Default | Description | | ------------- | -------- | ---------------------------------- | ------------------------------------------------------------------ | | `maxMemory` | `number` | `256 * 1024 * 1024` (256 MiB) | Total WASM linear memory in bytes | | `scratchSize` | `number` | `maxMemory / 16`, capped at 32 MiB | Scratch region for temporary kernel buffers (e.g. dtype promotion) | **Constraints:** * Must be called before any array creation or operation (throws otherwise) * `maxMemory` and `scratchSize` must both be positive **When to use:** * **Memory-constrained environments** (embedded, serverless) — reduce from the 256 MiB default to lower the resident footprint * **Large-array workloads** — increase the pool to keep more arrays in WASM memory and avoid JS fallback ## Scratch heap fallback Temporary input buffers for WASM kernels — e.g. integer→float conversion for `sin`, `cos`; `float16`→`float32` promotion — previously used a fixed scratch region. Large arrays (>500K elements with type conversion) could hit a hard out-of-memory error. In v1.3.0, when scratch space is exhausted, allocations transparently fall back to the persistent WASM heap, with the temporary buffers freed automatically on the next kernel call. **No user action is needed** — operations that previously failed now just work. ## Putting it all together ```ts theme={null} import { configureWasm } from 'numpy-ts'; import * as np from 'numpy-ts'; // 1. (Optional) Configure pool size at startup configureWasm({ maxMemory: 1024 * 1024 * 1024 }); // 1 GiB // 2. Tight loop — manually dispose intermediates const data = np.random.randn(1000, 1000); let acc = np.zeros([1000, 1000]); for (let i = 0; i < 100; i++) { using temp = np.multiply(data, i); // auto-dispose at iteration end acc = np.add(acc, temp); } // 3. Free the result when done acc.dispose(); ``` # NumPy Migration Guide Source: https://numpyts.dev/v1.5.x/guides/numpy-migration A side-by-side guide for Python NumPy users moving to numpy-ts in TypeScript/JavaScript. If you already know NumPy in Python, you will feel at home with numpy-ts. This guide covers the key differences so you can migrate existing code or switch between the two with confidence. ## Importing ```python Python theme={null} import numpy as np ``` ```typescript TypeScript theme={null} import * as np from 'numpy-ts'; // Or, for tree-shakeable imports (no method chaining): import { array, add, reshape } from 'numpy-ts/core'; ``` ## Creating arrays ```python Python theme={null} a = np.array([1, 2, 3]) b = np.array([[1, 2], [3, 4]], dtype=np.float32) z = np.zeros((3, 3)) r = np.arange(0, 10, 2) l = np.linspace(0, 1, 5) I = np.eye(3) ``` ```typescript TypeScript theme={null} const a = np.array([1, 2, 3]); const b = np.array([[1, 2], [3, 4]], 'float32'); const z = np.zeros([3, 3]); const r = np.arange(0, 10, 2); const l = np.linspace(0, 1, 5); const I = np.eye(3); ``` Shape arguments use **arrays** in numpy-ts, not separate arguments or tuples. Write `np.zeros([3, 3])`, not `np.zeros(3, 3)` or `np.zeros((3, 3))`. ## Arithmetic ```python Python theme={null} c = a + b d = a * b e = a ** 2 f = np.sqrt(a) g = np.dot(a, b) ``` ```typescript TypeScript theme={null} const c = np.add(a, b); // or a.add(b) const d = np.multiply(a, b); // or a.multiply(b) const e = np.power(a, 2); // or a.power(2) const f = np.sqrt(a); // or a.sqrt() const g = np.dot(a, b); ``` numpy-ts does not overload JavaScript operators. Use `np.add(a, b)` or `a.add(b)` instead of `a + b`. Method chaining (`.add()`, `.reshape()`, etc.) is available on `NDArray` from the full entry point. See [Tree-Shaking & Bundle Size](.//tree-shaking) for details on the two entry points. ## Indexing and slicing This is the biggest syntax change. Python uses bracket notation with colons; numpy-ts uses a `.slice()` method with string arguments. ```python Python theme={null} a = np.arange(12).reshape(3, 4) a[0, 2] # Single element a[0:2, :] # First 2 rows a[:, 1:3] # Columns 1-2 a[::-1, :] # Reverse rows a[-1, :] # Last row a[1] # Second row (implicit full slice on remaining dims) ``` ```typescript TypeScript theme={null} const a = np.arange(12).reshape([3, 4]); a.item(0, 2); // Single element a.slice('0:2', ':'); // First 2 rows a.slice(':', '1:3'); // Columns 1-2 a.slice('::-1', ':'); // Reverse rows a.slice('-1', ':'); // Last row a.slice('1', ':'); // Second row ``` ### Slicing syntax reference | Python | numpy-ts | Description | | ----------- | ------------------------------------------------- | ------------------- | | `a[i]` | `a.item(i)` (scalar) or `a.slice('i', ':')` (row) | Single index | | `a[i, j]` | `a.item(i, j)` | Single element | | `a[0:5]` | `a.slice('0:5')` | Range | | `a[::2]` | `a.slice('::2')` | Every other element | | `a[::-1]` | `a.slice('::-1')` | Reverse | | `a[0:5, :]` | `a.slice('0:5', ':')` | Multi-axis slice | | `a[:, 1:3]` | `a.slice(':', '1:3')` | Column slice | ## Reshaping ```python Python theme={null} b = a.reshape(3, 4) # Separate args c = a.reshape((3, 4)) # Tuple arg d = a.T # Transpose e = a.flatten() f = np.squeeze(a) ``` ```typescript TypeScript theme={null} const b = a.reshape([3, 4]); // Array arg (only option) const c = a.reshape([3, 4]); // Same const d = a.T; // Transpose (property, not method) const e = a.flatten(); const f = np.squeeze(a); ``` ## Reductions ```python Python theme={null} np.sum(a) np.sum(a, axis=0) np.mean(a, axis=1) np.std(a) np.var(a) np.min(a) np.max(a) np.argmax(a, axis=0) ``` ```typescript TypeScript theme={null} np.sum(a); np.sum(a, 0); // axis is positional, not keyword np.mean(a, 1); np.std(a); np.variance(a); // 'var' is reserved in JS np.min(a); np.max(a); np.argmax(a, 0); ``` ## Linear algebra ```python Python theme={null} np.linalg.inv(A) np.linalg.det(A) np.linalg.eig(A) np.linalg.svd(A) np.linalg.norm(v) np.linalg.solve(A, b) np.linalg.qr(A) np.linalg.cholesky(A) np.linalg.lstsq(A, b) np.linalg.matrix_rank(A) ``` ```typescript TypeScript theme={null} np.linalg.inv(A); np.linalg.det(A); np.linalg.eig(A); np.linalg.svd(A); np.linalg.norm(v); np.linalg.solve(A, b); np.linalg.qr(A); np.linalg.cholesky(A); np.linalg.lstsq(A, b); np.linalg.matrix_rank(A); ``` The `linalg` namespace is identical between NumPy and numpy-ts. No changes needed for these calls beyond the import. ## Random ```python Python theme={null} np.random.seed(42) np.random.random((3, 3)) np.random.normal(0, 1, (1000,)) np.random.randint(0, 10, size=(5,)) np.random.choice(arr, size=5, replace=False) np.random.shuffle(arr) ``` ```typescript TypeScript theme={null} np.random.seed(42); np.random.random([3, 3]); np.random.normal(0, 1, [1000]); np.random.randint(0, 10, [5]); np.random.choice(arr, 5, false); np.random.shuffle(arr); ``` As of v1.2.0, `numpy-ts` uses the same RNGs as NumPy (MT19937 for the legacy API, PCG64 for the modern API). Random outputs now match NumPy exactly for all distributions given the same seed. ## FFT ```python Python theme={null} spectrum = np.fft.fft(signal) freqs = np.fft.fftfreq(n, d=1/256) shifted = np.fft.fftshift(spectrum) ``` ```typescript TypeScript theme={null} const spectrum = np.fft.fft(signal); const freqs = np.fft.fftfreq(n, 1/256); const shifted = np.fft.fftshift(spectrum); ``` ## File I/O ```python Python theme={null} arr = np.load('data.npy') np.save('output.npy', arr) data = np.load('data.npz') np.savez('output.npz', x=arr1, y=arr2) table = np.loadtxt('data.csv', delimiter=',') np.savetxt('out.csv', arr, delimiter=',') ``` ```typescript TypeScript theme={null} import * as np from 'numpy-ts'; const arr = await np.loadNpy('data.npy'); await np.saveNpy('output.npy', arr); const data = await np.loadNpzFile('data.npz'); await np.savez('output.npz', { x: arr1, y: arr2 }); const table = await np.loadtxt('data.csv', { delimiter: ',' }); await np.savetxt('out.csv', arr, { delimiter: ',' }); ``` `numpy-ts/node` is deprecated as of v1.2.0 (it still works as an alias). All 22 file I/O functions are now available from the main `numpy-ts` entry point on Node, Bun, and Deno. ## Common gotchas | Gotcha | Python (NumPy) | TypeScript (numpy-ts) | Notes | | ------------------------ | ------------------------------------ | ------------------------------------------ | ---------------------------------------------------------------------- | | **Operator overloading** | `a + b`, `a * b` | `np.add(a, b)` or `a.add(b)` | JS does not support operator overloading | | **Shape arguments** | `reshape(3, 3)` or `reshape((3, 3))` | `reshape([3, 3])` | Always use an array, never separate args | | **Indexing** | `a[0:5, :]` | `a.slice('0:5', ':')` | String-based slicing | | **Keyword arguments** | `axis=0`, `keepdims=True` | Positional or options object | JS has no keyword args | | **`var` is reserved** | `np.var(a)` | `np.variance(a)` | `var` is a JS reserved word; `np.var` is also aliased but may conflict | | **Transpose** | `a.T` | `a.T` | Same syntax -- `.T` is a getter property | | **Array equality** | `a == b` (element-wise) | `np.equal(a, b)` or `np.array_equal(a, b)` | `==` compares object references in JS | | **Tuple axes** | `axis=(0, 2)` | `axis=[0, 2]` | Use arrays instead of tuples | | **int64/uint64** | Regular integers | `BigInt` values | TypedArray requirement; `np.array([1n, 2n, 3n], 'int64')` | | **In-place ops** | `a += b` | Not supported | numpy-ts operations always return new arrays | | **dtype specification** | `dtype=np.float32` | `'float32'` (string) | Dtypes are string literals in numpy-ts | ## Supported dtypes numpy-ts supports 14 data types that map to JavaScript TypedArrays: | Category | Dtypes | JS Storage | Notes | | -------------------- | ----------------------------------------- | ---------------------------------------------- | -------------------------------------------------------- | | **Floating point** | `float64` (default), `float32`, `float16` | `Float64Array`, `Float32Array`, `Float16Array` | `float16` uses `Float32Array` fallback on older runtimes | | **Signed integer** | `int8`, `int16`, `int32`, `int64` | `Int8Array` ... `BigInt64Array` | `int64` uses `BigInt` | | **Unsigned integer** | `uint8`, `uint16`, `uint32`, `uint64` | `Uint8Array` ... `BigUint64Array` | `uint64` uses `BigInt` | | **Boolean** | `bool` | `Uint8Array` | Stored as 0/1 | | **Complex** | `complex64`, `complex128` | Interleaved `Float32Array`/`Float64Array` | `Complex` class for elements | When working with `int64` or `uint64`, array elements are `BigInt` values. Use the `n` suffix for literals: `np.array([1n, 2n, 3n], 'int64')`. ## What about missing NumPy features? numpy-ts covers 476 of 507 NumPy functions (94%). A few categories have partial coverage: * **Structured arrays / record dtypes** -- not supported (JS has no equivalent) * **String operations (`np.char`)** -- not supported (use native JS string methods) * **`np.ma` (masked arrays)** -- not supported yet * **`np.matrix`** -- deprecated in NumPy itself; use 2D `NDArray` instead For everything else -- math, linear algebra, FFT, random, sorting, sets, polynomials, bitwise operations, I/O -- numpy-ts has you covered. ## Next steps Hands-on tutorial covering all major features. Complete function reference organized by category. Full library vs core: which entry point is right for you? # Quickstart Source: https://numpyts.dev/v1.5.x/guides/quickstart Create arrays, perform operations, and explore numpy-ts in 5 minutes. ## Install numpy-ts has zero runtime dependencies and works on all major JavaScript runtimes. ```bash npm theme={null} npm install numpy-ts ``` ```bash pnpm theme={null} pnpm add numpy-ts ``` ```bash yarn theme={null} yarn add numpy-ts ``` ```bash bun theme={null} bun add numpy-ts ``` ```bash deno theme={null} deno add npm:numpy-ts ``` Browser? Skip the install and load directly from a CDN — see [Installation](.//installation#cdn-usage-browsers). ## Import the library ```typescript theme={null} import * as np from 'numpy-ts'; ``` Or import individual functions: ```typescript theme={null} import { array, zeros, add, reshape, sum } from 'numpy-ts'; ``` ## Create your first array ```typescript theme={null} // From nested JavaScript arrays const a = np.array([[1, 2, 3], [4, 5, 6]]); console.log(a.shape); // [2, 3] console.log(a.dtype); // 'float64' // Common constructors const z = np.zeros([3, 3]); // 3x3 of zeros const o = np.ones([2, 4]); // 2x4 of ones const r = np.arange(0, 10, 2); // [0, 2, 4, 6, 8] const l = np.linspace(0, 1, 5); // [0, 0.25, 0.5, 0.75, 1] const I = np.eye(3); // 3x3 identity matrix ``` All creation functions accept an optional `dtype` parameter: `np.zeros([3, 3], 'float32')` or `np.array([1, 2, 3], 'int32')`. ## Basic operations numpy-ts supports element-wise arithmetic on arrays of any shape: ```typescript theme={null} const a = np.array([1, 2, 3, 4]); const b = np.array([10, 20, 30, 40]); const c = np.add(a, b); // [11, 22, 33, 44] const d = np.multiply(a, b); // [10, 40, 90, 160] const e = np.subtract(b, a); // [9, 18, 27, 36] const f = np.divide(b, a); // [10, 10, 10, 10] // Scalar operations const g = np.add(a, 100); // [101, 102, 103, 104] const h = np.power(a, 2); // [1, 4, 9, 16] ``` ## Method chaining When you import from `numpy-ts` (the full entry point), arrays are `NDArray` instances that support method chaining. This lets you write fluent, readable pipelines: ```typescript theme={null} const result = np.array([1, 2, 3, 4, 5, 6]) .reshape([2, 3]) // Shape: [2, 3] .multiply(10) // Scale by 10 .add(1) // Shift by 1 .T; // Transpose -> Shape: [3, 2] console.log(result); // array([[11, 41], // [21, 51], // [31, 61]]) ``` Every operation that exists as a standalone function (`np.add`, `np.reshape`, etc.) is also available as a method on `NDArray`. ## Standalone functions (core) If you use `numpy-ts/core` for tree-shaking, the same operations are available as standalone functions: ```typescript theme={null} import { array, reshape, multiply, add, transpose } from 'numpy-ts/core'; const a = array([1, 2, 3, 4, 5, 6]); const b = reshape(a, [2, 3]); const c = multiply(b, 10); const d = add(c, 1); const result = transpose(d); ``` `NDArrayCore` returned from `numpy-ts/core` still has properties like `.shape`, `.dtype`, `.T`, and `.toString()`. It only lacks the chainable operation methods (`.add()`, `.reshape()`, etc.). See the [Tree-Shaking guide](.//tree-shaking) for a full comparison. ## Reductions Reduce arrays along axes to compute statistics: ```typescript theme={null} const a = np.array([[1, 2, 3], [4, 5, 6]]); // Full array reductions (return scalars) np.sum(a); // 21 np.mean(a); // 3.5 np.std(a); // 1.707... np.min(a); // 1 np.max(a); // 6 // Reduce along an axis (return arrays) np.sum(a, 0); // [5, 7, 9] - sum each column np.sum(a, 1); // [6, 15] - sum each row np.mean(a, 0); // [2.5, 3.5, 4.5] np.mean(a, 1); // [2, 5] // Other reductions np.prod(a); // 720 np.argmax(a); // 5 (flat index of maximum) np.cumsum(a); // [1, 3, 6, 10, 15, 21] np.variance(a); // 2.916... ``` ## Indexing and slicing numpy-ts uses **string-based slicing** to emulate NumPy's bracket syntax: ```typescript theme={null} const a = np.arange(12).reshape([3, 4]); // array([[ 0, 1, 2, 3], // [ 4, 5, 6, 7], // [ 8, 9, 10, 11]]) // Single element a.item(1, 2); // 6 // Slice rows and columns (string-based) a.slice('0:2', ':'); // First 2 rows, all columns a.slice(':', '1:3'); // All rows, columns 1-2 a.slice('::-1', ':'); // Reverse row order // Negative indexing a.slice('-1', ':'); // Last row: [8, 9, 10, 11] a.slice(':', '-2:'); // Last 2 columns ``` Slicing syntax mirrors NumPy: `'start:stop:step'`. Omitted values default to the full range, just like in Python. ## Reshaping and manipulation ```typescript theme={null} const a = np.arange(12); // Reshape const b = a.reshape([3, 4]); const c = a.reshape([2, 2, 3]); // Flatten and ravel b.flatten(); // Back to 1D (copy) b.ravel(); // Back to 1D (view when possible) // Transpose b.T; // Shape: [4, 3] // Stack and concatenate const x = np.array([1, 2, 3]); const y = np.array([4, 5, 6]); np.concatenate([x, y]); // [1, 2, 3, 4, 5, 6] np.stack([x, y]); // [[1, 2, 3], [4, 5, 6]] np.vstack([x, y]); // [[1, 2, 3], [4, 5, 6]] np.hstack([x, y]); // [1, 2, 3, 4, 5, 6] ``` ## Linear algebra ```typescript theme={null} const A = np.array([[1, 2], [3, 4]]); const B = np.array([[5, 6], [7, 8]]); // Matrix multiplication np.matmul(A, B); // array([[19, 22], // [43, 50]]) // Dot product const u = np.array([1, 2, 3]); const v = np.array([4, 5, 6]); np.dot(u, v); // 32 // linalg namespace np.linalg.inv(A); // Matrix inverse np.linalg.det(A); // Determinant: -2 np.linalg.eig(A); // Eigenvalues and eigenvectors np.linalg.svd(A); // Singular value decomposition np.linalg.norm(u); // Vector norm: 3.741... np.linalg.solve(A, u.reshape([2, 1])); // Solve Ax = b ``` ## Random numbers ```typescript theme={null} // Set seed for reproducibility np.random.seed(42); // Uniform random [0, 1) np.random.random([3, 3]); // Normal distribution (mean=0, std=1) np.random.normal(0, 1, [1000]); // Random integers np.random.randint(0, 10, [5]); // 5 random ints in [0, 10) // Shuffle and choose const deck = np.arange(52); np.random.shuffle(deck); np.random.choice(deck, 5); // Draw 5 cards ``` ## Broadcasting Operations automatically broadcast arrays with compatible shapes, just like NumPy: ```typescript theme={null} const matrix = np.ones([3, 3]); const row = np.array([1, 2, 3]); // row (shape [3]) broadcasts to match matrix (shape [3, 3]) np.add(matrix, row); // array([[2, 3, 4], // [2, 3, 4], // [2, 3, 4]]) const col = np.array([[10], [20], [30]]); np.add(matrix, col); // array([[11, 11, 11], // [21, 21, 21], // [31, 31, 31]]) ``` ## FFT (Fast Fourier Transform) ```typescript theme={null} // Generate a signal const t = np.linspace(0, 1, 256); const signal = np.add( np.sin(np.multiply(t, 2 * Math.PI * 5)), // 5 Hz np.sin(np.multiply(t, 2 * Math.PI * 20)) // 20 Hz ); // Compute FFT const spectrum = np.fft.fft(signal); const freqs = np.fft.fftfreq(256, 1 / 256); ``` ## Print arrays ```typescript theme={null} const a = np.arange(12).reshape([3, 4]); console.log(a); // array([[ 0, 1, 2, 3], // [ 4, 5, 6, 7], // [ 8, 9, 10, 11]]) // Control print formatting np.set_printoptions({ precision: 2, suppress: true }); ``` ## Next steps Deep dive into NDArray properties, views, and copies. Learn about the 13 supported dtypes including complex numbers and BigInt. Understand how operations work across arrays of different shapes. # Roadmap Source: https://numpyts.dev/v1.5.x/guides/roadmap What's coming next for numpy-ts. The goal for `numpy-ts` is to be the best possible NumPy implementation for JavaScript and TypeScript. To get there, there's have a long list of features, optimizations, and API improvements to build. Here's a high-level roadmap of what's coming next: ## Async / Worker offloading Heavy operations like `matmul`, `svd`, `fft`, and `convolve` can block the main thread for tens of milliseconds on large inputs. We're designing an opt-in `np.async.*` namespace that transparently offloads these to a Web Worker pool: ```typescript theme={null} // Proposed API const result = await np.async.matmul(A, B); const { u, s, vt } = await np.async.linalg.svd(largeMatrix); ``` The worker pool will support two transport paths: * **SharedArrayBuffer** (zero-copy) when COOP/COEP headers are present * **postMessage** (universal fallback) for environments without cross-origin isolation ## Multi-threaded WASM Extend the WASM acceleration layer to use multiple threads via `WebAssembly.Memory` with `shared: true` and Web Workers. This would allow large matrix operations to be parallelized across CPU cores without leaving the WASM execution context. ## Ufunc framework A generalized ufunc (universal function) system that would allow users to define custom element-wise and reduction operations that automatically get broadcasting, dtype promotion, and axis handling: ```typescript theme={null} // Proposed API const clampedAdd = np.ufunc((a, b) => Math.min(a + b, 255), 2); clampedAdd(imageA, imageB); // broadcasts, handles dtypes, etc. ``` ## Masked arrays Support for arrays with a boolean mask that marks invalid or missing entries. Operations would automatically skip masked elements, similar to NumPy's `numpy.ma` module: ```typescript theme={null} // Proposed API const a = np.ma.array([1, 2, 3, 4], { mask: [false, false, true, false] }); np.mean(a); // 2.333... (skips index 2) ``` ## Structured arrays / record arrays Arrays with named, heterogeneous fields -- useful for tabular data without pulling in a full DataFrame library: ```typescript theme={null} // Proposed API const dt = np.dtype([['name', 'U10'], ['age', 'int32'], ['score', 'float64']]); const records = np.zeros(100, dt); ``` This is a significant undertaking and may be scoped to a subset of NumPy's structured array features. ## Graph-based chaining / fused kernels v1.3.0 introduced WASM-backed array storage so that data lives in WASM memory between kernel calls — eliminating per-kernel copy-in/copy-out for the common path. The next step is **kernel fusion**: collapsing chained operations like `a.add(b).multiply(c)` into a single kernel pass to eliminate intermediate writes to memory entirely. ## Strided ops in WASM A handful of operations (notably non-contiguous strided variants) still fall back to TypeScript when the input layout precludes the fast WASM path. The plan is to extend the WASM kernels to handle strided inputs directly so we can drop the JS fallback entirely. ## Complete fancy indexing `vindex` (added in v1.3.0) covers the bulk of NumPy's integer array indexing. Remaining work: full parity with NumPy's combined basic + advanced indexing semantics, including in-place assignment via `vindex` and broadcasting of mixed integer/slice/boolean indexers. ## WASM `memory64` build Currently the WASM linear memory is 32-bit, capping the pool at 4 GiB (with a 256 MiB default). A `memory64` build option would lift this ceiling on supporting runtimes, enabling much larger arrays for scientific workloads. The 32-bit build will remain the default for portability. ## Codebase modularization Split the monolithic core into smaller, independently versionable modules — including the `.zig` source. This will make the library easier to contribute to, easier to subset for ultra-light deployments, and lets us iterate on individual modules (e.g. `linalg`, `fft`) without rebuilding the world. This roadmap reflects current thinking, not commitments. Items may be reprioritized, combined, or dropped based on what the community actually needs. The best way to influence the roadmap is to [open an issue](https://github.com/dupontcyborg/numpy-ts/issues) with your use case. # Slicing & Indexing Source: https://numpyts.dev/v1.5.x/guides/slicing-indexing Select sub-arrays, individual elements, rows, and columns using string-based slicing and advanced indexing. ## Python vs TypeScript syntax Because TypeScript does not support Python's `arr[0:5, :]` subscript syntax, numpy-ts uses **string-based slicing** via the `.slice()` method. Every slice dimension is a string argument. ```python Python (NumPy) theme={null} a[0:5] a[0:5, :] a[::-1] a[::2, 1:3] a[2] # integer index, reduces dimension ``` ```typescript TypeScript (numpy-ts) theme={null} a.slice('0:5') a.slice('0:5', ':') a.slice('::-1') a.slice('::2', '1:3') a.slice('2') // integer index, reduces dimension ``` ## String slice syntax The slice syntax mirrors Python's `start:stop:step`: | Slice String | Meaning | Python equivalent | | ------------ | -------------------------------- | ----------------- | | `':'` | All elements | `[:]` | | `'0:5'` | Elements 0 through 4 | `[0:5]` | | `'2:'` | From index 2 to the end | `[2:]` | | `':3'` | First 3 elements | `[:3]` | | `'-3:'` | Last 3 elements | `[-3:]` | | `'::2'` | Every other element | `[::2]` | | `'::-1'` | Reversed | `[::-1]` | | `'1:7:2'` | From 1 to 6, step 2 | `[1:7:2]` | | `'3'` | Single index (reduces dimension) | `[3]` | ## Basic slicing All slicing returns a **view** -- no data is copied. Modifications to the slice affect the original array. ```typescript theme={null} import * as np from 'numpy-ts'; const a = np.arange(0, 10); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] // First 5 elements a.slice('0:5').toArray(); // [0, 1, 2, 3, 4] // Last 3 elements a.slice('-3:').toArray(); // [7, 8, 9] // Every other element a.slice('::2').toArray(); // [0, 2, 4, 6, 8] // Reversed a.slice('::-1').toArray(); // [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] // Elements 2 through 7, step 2 a.slice('2:8:2').toArray(); // [2, 4, 6] ``` ## Multi-dimensional slicing Pass one string argument per dimension: ```typescript theme={null} const m = np.array([ [0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], ]); // shape [3, 4] // First 2 rows, all columns m.slice('0:2', ':').toArray(); // [[0, 1, 2, 3], // [4, 5, 6, 7]] // All rows, columns 1 and 2 m.slice(':', '1:3').toArray(); // [[1, 2], // [5, 6], // [9, 10]] // Single row (integer index reduces dimension) m.slice('1').toArray(); // [4, 5, 6, 7] (1-D result) // Submatrix: rows 0-1, columns 2-3 m.slice('0:2', '2:4').toArray(); // [[2, 3], // [6, 7]] ``` When you pass an integer as a string (e.g. `'1'`), that dimension is removed from the result, just like NumPy's integer indexing. A range like `'1:2'` keeps the dimension. ## Ellipses The string `'...'` expands to the number of `':'` slices needed to index all dimensions. In most cases, this means the length of the expanded selection tuple equals `ndim`. There may only be a single ellipsis present. ```typescript theme={null} const m = np.array([[ [0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], ]]); // shape [1, 3, 4] // Slice just the last dimension m.slice('...', '1:3').toArray(); // shape [1, 3, 2] // [[[1, 2], // [5, 6], // [9, 10]]] ``` ## newaxis The string `'newaxis'` expands the dimensions of the result by one unit-length dimension. The added dimension is the position of the `newaxis` token in the indices. ```typescript theme={null} const m = np.array([ [0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], ]); // shape [3, 4] // Insert an axis m.slice(':', 'newaxis', ':').toArray(); // shape [3, 1, 4] // [[[0, 1, 2, 3]], // [[4, 5, 6, 7]], // [[8, 9, 10, 11]]] ``` ## Negative indices Negative indices count from the end, just like Python: ```typescript theme={null} const a = np.arange(0, 10); a.slice('-1').toArray(); // 9 (scalar -- dimension removed) a.slice('-3:').toArray(); // [7, 8, 9] a.slice(':-2').toArray(); // [0, 1, 2, 3, 4, 5, 6, 7] const m = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]); m.slice('-1').toArray(); // [7, 8, 9] (last row) m.slice(':', '-1').toArray(); // [3, 6, 9] (last column, 1-D) ``` ## Element access: `get()` and `set()` For reading or writing **individual elements**, use `get()` and `set()` with an array of indices: ```typescript theme={null} const m = np.array([[10, 20, 30], [40, 50, 60]]); // Read element at row 0, column 2 m.get([0, 2]); // 30 // Write element at row 1, column 1 m.set([1, 1], 99); m.get([1, 1]); // 99 // Negative indices work here too m.get([0, -1]); // 30 (last column of first row) m.get([-1, -1]); // 60 (last element) ``` The `get()` and `set()` methods require one index per dimension. Passing the wrong number of indices throws an error. ## Convenience methods: `row()`, `col()`, `rows()`, `cols()` For 2-D arrays, numpy-ts provides shorthand methods that are cleaner than manual slicing: ```typescript theme={null} const m = np.array([ [1, 2, 3], [4, 5, 6], [7, 8, 9], ]); // Single row or column m.row(0).toArray(); // [1, 2, 3] m.row(-1).toArray(); // [7, 8, 9] m.col(1).toArray(); // [2, 5, 8] // Range of rows or columns m.rows(0, 2).toArray(); // [[1, 2, 3], [4, 5, 6]] m.cols(1, 3).toArray(); // [[2, 3], [5, 6], [8, 9]] ``` | Method | Equivalent | Returns | | ------------------- | --------------------------- | ------------------------- | | `row(i)` | `.slice(String(i), ':')` | 1-D array (single row) | | `col(j)` | `.slice(':', String(j))` | 1-D array (single column) | | `rows(start, stop)` | `.slice(`${start}:$`, ':')` | 2-D sub-matrix | | `cols(start, stop)` | `.slice(':', `${start}:$`)` | 2-D sub-matrix | These methods require at least 2 dimensions. Calling `row()` on a 1-D array throws an error. ## Fancy indexing: `take` and `put` ### `take(indices, axis?)` Select elements at arbitrary positions along an axis. Returns a **new array** (not a view): ```typescript theme={null} const a = np.array([10, 20, 30, 40, 50]); // Take specific elements np.take(a, [0, 2, 4]).toArray(); // [10, 30, 50] np.take(a, [4, 3, 2, 1, 0]).toArray(); // [50, 40, 30, 20, 10] (reversed) // Take from a 2-D array along axis 0 (rows) const m = np.array([[1, 2], [3, 4], [5, 6]]); np.take(m, [0, 2], 0).toArray(); // [[1, 2], [5, 6]] // Also available as a method a.take([1, 3]).toArray(); // [20, 40] ``` ### `put(indices, values)` Modify elements at specified flat indices in-place: ```typescript theme={null} const a = np.array([0, 0, 0, 0, 0]); np.put(a, [1, 3], np.array([10, 20])); a.toArray(); // [0, 10, 0, 20, 0] // Also available as a method a.put([0, 4], np.array([99, 99])); a.toArray(); // [99, 10, 0, 20, 99] ``` ## Boolean indexing: `bindex` Select elements where a boolean mask is `true`. Returns a new 1-D array containing only the matching elements: ```typescript theme={null} const a = np.array([10, 20, 30, 40, 50]); const mask = np.array([1, 0, 1, 0, 1], { dtype: 'bool' }); // Using the function np.bindex(a, mask).toArray(); // [10, 30, 50] // Using the method a.bindex(mask).toArray(); // [10, 30, 50] // Common pattern: create mask from a comparison const data = np.array([1, -2, 3, -4, 5]); const positive = np.greater(data, 0); // bool array: [1, 0, 1, 0, 1] data.bindex(positive).toArray(); // [1, 3, 5] ``` ## Integer array indexing: `iindex` Select elements using an array of integer indices along a given axis. Similar to NumPy's fancy integer array indexing: ```typescript theme={null} const a = np.array([10, 20, 30, 40, 50]); // Select by index array a.iindex([3, 1, 4]).toArray(); // [40, 20, 50] // Duplicate indices are allowed a.iindex([0, 0, 2, 2]).toArray(); // [10, 10, 30, 30] // On a 2-D array, along axis 0 (default) const m = np.array([[1, 2], [3, 4], [5, 6]]); m.iindex([2, 0]).toArray(); // [[5, 6], [1, 2]] // Along axis 1 (columns) m.iindex([1, 0], 1).toArray(); // [[2, 1], [4, 3], [6, 5]] ``` ## Conditional selection: `where` The `where` function selects elements from one of two arrays based on a condition: ```typescript theme={null} const condition = np.array([1, 0, 1, 0], { dtype: 'bool' }); const x = np.array([1, 2, 3, 4]); const y = np.array([10, 20, 30, 40]); np.where(condition, x, y).toArray(); // [1, 20, 3, 40] ``` When called with only a condition, `where` returns the indices of non-zero elements: ```typescript theme={null} const a = np.array([0, 5, 0, 3, 0, 1]); const [indices] = np.where(a); indices.toArray(); // [1, 3, 5] ``` ## Views vs copies Understanding when an operation returns a view (shared data) versus a copy (new data) is important for both correctness and performance: **Views (shared memory):** * `slice()` -- all slicing operations * `T` / `transpose()` * `reshape()` (when the array is C-contiguous) * `squeeze()` / `expand_dims()` * `broadcast_to()` **Copies (new data):** * `flatten()` / `copy()` * `take()` / `iindex()` / `bindex()` / `vindex()` * Arithmetic operations (`add`, `multiply`, etc.) * `sort()`, `argsort()` ```typescript theme={null} const a = np.array([1, 2, 3, 4, 5]); // Slicing returns a view const view = a.slice('1:4'); view.set([0], 99); a.toArray(); // [1, 99, 3, 4, 5] -- modified! // Flatten returns a copy const flat = np.array([[1, 2], [3, 4]]).flatten(); flat.set([0], 99); // Original is NOT affected ``` Check `arr.base` to determine if an array is a view. If `base` is not `null`, the array shares data with its base. ## Summary: choosing the right tool | Task | Method | Returns | | -------------------------------- | -------------------------------------- | --------------- | | Contiguous sub-array | `.slice('0:5')` | View | | Single element (read) | `.get([i, j])` | Scalar | | Single element (write) | `.set([i, j], val)` | Void (in-place) | | Single row/column | `.row(i)`, `.col(j)` | View | | Row/column range | `.rows(a, b)`, `.cols(a, b)` | View | | Arbitrary integer positions | `.take(indices)` or `.iindex(indices)` | Copy | | Multi-axis vectorized indexing | `np.vindex(a, idx0, idx1, ...)` | Copy | | In-place by flat index | `.put(indices, values)` | Void (in-place) | | Boolean mask selection | `.bindex(mask)` | Copy | | Conditional pick from two arrays | `np.where(cond, x, y)` | Copy | ## Next steps How arrays with different shapes combine in operations. NDArray properties, creation, and conversion. # Tree-Shaking & Bundle Size Source: https://numpyts.dev/v1.5.x/guides/tree-shaking Understand the two ways to use numpy-ts and how they affect your bundle. numpy-ts gives you two choices for how to import it. The right choice depends on whether you care more about **developer experience** or **bundle size**. ## The full library (`numpy-ts`) ```typescript theme={null} import * as np from 'numpy-ts'; ``` This is the default and the easiest way to use numpy-ts. It gives you `NDArray` objects that support **method chaining** — the same fluent style you are used to from NumPy: ```typescript theme={null} const result = np.arange(12) .reshape([3, 4]) .multiply(2) .add(1) .sum(0); ``` Every standalone function (`np.add`, `np.reshape`, `np.sum`, etc.) is also available as a method on the array itself (`.add()`, `.reshape()`, `.sum()`). This makes code shorter and more readable. **The trade-off:** importing *anything* from `numpy-ts` pulls the entire library into your bundle (\~200-300 KB minified). The bundler cannot tree-shake it because all 100+ methods are attached to the `NDArray` class at import time. \~200-300 KB is still small compared to alternatives that ship WebAssembly or native modules. For Node.js servers, scripts, and most applications this is perfectly fine. **Use the full library when:** * You are building an application (not a library) * You are running on Node.js or Bun where bundle size is irrelevant * You want the best developer experience with method chaining * You are using many functions from across the API ## The tree-shakeable core (`numpy-ts/core`) ```typescript theme={null} import { array, add, reshape, sum } from 'numpy-ts/core'; ``` The core entry point returns `NDArrayCore` objects — a minimal array class with properties (`shape`, `dtype`, `ndim`, `data`, `T`, etc.) but **no operation methods**. Instead, you use standalone functions for everything: ```typescript theme={null} const a = array([1, 2, 3, 4, 5, 6]); const b = reshape(a, [2, 3]); const c = add(b, 1); const result = sum(c, 0); ``` Your bundler (Vite, webpack, esbuild, Rollup) analyzes which functions you actually imported and excludes everything else. The result: your bundle only contains what you use. **How much does this save?** | What you import | Bundle size | | --------------------------------------------------- | ------------ | | `array` + `zeros` | \~10 KB | | Basic arithmetic (`add`, `subtract`, `multiply`) | \~15 KB | | Arithmetic + reductions (`sum`, `mean`, `std`) | \~20 KB | | Arithmetic + linear algebra (`dot`, `inv`, `solve`) | \~40 KB | | Full library (any import from `numpy-ts`) | \~200-300 KB | **Use the core entry point when:** * You are building a **browser app** where every kilobyte matters * You are publishing a **library** on npm (let your consumers choose their bundle) * You only need a small subset of numpy-ts functions ## What about Node.js file I/O? There is a third entry point, `numpy-ts/node`, that adds file system operations (`load`, `save`, `savez`, `loadtxt`, `savetxt`) on top of the full library. It behaves like `numpy-ts` but also includes Node.js `fs` bindings. ```typescript theme={null} import * as np from 'numpy-ts/node'; const arr = await np.loadNpy('data.npy'); await np.saveNpy('output.npy', arr); ``` `numpy-ts/node` only works in Node.js and Bun. It will not work in browsers. For browser-based I/O, use `parseNpy` / `serializeNpy` from either `numpy-ts` or `numpy-ts/core`. ## NDArray vs NDArrayCore The two array types share the same core — `NDArray` extends `NDArrayCore`. This means: * Every `NDArray` *is* an `NDArrayCore` (you can pass it to any function that accepts `NDArrayCore`) * `NDArrayCore` is *not* an `NDArray` (it lacks the chaining methods) * Both types expose the same properties: `shape`, `ndim`, `size`, `dtype`, `data`, `strides`, `flags`, `base`, `T`, `itemsize`, `nbytes` Standalone functions from `numpy-ts/core` accept both types as input: ```typescript theme={null} import { add } from 'numpy-ts/core'; import { array as fullArray } from 'numpy-ts'; import { array as coreArray } from 'numpy-ts/core'; const full = fullArray([1, 2, 3]); // NDArray const core = coreArray([1, 2, 3]); // NDArrayCore // Both work fine with standalone functions add(full, 1); // OK add(core, 1); // OK // Only NDArray has chaining methods full.add(1); // OK // core.add(1); // TypeError — not a function ``` If you are writing a library that depends on numpy-ts, accept `NDArrayCore` in your public API. This lets your consumers use either entry point. ## Side-by-side comparison The same operation written with both entry points: ```typescript Full library (numpy-ts) theme={null} import * as np from 'numpy-ts'; const a = np.array([[1, 2, 3], [4, 5, 6]]); // Method chaining const result = a .reshape([3, 2]) .multiply(10) .add(1) .T; console.log(np.sum(result, 0).toArray()); ``` ```typescript Core (numpy-ts/core) theme={null} import { array, reshape, multiply, add, transpose, sum } from 'numpy-ts/core'; const a = array([[1, 2, 3], [4, 5, 6]]); // Standalone functions const b = reshape(a, [3, 2]); const c = multiply(b, 10); const d = add(c, 1); const result = transpose(d); console.log(sum(result, 0).toArray()); ``` Both produce identical results. The difference is only in style and bundle size. ## Quick reference | | `numpy-ts` | `numpy-ts/core` | `numpy-ts/node` | | ------------------- | ------------- | ------------------- | ------------------ | | **Array class** | `NDArray` | `NDArrayCore` | `NDArray` | | **Method chaining** | Yes | No | Yes | | **Tree-shakeable** | No | Yes | No | | **Bundle size** | \~200-300 KB | \~10-40 KB | \~200-300 KB + fs | | **File I/O** | Browser only | Browser only | Node.js + Browser | | **Best for** | Apps, scripts | Libraries, browsers | Node.js with files | # TypeScript Patterns Source: https://numpyts.dev/v1.5.x/guides/typescript-patterns Type-safe patterns for using numpy-ts in TypeScript projects and libraries. numpy-ts is written in TypeScript and exports types for arrays, dtypes, and all function signatures. This guide covers patterns for writing type-safe numerical code. ## Importing types Use `import type` for type-only imports to ensure they are erased at compile time: ```typescript theme={null} import { array, zeros, sum } from 'numpy-ts'; import type { NDArray, DType } from 'numpy-ts'; ``` Or from the core entry point: ```typescript theme={null} import { array, add, reshape } from 'numpy-ts/core'; import type { NDArrayCore, DType } from 'numpy-ts/core'; ``` ## The DType type `DType` is a string union representing all supported data types: ```typescript theme={null} type DType = | 'float64' | 'float32' | 'complex128' | 'complex64' | 'int64' | 'int32' | 'int16' | 'int8' | 'uint64' | 'uint32' | 'uint16' | 'uint8' | 'bool'; ``` Use it to type dtype parameters in your functions: ```typescript theme={null} import { zeros } from 'numpy-ts'; import type { DType } from 'numpy-ts'; function createBuffer(shape: number[], dtype: DType = 'float64') { return zeros(shape, { dtype }); } createBuffer([3, 3], 'float32'); // OK createBuffer([3, 3], 'int32'); // OK // createBuffer([3, 3], 'string'); // Type error ``` ## NDArray vs NDArrayCore The two array types correspond to the two main entry points: | Type | Entry point | Has methods | Tree-shakeable | | ------------- | --------------- | --------------------------------------- | -------------- | | `NDArrayCore` | `numpy-ts/core` | No (properties only) | Yes | | `NDArray` | `numpy-ts` | Yes (`.add()`, `.reshape()`, `.T`, ...) | No | `NDArray` extends `NDArrayCore`, so every `NDArray` satisfies the `NDArrayCore` type. Both types expose the same properties: `shape`, `ndim`, `size`, `dtype`, `data`, `strides`, `flags`, `base`, `itemsize`, `nbytes`. ```typescript theme={null} import type { NDArray } from 'numpy-ts'; import type { NDArrayCore } from 'numpy-ts/core'; // NDArray is assignable to NDArrayCore function acceptsCore(arr: NDArrayCore): void { /* ... */ } declare const full: NDArray; acceptsCore(full); // OK // NDArrayCore is NOT assignable to NDArray (missing methods) function acceptsFull(arr: NDArray): void { /* ... */ } declare const core: NDArrayCore; // acceptsFull(core); // Type error ``` ## Writing generic functions ### Accept NDArrayCore for maximum compatibility If your function only uses standalone functions (not method chaining), accept `NDArrayCore`. This lets callers pass either type: ```typescript theme={null} import { add, sum, reshape } from 'numpy-ts/core'; import type { NDArrayCore } from 'numpy-ts/core'; function normalize(arr: NDArrayCore): NDArrayCore { const total = sum(arr) as number; return add(arr, -total); } ``` ### Accept NDArray when you need methods If your function uses method chaining, require `NDArray`: ```typescript theme={null} import type { NDArray } from 'numpy-ts'; function processMatrix(arr: NDArray): NDArray { return arr.reshape([-1]).add(1); } ``` ## Pattern for library authors If you are publishing a library that depends on numpy-ts, accept `NDArrayCore` in your public API and return `NDArrayCore`. This gives your users the choice of which entry point to use. ```typescript theme={null} // my-stats-lib/src/index.ts import { mean, subtract, sqrt, sum, multiply } from 'numpy-ts/core'; import type { NDArrayCore } from 'numpy-ts/core'; /** * Compute the standard deviation of an array. * Accepts both NDArray and NDArrayCore. */ export function standardDeviation(arr: NDArrayCore): number { const avg = mean(arr) as number; const diff = subtract(arr, avg); const variance = sum(multiply(diff, diff)) as number / arr.size; return Math.sqrt(variance); } ``` By depending on `numpy-ts/core`, your library only pulls in the functions it uses. If a consumer imports your library alongside `numpy-ts`, there is no duplication -- the same underlying computation code is shared. ## Type narrowing with dtype The `dtype` property is typed as `string` on the array class. When you need to branch on dtype, narrow it to `DType`: ```typescript theme={null} import type { NDArray, DType } from 'numpy-ts'; function describeArray(arr: NDArray): string { const dtype = arr.dtype as DType; switch (dtype) { case 'float64': case 'float32': return `Float array with ${arr.size} elements`; case 'int32': case 'int16': case 'int8': return `Integer array with ${arr.size} elements`; case 'bool': return `Boolean mask with ${arr.size} elements`; case 'complex128': case 'complex64': return `Complex array with ${arr.size} elements`; default: return `Array (${dtype}) with ${arr.size} elements`; } } ``` You can also use the utility functions `isIntegerDType`, `isFloatDType`, and `isComplexDType`: ```typescript theme={null} import { isIntegerDType, isFloatDType, isComplexDType } from 'numpy-ts'; import type { DType } from 'numpy-ts'; function requireFloat(dtype: DType): void { if (!isFloatDType(dtype)) { throw new Error(`Expected float dtype, got ${dtype}`); } } ``` ## Shape inference Array shapes are exposed as `readonly number[]`. You can use this to write shape-aware utilities: ```typescript theme={null} import { reshape, zeros } from 'numpy-ts/core'; import type { NDArrayCore } from 'numpy-ts/core'; function ensureMatrix(arr: NDArrayCore): NDArrayCore { if (arr.ndim === 1) { // Reshape 1D to column vector return reshape(arr, [arr.shape[0], 1]); } if (arr.ndim !== 2) { throw new Error(`Expected 1D or 2D array, got ${arr.ndim}D`); } return arr; } function outerProduct(a: NDArrayCore, b: NDArrayCore): NDArrayCore { if (a.ndim !== 1 || b.ndim !== 1) { throw new Error('Both inputs must be 1D'); } const col = reshape(a, [a.shape[0], 1]); // Column vector const row = reshape(b, [1, b.shape[0]]); // Row vector // Broadcasting handles the multiplication const { multiply } = require('numpy-ts/core'); return multiply(col, row); } ``` ## Combining with generics For functions that work with multiple array types, use TypeScript generics: ```typescript theme={null} import { copy } from 'numpy-ts/core'; import type { NDArrayCore } from 'numpy-ts/core'; function cloneAndFill(arr: T, value: number): T { const result = copy(arr); result.fill(value); return result as T; } ``` Since `NDArray` extends `NDArrayCore`, a generic bounded by `NDArrayCore` accepts both types. The return type preserves the caller's type through the generic parameter. ## Type-safe creation functions All creation functions accept a `dtype` option: ```typescript theme={null} import { zeros, ones, array, arange, linspace } from 'numpy-ts'; const f64 = zeros([3, 3]); // float64 (default) const f32 = zeros([3, 3], { dtype: 'float32' }); // float32 const i32 = ones([10], { dtype: 'int32' }); // int32 const ints = arange(0, 10, 1, 'int32'); // int32 const floats = linspace(0, 1, 100); // float64 ``` When creating arrays from data, the dtype is inferred from the values or can be explicitly set: ```typescript theme={null} import { array } from 'numpy-ts'; const a = array([1, 2, 3]); // float64 (default) const b = array([1, 2, 3], { dtype: 'int32' }); // int32 const c = array([true, false, true], { dtype: 'bool' }); // bool ``` # Views & Copies Source: https://numpyts.dev/v1.5.x/guides/views-copies Understand when numpy-ts shares memory between arrays and when it allocates new data. ## What is a view? A **view** is an array that shares its underlying data buffer with another array. When you create a view, no data is copied -- the new array simply references the same memory with different shape, strides, or offset metadata. Modifying elements through a view changes the original array, and vice versa. A **copy** is an independent array with its own data buffer. Changes to a copy never affect the original. ```typescript theme={null} import { array, reshape } from 'numpy-ts'; const a = array([1, 2, 3, 4, 5, 6]); // reshape returns a view -- same data, different shape const b = reshape(a, [2, 3]); // Modifying the view changes the original b.set([0, 0], 99); console.log(a.toArray()); // [99, 2, 3, 4, 5, 6] ``` ## Operations that return views These operations return arrays that share data with the input. No memory is allocated for array elements. | Operation | Description | | ------------------ | --------------------------------------------- | | `slice` / `.get()` | Basic slicing with start:stop:step | | `transpose` / `.T` | Reverses or permutes axes | | `swapaxes` | Swaps two axes | | `moveaxis` | Moves axes to new positions | | `squeeze` | Removes size-1 dimensions | | `expand_dims` | Adds a size-1 dimension | | `reshape` | New shape (if the array is C-contiguous) | | `ravel` | Flattens to 1D (if the array is C-contiguous) | | `broadcast_to` | Broadcasts to a larger shape | `reshape` and `ravel` return views **only** when the source array is C-contiguous. If the array is not contiguous in memory (for example, after a transpose), these operations must allocate a copy. ## Operations that return copies These operations always allocate a new data buffer. | Operation | Description | | ------------------------------------------ | ---------------------------------------------- | | `flatten` | Always copies, even if the array is contiguous | | `copy` | Explicit copy | | `astype` | Converts dtype (always copies) | | `concatenate`, `stack`, `hstack`, `vstack` | Joins arrays into a new buffer | | `repeat`, `tile` | Repeats data | | Arithmetic (`add`, `multiply`, ...) | Element-wise operations produce new arrays | | Reductions (`sum`, `mean`, ...) | Produce smaller arrays | ## Detecting views: `base` and `flags` ### The `base` property Every view has a `base` property pointing to the array that owns the data. Arrays that own their data return `null`. ```typescript theme={null} import { array, transpose } from 'numpy-ts'; const a = array([[1, 2], [3, 4]]); const v = transpose(a); console.log(a.base); // null -- a owns its data console.log(v.base); // NDArray [[1, 2], [3, 4]] -- v is a view of a console.log(v.base === a); // true ``` ### The `flags` property The `flags` object exposes three boolean flags: | Flag | Meaning | | -------------- | --------------------------------------------------------------- | | `OWNDATA` | `true` if the array owns its data buffer; `false` for views | | `C_CONTIGUOUS` | `true` if elements are laid out in row-major (C) order | | `F_CONTIGUOUS` | `true` if elements are laid out in column-major (Fortran) order | ```typescript theme={null} import { array, transpose } from 'numpy-ts'; const a = array([[1, 2, 3], [4, 5, 6]]); console.log(a.flags); // { C_CONTIGUOUS: true, F_CONTIGUOUS: false, OWNDATA: true } const v = transpose(a); console.log(v.flags); // { C_CONTIGUOUS: false, F_CONTIGUOUS: true, OWNDATA: false } ``` ## View mutation affects the original This is the most important consequence of views. Changing a value through any view changes the underlying data for every array that references it. ```typescript theme={null} import { array } from 'numpy-ts'; const a = array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]); // Slice out the first row (view) const row = a.get([0, ':']); // Slice out the first column (view) const col = a.get([':', 0]); // Modify through the row view row.set([1], 20); // Both the original and the column view see the change console.log(a.get([0, 1]).item()); // 20 console.log(a.toArray()); // [[1, 20, 3], [4, 5, 6], [7, 8, 9]] ``` ## Contiguity and reshape An array is **C-contiguous** when its elements are stored in row-major order with no gaps. Most freshly created arrays are C-contiguous. However, operations like `transpose` change the stride pattern without moving data, producing arrays that are no longer C-contiguous. This matters because `reshape` can only return a view when the data is already in the right memory order: ```typescript theme={null} import { array, reshape, transpose } from 'numpy-ts'; const a = array([[1, 2, 3], [4, 5, 6]]); console.log(a.flags.C_CONTIGUOUS); // true // reshape on a contiguous array returns a view const b = reshape(a, [3, 2]); console.log(b.flags.OWNDATA); // false (view) // After transpose, the array is no longer C-contiguous const t = transpose(a); console.log(t.flags.C_CONTIGUOUS); // false // reshape must copy because the data layout does not match the new shape const c = reshape(t, [6]); console.log(c.flags.OWNDATA); // true (copy) ``` If you need to ensure an array is contiguous before reshaping (to guarantee a view), use `ascontiguousarray` first. ## Explicit copies When you need an independent copy, use `copy`: ```typescript theme={null} import { array, copy } from 'numpy-ts'; const a = array([1, 2, 3, 4]); const b = copy(a); b.set([0], 99); console.log(a.toArray()); // [1, 2, 3, 4] -- unchanged console.log(b.toArray()); // [99, 2, 3, 4] ``` Or use the method form on `NDArray`: ```typescript theme={null} import { array } from 'numpy-ts'; const a = array([1, 2, 3, 4]); const b = a.copy(); ``` If you pass an array to a function and want to prevent the function from modifying your data, pass `copy(arr)` instead. numpy-ts follows NumPy's convention: slicing returns views, not copies. ## Summary | Question | Answer | | ------------------------------------- | ---------------------------------------------------- | | Does slicing copy data? | No, slices are views | | Does `reshape` copy data? | Only if the array is not C-contiguous | | Does `flatten` copy data? | Always | | Does `ravel` copy data? | Only if the array is not C-contiguous | | How do I check if an array is a view? | `arr.flags.OWNDATA === false` or `arr.base !== null` | | How do I force a copy? | `copy(arr)` or `arr.copy()` | # WASM Acceleration Source: https://numpyts.dev/v1.5.x/guides/wasm-acceleration Starting in `v1.1.0`, numpy-ts ships Zig-compiled WebAssembly microkernels that transparently accelerate compute- and memory-bound operations. The library remains lightweight and tree-shakeable. WASM acceleration is invisible to the end user, just faster. ## How it works numpy-ts detects at runtime when a WASM kernel is available and beneficial for a given operation. If the input meets the dispatch criteria (sufficient size, supported dtype, contiguous memory layout), the operation runs through the WASM kernel. Otherwise, the pure TypeScript implementation is used. There is no API change: the same functions, the same signatures, the same results. Each WASM kernel is: * **Compiled from Zig** with `ReleaseFast` optimizations and WASM SIMD128 enabled * **Embedded as base64** in the JavaScript bundle. No extra network requests, no `.wasm` files to serve, works with any bundler * **Lazily initialized** on first use -- zero startup cost if a kernel is never called * **Sharing a single `WebAssembly.Memory`** instance across all kernels to minimize overhead ## What's accelerated WASM kernels cover 97 operations across these modules: | Module | Speedup | vs NumPy (before) | vs NumPy (after) | | ------------------------ | ------- | ----------------- | ---------------- | | **Arithmetic** | \~23x | 65x slower | 2.75x slower | | **Linear Algebra** | \~19x | 61x slower | 3.2x slower | | **Logic** | \~27x | 48x slower | 1.8x slower | | **Manipulation** | \~9x | 15x slower | 1.6x slower | | **Gradient** | \~60x | 30x slower | 2x *faster* | | **FFT** | \~3x | 22x slower | 8x slower | | **Random** (Zig rewrite) | \~6x | 11x slower | 1.9x slower | | **Indexing** | \~5.5x | 12x slower | 2.2x slower | All benchmarks measure computation time from JS\<->numpy-ts and Python\<->NumPy respectively. This gives an apples-to-apples comparison of the numerical computation itself. For more info, see [performance benchmarks](/performance). ## Random module (Zig rewrite) In `v1.2.0`, the entire `np.random` module was rewritten as a Zig-compiled WASM kernel. Both the legacy **MT19937** and modern **PCG64/SeedSequence** implementations now run in WASM, replacing the previous pure-TypeScript versions. The rewrite achieves **bit-for-bit output matching with NumPy** for both the `np.random.seed()` (legacy) and `np.random.default_rng()` (modern) APIs. All distributions -- not just uniform and normal -- now produce identical sequences to NumPy given the same seed. This includes gamma, beta, chi-square, Poisson, binomial, multivariate normal, and every other distribution in the module. Performance improved by **\~6x** compared to the previous TypeScript implementation, bringing numpy-ts random generation to within 1.9x of native NumPy speed. ## Architecture ### Zig kernels The WASM kernels are implemented in Zig, chosen for its: * First-class WASM target support with SIMD intrinsics * Zero-overhead abstractions and comptime generics * No runtime or GC; minimal binary size Each kernel module (e.g., `matmul`, `reduction`, `unary`, `binary`, `sort`, `linalg`, `fft`) is compiled to a standalone `.wasm` binary, then base64-encoded into a TypeScript wrapper. ### Memory model All kernels share a single `WebAssembly.Memory` instance with a bump allocator. Before each kernel call: 1. The allocator resets to `heapBase` (zero-cost reset) 2. Input data is written into WASM memory 3. The kernel operates in-place or writes output to a separate region 4. Results are read back into JavaScript typed arrays This avoids repeated memory allocation/deallocation overhead and keeps the memory footprint predictable. ### Dispatch logic Each accelerated function checks: * **Size threshold**: small arrays are faster in pure JS (no WASM call overhead) * **Dtype support**: the kernel must support the input dtype (e.g., float32, float64) * **Contiguity**: many kernels require C-contiguous input for optimal performance If any check fails, the function falls back to the pure TypeScript path silently. ## Tree-shaking WASM kernels are individually tree-shakeable. If your application only uses `add` and `matmul`, only those kernel wrappers (and the shared memory instance) are included in your bundle. Unused kernels are eliminated by your bundler. ```typescript theme={null} // Only the add and matmul WASM kernels are bundled import { add, matmul } from 'numpy-ts'; ``` ## Running on a Web Worker For long-running operations that might block the main thread, you can run numpy-ts in a Web Worker: ```typescript theme={null} // worker.ts import * as np from 'numpy-ts'; self.onmessage = (e) => { const { data, shape } = e.data; const arr = np.array(data).reshape(shape); const result = np.linalg.svd(arr); self.postMessage({ u: result.u.toArray(), s: result.s.toArray(), vt: result.vt.toArray(), }); }; ``` ```typescript theme={null} // main.ts const worker = new Worker(new URL('./worker.ts', import.meta.url)); worker.postMessage({ data: myData, shape: [1000, 500] }); worker.onmessage = (e) => { console.log('SVD complete:', e.data.s); }; ``` This works out of the box -- WASM kernels initialize independently in each worker context. # Node.js, Deno & Bun Source: https://numpyts.dev/v1.5.x/performance/deno-bun numpy-ts runs on Node.js, Deno, and Bun (and more). This page compares runtime performance head-to-head, with Node.js as the baseline. All benchmarks measure computation time from JS and Python, respectively. To learn more, check out [benchmark methodology](./methodology).
## Runtime Comparison Summary Baseline: Node.js. Machine: Apple M4 Max (16 cores, 128 GB, arm64) ### Overall (vs Node.js) | Runtime | Avg Speedup | Best Case | Worst Case | Benchmarks | | ------- | ----------- | --------- | ---------- | ---------- | | node | 1.00x | 1.00x | 1.00x | 2390 | | deno | 0.97x | 1.90x | 0.27x | 2390 | | bun | 1.08x | 6.85x | 0.06x | 2390 | ### By Category | Category | node avg | deno avg | bun avg | | ------------ | -------- | -------- | ------- | | creation | 1.00x | 1.05x | 1.36x | | arithmetic | 1.00x | 0.98x | 1.21x | | math | 1.00x | 1.03x | 1.05x | | trig | 1.00x | 0.99x | 1.05x | | gradient | 1.00x | 0.98x | 1.13x | | linalg | 1.00x | 0.94x | 1.09x | | reductions | 1.00x | 0.91x | 0.88x | | manipulation | 1.00x | 1.05x | 0.93x | | io | 1.00x | 0.90x | 1.38x | | indexing | 1.00x | 1.02x | 1.22x | | bitwise | 1.00x | 0.96x | 1.31x | | sorting | 1.00x | 0.87x | 0.93x | | logic | 1.00x | 0.92x | 1.31x | | statistics | 1.00x | 0.90x | 0.59x | | sets | 1.00x | 1.06x | 1.66x | | random | 1.00x | 0.96x | 1.07x | | polynomials | 1.00x | 1.03x | 1.76x | | fft | 1.00x | 0.92x | 0.83x |
# Benchmark Methodology Source: https://numpyts.dev/v1.5.x/performance/methodology numpy-ts ships with a comprehensive benchmark suite that measures performance against Python NumPy. This page explains how the benchmarks work. ## What's tested The suite contains **308 benchmark specifications** across **18 categories**: creation, arithmetic, math, trig, gradient, linalg, reductions, manipulation, io, indexing, bitwise, sorting, logic, statistics, sets, random, polynomials, and fft. Each specification is tested across multiple dtypes (`float64`, `float32`, `float16`, `int8`-`int64`, `uint8`-`uint64`, `complex64`, `complex128`, `bool`) where applicable, producing **\~2,400 individual benchmarks** in a full run. Array sizes are configurable: | Scale | Array size | Matrix size | | ---------------- | --------------- | ----------- | | Small | 100 elements | 32×32 | | Medium (default) | 1,000 elements | 100×100 | | Large | 10,000 elements | 1,000×1,000 | All benchmark specifications are defined in [`benchmarks/src/specs.ts`](https://github.com/dupontcyborg/numpy-js/blob/main/benchmarks/src/specs.ts). ## How timing works Both sides use high-resolution timers: [`performance.now()`](https://github.com/dupontcyborg/numpy-js/blob/main/benchmarks/src/runtime-runner.ts#L131-L135) in the [JS runner](https://github.com/dupontcyborg/numpy-js/blob/main/benchmarks/src/runtime-runner.ts) and [`time.perf_counter()`](https://github.com/dupontcyborg/numpy-js/blob/main/benchmarks/scripts/numpy_benchmark.py#L971-L978) in the [Python runner](https://github.com/dupontcyborg/numpy-js/blob/main/benchmarks/scripts/numpy_benchmark.py). The benchmark measures **computation time only**, from the JS side for numpy-ts and from the Python side for NumPy. This gives an apples-to-apples comparison of the numerical computation itself, without being skewed by JS↔Python interop overhead. ### Auto-calibration Each benchmark automatically calibrates how many operations to run per sample, targeting a minimum sample time of **100ms**. This eliminates timer resolution noise: if an operation takes 0.001ms, the runner batches 100,000 of them into a single sample rather than measuring one at a time. The calibration uses exponential scaling (×10 → ×2 → exact) to converge quickly, with a cap of 10 calibration rounds. ### Warmup Before measurement, each benchmark runs a configurable number of **warmup iterations** to stabilize JIT compilation and ensure WASM modules are compiled: | Mode | Warmup iterations | Min sample time | Samples | | -------- | ----------------- | --------------- | ------- | | Quick | 3 | 50ms | 1 | | Standard | 10 | 100ms | 5 | | Full | 20 | 100ms | 5 | The published benchmarks on this site use **full mode**. ### Measurement After warmup and calibration, the runner collects **5 independent samples**. Each sample runs the calibrated number of operations and records the per-operation time. The suite reports: * **Mean** and **median** time per operation * **Min** and **max** across samples * **Standard deviation** * **Ops/second** (derived from mean time) The **speedup ratio** shown on the benchmark pages is `numpy-ts ops/s ÷ NumPy ops/s`. A ratio above 1.0x means numpy-ts was faster. ## Fairness A few design decisions to keep the comparison honest: * **Same operations, same data.** Both sides run the same algorithm on the same array shapes and dtypes. The Python runner ([`numpy_benchmark.py`](https://github.com/dupontcyborg/numpy-js/blob/main/benchmarks/scripts/numpy_benchmark.py)) mirrors the JS specifications exactly. * **Computation only.** Timing happens on each side of the boundary. numpy-ts is timed from JS; NumPy is timed from Python. Neither side pays for cross-language overhead. * **No cherry-picking.** Every benchmark in the spec file runs. Categories where NumPy is faster (trig, math, indexing) are reported alongside categories where numpy-ts wins. * **Geometric mean for ratios.** Category and overall averages use the geometric mean, which is the [correct method for averaging ratios](https://en.wikipedia.org/wiki/Geometric_mean#Applications). ## Running benchmarks yourself ```bash theme={null} # Standard run (~5-10 min) pnpm run bench # Full run with more warmup (~30-60 min, used for published results) pnpm run bench:full # Quick sanity check (~1-2 min) pnpm run bench:quick # Test different array sizes pnpm run bench -- --size small pnpm run bench -- --size large # Compare across runtimes (Node, Deno, Bun) pnpm run bench -- --runtimes ``` Results are saved to `benchmarks/results/` as JSON files. ### Caching Benchmark results are cached for 24 hours, keyed by machine fingerprint. This prevents stale cross-comparisons when hardware or environment changes. Use `--fresh` to skip the cache and re-run Python benchmarks. # Performance Overview Source: https://numpyts.dev/v1.5.x/performance/overview numpy-ts is **1.25x faster** than NumPy on average across 7,159 benchmarks (small, medium, and large arrays) and runs natively in JavaScript + WASM with zero dependencies. All benchmarks compare computation time between JS (numpy-ts) and Python (NumPy with OpenBLAS), measured on each side respectively. See [methodology](./methodology) for details. ## Performance by Category numpy-ts outperforms NumPy in most categories. NumPy leads in bitwise, trig, and math — all active areas of improvement. Performance by category: numpy-ts vs NumPy Performance by category: numpy-ts vs NumPy See the full breakdown of category results on the [numpy-ts vs. NumPy page](./vs-numpy). ## Performance by Data Type Smaller data types see the biggest gains — numpy-ts's SIMD kernels process more elements per instruction for `int8`, `uint8`, and `float16`. Even `float64` (NumPy's home turf) is on par. Performance by data type: numpy-ts vs NumPy Performance by data type: numpy-ts vs NumPy See the full breakdown of dtype results on the [numpy-ts vs. NumPy page](./vs-numpy). ## Performance by Array Size numpy-ts is as fast or faster than NumPy at every tested array scale — from small (100-element) arrays where low overhead matters, to large (10K-element) arrays where SIMD throughput dominates. Performance by array size: numpy-ts vs NumPy Performance by array size: numpy-ts vs NumPy See the full breakdown of array size results on the [size scaling page](./size-scaling). ## All Benchmarks How does numpy-ts compare to NumPy running natively in Python with OpenBLAS? How does numpy-ts compare to NumPy running in WebAssembly via Pyodide? How does numpy-ts performance scale across small, medium, and large array sizes? How does numpy-ts perform across different JavaScript runtimes? # Performance by Array Size Source: https://numpyts.dev/v1.5.x/performance/size-scaling How does numpy-ts performance scale with array size compared to NumPy? This page shows the full picture. All benchmarks measure computation time from JS and Python, respectively. To learn more, check out [benchmark methodology](./methodology).
## Size Scaling Summary | Array Size | Avg Speedup | Best Case | Worst Case | Benchmarks | | ----------- | ----------- | --------- | ---------- | ---------- | | Small (100) | 1.29x | 39.15x | 0.15x | 2390 | | Medium (1K) | 1.10x | 41.60x | 0.12x | 2390 | | Large (10K) | 1.37x | 2363.88x | 0.04x | 2379 | ### Small (100) — by Category | Category | Avg Speedup | Count | | ------------ | ----------- | ----- | | creation | 1.45x | 213 | | arithmetic | 0.81x | 295 | | math | 1.34x | 125 | | trig | 1.02x | 216 | | gradient | 3.86x | 22 | | linalg | 1.48x | 269 | | reductions | 1.99x | 413 | | manipulation | 1.02x | 231 | | io | 3.23x | 66 | | indexing | 0.91x | 115 | | bitwise | 0.55x | 10 | | sorting | 0.67x | 75 | | logic | 0.97x | 142 | | statistics | 3.18x | 26 | | sets | 2.88x | 33 | | random | 1.03x | 46 | | polynomials | 2.11x | 27 | | fft | 1.18x | 66 | ### Medium (1K) — by Category | Category | Avg Speedup | Count | | ------------ | ----------- | ----- | | creation | 1.14x | 213 | | arithmetic | 1.05x | 295 | | math | 1.42x | 125 | | trig | 1.01x | 216 | | gradient | 3.39x | 22 | | linalg | 1.54x | 269 | | reductions | 0.99x | 413 | | manipulation | 0.94x | 231 | | io | 2.04x | 66 | | indexing | 0.56x | 115 | | bitwise | 0.52x | 10 | | sorting | 0.78x | 75 | | logic | 1.32x | 142 | | statistics | 1.37x | 26 | | sets | 2.32x | 33 | | random | 0.83x | 46 | | polynomials | 2.05x | 27 | | fft | 0.67x | 66 | ### Large (10K) — by Category | Category | Avg Speedup | Count | | ------------ | ----------- | ----- | | creation | 3.14x | 213 | | arithmetic | 1.93x | 295 | | math | 1.63x | 125 | | trig | 1.22x | 216 | | gradient | 5.96x | 22 | | linalg | 1.43x | 269 | | reductions | 0.63x | 413 | | manipulation | 1.23x | 231 | | io | 2.50x | 55 | | indexing | 0.55x | 115 | | bitwise | 1.01x | 10 | | sorting | 0.97x | 75 | | logic | 3.67x | 142 | | statistics | 0.94x | 26 | | sets | 5.10x | 33 | | random | 0.84x | 46 | | polynomials | 1.99x | 27 | | fft | 0.89x | 66 |
# numpy-ts vs. NumPy (Native) Source: https://numpyts.dev/v1.5.x/performance/vs-numpy Benchmark snapshot comparing numpy-ts against native Python NumPy (OpenBLAS-backed) across small, medium, and large array sizes. Run your own via `pnpm run bench`. All benchmarks measure computation time from JS and Python, respectively. To learn more, check out [benchmark methodology](./methodology).
## Benchmark Summary * **Average speedup**: 1.25x vs NumPy * **Best case**: 2363.88x * **Worst case**: 0.10x * **Total benchmarks**: 7159 * **Machine**: Apple M4 Max (16 cores, 128 GB, arm64) * **numpy-ts version**: 1.5.0 ### Performance by Category | Category | Avg Speedup | Count | Faster | Slower | | ------------ | ----------- | ----- | ------ | ------ | | creation | 1.73x | 639 | 465 | 174 | | arithmetic | 1.18x | 885 | 434 | 451 | | math | 1.46x | 375 | 280 | 95 | | trig | 1.08x | 648 | 365 | 283 | | gradient | 4.27x | 66 | 66 | 0 | | linalg | 1.48x | 807 | 514 | 293 | | reductions | 1.07x | 1239 | 658 | 581 | | manipulation | 1.06x | 693 | 299 | 394 | | io | 2.55x | 187 | 162 | 25 | | indexing | 0.66x | 345 | 118 | 227 | | bitwise | 0.66x | 30 | 7 | 23 | | sorting | 0.80x | 225 | 56 | 169 | | logic | 1.68x | 426 | 241 | 185 | | statistics | 1.60x | 78 | 52 | 26 | | sets | 3.24x | 99 | 78 | 21 | | random | 0.89x | 138 | 43 | 95 | | polynomials | 2.05x | 81 | 63 | 18 | | fft | 0.89x | 198 | 85 | 113 | ### Performance by DType | DType | Avg Speedup | Median Speedup | Count | | ---------- | ----------- | -------------- | ----- | | float64 | 1.17x | 1.01x | 863 | | float32 | 1.14x | 1.05x | 713 | | float16 | 1.44x | 1.44x | 632 | | int64 | 1.14x | 1.05x | 587 | | uint64 | 1.14x | 1.06x | 563 | | int32 | 1.30x | 1.21x | 611 | | uint32 | 1.34x | 1.26x | 566 | | int16 | 1.33x | 1.20x | 554 | | uint16 | 1.33x | 1.20x | 551 | | int8 | 1.42x | 1.28x | 554 | | uint8 | 1.41x | 1.30x | 557 | | complex128 | 0.93x | 0.89x | 204 | | complex64 | 0.86x | 0.79x | 204 |
# numpy-ts vs. NumPy (Pyodide) Source: https://numpyts.dev/v1.5.x/performance/vs-pyodide Benchmark snapshot comparing numpy-ts against [Pyodide](https://github.com/pyodide/pyodide) NumPy (WASM-compiled CPython + NumPy). All benchmarks measure computation time from JS and Python, respectively. To learn more, check out [benchmark methodology](./methodology).
## Benchmark Summary * **Average speedup**: 2.21x vs NumPy * **Best case**: 82.61x * **Worst case**: 0.20x * **Total benchmarks**: 2390 * **Machine**: Apple M4 Max (16 cores, 128 GB, arm64) * **numpy-ts version**: 1.5.0 ### Performance by Category | Category | Avg Speedup | Count | Faster | Slower | | ------------ | ----------- | ----- | ------ | ------ | | creation | 2.35x | 213 | 184 | 29 | | arithmetic | 2.75x | 295 | 287 | 8 | | math | 2.19x | 125 | 105 | 20 | | trig | 2.02x | 216 | 189 | 27 | | gradient | 7.02x | 22 | 22 | 0 | | linalg | 2.96x | 269 | 228 | 41 | | reductions | 1.90x | 413 | 339 | 74 | | manipulation | 1.93x | 231 | 164 | 67 | | io | 2.88x | 66 | 50 | 16 | | indexing | 1.14x | 115 | 51 | 64 | | bitwise | 1.80x | 10 | 10 | 0 | | sorting | 1.10x | 75 | 36 | 39 | | logic | 3.61x | 142 | 129 | 13 | | statistics | 3.88x | 26 | 23 | 3 | | sets | 2.92x | 33 | 25 | 8 | | random | 1.55x | 46 | 41 | 5 | | polynomials | 4.01x | 27 | 21 | 6 | | fft | 1.06x | 66 | 28 | 38 | ### Performance by DType | DType | Avg Speedup | Median Speedup | Count | | ---------- | ----------- | -------------- | ----- | | float64 | 2.05x | 1.73x | 288 | | float32 | 2.28x | 2.26x | 238 | | float16 | 2.22x | 2.21x | 211 | | int64 | 1.65x | 1.40x | 196 | | uint64 | 1.62x | 1.36x | 188 | | int32 | 2.13x | 2.14x | 204 | | uint32 | 2.21x | 2.16x | 189 | | int16 | 2.59x | 2.84x | 185 | | uint16 | 2.56x | 2.84x | 184 | | int8 | 2.90x | 3.01x | 185 | | uint8 | 2.86x | 2.99x | 186 | | complex128 | 1.96x | 1.68x | 68 | | complex64 | 1.86x | 1.57x | 68 |
# Playground Source: https://numpyts.dev/v1.5.x/playground Write and run numpy-ts code directly in your browser. Write numpy-ts code and see results instantly — everything runs right in your browser using the same library you'd install from npm. ## How it works * Code executes **in your browser** using the numpy-ts browser bundle loaded from CDN * The `np` object is pre-loaded with all numpy-ts functions * Use `console.log()` to print output, or return a value from the last expression # Changelog & Release Notes Source: https://numpyts.dev/changelog ## numpy-ts v1.6.0 Compile-time `dtype` typing across the public API. `NDArray` now tracks its dtype through nearly every operation at zero runtime cost, so results report their exact dtype (`add(int32Arr, float32Arr)` → `NDArray<'float64'>`) and element access is typed per dtype (`bigint` for `int64`/`uint64`, `Complex` for complex, `number` otherwise). See [Compile-time dtype tracking](/next/guides/typescript-patterns#compile-time-dtype-tracking). ### Breaking * Floating-point reductions now preserve narrow floats instead of widening to `float64`, matching NumPy. `std`, `var`, `median`, `percentile`, `quantile`, and the `nan*` family return `float16`/`float32` for `float16`/`float32` input; `var`/`std` on `complex64` return `float32`; `round`/`around` upcast `bool → float16`. Add an explicit `.astype('float64')` if you relied on the old output. ### Notes * Validation test suite pinned to NumPy 2.4 (`numpy>=2.4,<2.5`). ## numpy-ts v1.5.0 Performance release. A large batch of element-wise and reduction operations move from JavaScript into hand-written Zig/WASM SIMD kernels, and the project's tooling migrates from npm to pnpm. numpy-ts is now, on average [1.25x faster than native NumPy](/v1.5.x/performance/vs-numpy)! ### Breaking * Dropped support for Node 20, which has reached [end-of-life](https://nodejs.org/en/about/previous-releases). numpy-ts now requires Node 22 or later. ### New WASM SIMD kernels The following operations now run through vectorized WASM kernels (previously JS fallbacks), with integer and complex dtype variants where applicable: * Trigonometric: `sin`, `cos`, `tan` * Hyperbolic: `sinh`, `cosh`, `tanh` * Inverse trig / hyperbolic: `arctan`, `arctan2`, `arcsinh`, `arccosh`, `arctanh` * Exp / log family: `exp`, `exp2`, `expm1`, `log`, `log1p`, `logaddexp`, `logaddexp2` * Other element-wise: `power`, `heaviside`, `modulo`, `sinc` * Signal: `convolve`, `correlate` (relaxed-FMA variants) * Strided reductions: `argmin`, `argmax`, `prod` * Cumulative: `cumsum`, `cumprod` These share new common SIMD utilities for transcendental functions and complex arithmetic, and redundant relaxed-FMA kernel variants were removed. ### Tooling & CI * Migrated the toolchain from npm to pnpm (workspaces, CI, and `pnpm publish` via OIDC Trusted Publishing). Thanks [@cyfung1031](https://github.com/dupontcyborg/numpy-ts/pull/128)! * Fixed missing `DType` type export from `numpy-ts` entrypoint, thanks to [@OSquiddy](https://github.com/dupontcyborg/numpy-ts/pull/133)! * Validation tests moved from a per-test NumPy process to a per-worker NumPy "server" for faster oracle comparisons. * Dependency bumps across dev and bench tooling. ## numpy-ts v1.4.0 NumPy-compatibility release. A wide set of public functions gain the kwargs / overloads they were missing relative to NumPy, and several signatures accept `ArrayLike` so plain `number[]` no longer needs `np.array(...)` wrapping. Small (2-5%) performance regressions due to additional correctness checks which will be optimized in a later release. ### Breaking * `meshgrid` (`numpy-ts/core` only) - default `indexing` is now `'xy'` (NumPy-compatible). Previously `core/meshgrid` had no options and effectively produced `'ij'` shapes. If you import `meshgrid` from `numpy-ts/core` and relied on the old behavior, pass `{ indexing: 'ij' }` explicitly. Importers from `numpy-ts` (the full API) are unaffected - that wrapper already defaulted to `'xy'`. ### New kwargs / overloads * Reductions `where` / `initial` / `dtype` - `sum`, `prod`, `max`/`amax`, `min`/`amin`, `all`, `any` now accept a `ReductionOpts` bag as a 4th argument for masked reductions, seeded accumulation, and dtype-controlled accumulation. * `argmin` / `argmax` - accept `keepdims?: boolean` (NumPy 1.22+). * `ptp` - `axis` widened to `number | number[]`, consistent with the other reductions. * `average` - new `returned?: boolean`. When `true`, returns `[avg, sum_of_weights]` matching `np.average(..., returned=True)`. * `concatenate` / `concat` - `axis` widened to `number | null`. `axis=null` flattens each input and concatenates along axis 0. * `diff` - new `prepend?` and `append?` arguments (`ArrayLike`). * `interp` - new `period?: number` for circular/phase data; `xp` is normalized into `[0, period)` and wraps at the boundary. * `gradient` - per-axis spacing can now be a 1-D coordinate array (non-uniform second-order central-difference), not just a scalar. * `apply_along_axis` - accepts trailing `...args` forwarded to `func1d`, matching `np.apply_along_axis(func1d, axis, arr, *args)`. * `pad` - `pad_width` and `constant_values` accept the full NumPy broadcast forms (scalar, `[n]`, `[before, after]`, per-axis scalars, `[[b, a]]`, per-axis pairs, mixed). New exported types `PadWidthArg` and `PadValueArg`. * `meshgrid` - `MeshgridOptions { indexing?, sparse?, copy? }` bag. `sparse: true` returns open grids; `copy: false` returns broadcast views. * `block` - accepts nested sequences (`NestedNDArrays[]`) with NumPy's innermost-to-outer axis semantics. Flat-list calls behave identically. ### `ArrayLike` widening These now accept plain `number[]`, nested arrays, and scalars in addition to `NDArrayCore`. Behavior for existing NDArray callers is unchanged. * `take` - `indices: ArrayLike` (scalar, 1-D, 2-D, or 3-D). Output preserves the shape of `indices`. * `where` - `x` and `y` are `ArrayLike`. Also fixes a falsy-zero bug: `where(cond, x, 0)` now honors the `0` branch instead of falling through to the indices form. * `select` - `condlist`, `choicelist`, and `defaultVal` widened to `ArrayLike` / `ArrayLike[]`. * `bincount`, `digitize`, `histogram`, `histogram2d`, `histogramdd` - data, weights, and bin arguments are `ArrayLike`. ## numpy-ts v1.3.2 Patch release with a couple of bug fixes and improvements: * Fixed issue with `[Symbol.dispose]` not working properly on Safari & old runtimes * Added `wasmFreeBytes` to public exports to allow memory usage checks ## numpy-ts v1.3.1 Patch release with new WASM kernels, performance improvements, and export fixes. * **New WASM kernels**: `conj`, `deg2rad`, `modf`, `packbits`, `nanquantile`, `argwhere`, and `float32` SVD * Optimized WASM SIMD for `argmin`/`argmax` int16/uint16 paths * Reduced WASM base threshold for earlier WASM dispatch on smaller arrays * **Export fixes**: `configureWasm`, `wasmConfig`, and `hasFloat16` now exported from `core` and `full` entrypoints * Benchmark runner refactored from if-else chain to dictionary lookup ## numpy-ts v1.3.0 **This is the first release where numpy-ts is faster than native NumPy on average** — **1.13x** across 7,200 benchmarks, leading in 12 of 18 categories. See the [Performance Overview](./v1.3.x/performance/overview) for the full breakdown. v1.3.0 gets there by moving array storage into WebAssembly linear memory for true zero-copy WASM kernel execution, ships a new memory-management API, and brings a stack of NumPy compatibility fixes. * **WASM-backed array storage**: Arrays now live directly in a shared WebAssembly memory pool (default 256 MiB). WASM kernels operate on these pointers with **zero copy-in/copy-out overhead**. * Bandwidth-bound operations (`add`, `multiply`, bitwise) see up to **2.6x improvement** * Compute-bound operations (`matmul`, `svd`) see minimal change (already kernel-dominated) * Graceful fallback to JS `TypedArray`s when the pool is full * Comes with new `dispose()` and `configureWasm()` methods for manual memory management and pool configuration * **Browser bundle is ESM**. Load via `