.. _adding:

========================================
Baby steps - Adding two numbers together
========================================


Adding two scalars
==================

So, to get us started with Theano and get a feel of what we're working with, 
let's make a simple function: add two numbers together. Here is how you do
it:

>>> x = T.dscalar('x')
>>> y = T.dscalar('y')
>>> z = x + y
>>> f = function([x, y], z)

And now that we've created our function we can use it:

>>> f(2, 3)
array(5.0)
>>> f(16.3, 12.1)
array(28.4)


Let's break this down into several steps. The first step is to define
two symbols (*Variables*) representing the quantities that you want
to add. Note that from now on, we will use the term 
*Variable* to mean "symbol" (in other words, 
``x``, ``y``, ``z`` are all *Variable* objects). The output of the function 
``f`` is a ``numpy.ndarray`` with zero dimensions.

If you are following along and typing into an interpreter, you may have
noticed that there was a slight delay in executing the ``function``
instruction. Behind the scenes, ``f`` was being compiled into C code.


.. note:

  A *Variable* is the main data structure you work with when
  using Theano. The symbolic inputs that you operate on are
  *Variables* and what you get from applying various operations to
  these inputs are also *Variables*. For example, when I type
  
  >>> x = theano.tensor.ivector()
  >>> y = -x
  
  ``x`` and ``y`` are both Variables, i.e. instances of the
  ``theano.gof.graph.Variable`` class. The
  type of both ``x`` and ``y`` is ``theano.tensor.ivector``.


-------------------------------------------

**Step 1**

>>> x = T.dscalar('x')
>>> y = T.dscalar('y')

In Theano, all symbols must be typed. In particular, ``T.dscalar``
is the type we assign to "0-dimensional arrays (`scalar`) of doubles
(`d`)". It is a Theano :ref:`type`.

``dscalar`` is not a class. Therefore, neither ``x`` nor ``y``
are actually instances of ``dscalar``. They are instances of
:class:`TensorVariable`. ``x`` and ``y``
are, however, assigned the theano Type ``dscalar`` in their ``type``
field, as you can see here:

>>> type(x)
<class 'theano.tensor.basic.TensorVariable'>
>>> x.type
TensorType(float64, scalar)
>>> T.dscalar
TensorType(float64, scalar)
>>> x.type is T.dscalar
True

You can learn more about the structures in Theano in :ref:`graphstructures`.

By calling ``T.dscalar`` with a string argument, you create a
*Variable* representing a floating-point scalar quantity with the
given name. If you provide no argument, the symbol will be unnamed. Names
are not required, but they can help debugging.


-------------------------------------------

**Step 2**

The second step is to combine ``x`` and ``y`` into their sum ``z``:

>>> z = x + y

``z`` is yet another *Variable* which represents the addition of
``x`` and ``y``. You can use the :ref:`pp <libdoc_printing>`
function to pretty-print out the computation associated to ``z``.

>>> print pp(z)
(x + y)

-------------------------------------------

**Step 3**

The last step is to create a function taking ``x`` and ``y`` as inputs
and giving ``z`` as output:

>>> f = function([x, y], z)

The first argument to :func:`function <function.function>` is a list of Variables
that will be provided as inputs to the function. The second argument
is a single Variable *or* a list of Variables. For either case, the second
argument is what we want to see as output when we apply the function.

``f`` may then be used like a normal Python function.


Adding two matrices
===================

You might already have guessed how to do this. Indeed, the only change
from the previous example is that you need to instantiate ``x`` and
``y`` using the matrix Types:

>>> x = T.dmatrix('x')
>>> y = T.dmatrix('y')
>>> z = x + y
>>> f = function([x, y], z)

``dmatrix`` is the Type for matrices of doubles. And then we can use
our new function on 2D arrays:

>>> f([[1, 2], [3, 4]], [[10, 20], [30, 40]])
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.]])

It is possible to add scalars to matrices, vectors to matrices,
scalars to vectors, etc. The behavior of these operations is defined
by :ref:`broadcasting <libdoc_tensor_broadcastable>`.

The following types are available:

* **byte**: bscalar, bvector, bmatrix, brow, bcol, btensor3, btensor4
* **32-bit integers**: iscalar, ivector, imatrix, irow, icol, itensor3, itensor4
* **64-bit integers**: lscalar, lvector, lmatrix, lrow, lcol, ltensor3, ltensor4
* **float**: fscalar, fvector, fmatrix, frow, fcol, ftensor3, ftensor4
* **double**: dscalar, dvector, dmatrix, drow, dcol, dtensor3, dtensor4
* **complex**: cscalar, cvector, cmatrix, crow, ccol, ctensor3, ctensor4

The previous list is not exhaustive. A guide to all types compatible
with numpy arrays may be found :ref:`here <libdoc_tensor_creation>`.

.. note::

   You, the user---not the system architecture---have to choose whether your
   program will use 32- or 64-bit integers (``i`` prefix vs. the ``l`` prefix)
   and floats (``f`` prefix vs. the ``d`` prefix).
