(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.
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.
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.
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 `