Skip to content

Commit a8fda78

Browse files
committed
Removed stdlib ndarray and implemented our own
1 parent d6c7f2b commit a8fda78

11 files changed

Lines changed: 1671 additions & 1773 deletions

File tree

benchmarks/results/latest.json

Lines changed: 762 additions & 789 deletions
Large diffs are not rendered by default.

benchmarks/results/plots/latest.html

Lines changed: 175 additions & 182 deletions
Large diffs are not rendered by default.
-1.26 KB
Loading

package-lock.json

Lines changed: 1 addition & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,7 @@
106106
"vitest": "^4.0.3"
107107
},
108108
"dependencies": {
109-
"@stdlib/blas": "^0.3.3",
110-
"@stdlib/ndarray": "^0.3.3"
109+
"@stdlib/blas": "^0.3.3"
111110
},
112111
"engines": {
113112
"node": ">=18.0.0"

src/core/broadcasting.ts

Lines changed: 99 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
/**
22
* Broadcasting utilities for NumPy-compatible array operations
33
*
4-
* Wraps @stdlib broadcasting functions with NumPy-compatible API
4+
* Implements NumPy broadcasting rules without external dependencies
55
*/
66

7-
import broadcastShapes from '@stdlib/ndarray/base/broadcast-shapes';
8-
import broadcastArrays from '@stdlib/ndarray/broadcast-arrays';
9-
10-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
11-
type StdlibNDArray = any;
7+
import { ArrayStorage } from './storage';
128

139
/**
1410
* Check if two or more shapes are broadcast-compatible
@@ -33,10 +29,31 @@ export function computeBroadcastShape(shapes: readonly number[][]): number[] | n
3329
return Array.from(shapes[0]!);
3430
}
3531

36-
// Use @stdlib's broadcastShapes
37-
// It returns null if shapes are incompatible
38-
const result = broadcastShapes(shapes.map((s) => Array.from(s)));
39-
return result ? Array.from(result as ArrayLike<number>) : null;
32+
// Find max number of dimensions
33+
const maxNdim = Math.max(...shapes.map((s) => s.length));
34+
const result = new Array(maxNdim);
35+
36+
for (let i = 0; i < maxNdim; i++) {
37+
let dim = 1;
38+
for (const shape of shapes) {
39+
const shapeIdx = shape.length - maxNdim + i;
40+
const shapeDim = shapeIdx < 0 ? 1 : shape[shapeIdx]!;
41+
42+
if (shapeDim === 1) {
43+
// Can be broadcast
44+
continue;
45+
} else if (dim === 1) {
46+
// First non-1 dimension
47+
dim = shapeDim;
48+
} else if (dim !== shapeDim) {
49+
// Incompatible
50+
return null;
51+
}
52+
}
53+
result[i] = dim;
54+
}
55+
56+
return result;
4057
}
4158

4259
/**
@@ -58,39 +75,89 @@ export function areBroadcastable(shape1: readonly number[], shape2: readonly num
5875
}
5976

6077
/**
61-
* Broadcast multiple stdlib ndarrays to a common shape
78+
* Compute the strides for broadcasting an array to a target shape
79+
* Returns strides where dimensions that need broadcasting have stride 0
80+
*/
81+
function broadcastStrides(
82+
shape: readonly number[],
83+
strides: readonly number[],
84+
targetShape: readonly number[]
85+
): number[] {
86+
const ndim = shape.length;
87+
const targetNdim = targetShape.length;
88+
const result = new Array(targetNdim).fill(0);
89+
90+
// Align dimensions from the right
91+
for (let i = 0; i < ndim; i++) {
92+
const targetIdx = targetNdim - ndim + i;
93+
const dim = shape[i]!;
94+
const targetDim = targetShape[targetIdx]!;
95+
96+
if (dim === targetDim) {
97+
// Same size, use original stride
98+
result[targetIdx] = strides[i]!;
99+
} else if (dim === 1) {
100+
// Broadcasting, stride is 0 (repeat along this dimension)
101+
result[targetIdx] = 0;
102+
} else {
103+
// This shouldn't happen if shapes were validated
104+
throw new Error('Invalid broadcast');
105+
}
106+
}
107+
108+
return result;
109+
}
110+
111+
/**
112+
* Broadcast an ArrayStorage to a target shape
113+
* Returns a view with modified strides for broadcasting
114+
*
115+
* @param storage - The storage to broadcast
116+
* @param targetShape - The target shape to broadcast to
117+
* @returns A new ArrayStorage view with broadcasting strides
118+
*/
119+
export function broadcastTo(storage: ArrayStorage, targetShape: readonly number[]): ArrayStorage {
120+
const broadcastedStrides = broadcastStrides(storage.shape, storage.strides, targetShape);
121+
return ArrayStorage.fromData(
122+
storage.data,
123+
Array.from(targetShape),
124+
storage.dtype,
125+
broadcastedStrides,
126+
storage.offset
127+
);
128+
}
129+
130+
/**
131+
* Broadcast multiple ArrayStorage objects to a common shape
62132
*
63-
* Returns read-only views of the input arrays broadcast to the same shape.
133+
* Returns views of the input arrays broadcast to the same shape.
64134
* Views share memory with the original arrays.
65135
*
66-
* @param arrays - Stdlib ndarrays to broadcast
67-
* @returns Array of broadcast stdlib ndarrays
136+
* @param storages - ArrayStorage objects to broadcast
137+
* @returns Array of broadcast ArrayStorage views
68138
* @throws Error if arrays have incompatible shapes
69-
*
70-
* @example
71-
* ```typescript
72-
* const [a, b] = broadcastStdlibArrays([arr1._data, arr2._data]);
73-
* ```
74139
*/
75-
export function broadcastStdlibArrays(arrays: StdlibNDArray[]): StdlibNDArray[] {
76-
if (arrays.length === 0) {
140+
export function broadcastArrays(storages: ArrayStorage[]): ArrayStorage[] {
141+
if (storages.length === 0) {
77142
return [];
78143
}
79144

80-
if (arrays.length === 1) {
81-
return arrays;
145+
if (storages.length === 1) {
146+
return storages;
82147
}
83148

84-
// Use @stdlib's broadcastArrays
85-
// This returns read-only views, which is perfect for efficient broadcasting
86-
try {
87-
const result = broadcastArrays(arrays);
88-
return result;
89-
} catch (error: unknown) {
90-
// @stdlib throws for incompatible shapes
91-
const err = error as Error;
92-
throw new Error(`operands could not be broadcast together: ${err.message}`);
149+
// Compute broadcast shape
150+
const shapes = storages.map((s) => Array.from(s.shape));
151+
const targetShape = computeBroadcastShape(shapes);
152+
153+
if (targetShape === null) {
154+
throw new Error(
155+
`operands could not be broadcast together with shapes ${shapes.map((s) => JSON.stringify(s)).join(' ')}`
156+
);
93157
}
158+
159+
// Broadcast each storage to the target shape
160+
return storages.map((s) => broadcastTo(s, targetShape));
94161
}
95162

96163
/**

0 commit comments

Comments
 (0)