.. _reference_tensor_create:

.. currentmodule:: aesara.tensor


Tensor creation
===============

Aesara provides a list of predefined tensor types that can be used
to create a tensor variables.  Variables can be named to facilitate debugging,
and all of these constructors accept an optional ``name`` argument.
For example, the following each produce a `TensorVariable` instance that stands
for a 0-dimensional `ndarray` of integers with the name ``'myvar'``:

>>> import aesara.tensor as at
>>> x = at.scalar('myvar', dtype='int32')
>>> x = at.iscalar('myvar')
>>> x = at.tensor(dtype='int32', shape=(), name='myvar')
>>> from aesara.tensor.type import TensorType
>>> x = TensorType(dtype='int32', shape=())('myvar')

Basic constructors
------------------

These are the simplest and often-preferred methods for creating symbolic
variables in your code.  By default, they produce floating-point variables
(with dtype determined by `aesara.config.floatX`) so if you use
these constructors it is easy to switch your code between different levels of
floating-point precision.

.. currentmodule:: aesara.tensor

.. autosummary::
   :toctree: _autosummary

   scalar
   vector
   row
   col
   matrix
   tensor3
   tensor4
   tensor5
   tensor6
   tensor7

.. doctest:: creation

    >>> x = at.scalar()
    >>> x.type.shape
    ()
    >>> y = at.vector()
    >>> y.type.shape
    (None,)

.. #COMMENT
    Each of the types described above can be constructed by two methods:
    a singular version (e.g., :ref:`dmatrix <libdoc_tensor_creation>`)
    and a plural version (:ref:`dmatrices <libdoc_tensor_creation>`).
    When called, the singular version takes a single
    argument which is the name of the *Variable* we want to make and it
    makes a single Variable of that type. The plural version can either take


Typed Constructors
-------------------

The following `TensorType` instances are provided in the `aesara.tensor` module.
They are all callable, and accept an optional ``name`` argument.  So for example:

.. testcode:: constructors

   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'

.. #COMMENT
    table generated by
    $ python Aesara/doc/generate_dtype_tensor_table.py

============ =========== ==== ================ ===================================
Constructor  dtype       ndim shape            broadcastable
============ =========== ==== ================ ===================================
bscalar      int8        0    ()               ()
bvector      int8        1    (?,)             (False,)
brow         int8        2    (1,?)            (True, False)
bcol         int8        2    (?,1)            (False, True)
bmatrix      int8        2    (?,?)            (False, False)
btensor3     int8        3    (?,?,?)          (False, False, False)
btensor4     int8        4    (?,?,?,?)        (False, False, False, False)
btensor5     int8        5    (?,?,?,?,?)      (False, False, False, False, False)
btensor6     int8        6    (?,?,?,?,?,?)    (False,) * 6
btensor7     int8        7    (?,?,?,?,?,?,?)  (False,) * 7
wscalar      int16       0    ()               ()
wvector      int16       1    (?,)             (False,)
wrow         int16       2    (1,?)            (True, False)
wcol         int16       2    (?,1)            (False, True)
wmatrix      int16       2    (?,?)            (False, False)
wtensor3     int16       3    (?,?,?)          (False, False, False)
wtensor4     int16       4    (?,?,?,?)        (False, False, False, False)
wtensor5     int16       5    (?,?,?,?,?)      (False, False, False, False, False)
wtensor6     int16       6    (?,?,?,?,?,?)    (False,) * 6
wtensor7     int16       7    (?,?,?,?,?,?,?)  (False,) * 7
iscalar      int32       0    ()               ()
ivector      int32       1    (?,)             (False,)
irow         int32       2    (1,?)            (True, False)
icol         int32       2    (?,1)            (False, True)
imatrix      int32       2    (?,?)            (False, False)
itensor3     int32       3    (?,?,?)          (False, False, False)
itensor4     int32       4    (?,?,?,?)        (False, False, False, False)
itensor5     int32       5    (?,?,?,?,?)      (False, False, False, False, False)
itensor6     int32       6    (?,?,?,?,?,?)    (False,) * 6
itensor7     int32       7    (?,?,?,?,?,?,?)  (False,) * 7
lscalar      int64       0    ()               ()
lvector      int64       1    (?,)             (False,)
lrow         int64       2    (1,?)            (True, False)
lcol         int64       2    (?,1)            (False, True)
lmatrix      int64       2    (?,?)            (False, False)
ltensor3     int64       3    (?,?,?)          (False, False, False)
ltensor4     int64       4    (?,?,?,?)        (False, False, False, False)
ltensor5     int64       5    (?,?,?,?,?)      (False, False, False, False, False)
ltensor6     int64       6    (?,?,?,?,?,?)    (False,) * 6
ltensor7     int64       7    (?,?,?,?,?,?,?)  (False,) * 7
dscalar      float64     0    ()               ()
dvector      float64     1    (?,)             (False,)
drow         float64     2    (1,?)            (True, False)
dcol         float64     2    (?,1)            (False, True)
dmatrix      float64     2    (?,?)            (False, False)
dtensor3     float64     3    (?,?,?)          (False, False, False)
dtensor4     float64     4    (?,?,?,?)        (False, False, False, False)
dtensor5     float64     5    (?,?,?,?,?)      (False, False, False, False, False)
dtensor6     float64     6    (?,?,?,?,?,?)    (False,) * 6
dtensor7     float64     7    (?,?,?,?,?,?,?)  (False,) * 7
fscalar      float32     0    ()               ()
fvector      float32     1    (?,)             (False,)
frow         float32     2    (1,?)            (True, False)
fcol         float32     2    (?,1)            (False, True)
fmatrix      float32     2    (?,?)            (False, False)
ftensor3     float32     3    (?,?,?)          (False, False, False)
ftensor4     float32     4    (?,?,?,?)        (False, False, False, False)
ftensor5     float32     5    (?,?,?,?,?)      (False, False, False, False, False)
ftensor6     float32     6    (?,?,?,?,?,?)    (False,) * 6
ftensor7     float32     7    (?,?,?,?,?,?,?)  (False,) * 7
cscalar      complex64   0    ()               ()
cvector      complex64   1    (?,)             (False,)
crow         complex64   2    (1,?)            (True, False)
ccol         complex64   2    (?,1)            (False, True)
cmatrix      complex64   2    (?,?)            (False, False)
ctensor3     complex64   3    (?,?,?)          (False, False, False)
ctensor4     complex64   4    (?,?,?,?)        (False, False, False, False)
ctensor5     complex64   5    (?,?,?,?,?)      (False, False, False, False, False)
ctensor6     complex64   6    (?,?,?,?,?,?)    (False,) * 6
ctensor7     complex64   7    (?,?,?,?,?,?,?)  (False,) * 7
zscalar      complex128  0    ()               ()
zvector      complex128  1    (?,)             (False,)
zrow         complex128  2    (1,?)            (True, False)
zcol         complex128  2    (?,1)            (False, True)
zmatrix      complex128  2    (?,?)            (False, False)
ztensor3     complex128  3    (?,?,?)          (False, False, False)
ztensor4     complex128  4    (?,?,?,?)        (False, False, False, False)
ztensor5     complex128  5    (?,?,?,?,?)      (False, False, False, False, False)
ztensor6     complex128  6    (?,?,?,?,?,?)    (False,) * 6
ztensor7     complex128  7    (?,?,?,?,?,?,?)  (False,) * 7
============ =========== ==== ================ ===================================

Plural Constructors
-------------------

There are several constructors that can produce multiple variables at once.
These are not frequently used in practice, but often used in tutorial examples to save space!

.. function:: iscalars, lscalars, fscalars, dscalars

    Return one or more scalar variables.

.. function:: ivectors, lvectors, fvectors, dvectors

    Return one or more vector variables.

.. function:: irows, lrows, frows, drows

    Return one or more row variables.

.. function:: icols, lcols, fcols, dcols

    Return one or more col variables.

.. function:: imatrices, lmatrices, fmatrices, dmatrices

    Return one or more matrix variables.

Each of these plural constructors accepts
an integer or several strings. If an integer is provided, the method
will return that many `Variables` and if strings are provided, it will
create one `Variable` for each string, using the string as the `Variable`'s
name. For example:

.. testcode:: constructors

   # Creates three matrix `Variable`s with no names
   x, y, z = at.dmatrices(3)
   # Creates three matrix `Variables` named 'x', 'y' and 'z'
   x, y, z = at.dmatrices('x', 'y', 'z')


Custom tensor types
-------------------

If you would like to construct a tensor variable with a non-standard
broadcasting pattern, or a larger number of dimensions you'll need to create
your own :class:`TensorType` instance.  You create such an instance by passing
the dtype and broadcasting pattern to the constructor.  For example, you
can create your own 8-dimensional tensor type

>>> dtensor8 = TensorType(dtype='float64', shape=(None,)*8)
>>> x = dtensor8()
>>> z = dtensor8('z')

You can also redefine some of the provided types and they will interact
correctly:

>>> my_dmatrix = TensorType('float64', shape=(None,)*2)
>>> x = my_dmatrix()  # allocate a matrix variable
>>> my_dmatrix == dmatrix
True

See :class:`TensorType` for more information about creating new types of
tensors.


Converting from Python Objects
-------------------------------

One can convert python objects by calling either :func:`aesara.tensor.as_tensor_variable` or :func:`aesara.shared`.


``aesara.tensor.as_tensor_variable``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. note::

    This is the default way of converting a python object to a `TensorVariable`. Unless you have a need for shared variables, use this function instead.

.. function:: as_tensor_variable(x, name=None, ndim=None)

    Turn an argument ``x` into a `TensorVariable` or `TensorConstant`.

    Many tensor `Op`\s run their arguments through this function as
    pre-processing.  It passes through `TensorVariable` instances, and tries to
    wrap other objects into `TensorConstant`.

    When `x` is a Python number, the dtype is inferred as described above.

    When `x` is a `list` or `tuple` it is passed through `np.asarray`

    If the `ndim` argument is not ``None``, it must be an integer and the output
    will be broadcasted if necessary in order to have this many dimensions.

    :rtype: :class:`TensorVariable` or :class:`TensorConstant`


.. doctest:: as_tensor_variable

   >>> import numpy as np
   >>> x = np.array([[1, 2], [3, 4]])
   >>> y = at.as_tensor(x)
   >>> y.type.shape
   (2, 2)

Finally, when you use a NumPy `ndarray` or a Python number together with
:class:`TensorVariable` instances in arithmetic expressions, the result is a
:class:`TensorVariable`. What happens to the `ndarray` or the number?
Aesara requires that the inputs to all expressions be `Variable` instances, so
Aesara automatically wraps them in a :class:`TensorConstant`.

.. doctest:: as_tensor_variable

   >>> x = at.vector()
   >>> b = at.add(x, np.ones(3))
   >>> type(b)
   <class 'aesara.tensor.var.TensorVariable'>
   >>> b.type.shape
   (3,)
   >>> b.owner.inputs[1]
   >>> TensorConstant{(3,) of 1.0}

.. note::

    Aesara makes a copy of any `ndarray` that is used in an expression, so
    subsequent changes to that `ndarray` will not have any effect on the Aesara
    expression in which they're contained.

``aesara.shared``
~~~~~~~~~~~~~~~~~

Another way of creating a `TensorVariable` (a `TensorSharedVariable` to be
precise) is by calling :func:`aesara.shared`

.. testcode::

    x = aesara.shared(np.random.standard_normal((3, 4)))

This will return a :term:`shared variable <shared variable>` whose ``.value`` is
a NumPy `ndarray`.  The number of dimensions and dtype of the `Variable` are
inferred from the `ndarray` argument.

.. danger::
    The argument to `shared` *will not be copied*, and subsequent changes will be reflected in ``x.value``.


For additional information, see the :ref:`documentation for shared variables <reference_shared>`.


dtype and shape
~~~~~~~~~~~~~~~

For NumPy `ndarrays` the dtype is given, but the static shape/broadcastable pattern must be
inferred.  The `TensorConstant` is given a type with a matching dtype,
and a static shape/broadcastable pattern with a ``1``\/``True`` for every shape
dimension that is one and ``None``\/``False`` for every dimension with an unknown
shape.

For Python numbers, the static shape/broadcastable pattern is ``()`` but the dtype must be
inferred.  Python integers are stored in the smallest dtype that can hold
them, so small constants like ``1`` are stored in a `bscalar`.
Likewise, Python floats are stored in an `fscalar` if `fscalar` suffices to hold
them perfectly, but a `dscalar` otherwise.

.. note::

    When ``config.floatX == float32`` (see :mod:`config`), then Python floats
    are stored instead as single-precision floats.

    For fine control of this rounding policy, see
    `aesara.tensor.basic.autocast_float`.


Loading from file
-----------------

One can also create tensor by loading NumPy arrays from a `.npy` file.

.. automodule:: aesara.tensor.io
    :members:
