|  | 
|  | 1 | +import numpy as np | 
|  | 2 | +import pytest | 
|  | 3 | +from link.jax.test_basic import compare_jax_and_py | 
|  | 4 | + | 
|  | 5 | +from pytensor import function, shared | 
|  | 6 | +from pytensor.graph import FunctionGraph | 
|  | 7 | +from pytensor.loop.basic import scan | 
|  | 8 | +from pytensor.scan import until | 
|  | 9 | +from pytensor.tensor import scalar, vector, zeros | 
|  | 10 | +from pytensor.tensor.random import normal | 
|  | 11 | + | 
|  | 12 | + | 
|  | 13 | +def test_scan_with_single_sequence(): | 
|  | 14 | +    xs = vector("xs") | 
|  | 15 | +    _, [ys] = scan(lambda x: x * 100, sequences=[xs]) | 
|  | 16 | + | 
|  | 17 | +    out_fg = FunctionGraph([xs], [ys]) | 
|  | 18 | +    compare_jax_and_py(out_fg, [np.arange(10)]) | 
|  | 19 | + | 
|  | 20 | + | 
|  | 21 | +def test_scan_with_single_sequence_shortened_by_nsteps(): | 
|  | 22 | +    xs = vector("xs", shape=(10,))  # JAX needs the length to be constant | 
|  | 23 | +    _, [ys] = scan( | 
|  | 24 | +        lambda x: x * 100, | 
|  | 25 | +        sequences=[xs], | 
|  | 26 | +        n_steps=9, | 
|  | 27 | +    ) | 
|  | 28 | + | 
|  | 29 | +    out_fg = FunctionGraph([xs], [ys]) | 
|  | 30 | +    compare_jax_and_py(out_fg, [np.arange(10)]) | 
|  | 31 | + | 
|  | 32 | + | 
|  | 33 | +def test_scan_with_multiple_sequences(): | 
|  | 34 | +    # JAX can only handle constant n_steps | 
|  | 35 | +    xs = vector("xs", shape=(10,)) | 
|  | 36 | +    ys = vector("ys", shape=(10,)) | 
|  | 37 | +    _, [zs] = scan( | 
|  | 38 | +        fn=lambda x, y: x * y, | 
|  | 39 | +        sequences=[xs, ys], | 
|  | 40 | +    ) | 
|  | 41 | + | 
|  | 42 | +    out_fg = FunctionGraph([xs, ys], [zs]) | 
|  | 43 | +    compare_jax_and_py( | 
|  | 44 | +        out_fg, [np.arange(10, dtype=xs.dtype), np.arange(10, dtype=ys.dtype)] | 
|  | 45 | +    ) | 
|  | 46 | + | 
|  | 47 | + | 
|  | 48 | +def test_scan_with_carried_and_non_carried_states(): | 
|  | 49 | +    x = scalar("x") | 
|  | 50 | +    _, [ys1, ys2] = scan( | 
|  | 51 | +        fn=lambda xtm1: (xtm1 + 1, (xtm1 + 1) * 2), | 
|  | 52 | +        init_states=[x, None], | 
|  | 53 | +        n_steps=10, | 
|  | 54 | +    ) | 
|  | 55 | +    out_fg = FunctionGraph([x], [ys1, ys2]) | 
|  | 56 | +    compare_jax_and_py(out_fg, [-1]) | 
|  | 57 | + | 
|  | 58 | + | 
|  | 59 | +def test_scan_with_sequence_and_carried_state(): | 
|  | 60 | +    xs = vector("xs") | 
|  | 61 | +    _, [ys] = scan( | 
|  | 62 | +        fn=lambda x, ytm1: (ytm1 + 1) * x, | 
|  | 63 | +        init_states=[zeros(())], | 
|  | 64 | +        sequences=[xs], | 
|  | 65 | +    ) | 
|  | 66 | +    out_fg = FunctionGraph([xs], [ys]) | 
|  | 67 | +    compare_jax_and_py(out_fg, [[1, 2, 3]]) | 
|  | 68 | + | 
|  | 69 | + | 
|  | 70 | +def test_scan_with_rvs(): | 
|  | 71 | +    rng = shared(np.random.default_rng(123)) | 
|  | 72 | + | 
|  | 73 | +    [next_rng, _], [_, xs] = scan( | 
|  | 74 | +        fn=lambda prev_rng: normal(rng=prev_rng).owner.outputs, | 
|  | 75 | +        init_states=[rng, None], | 
|  | 76 | +        n_steps=10, | 
|  | 77 | +    ) | 
|  | 78 | + | 
|  | 79 | +    # First without updates | 
|  | 80 | +    fn = function([], xs, mode="JAX", updates=None) | 
|  | 81 | +    res1 = fn() | 
|  | 82 | +    res2 = fn() | 
|  | 83 | +    assert not set(tuple(np.array(res1))) ^ set(tuple(np.array(res2))) | 
|  | 84 | + | 
|  | 85 | +    # Now with updates | 
|  | 86 | +    fn = function([], xs, mode="JAX", updates={rng: next_rng}) | 
|  | 87 | +    res1 = fn() | 
|  | 88 | +    res2 = fn() | 
|  | 89 | +    assert not set(tuple(np.array(res1))) & set(tuple(np.array(res2))) | 
|  | 90 | + | 
|  | 91 | + | 
|  | 92 | +def test_while_scan_fails(): | 
|  | 93 | +    _, [xs] = scan( | 
|  | 94 | +        fn=lambda x: (x + 1, until((x + 1) >= 9)), | 
|  | 95 | +        init_states=[-1], | 
|  | 96 | +        n_steps=20, | 
|  | 97 | +    ) | 
|  | 98 | + | 
|  | 99 | +    out_fg = FunctionGraph([], [xs]) | 
|  | 100 | +    with pytest.raises( | 
|  | 101 | +        NotImplementedError, | 
|  | 102 | +        match="Scan ops with while condition cannot be transpiled JAX", | 
|  | 103 | +    ): | 
|  | 104 | +        compare_jax_and_py(out_fg, []) | 
0 commit comments