@@ -212,13 +212,17 @@ def reduce(self, monoid, body, streams):
212212 used = [
213213 k
214214 for k , v in streams .items ()
215- if issubclass (typeof (v ), jax .Array | jax .core .Tracer ) and k in body_fvs
215+ if k in body_fvs
216+ and (
217+ issubclass (typeof (v ), jax .Array | jax .core .Tracer )
218+ or isinstance (v , range )
219+ )
216220 ]
217221 if not used :
218222 return fwd ()
219223
220224 # an empty reduction axis collapses the whole reduce to the identity
221- if any (_arange_length (streams [k ]) == 0 for k in used ):
225+ if any (not isinstance ( streams [ k ], Term ) and len (streams [k ]) == 0 for k in used ):
222226 return monoid .identity
223227
224228 tail_streams = {k : v for (k , v ) in streams .items () if k not in used }
@@ -238,62 +242,15 @@ def delta(_index: tuple[int, ...], _weight: jax.Array) -> jax.Array:
238242 raise NotHandled
239243
240244
241- arange = Operation .define (jnp .arange )
242-
243-
244- def _range_start (term : Term ):
245- assert term .op == arange
246- if "start" in term .kwargs :
247- return term .kwargs ["start" ]
248- if len (term .args ) < 2 :
249- return 0
250- return term .args [0 ]
251-
252-
253245def _range_stop (term : Term ):
254- assert term .op == arange
246+ assert term .op == jnp . arange
255247 if "stop" in term .kwargs :
256248 return term .kwargs ["stop" ]
257249 if len (term .args ) < 2 :
258250 return term .args [0 ]
259251 return term .args [1 ]
260252
261253
262- def _range_step (term : Term ):
263- assert term .op == arange
264- if "step" in term .kwargs :
265- return term .kwargs ["step" ]
266- if len (term .args ) < 3 :
267- return 1
268- return term .args [2 ]
269-
270-
271- def _is_simple_range (term : Term ) -> bool :
272- if term .op != arange :
273- return False
274-
275- start = _range_start (term )
276- step = _range_step (term )
277- return (
278- not isinstance (start , Term )
279- and start == 0
280- and not isinstance (step , Term )
281- and step == 1
282- )
283-
284-
285- def _arange_length (term ) -> int | None :
286- """Concrete length of an ``arange`` term, or ``None`` if it is not an
287- ``arange`` term or any of its bounds is symbolic (a ``Term``).
288- """
289- if not (isinstance (term , Term ) and term .op is arange ):
290- return None
291- a , b , s = _range_start (term ), _range_stop (term ), _range_step (term )
292- if any (isinstance (x , Term ) for x in (a , b , s )):
293- return None
294- return len (range (int (a ), int (b ), int (s )))
295-
296-
297254def _substitute_streams (expr , streams , vars ):
298255 """Substitute each op in ``vars`` into its uses within ``expr``, replacing it
299256 with a fresh named dimension.
@@ -322,24 +279,20 @@ def _substitute_streams(expr, streams, vars):
322279
323280 # concrete-bounded arange streams slice their direct-index uses; the rest
324281 # gather.
325- arange_bounds : dict [Operation , tuple ] = {}
326- for k in vars :
327- v = streams [k ]
328- if isinstance (v , Term ) and v .op is arange :
329- a , b , s = _range_start (v ), _range_stop (v ), _range_step (v )
330- if not any (isinstance (x , Term ) for x in (a , b , s )):
331- arange_bounds [k ] = (a , b , s )
282+ range_streams : dict [Operation , range ] = {
283+ k : streams [k ] for k in vars if isinstance (streams [k ], range )
284+ }
332285
333286 # PHASE 1 (direct): slice a bare range var used as an array index.
334- if arange_bounds :
287+ if range_streams :
335288
336289 def _jax_getitem (arr , index ):
337290 inner_index , outer_index = [], []
338291 progress = False
339292 for i in index :
340- if isinstance (i , Term ) and i .op in arange_bounds :
341- a , b , s = arange_bounds [i .op ]
342- inner_index .append (slice (a , b , s ))
293+ if isinstance (i , Term ) and i .op in range_streams :
294+ r = range_streams [i .op ]
295+ inner_index .append (slice (r . start , r . stop , r . step ))
343296 outer_index .append (fresh [i .op ]())
344297 progress = True
345298 else :
@@ -356,13 +309,13 @@ def _jax_getitem(arr, index):
356309 # PHASE 2 (indirect): substitute any remaining uses of each var.
357310 subst : dict = {}
358311 for k in vars :
359- if k in arange_bounds :
360- a , b , s = arange_bounds [k ]
361- subst [k ] = deffn (unbind_dims (jnp .arange (a , b , s ), fresh [k ]))
312+ if k in range_streams :
313+ r = range_streams [k ]
314+ subst [k ] = deffn (unbind_dims (jnp .arange (r . start , r . stop , r . step ), fresh [k ]))
362315 else :
363316 subst [k ] = deffn (unbind_dims (streams [k ], fresh [k ]))
364- expr = handler (typing .cast (Interpretation , subst ))(evaluate )(expr )
365317
318+ expr = handler (typing .cast (Interpretation , subst ))(evaluate )(expr )
366319 return expr , fresh
367320
368321
@@ -420,7 +373,11 @@ def _(self, monoid: Monoid, body, streams: Streams):
420373
421374 head_op : Operation = head_index .op
422375 head_stream = streams [head_op ]
423- if not (isinstance (head_stream , Term ) and _is_simple_range (head_stream )):
376+ if not (
377+ isinstance (head_stream , range )
378+ and head_stream .start == 0
379+ and head_stream .step == 1
380+ ):
424381 return fwd ()
425382
426383 # peel the head index: substitute it into the weight (slicing direct
@@ -485,15 +442,16 @@ def _(self, monoid: Monoid, body, streams: Streams):
485442 simple_ranges = {
486443 k : v
487444 for (k , v ) in streams .items ()
488- if isinstance (v , Term ) and _is_simple_range ( v )
445+ if isinstance (v , range ) and v . start == 0 and v . step == 1
489446 }
490447 for u , u_stream in simple_ranges .items ():
491448 if fvsof (u_stream ) & stream_vars :
492449 continue
493450
494- for v , v_stream in simple_ranges .items ():
451+ for v , v_stream in streams .items ():
495452 if (
496453 isinstance (v_stream , Term )
454+ and v_stream .op == jnp .arange
497455 and isinstance (_range_stop (v_stream ), Term )
498456 and _range_stop (v_stream ).op == u
499457 ):
@@ -516,27 +474,6 @@ def _(self, monoid: Monoid, body, streams: Streams):
516474 return fwd ()
517475
518476
519- class ReduceRange (ObjectInterpretation ):
520- """Replace concrete-range stream values with materialized ``jnp.arange``.
521-
522- reduce(M, streams ∪ {v: range(a, b, s)}, body)
523- ≡ reduce(M, streams ∪ {v: jnp.arange(a, b, s)}, body)
524-
525- when ``a``, ``b``, ``s`` are concrete and ``body`` is not a delta term.
526- Delegates the actual reduction to whichever handler picks up the
527- materialized ``jax.Array`` streams.
528- """
529-
530- @implements (Monoid .reduce )
531- def _ (self , monoid : Monoid , body , streams : Streams ):
532- if arange in fvsof ((body , streams )):
533- intp : Interpretation = {arange : jnp .arange }
534- subst_body = handler (intp )(evaluate )(body )
535- subst_streams = handler (intp )(evaluate )(streams )
536- return monoid .reduce (subst_body , subst_streams )
537- return fwd ()
538-
539-
540477# Cross-cutting delta rules not yet implemented:
541478#
542479# - **Delta-commuting** (DC-hoist): for any pure op ``f`` (no Scoped binders
@@ -649,7 +586,7 @@ def einsum(subscripts: str, /, *operands: jax.Array) -> jax.Array:
649586 body = Product .plus (* factors )
650587
651588 out_tuple = tuple (ops [c ]() for c in out_spec )
652- streams = {op : arange (sizes [c ]) for c , op in ops .items ()}
589+ streams = {op : range (sizes [c ]) for c , op in ops .items ()}
653590 with handler (NormalizeIntp ):
654591 norm = deffn (Sum .reduce (delta (out_tuple , body ), streams ), * arrays )
655592 result = norm (* operands )
@@ -658,7 +595,6 @@ def einsum(subscripts: str, /, *operands: jax.Array) -> jax.Array:
658595
659596
660597NormalizeIntp .extend (
661- # ReduceRange(),
662598 ArrayReduce (),
663599 ReduceSumProductContraction (),
664600 ReduceDeltaIndependent (),
0 commit comments