diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000000..c4ca8cbc2c --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,82 @@ +name: Tests + +on: + push: + branches: + - main + - checks + pull_request: + branches: + - main + +# Cancels all previous workflow runs for pull requests that have not completed. +concurrency: + # The concurrency group contains the workflow name and the branch name for pull requests + # or the commit hash for any other events. + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} + cancel-in-progress: true + +jobs: + test: + name: "Doctest${{ matrix.python-version }}: ${{ matrix.part }}" + runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + python-version: ["3.7", "3.9"] + fast-compile: [0] + float32: [0] + install-numba: [1] + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Set up Python ${{ matrix.python-version }} + uses: conda-incubator/setup-miniconda@v2 + with: + mamba-version: "*" + channels: conda-forge,defaults + channel-priority: true + python-version: ${{ matrix.python-version }} + auto-update-conda: true + + - name: Create matrix id + id: matrix-id + env: + MATRIX_CONTEXT: ${{ toJson(matrix) }} + run: | + echo $MATRIX_CONTEXT + export MATRIX_ID=`echo $MATRIX_CONTEXT | md5sum | cut -c 1-32` + echo $MATRIX_ID + echo "::set-output name=id::$MATRIX_ID" + + - name: Install dependencies + shell: bash -l {0} + run: | + mamba install --yes -q "python~=${PYTHON_VERSION}=*_cpython" mkl numpy scipy pip mkl-service graphviz cython pytest coverage pytest-cov sympy + if [[ $INSTALL_NUMBA == "1" ]]; then mamba install --yes -q -c conda-forge "python~=${PYTHON_VERSION}=*_cpython" "numba>=0.55" numba-scipy; fi + mamba install --yes -q -c conda-forge "python~=${PYTHON_VERSION}=*_cpython" jax "jaxlib!=0.3.15" + pip install -e ./ + pip install -r requirements-rtd.txt + mamba list && pip freeze + python -c 'import aesara; print(aesara.config.__str__(print_doc=False))' + python -c 'import aesara; assert(aesara.config.blas__ldflags != "")' + env: + PYTHON_VERSION: ${{ matrix.python-version }} + INSTALL_NUMBA: ${{ matrix.install-numba }} + + - name: Run doctests + shell: bash -l {0} + run: | + if [[ $FAST_COMPILE == "1" ]]; then export AESARA_FLAGS=$AESARA_FLAGS,mode=FAST_COMPILE; fi + if [[ $FLOAT32 == "1" ]]; then export AESARA_FLAGS=$AESARA_FLAGS,floatX=float32; fi + export AESARA_FLAGS=$AESARA_FLAGS,warn__ignore_bug_before=all,on_opt_error=raise,on_shape_error=raise,gcc__cxxflags=-pipe + python ./doc/scripts/docgen.py --test + env: + MATRIX_ID: ${{ steps.matrix-id.outputs.id }} + MKL_THREADING_LAYER: GNU + MKL_NUM_THREADS: 1 + OMP_NUM_THREADS: 1 + PART: ${{ matrix.part }} + FAST_COMPILE: ${{ matrix.fast-compile }} + FLOAT32: ${{ matrix.float32 }} diff --git a/aesara/gradient.py b/aesara/gradient.py index 51b2bb77ad..d82bf0b897 100644 --- a/aesara/gradient.py +++ b/aesara/gradient.py @@ -2319,7 +2319,7 @@ def grad_clip(x, lower_bound, upper_bound): >>> z2 = aesara.gradient.grad(x**2, x) >>> f = aesara.function([x], outputs = [z, z2]) >>> print(f(2.0)) - [array(1.0), array(4.0)] + [array(1.), array(4.)] Notes ----- diff --git a/aesara/graph/basic.py b/aesara/graph/basic.py index ae97742432..12f7cfa7cf 100644 --- a/aesara/graph/basic.py +++ b/aesara/graph/basic.py @@ -926,6 +926,7 @@ def orphans_between( Examples -------- + >>> from aesara.graph.basic import orphans_between >>> orphans_between([x], [(x+y).out]) [y] diff --git a/aesara/tensor/rewriting/math.py b/aesara/tensor/rewriting/math.py index 3c38a6086b..2b0c1acfdd 100644 --- a/aesara/tensor/rewriting/math.py +++ b/aesara/tensor/rewriting/math.py @@ -648,9 +648,9 @@ class AlgebraicCanonizer(NodeRewriter): -------- >>> import aesara.tensor as at >>> from aesara.tensor.rewriting.math import AlgebraicCanonizer - >>> add_canonizer = AlgebraicCanonizer(add, sub, neg, \\ + >>> add_canonizer = AlgebraicCanonizer(add, sub, neg, \ ... lambda n, d: sum(n) - sum(d)) - >>> mul_canonizer = AlgebraicCanonizer(mul, true_div, inv, \\ + >>> mul_canonizer = AlgebraicCanonizer(mul, true_div, inv, \ ... lambda n, d: prod(n) / prod(d)) Examples of rewrites `mul_canonizer` can perform: diff --git a/doc/extending/creating_an_op.rst b/doc/extending/creating_an_op.rst index a5b906b0e6..dca3535361 100644 --- a/doc/extending/creating_an_op.rst +++ b/doc/extending/creating_an_op.rst @@ -516,6 +516,9 @@ We can test this by running the following segment: .. testcode:: properties + import numpy as np + import aesara.tensor as at + mult4plus5op = AXPBOp(4, 5) another_mult4plus5op = AXPBOp(4, 5) mult2plus3op = AXPBOp(2, 3) @@ -523,7 +526,7 @@ We can test this by running the following segment: assert mult4plus5op == another_mult4plus5op assert mult4plus5op != mult2plus3op - x = aesara.tensor.matrix() + x = at.matrix() f = aesara.function([x], mult4plus5op(x)) g = aesara.function([x], mult2plus3op(x)) @@ -667,8 +670,8 @@ For instance, to verify the :meth:`Rop` method of the ``DoubleOp``, you can use .. testcode:: tests - import numpy import tests + import numpy as np from tests.test_rop import RopLop_checker class TestDoubleRop(RopLop_checker): def setUp(self): diff --git a/doc/extending/ctype.rst b/doc/extending/ctype.rst index b05c2c6986..799649c02f 100644 --- a/doc/extending/ctype.rst +++ b/doc/extending/ctype.rst @@ -459,7 +459,7 @@ Final version .. testcode:: - from aesara.graph.type import + from aesara.graph.type import Type class Double(Type): diff --git a/doc/extending/graph_rewriting.rst b/doc/extending/graph_rewriting.rst index 9eb8d282ec..d8557d858f 100644 --- a/doc/extending/graph_rewriting.rst +++ b/doc/extending/graph_rewriting.rst @@ -382,7 +382,7 @@ results as follows: >>> res.evaled_obj add.0 >>> aesara.dprint(res.evaled_obj) -add [id A] '' +add [id A] |y [id B] |y [id B] @@ -400,10 +400,10 @@ varying number of arguments: >>> args_lv = var() >>> s = unify(cons(op_lv, args_lv), add(x, y)) >>> s -{~_2: , ~_3: e(x, y)} +{~_2: , ~_3: ExpressionTuple((x, y))} >>> s = unify(cons(op_lv, args_lv), add(x, y, z)) >>> s -{~_2: , ~_3: e(x, y, z)} +{~_2: , ~_3: ExpressionTuple((x, y, z))} From here, we can check ``s[op_lv] == add`` to confirm that we have the correct :class:`Op` and proceed with our rewrite. @@ -412,7 +412,7 @@ proceed with our rewrite. >>> res e(, x, y, z) >>> aesara.dprint(res.evaled_obj) -mul [id A] '' +mul [id A] |x [id B] |y [id C] |z [id D] @@ -440,52 +440,49 @@ turning :mod:`kanren` relations into :class:`NodeRewriter`\s; however, The following is an example that distributes dot products across additions. -.. code:: - - import aesara - import aesara.tensor as at - from aesara.graph.rewriting.kanren import KanrenRelationSub - from aesara.graph.rewriting.basic import EquilibriumGraphRewriter - from aesara.graph.rewriting.utils import rewrite_graph - from aesara.tensor.math import _dot - from etuples import etuple - from kanren import conso, eq, fact, heado, tailo - from kanren.assoccomm import assoc_flatten, associative - from kanren.core import lall - from kanren.graph import mapo - from unification import vars as lvars - - - # Make the graph pretty printing results a little more readable - aesara.pprint.assign( - _dot, aesara.printing.OperatorPrinter("@", -1, "left") - ) - - # Tell `kanren` that `add` is associative - fact(associative, at.add) - - - def dot_distributeo(in_lv, out_lv): - """A `kanren` goal constructor relation for the relation ``A.dot(a + b ...) == A.dot(a) + A.dot(b) ...``.""" - A_lv, add_term_lv, add_cdr_lv, dot_cdr_lv, add_flat_lv = lvars(5) - - return lall( - # Make sure the input is a `_dot` - eq(in_lv, etuple(_dot, A_lv, add_term_lv)), - # Make sure the term being `_dot`ed is an `add` - heado(at.add, add_term_lv), - # Flatten the associative pairings of `add` operations - assoc_flatten(add_term_lv, add_flat_lv), - # Get the flattened `add` arguments - tailo(add_cdr_lv, add_flat_lv), - # Add all the `_dot`ed arguments and set the output - conso(at.add, dot_cdr_lv, out_lv), - # Apply the `_dot` to all the flattened `add` arguments - mapo(lambda x, y: conso(_dot, etuple(A_lv, x), y), add_cdr_lv, dot_cdr_lv), - ) - +.. testcode:: - dot_distribute_rewrite = EquilibriumGraphRewriter([KanrenRelationSub(dot_distributeo)], max_use_ratio=10) + import aesara + import aesara.tensor as at + from aesara.graph.rewriting.kanren import KanrenRelationSub + from aesara.graph.rewriting.basic import EquilibriumGraphRewriter + from aesara.graph.rewriting.utils import rewrite_graph + from aesara.tensor.math import _dot + from etuples import etuple + from kanren import conso, eq, fact, heado, tailo + from kanren.assoccomm import assoc_flatten, associative + from kanren.core import lall + from kanren.graph import mapo + from unification import vars as lvars + + # Make the graph pretty printing results a little more readable + aesara.pprint.assign( + _dot, aesara.printing.OperatorPrinter("@", -1, "left") + ) + + # Tell `kanren` that `add` is associative + fact(associative, at.add) + + def dot_distributeo(in_lv, out_lv): + """A `kanren` goal constructor relation for the relation ``A.dot(a + b ...) == A.dot(a) + A.dot(b) ...``.""" + A_lv, add_term_lv, add_cdr_lv, dot_cdr_lv, add_flat_lv = lvars(5) + + return lall( + # Make sure the input is a `_dot` + eq(in_lv, etuple(_dot, A_lv, add_term_lv)), + # Make sure the term being `_dot`ed is an `add` + heado(at.add, add_term_lv), + # Flatten the associative pairings of `add` operations + assoc_flatten(add_term_lv, add_flat_lv), + # Get the flattened `add` arguments + tailo(add_cdr_lv, add_flat_lv), + # Add all the `_dot`ed arguments and set the output + conso(at.add, dot_cdr_lv, out_lv), + # Apply the `_dot` to all the flattened `add` arguments + mapo(lambda x, y: conso(_dot, etuple(A_lv, x), y), add_cdr_lv, dot_cdr_lv), + ) + + dot_distribute_rewrite = EquilibriumGraphRewriter([KanrenRelationSub(dot_distributeo)], max_use_ratio=10) Below, we apply `dot_distribute_rewrite` to a few example graphs. First we create simple test graph: @@ -506,9 +503,10 @@ Next we apply the rewrite to the graph: We see that the dot product has been distributed, as desired. Now, let's try a few more test cases: +>>> import aesara.tensor as at >>> z_at = at.vector("z") >>> w_at = at.vector("w") ->>> test_at = A_at.dot((x_at + y_at) + (z_at + w_at)) +>>> test_at = at.dot((x_at + y_at) + (z_at + w_at)) >>> print(aesara.pprint(test_at)) (A @ ((x + y) + (z + w))) >>> res = rewrite_graph(test_at, include=[], custom_rewrite=dot_distribute_rewrite, clone=False) @@ -517,7 +515,7 @@ few more test cases: >>> B_at = at.matrix("B") >>> w_at = at.vector("w") ->>> test_at = A_at.dot(x_at + (y_at + B_at.dot(z_at + w_at))) +>>> test_at = at.dot(x_at + (y_at + B_at.dot(z_at + w_at))) >>> print(aesara.pprint(test_at)) (A @ (x + (y + ((B @ z) + (B @ w))))) >>> res = rewrite_graph(test_at, include=[], custom_rewrite=dot_distribute_rewrite, clone=False) diff --git a/doc/extending/graphstructures.rst b/doc/extending/graphstructures.rst index 41f16504f5..33c178ad2a 100644 --- a/doc/extending/graphstructures.rst +++ b/doc/extending/graphstructures.rst @@ -108,9 +108,9 @@ All of the above can be succinctly summarized with the :func:`aesara.dprint` function: >>> aesara.dprint(y) -Elemwise{mul,no_inplace} [id A] '' +Elemwise{mul,no_inplace} [id A] |x [id B] - |InplaceDimShuffle{x,x} [id C] '' + |InplaceDimShuffle{x,x} [id C] |TensorConstant{2.0} [id D] Starting from this graph structure it is easier to understand how @@ -353,7 +353,7 @@ Consider the following example of rewrites: >>> b = a + a ** 10 # build symbolic expression >>> f = aesara.function([a], b) # compile function >>> print(f([0, 1, 2])) # prints `array([0,2,1026])` -[ 0. 2. 1026.] +[ 0. 2. 1026.] >>> aesara.printing.pydotprint(b, outfile="./pics/symbolic_graph_no_rewrite.png", var_with_name_simple=True) # doctest: +SKIP The output file is available at ./pics/symbolic_graph_no_rewrite.png >>> aesara.printing.pydotprint(f, outfile="./pics/symbolic_graph_rewite.png", var_with_name_simple=True) # doctest: +SKIP diff --git a/doc/extending/type.rst b/doc/extending/type.rst index 39accfc687..e57aaf6bff 100644 --- a/doc/extending/type.rst +++ b/doc/extending/type.rst @@ -349,12 +349,12 @@ must define ``filter`` and ``values_eq_approx``. # note that we shadow python's function ``filter`` with this # definition. - def filter(x, strict=false, allow_downcast=none): + def filter(x, strict=False, allow_downcast=None): if strict: if isinstance(x, float): return x else: - raise typeerror('expected a float!') + raise TypeError('expected a float!') elif allow_downcast: return float(x) else: # covers both the false and none cases. diff --git a/doc/library/compile/function.rst b/doc/library/compile/function.rst index bf7f409798..f1374bb121 100644 --- a/doc/library/compile/function.rst +++ b/doc/library/compile/function.rst @@ -22,7 +22,7 @@ You've already seen example usage in the basic tutorial... something like this: >>> x = aesara.tensor.dscalar() >>> f = aesara.function([x], 2*x) >>> f(4) -array(8.0) +array(8.) The idea here is that we've compiled the symbolic graph (``2*x``) into a function that can be called on a number and will do some computations. diff --git a/doc/library/compile/io.rst b/doc/library/compile/io.rst index 2408a4dd07..50dc66468a 100644 --- a/doc/library/compile/io.rst +++ b/doc/library/compile/io.rst @@ -118,7 +118,7 @@ We can also assign to ``inc[s]`` directly: >>> inc[s] = 10 >>> inc[s] -array(10.0) +array(10.) Input Argument Restrictions --------------------------- @@ -195,21 +195,21 @@ True True >>> fn['s'] -array(10.0) +array(10.) >>> fn(1, 2) [] >>> fn['s'] -array(13.0) +array(13.) >>> fn['s'] = 99.0 >>> fn(1, 0) [] >>> fn['s'] -array(100.0) +array(100.) >>> fn.value[c] = 99.0 >>> fn(1,0) [] >>> fn['s'] -array(100.0) +array(100.) >>> fn['s'] == fn.value[c] True >>> fn['s'] == fn.container[c].value @@ -255,21 +255,21 @@ Traceback (most recent call last): ... TypeError: Missing required input: y >>> fn(1, 2) # legal, z is 42, w goes 0 -> 1 (because w <- w + x) -array(45.0) +array(45.) >>> fn(1, y=2) # legal, z is 42, w goes 1 -> 2 -array(45.0) +array(45.) >>> fn(x=1, y=2) # illegal because x was not named # doctest: +ELLIPSIS Traceback (most recent call last): ... TypeError: Unknown input or state: x. The function has 3 named inputs (y, z, w), and 1 unnamed input which thus cannot be accessed through keyword argument (use 'name=...' in a variable's constructor to give it a name). >>> fn(1, 2, 3) # legal, z is 3, w goes 2 -> 3 -array(6.0) +array(6.) >>> fn(1, z=3, y=2) # legal, z is 3, w goes 3 -> 4 -array(6.0) +array(6.) >>> fn(1, 2, w=400) # legal, z is 42 again, w goes 400 -> 401 -array(45.0) +array(45.) >>> fn(1, 2) # legal, z is 42, w goes 401 -> 402 -array(45.0) +array(45.) In the example above, ``z`` has value 42 when no value is explicitly given. This default value is potentially used at every function invocation, because @@ -313,18 +313,18 @@ If a list of ``Variable`` or ``Out`` instances is given as argument, then the co >>> # print a list of 2 ndarrays >>> fn1 = aesara.function([x], [x+x, Out((x+x).T, borrow=True)]) >>> fn1(numpy.asarray([[1,0],[0,1]])) -[array([[ 2., 0.], - [ 0., 2.]]), array([[ 2., 0.], - [ 0., 2.]])] +[array([[2., 0.], + [0., 2.]]), array([[2., 0.], + [0., 2.]])] >>> # print a list of 1 ndarray >>> fn2 = aesara.function([x], [x+x]) >>> fn2(numpy.asarray([[1,0],[0,1]])) -[array([[ 2., 0.], - [ 0., 2.]])] +[array([[2., 0.], + [0., 2.]])] >>> # print an ndarray >>> fn3 = aesara.function([x], outputs=x+x) >>> fn3(numpy.asarray([[1,0],[0,1]])) -array([[ 2., 0.], - [ 0., 2.]]) +array([[2., 0.], + [0., 2.]]) diff --git a/doc/library/printing.rst b/doc/library/printing.rst index 7157c4beda..733009ccf2 100644 --- a/doc/library/printing.rst +++ b/doc/library/printing.rst @@ -29,7 +29,7 @@ Instead there is the :class:`Print` Op. >>> printed_x = hello_world_op(x) >>> f = function([x], printed_x) >>> r = f([1, 2, 3]) -hello world __str__ = [ 1. 2. 3.] +hello world __str__ = [1. 2. 3.] If you print more than one thing in a function like `f`, they will not necessarily be printed in the order that you think. The order might even depend @@ -51,16 +51,16 @@ Aesara also provides :func:`aesara.printing.pydotprint` that creates a png image 1) The first is :func:`aesara.pp`. ->>> from aesara import pp, grad, ->>> from aesara import tensor as at +>>> from aesara import pp, grad +>>> import aesara.tensor as at >>> x = at.dscalar('x') >>> y = x ** 2 >>> gy = grad(y, x) >>> pp(gy) # print out the gradient prior to rewriting -'((fill((x ** TensorConstant{2}), TensorConstant{1.0}) * TensorConstant{2}) * (x ** (TensorConstant{2} - TensorConstant{1})))' +'((fill((x ** 2), 1.0) * 2) * (x ** (2 - 1)))' >>> f = function([x], gy) >>> pp(f.maker.fgraph.outputs[0]) -'(TensorConstant{2.0} * x)' +'(2.0 * x)' The parameter in at.dscalar('x') in the first line is the name of this variable in the graph. This name is used when printing the graph to make it more readable. diff --git a/doc/library/scan.rst b/doc/library/scan.rst index 0abcbcd6ed..ea0892d3f4 100644 --- a/doc/library/scan.rst +++ b/doc/library/scan.rst @@ -62,10 +62,9 @@ The equivalent Aesara code would be: .. testoutput:: - [ 0. 1. 4. 9. 16. 25. 36. 49. 64. 81.] - [ 0.00000000e+00 1.00000000e+00 1.60000000e+01 8.10000000e+01 - 2.56000000e+02 6.25000000e+02 1.29600000e+03 2.40100000e+03 - 4.09600000e+03 6.56100000e+03] + [ 0. 1. 4. 9. 16. 25. 36. 49. 64. 81.] + [0.000e+00 1.000e+00 1.600e+01 8.100e+01 2.560e+02 6.250e+02 1.296e+03 + 2.401e+03 4.096e+03 6.561e+03] Let us go through the example line by line. What we did is first to construct a function (using a lambda expression) that given ``prior_result`` and @@ -230,17 +229,17 @@ with all values set to zero except at the provided array indices. .. testoutput:: - [[[ 0. 0. 0. 0. 0.] - [ 0. 42. 0. 0. 0.] - [ 0. 0. 0. 0. 0.] - [ 0. 0. 0. 0. 0.] - [ 0. 0. 0. 0. 0.]] + [[[ 0. 0. 0. 0. 0.] + [ 0. 42. 0. 0. 0.] + [ 0. 0. 0. 0. 0.] + [ 0. 0. 0. 0. 0.] + [ 0. 0. 0. 0. 0.]] - [[ 0. 0. 0. 0. 0.] - [ 0. 0. 0. 0. 0.] - [ 0. 0. 0. 50. 0.] - [ 0. 0. 0. 0. 0.] - [ 0. 0. 0. 0. 0.]]] + [[ 0. 0. 0. 0. 0.] + [ 0. 0. 0. 0. 0.] + [ 0. 0. 0. 50. 0.] + [ 0. 0. 0. 0. 0.] + [ 0. 0. 0. 0. 0.]]] This demonstrates that you can introduce new Aesara variables into a scan function. @@ -457,20 +456,18 @@ In this case we have a sequence over which we need to iterate ``u``, and two outputs ``x`` and ``y``. To implement this with scan we first construct a function that computes one iteration step : -.. testsetup:: scan3 - - import aesara - from aesara import tensor as at - .. testcode:: scan3 + + import aesara + import aesara.tensor as at def oneStep(u_tm4, u_t, x_tm3, x_tm1, y_tm1, W, W_in_1, W_in_2, W_feedback, W_out): - x_t = at.tanh(aesara.dot(x_tm1, W) + \ - aesara.dot(u_t, W_in_1) + \ - aesara.dot(u_tm4, W_in_2) + \ - aesara.dot(y_tm1, W_feedback)) - y_t = aesara.dot(x_tm3, W_out) + x_t = at.tanh(at.dot(x_tm1, W) + \ + at.dot(u_t, W_in_1) + \ + at.dot(u_tm4, W_in_2) + \ + at.dot(y_tm1, W_feedback)) + y_t = at.dot(x_tm3, W_out) return [x_t, y_t] diff --git a/doc/library/tensor/basic.rst b/doc/library/tensor/basic.rst index b9e52e6684..4b0f04264b 100644 --- a/doc/library/tensor/basic.rst +++ b/doc/library/tensor/basic.rst @@ -119,6 +119,7 @@ They are all callable, and accept an optional ``name`` argument. So for example .. testcode:: constructors + import aesara.tensor as at x = at.dmatrix() # creates one Variable with no name x = at.dmatrix('x') # creates one Variable with name 'x' xyz = at.dmatrix('xyz') # creates one Variable with name 'xyz' @@ -246,6 +247,8 @@ name. For example: .. testcode:: constructors + import aesara.tensor as at + # Creates three matrix `Variable`s with no names x, y, z = at.dmatrices(3) # Creates three matrix `Variables` named 'x', 'y' and 'z' @@ -886,8 +889,8 @@ Creating Tensors >>> X = stacklists([[a, b], [c, d]]) >>> f = function([a, b, c, d], X) >>> f(1, 2, 3, 4) - array([[ 1., 2.], - [ 3., 4.]]) + array([[1., 2.], + [3., 4.]]) We can also stack arbitrarily shaped tensors. Here we stack matrices into a 2 by 2 grid: diff --git a/doc/library/tensor/nnet/basic.rst b/doc/library/tensor/nnet/basic.rst index c9a71d97b5..d9ef40c915 100644 --- a/doc/library/tensor/nnet/basic.rst +++ b/doc/library/tensor/nnet/basic.rst @@ -113,7 +113,7 @@ x, y, b = at.dvectors('x', 'y', 'b') W = at.dmatrix('W') - y = at.nnet.softplus(at.dot(W,x) + b) + y = at.nnet.basic.softplus(at.dot(W,x) + b) .. function:: softsign(x) @@ -206,7 +206,7 @@ x_precons = at.dot(V, h) + c # final reconstructions are given by sigmoid(x_precons), but we leave # them unnormalized as sigmoid_binary_crossentropy applies sigmoid - recon_cost = at.sigmoid_binary_crossentropy(x_precons, x).mean() + recon_cost = at.nnet.basic.sigmoid_binary_crossentropy(x_precons, x).mean() .. function:: categorical_crossentropy(coding_dist,true_dist) diff --git a/doc/library/typed_list.rst b/doc/library/typed_list.rst index cb246cad4a..1136ecfc19 100644 --- a/doc/library/typed_list.rst +++ b/doc/library/typed_list.rst @@ -23,7 +23,7 @@ the same Aesara type. Here is an example: >>> o = aesara.typed_list.append(tl, v) >>> f = aesara.function([tl, v], o) >>> f([[1, 2, 3], [4, 5]], [2]) -[array([ 1., 2., 3.], dtype=float32), array([ 4., 5.], dtype=float32), array([ 2.], dtype=float32)] +[array([1., 2., 3.], dtype=float32), array([4., 5.], dtype=float32), array([2.], dtype=float32)] A second example with Scan. Scan doesn't yet have direct support of TypedList, so you can only use it as non_sequences (not in sequences or @@ -37,7 +37,7 @@ as outputs): ... sequences=[aesara.tensor.arange(l, dtype='int64')]) >>> f = aesara.function([a], s) >>> f([[1, 2, 3], [4, 5]]) -array([ 6., 9.], dtype=float32) +array([6., 9.], dtype=float32) .. automodule:: aesara.typed_list.basic :members: diff --git a/doc/tutorial/adding.rst b/doc/tutorial/adding.rst index e0276a2ff8..8365c60553 100644 --- a/doc/tutorial/adding.rst +++ b/doc/tutorial/adding.rst @@ -22,7 +22,7 @@ it: And now that we've created our function we can use it: >>> f(2, 3) -array(5.0) +array(5.) >>> numpy.allclose(f(16.3, 12.1), 28.4) True @@ -158,16 +158,16 @@ from the previous example is that you need to instantiate *x* and our new function on 2D arrays: >>> f([[1, 2], [3, 4]], [[10, 20], [30, 40]]) -array([[ 11., 22.], - [ 33., 44.]]) +array([[11., 22.], + [33., 44.]]) The variable is a NumPy array. We can also use NumPy arrays directly as inputs: >>> import numpy >>> f(numpy.array([[1, 2], [3, 4]]), numpy.array([[10, 20], [30, 40]])) -array([[ 11., 22.], - [ 33., 44.]]) +array([[11., 22.], + [33., 44.]]) It is possible to add scalars to matrices, vectors to matrices, scalars to vectors, etc. The behavior of these operations is defined @@ -200,14 +200,15 @@ Exercise .. testcode:: import aesara - a = aesara.tensor.vector() # declare variable - out = a + a ** 10 # build symbolic expression - f = aesara.function([a], out) # compile function + import aesara.tensor as at + a = at.vector() # declare variable + out = a + a ** 10 # build symbolic expression + f = aesara.function([a], out) # compile function print(f([0, 1, 2])) .. testoutput:: - [ 0. 2. 1026.] + [ 0. 2. 1026.] Modify and execute this code to compute this expression: a ** 2 + b ** 2 + 2 * a * b. diff --git a/doc/tutorial/aliasing.rst b/doc/tutorial/aliasing.rst index d2bed06039..85f52f6304 100644 --- a/doc/tutorial/aliasing.rst +++ b/doc/tutorial/aliasing.rst @@ -78,9 +78,9 @@ subsequently make to ``np_array`` have no effect on our shared variable. .. testoutput:: borrow - [ 1. 1.] - [ 1. 1.] - [ 2. 2.] + [1. 1.] + [1. 1.] + [2. 2.] If we are running this with the CPU as the device, diff --git a/doc/tutorial/broadcasting.rst b/doc/tutorial/broadcasting.rst index 66740bb66a..a932a82a9d 100644 --- a/doc/tutorial/broadcasting.rst +++ b/doc/tutorial/broadcasting.rst @@ -55,9 +55,9 @@ array([[0, 1, 2], [3, 4, 5], [6, 7, 8]]) >>> f_row(R, M) -[array([[ 0., 2., 4.], - [ 3., 5., 7.], - [ 6., 8., 10.]])] +[array([[ 0., 2., 4.], + [ 3., 5., 7.], + [ 6., 8., 10.]])] >>> c = at.col() >>> c.broadcastable (False, True) @@ -69,9 +69,9 @@ array([[0], [2]]) >>> M = np.arange(9).reshape(3, 3) >>> f_col(C, M) -[array([[ 0., 1., 2.], - [ 4., 5., 6.], - [ 8., 9., 10.]])] +[array([[ 0., 1., 2.], + [ 4., 5., 6.], + [ 8., 9., 10.]])] In these examples, we can see that both the row vector and the column vector are broadcasted in order to be be added to the matrix. diff --git a/doc/tutorial/debug_faq.rst b/doc/tutorial/debug_faq.rst index 7e4d6a12e9..bb5b5d71a4 100644 --- a/doc/tutorial/debug_faq.rst +++ b/doc/tutorial/debug_faq.rst @@ -246,7 +246,7 @@ Running the code above returns the following output: .. testoutput:: printtestvalue x - array(42.0) + array(42.) x @@ -275,7 +275,7 @@ Aesara provides a :class:`Print`\ :class:`Op` to do this. .. testoutput:: - this is a very important value __str__ = [ 1. 2. 3.] + this is a very important value __str__ = [1. 2. 3.] Since Aesara runs your program in a topological order, you won't have precise control over the order in which multiple :class:`Print`\ `Op`\s are evaluated. For a more @@ -355,7 +355,7 @@ shows how to print all inputs and outputs: .. testoutput:: - 0 Elemwise{mul,no_inplace}(TensorConstant{5.0}, x) input(s) value(s): [array(5.0), array(3.0)] output(s) value(s): [array(15.0)] + 0 Elemwise{mul,no_inplace}(TensorConstant{5.0}, x) input(s) value(s): [array(5.), array(3.)] output(s) value(s): [array(15.)] When using these ``inspect_inputs`` and ``inspect_outputs`` functions with `MonitorMode`, you should see (potentially a lot of) printed output. @@ -371,9 +371,9 @@ computations, which can be achieved as follows: .. testcode:: compiled - import numpy - import aesara + import aesara.tensor as at + import numpy as np # This is the current suggested detect_nan implementation to # show you how it work. That way, you can modify it for your @@ -391,9 +391,9 @@ computations, which can be achieved as follows: print('Outputs: %s' % [output[0] for output in fn.outputs]) break - x = aesara.tensor.dscalar('x') + x = at.dscalar('x') f = aesara.function( - [x], [aesara.tensor.log(x) * x], + [x], [at.log(x) * x], mode=aesara.compile.MonitorMode( post_func=detect_nan) ) diff --git a/doc/tutorial/examples.rst b/doc/tutorial/examples.rst index 6b1770a453..095bd9446e 100644 --- a/doc/tutorial/examples.rst +++ b/doc/tutorial/examples.rst @@ -46,8 +46,8 @@ Well, what you do is this: >>> s = 1 / (1 + at.exp(-x)) >>> logistic = aesara.function([x], s) >>> logistic([[0, 1], [-1, -2]]) -array([[ 0.5 , 0.73105858], - [ 0.26894142, 0.11920292]]) +array([[0.5 , 0.73105858], + [0.26894142, 0.11920292]]) The reason the logistic is applied element-wise is because all of its operations--division, addition, exponentiation, and division--are @@ -67,8 +67,8 @@ We can verify that this alternate form produces the same values: >>> s2 = (1 + at.tanh(x / 2)) / 2 >>> logistic2 = aesara.function([x], s2) >>> logistic2([[0, 1], [-1, -2]]) -array([[ 0.5 , 0.73105858], - [ 0.26894142, 0.11920292]]) +array([[0.5 , 0.73105858], + [0.26894142, 0.11920292]]) Computing More than one Thing at the Same Time @@ -97,9 +97,9 @@ was reformatted for readability): >>> f([[1, 1], [1, 1]], [[0, 1], [2, 3]]) [array([[ 1., 0.], - [-1., -2.]]), array([[ 1., 0.], - [ 1., 2.]]), array([[ 1., 0.], - [ 1., 4.]])] + [-1., -2.]]), array([[1., 0.], + [1., 2.]]), array([[1., 0.], + [1., 4.]])] Setting a Default Value for an Argument @@ -118,9 +118,9 @@ one. You can do it like this: >>> z = x + y >>> f = function([x, In(y, value=1)], z) >>> f(33) -array(34.0) +array(34.) >>> f(33, 2) -array(35.0) +array(35.) This makes use of the :ref:`In ` class which allows you to specify properties of your function's parameters with greater detail. Here we @@ -139,15 +139,15 @@ parameters can be set positionally or by name, as in standard Python: >>> z = (x + y) * w >>> f = function([x, In(y, value=1), In(w, value=2, name='w_by_name')], z) >>> f(33) -array(68.0) +array(68.) >>> f(33, 2) -array(70.0) +array(70.) >>> f(33, 0, 1) -array(33.0) +array(33.) >>> f(33, w_by_name=1) -array(34.0) +array(34.) >>> f(33, w_by_name=1, y=0) -array(33.0) +array(33.) .. note:: `In` does not know the name of the local variables ``y`` and ``w`` @@ -316,7 +316,7 @@ using the ``swap`` parameter, which is a dictionary of shared variables to excha >>> new_state = aesara.shared(0) >>> new_accumulator = accumulator.copy(swap={state:new_state}) >>> new_accumulator(100) -[array(0)] +array(0) >>> print(new_state.get_value()) 100 @@ -333,7 +333,7 @@ parameter, which is set to ``False`` by default: As expected, the shared state is no longer updated: >>> null_accumulator(9000) -[array(10)] +array(10) >>> print(state.get_value()) 10 diff --git a/doc/tutorial/gradients.rst b/doc/tutorial/gradients.rst index ecaac80812..e850d264b4 100644 --- a/doc/tutorial/gradients.rst +++ b/doc/tutorial/gradients.rst @@ -28,10 +28,10 @@ Here is the code to compute this gradient: >>> y = x ** 2 >>> gy = at.grad(y, x) >>> pp(gy) # print out the gradient prior to optimization -'((fill((x ** TensorConstant{2}), TensorConstant{1.0}) * TensorConstant{2}) * (x ** (TensorConstant{2} - TensorConstant{1})))' +'((fill((x ** 2), 1.0) * 2) * (x ** (2 - 1)))' >>> f = aesara.function([x], gy) >>> f(4) -array(8.0) +array(8.) >>> numpy.allclose(f(94.2), 188.4) True @@ -69,8 +69,8 @@ logistic is: :math:`ds(x)/dx = s(x) \cdot (1 - s(x))`. >>> gs = at.grad(s, x) >>> dlogistic = aesara.function([x], gs) >>> dlogistic([[0, 1], [-1, -2]]) -array([[ 0.25 , 0.19661193], - [ 0.19661193, 0.10499359]]) +array([[0.25 , 0.19661193], + [0.19661193, 0.10499359]]) In general, for any **scalar** expression ``s``, ``at.grad(s, w)`` provides the Aesara expression for computing :math:`\frac{\partial s}{\partial w}`. In @@ -123,8 +123,8 @@ do is to loop over the entries in ``y`` and compute the gradient of >>> J, updates = aesara.scan(lambda i, y, x : at.grad(y[i], x), sequences=at.arange(y.shape[0]), non_sequences=[y, x]) >>> f = aesara.function([x], J, updates=updates) >>> f([4, 4]) -array([[ 8., 0.], - [ 0., 8.]]) +array([[8., 0.], + [0., 8.]]) What we do in this code is to generate a sequence of integers from ``0`` to ``y.shape[0]`` using `at.arange`. Then we loop through this sequence, and @@ -162,8 +162,8 @@ scalar. >>> H, updates = aesara.scan(lambda i, gy,x : at.grad(gy[i], x), sequences=at.arange(gy.shape[0]), non_sequences=[gy, x]) >>> f = aesara.function([x], H, updates=updates) >>> f([4, 4]) -array([[ 2., 0.], - [ 0., 2.]]) +array([[2., 0.], + [0., 2.]]) Jacobian times a Vector @@ -202,7 +202,7 @@ you need to do something similar to this: >>> JV = aesara.gradient.Rop(y, W, V) >>> f = aesara.function([W, V, x], JV) >>> f([[1, 1], [1, 1]], [[2, 2], [2, 2]], [0,1]) -array([ 2., 2.]) +array([2., 2.]) :ref:`List ` of Op that implement Rop. @@ -221,8 +221,8 @@ f(x)}{\partial x}`. The L-operator is also supported for generic tensors >>> VJ = aesara.gradient.Lop(y, W, v) >>> f = aesara.function([v,x], VJ) >>> f([2, 2], [0, 1]) -array([[ 0., 0.], - [ 2., 2.]]) +array([[0., 0.], + [2., 2.]]) .. note:: @@ -253,7 +253,7 @@ Hence, we suggest profiling the methods before using either one of the two: >>> vH = at.grad(at.sum(gy * v), x) >>> f = aesara.function([x, v], vH) >>> f([4, 4], [2, 2]) -array([ 4., 4.]) +array([4., 4.]) or, making use of the R-operator: @@ -265,7 +265,7 @@ or, making use of the R-operator: >>> Hv = aesara.gradient.Rop(gy, x, v) >>> f = aesara.function([x, v], Hv) >>> f([4, 4], [2, 2]) -array([ 4., 4.]) +array([4., 4.]) Final Pointers diff --git a/doc/tutorial/loop.rst b/doc/tutorial/loop.rst index 8c88fa661e..27bd34a5f6 100644 --- a/doc/tutorial/loop.rst +++ b/doc/tutorial/loop.rst @@ -55,10 +55,10 @@ The full documentation can be found in the library: :ref:`Scan `. .. testoutput:: - [[ 0.96402758 0.99505475] - [ 0.96402758 0.99505475]] - [[ 0.96402758 0.99505475] - [ 0.96402758 0.99505475]] + [[0.96402758 0.99505475] + [0.96402758 0.99505475]] + [[0.96402758 0.99505475] + [0.96402758 0.99505475]] **Scan Example: Computing the sequence x(t) = tanh(x(t - 1).dot(W) + y(t).dot(U) + p(T - t).dot(V))** @@ -136,8 +136,8 @@ The full documentation can be found in the library: :ref:`Scan `. .. testoutput:: - [ 1. 2. 3. 4. 5. 0.] - [ 1. 2. 3. 4. 5. 0.] + [1. 2. 3. 4. 5. 0.] + [1. 2. 3. 4. 5. 0.] **Scan Example: Computing norms of columns of X** @@ -161,8 +161,8 @@ The full documentation can be found in the library: :ref:`Scan `. .. testoutput:: - [ 0. 1. 2. 3. 4. 5.] - [ 0. 1. 2. 3. 4. 5.] + [0. 1. 2. 3. 4. 5.] + [0. 1. 2. 3. 4. 5.] **Scan Example: Computing trace of X** @@ -238,26 +238,26 @@ The full documentation can be found in the library: :ref:`Scan `. .. testoutput:: - [[ 1.40514825 1.40514825] - [ 2.88898899 2.38898899] - [ 4.34018291 4.34018291] - [ 6.53463142 6.78463142] - [ 9.82972243 9.82972243] - [ 14.22203814 14.09703814] - [ 20.07439936 20.07439936] - [ 28.12291843 28.18541843] - [ 39.1913681 39.1913681 ] - [ 54.28407732 54.25282732]] - [[ 1.40514825 1.40514825] - [ 2.88898899 2.38898899] - [ 4.34018291 4.34018291] - [ 6.53463142 6.78463142] - [ 9.82972243 9.82972243] - [ 14.22203814 14.09703814] - [ 20.07439936 20.07439936] - [ 28.12291843 28.18541843] - [ 39.1913681 39.1913681 ] - [ 54.28407732 54.25282732]] + [[ 1.40514825 1.40514825] + [ 2.88898899 2.38898899] + [ 4.34018291 4.34018291] + [ 6.53463142 6.78463142] + [ 9.82972243 9.82972243] + [14.22203814 14.09703814] + [20.07439936 20.07439936] + [28.12291843 28.18541843] + [39.1913681 39.1913681 ] + [54.28407732 54.25282732]] + [[ 1.40514825 1.40514825] + [ 2.88898899 2.38898899] + [ 4.34018291 4.34018291] + [ 6.53463142 6.78463142] + [ 9.82972243 9.82972243] + [14.22203814 14.09703814] + [20.07439936 20.07439936] + [28.12291843 28.18541843] + [39.1913681 39.1913681 ] + [54.28407732 54.25282732]] **Scan Example: Computing the Jacobian of y = tanh(v.dot(A)) wrt x** @@ -286,12 +286,12 @@ The full documentation can be found in the library: :ref:`Scan `. .. testoutput:: - [[ 0.41997434 0. 0.41997434 0. 0. ] - [ 0. 1. 1. 0. 0. ] - [ 0. 0. 1. 0. 0. ]] - [[ 0.41997434 0. 0.41997434 0. 0. ] - [ 0. 1. 1. 0. 0. ] - [ 0. 0. 1. 0. 0. ]] + [[0.41997434 0. 0.41997434 0. 0. ] + [0. 1. 1. 0. 0. ] + [0. 0. 1. 0. 0. ]] + [[0.41997434 0. 0.41997434 0. 0. ] + [0. 1. 1. 0. 0. ] + [0. 0. 1. 0. 0. ]] Note that we need to iterate over the indices of ``y`` and not over the elements of ``y``. The reason is that scan create a placeholder variable for its internal function and this placeholder variable does not have the same dependencies than the variables that will replace it. @@ -384,7 +384,7 @@ Note that if you want to use a random variable ``d`` that will not be updated th .. testoutput:: - [ 0. 1. 4. 9. 16. 25. 36. 49. 64. 81.] + [ 0. 1. 4. 9. 16. 25. 36. 49. 64. 81.] **Scan Example: Calculating a Polynomial** diff --git a/doc/tutorial/numpy.rst b/doc/tutorial/numpy.rst index 5c70830b41..df9762995c 100644 --- a/doc/tutorial/numpy.rst +++ b/doc/tutorial/numpy.rst @@ -32,9 +32,9 @@ layer would represent a matrix of size (5, #hid). Consider this array: >>> numpy.asarray([[1., 2], [3, 4], [5, 6]]) -array([[ 1., 2.], - [ 3., 4.], - [ 5., 6.]]) +array([[1., 2.], + [3., 4.], + [5., 6.]]) >>> numpy.asarray([[1., 2], [3, 4], [5, 6]]).shape (3, 2) @@ -62,7 +62,7 @@ compatible shapes. The example below shows an instance of >>> a = numpy.asarray([1.0, 2.0, 3.0]) >>> b = 2.0 >>> a * b -array([ 2., 4., 6.]) +array([2., 4., 6.]) The smaller array ``b`` (actually a scalar here, which works like a 0-d array) in this case is *broadcasted* to the same size as ``a`` during the multiplication. This trick is often useful in diff --git a/doc/tutorial/printing_drawing.rst b/doc/tutorial/printing_drawing.rst index 2323ee0560..7cef7a10b9 100644 --- a/doc/tutorial/printing_drawing.rst +++ b/doc/tutorial/printing_drawing.rst @@ -56,8 +56,7 @@ Pretty Printing =============== >>> aesara.printing.pprint(prediction) # doctest: +NORMALIZE_WHITESPACE -'gt((TensorConstant{1} / (TensorConstant{1} + exp(((-(x \\dot w)) - b)))), -TensorConstant{0.5})' +'gt((1 / (1 + exp(((-(x \\dot w)) - b)))), 0.5)' Debug Print @@ -65,38 +64,38 @@ Debug Print The pre-compilation graph: ->>> aesara.printing.debugprint(prediction) # doctest: +NORMALIZE_WHITESPACE, +ELLIPSIS -Elemwise{gt,no_inplace} [id A] '' - |Elemwise{true_div,no_inplace} [id B] '' - | |InplaceDimShuffle{x} [id C] '' +>>> aesara.printing.debugprint(prediction) +Elemwise{gt,no_inplace} [id A] + |Elemwise{true_div,no_inplace} [id B] + | |InplaceDimShuffle{x} [id C] | | |TensorConstant{1} [id D] - | |Elemwise{add,no_inplace} [id E] '' - | |InplaceDimShuffle{x} [id F] '' - | | |TensorConstant{1} [id D] - | |Elemwise{exp,no_inplace} [id G] '' - | |Elemwise{sub,no_inplace} [id H] '' - | |Elemwise{neg,no_inplace} [id I] '' - | | |dot [id J] '' - | | |x [id K] - | | |w [id L] - | |InplaceDimShuffle{x} [id M] '' - | |b [id N] - |InplaceDimShuffle{x} [id O] '' - |TensorConstant{0.5} [id P] + | |Elemwise{add,no_inplace} [id E] + | |InplaceDimShuffle{x} [id F] + | | |TensorConstant{1} [id G] + | |Elemwise{exp,no_inplace} [id H] + | |Elemwise{sub,no_inplace} [id I] + | |Elemwise{neg,no_inplace} [id J] + | | |dot [id K] + | | |x [id L] + | | |w [id M] + | |InplaceDimShuffle{x} [id N] + | |b [id O] + |InplaceDimShuffle{x} [id P] + |TensorConstant{0.5} [id Q] The post-compilation graph: >>> aesara.printing.debugprint(predict) # doctest: +NORMALIZE_WHITESPACE, +ELLIPSIS -Elemwise{Composite{GT(scalar_sigmoid((-((-i0) - i1))), i2)}} [id A] '' 4 - |...Gemv{inplace} [id B] '' 3 - | |AllocEmpty{dtype='float64'} [id C] '' 2 - | | |Shape_i{0} [id D] '' 1 +Elemwise{Composite{GT(sigmoid((-((-i0) - i1))), i2)}} [id A] 4 + |CGemv{inplace} [id B] 3 + | |AllocEmpty{dtype='float64'} [id C] 2 + | | |Shape_i{0} [id D] 1 | | |x [id E] | |TensorConstant{1.0} [id F] | |x [id E] | |w [id G] | |TensorConstant{0.0} [id H] - |InplaceDimShuffle{x} [id I] '' 0 + |InplaceDimShuffle{x} [id I] 0 | |b [id J] |TensorConstant{(1,) of 0.5} [id K] diff --git a/doc/tutorial/shape_info.rst b/doc/tutorial/shape_info.rst index 002a48c2fc..c033b17202 100644 --- a/doc/tutorial/shape_info.rst +++ b/doc/tutorial/shape_info.rst @@ -19,10 +19,10 @@ Example: >>> x = aesara.tensor.matrix('x') >>> f = aesara.function([x], (x ** 2).shape) >>> aesara.dprint(f) -MakeVector{dtype='int64'} [id A] '' 2 - |Shape_i{0} [id B] '' 1 +MakeVector{dtype='int64'} [id A] 2 + |Shape_i{0} [id B] 1 | |x [id C] - |Shape_i{1} [id D] '' 0 + |Shape_i{1} [id D] 0 |x [id C] @@ -78,14 +78,14 @@ Sometimes this can lead to errors. Consider this example: >>> yv = np.random.random((3, 3)) >>> f = aesara.function([x, y], z.shape) ->>> aesara.printing.debugprint(f) # doctest: +NORMALIZE_WHITESPACE -MakeVector{dtype='int64'} [id A] '' 4 - |Elemwise{Add}[(0, 0)] [id B] '' 3 - | |Shape_i{0} [id C] '' 2 +>>> aesara.printing.debugprint(f) +MakeVector{dtype='int64'} [id A] 4 + |Elemwise{Add}[(0, 0)] [id B] 3 + | |Shape_i{0} [id C] 2 | | |x [id D] - | |Shape_i{0} [id E] '' 1 + | |Shape_i{0} [id E] 1 | |y [id F] - |Shape_i{1} [id G] '' 0 + |Shape_i{1} [id G] 0 |x [id D] >>> f(xv, yv) # DOES NOT RAISE AN ERROR AS SHOULD BE. @@ -93,7 +93,7 @@ array([8, 4]) >>> f = aesara.function([x,y], z)# Do not take the shape. >>> aesara.printing.debugprint(f) # doctest: +NORMALIZE_WHITESPACE -Join [id A] '' 0 +Join [id A] 0 |TensorConstant{0} [id B] |x [id C] |y [id D] diff --git a/doc/tutorial/sparse.rst b/doc/tutorial/sparse.rst index c7a0436e46..2191d0b301 100644 --- a/doc/tutorial/sparse.rst +++ b/doc/tutorial/sparse.rst @@ -170,9 +170,9 @@ provide a structured gradient. More explication below. [ 0. -2. 1.] [ 3. 0. 0.]] >>> print(f(a).toarray()) -[[ 0. 0. 1.] - [ 0. 0. 3.] - [ 5. 0. 0.]] +[[0. 0. 1.] + [0. 0. 3.] + [5. 0. 0.]] .. _tutsparse_gradient: diff --git a/stored-array.npy b/stored-array.npy new file mode 100644 index 0000000000..7a58636be3 Binary files /dev/null and b/stored-array.npy differ