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