# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Explanation: Copyright 2017 Google LLC.
End of explanation
"""


from __future__ import print_function

import tensorflow as tf

"""
Explanation: # Creación y manipulación de tensores
Objetivos de aprendizaje:
  * inicializar y asignar las variables de TensorFlow
  * crear y manipular tensores
  * repasar la suma y el producto en álgebra lineal (si estos temas son nuevos para ti, consulta una introducción a la suma y el producto
  * familiarizarte con operaciones básicas de matemática y matrices de TensorFlow
End of explanation
"""


with tf.Graph().as_default():
  # Create a six-element vector (1-D tensor).
  primes = tf.constant([2, 3, 5, 7, 11, 13], dtype=tf.int32)

  # Create another six-element vector. Each element in the vector will be
  # initialized to 1. The first argument is the shape of the tensor (more
  # on shapes below).
  ones = tf.ones([6], dtype=tf.int32)

  # Add the two vectors. The resulting tensor is a six-element vector.
  just_beyond_primes = tf.add(primes, ones)

  # Create a session to run the default graph.
  with tf.Session() as sess:
    print(just_beyond_primes.eval())

"""
Explanation: ## Suma de vectores
Puedes realizar muchas operaciones matemáticas en los tensores (TF API). El siguiente código crea y manipula dos vectores (tensores 1-D), cada uno con seis elementos:
End of explanation
"""


with tf.Graph().as_default():
  # A scalar (0-D tensor).
  scalar = tf.zeros([])

  # A vector with 3 elements.
  vector = tf.zeros([3])

  # A matrix with 2 rows and 3 columns.
  matrix = tf.zeros([2, 3])

  with tf.Session() as sess:
    print('scalar has shape', scalar.get_shape(), 'and value:\n', scalar.eval())
    print('vector has shape', vector.get_shape(), 'and value:\n', vector.eval())
    print('matrix has shape', matrix.get_shape(), 'and value:\n', matrix.eval())


"""
Explanation: ### Formas de tensores
Las formas se usan para describir el tamaño y la cantidad de dimensiones de un tensor. La forma de un tensor se expresa como list, donde el elemento con índice i representa el tamaño en la dimensión i. La longitud de la lista indica la clasificación del tensor (p. ej., la cantidad de dimensiones).
Para obtener más información, consulta la documentación de TensorFlow.
Algunos ejemplos básicos:
End of explanation
"""


with tf.Graph().as_default():
  # Create a six-element vector (1-D tensor).
  primes = tf.constant([2, 3, 5, 7, 11, 13], dtype=tf.int32)

  # Create a constant scalar with value 1.
  ones = tf.constant(1, dtype=tf.int32)

  # Add the two tensors. The resulting tensor is a six-element vector.
  just_beyond_primes = tf.add(primes, ones)

  with tf.Session() as sess:
    print(just_beyond_primes.eval())

"""
Explanation: ### Emisión
En términos matemáticos, solo puedes realizar operaciones basadas en elementos (p. ej., suma e iguales) en los tensores de la misma forma. Sin embargo, en TensorFlow, puedes realizar operaciones en tensores que tradicionalmente eran incompatibles. TensorFlow es compatible con la emisión (un concepto acuñado de NumPy), donde la matriz más pequeña en una operación basada en elementos se amplía para que tenga la misma forma que la matriz más grande. Por ejemplo, mediante la emisión:

Si un operando requiere un tensor de tamaño [6], un tensor de tamaño [1] o [] puede servir como operando.
Si un operando requiere un tensor de tamaño [4, 6], cualquiera de los siguientes tamaños puede servir como operando:
[1, 6]
[6]
[]

Si una operación requiere un tensor de tamaño [3, 5, 6], cualquiera de los siguientes tamaños puede servir como operando:


[1, 5, 6]

[3, 1, 6]
[3, 5, 1]
[1, 1, 1]
[5, 6]
[1, 6]
[6]
[1]
[]

NOTA: Cuando un tensor es de emisión, sus entradas se copian conceptualmente. (Por cuestiones de rendimiento, no se copian realmente; la emisión se creó como una optimización del rendimiento).
El conjunto de reglas completo de la emisión se describe en detalle en la documentación sobre la emisión de NumPy.
El siguiente código realiza la misma suma de tensores que antes, pero mediante la emisión:
End of explanation
"""


with tf.Graph().as_default():
  # Create a matrix (2-d tensor) with 3 rows and 4 columns.
  x = tf.constant([[5, 2, 4, 3], [5, 1, 6, -2], [-1, 3, -1, -2]],
                  dtype=tf.int32)

  # Create a matrix with 4 rows and 2 columns.
  y = tf.constant([[2, 2], [3, 5], [4, 5], [1, 6]], dtype=tf.int32)

  # Multiply `x` by `y`. 
  # The resulting matrix will have 3 rows and 2 columns.
  matrix_multiply_result = tf.matmul(x, y)

  with tf.Session() as sess:
    print(matrix_multiply_result.eval())

"""
Explanation: ## Producto de arreglos
En álgebra lineal, cuando se multiplican dos arreglos, la cantidad de columnas del primer arreglo debe ser igual a la cantidad de filas en el segundo arreglo.

Es válido para multiplicar un arreglo de 3 × 4 por uno de 4 × 2. El resultado será un arreglo de 3 × 2.
Es inválido para multiplicar un arreglo de 4 × 2 por uno de 3 × 4.
End of explanation
"""


with tf.Graph().as_default():
  # Create an 8x2 matrix (2-D tensor).
  matrix = tf.constant([[1,2], [3,4], [5,6], [7,8],
                        [9,10], [11,12], [13, 14], [15,16]], dtype=tf.int32)

  # Reshape the 8x2 matrix into a 2x8 matrix.
  reshaped_2x8_matrix = tf.reshape(matrix, [2,8])
  
  # Reshape the 8x2 matrix into a 4x4 matrix
  reshaped_4x4_matrix = tf.reshape(matrix, [4,4])

  with tf.Session() as sess:
    print("Original matrix (8x2):")
    print(matrix.eval())
    print("Reshaped matrix (2x8):")
    print(reshaped_2x8_matrix.eval())
    print("Reshaped matrix (4x4):")
    print(reshaped_4x4_matrix.eval())

"""
Explanation: ## Cambio de formas de tensores
Dado que la suma de tensores y el producto de arreglos tienen restricciones en los operandos, los programadores de TensorFlow con frecuencia deben cambiar la forma de los tensores.
Para cambiar la forma de un tensor, puedes usar el método tf.reshape.
Por ejemplo, puedes cambiar la forma de un tensor de 8 × 2 a uno de 2 × 8 o 4 × 4:
End of explanation
"""


with tf.Graph().as_default():
  # Create an 8x2 matrix (2-D tensor).
  matrix = tf.constant([[1,2], [3,4], [5,6], [7,8],
                        [9,10], [11,12], [13, 14], [15,16]], dtype=tf.int32)

  # Reshape the 8x2 matrix into a 3-D 2x2x4 tensor.
  reshaped_2x2x4_tensor = tf.reshape(matrix, [2,2,4])
  
  # Reshape the 8x2 matrix into a 1-D 16-element tensor.
  one_dimensional_vector = tf.reshape(matrix, [16])

  with tf.Session() as sess:
    print("Original matrix (8x2):")
    print(matrix.eval())
    print("Reshaped 3-D tensor (2x2x4):")
    print(reshaped_2x2x4_tensor.eval())
    print("1-D vector:")
    print(one_dimensional_vector.eval())

"""
Explanation: También puedes usar tf.reshape para cambiar la cantidad de dimensiones (la "clasificación") del tensor.
Por ejemplo, puedes cambiar la forma de un tensor de 8 × 2 a uno de 3-D de 2 × 2 × 4 o uno de 1-D de 16 elementos.
End of explanation
"""


  # Write your code for Task 1 here.

"""
Explanation: ### Ejercicio n.º 1: Cambia la forma de dos tensores para poder multiplicarlos.
Los siguientes dos vectores son incompatibles para calcular el producto de arreglos:

a = tf.constant([5, 3, 2, 7, 1, 4])
b = tf.constant([4, 6, 3])

Cambia la forma de estos vectores para que sean operandos compatibles y posibilitar el producto de arreglos.
Luego, invoca una operación de producto de arreglos en los tensores a los que les cambiaste la forma.
End of explanation
"""


with tf.Graph().as_default(), tf.Session() as sess:
  # Task: Reshape two tensors in order to multiply them
  
  # Here are the original operands, which are incompatible
  # for matrix multiplication:
  a = tf.constant([5, 3, 2, 7, 1, 4])
  b = tf.constant([4, 6, 3])
  # We need to reshape at least one of these operands so that
  # the number of columns in the first operand equals the number
  # of rows in the second operand.

  # Reshape vector "a" into a 2-D 2x3 matrix:
  reshaped_a = tf.reshape(a, [2,3])

  # Reshape vector "b" into a 2-D 3x1 matrix:
  reshaped_b = tf.reshape(b, [3,1])

  # The number of columns in the first matrix now equals
  # the number of rows in the second matrix. Therefore, you
  # can matrix mutiply the two operands.
  c = tf.matmul(reshaped_a, reshaped_b)
  print(c.eval())

  # An alternate approach: [6,1] x [1, 3] -> [6,3]

"""
Explanation: ### Solución
Haz clic a continuación para obtener una solución.
End of explanation
"""


g = tf.Graph()
with g.as_default():
  # Create a variable with the initial value 3.
  v = tf.Variable([3])

  # Create a variable of shape [1], with a random initial value,
  # sampled from a normal distribution with mean 1 and standard deviation 0.35.
  w = tf.Variable(tf.random_normal([1], mean=1.0, stddev=0.35))

"""
Explanation: ## Variables, Inicialización y asignación
Hasta el momento, todas las operaciones que realizamos fueron en valores estáticos (tf.constant); al invocar a eval(), el resultado fue siempre el mismo. TensorFlow permite definir objetos de Variable, cuyos valores pueden modificarse.
Cuando se crea una variable, puedes establecer un valor inicial de forma explícita o usar un inicializador (como una distribución):
End of explanation
"""


with g.as_default():
  with tf.Session() as sess:
    try:
      v.eval()
    except tf.errors.FailedPreconditionError as e:
      print("Caught expected error: ", e)

"""
Explanation: Una particularidad de TensorFlow es que la inicialización de variables no es automática. Por ejemplo, el siguiente bloque generará un error:
End of explanation
"""


with g.as_default():
  with tf.Session() as sess:
    initialization = tf.global_variables_initializer()
    sess.run(initialization)
    # Now, variables can be accessed normally, and have values assigned to them.
    print(v.eval())
    print(w.eval())


"""
Explanation: La forma más fácil de inicializar una variable es invocando a global_variables_initializer. Ten en cuenta el uso de Session.run(), que es prácticamente equivalente a eval().
End of explanation
"""


with g.as_default():
  with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    # These three prints will print the same value.
    print(w.eval())
    print(w.eval())
    print(w.eval())

"""
Explanation: Una vez inicializadas, las variables mantendrán su valor en la misma sesión (sin embargo, al iniciar una nueva sesión, deberás volver a inicializarlas):
End of explanation
"""


with g.as_default():
  with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    # This should print the variable's initial value.
    print(v.eval())

    assignment = tf.assign(v, [7])
    # The variable has not been changed yet!
    print(v.eval())

    # Execute the assignment op.
    sess.run(assignment)
    # Now the variable is updated.
    print(v.eval())

"""
Explanation: Para cambiar el valor de una variable, usa el operando assign. Ten en cuenta que, con solo crear el operando assign, no se obtendrá ningún efecto. Al igual que con la inicialización, deberás ejecutar el operando de asignación para actualizar el valor de la variable:
End of explanation
"""


# Write your code for Task 2 here.

"""
Explanation: Hay muchos más temas sobre variables que no tratamos, como la carga y el almacenamiento. Para obtener más información, consulta los documentos de TensorFlow.
### Ejercicio n.º 2: Simula 10 giros de dos dados.
Crea una simulación de dados, en la que se genere un tensor de 2-D de 10 × 3 con las siguientes características:

Que las columnas 1 y 2 incluyan una tirada de uno de los dados.
Que la columna 3 incluya la suma de las columnas 1 y 2 en la misma fila.

Por ejemplo, la primera fila puede tener los siguientes valores:

La columna 1 incluye 4
La columna 2 incluye 3
La columna 3 incluye 7

Consulta la documentación de TensorFlow para resolver esta tarea.
End of explanation
"""


with tf.Graph().as_default(), tf.Session() as sess:
  # Task 2: Simulate 10 throws of two dice. Store the results
  # in a 10x3 matrix.

  # We're going to place dice throws inside two separate
  # 10x1 matrices. We could have placed dice throws inside
  # a single 10x2 matrix, but adding different columns of
  # the same matrix is tricky. We also could have placed
  # dice throws inside two 1-D tensors (vectors); doing so
  # would require transposing the result.
  dice1 = tf.Variable(tf.random_uniform([10, 1],
                                        minval=1, maxval=7,
                                        dtype=tf.int32))
  dice2 = tf.Variable(tf.random_uniform([10, 1],
                                        minval=1, maxval=7,
                                        dtype=tf.int32))

  # We may add dice1 and dice2 since they share the same shape
  # and size.
  dice_sum = tf.add(dice1, dice2)

  # We've got three separate 10x1 matrices. To produce a single
  # 10x3 matrix, we'll concatenate them along dimension 1.
  resulting_matrix = tf.concat(
      values=[dice1, dice2, dice_sum], axis=1)

  # The variables haven't been initialized within the graph yet,
  # so let's remedy that.
  sess.run(tf.global_variables_initializer())

  print(resulting_matrix.eval())

"""
Explanation: ### Solución
Haz clic a continuación para obtener una solución.
End of explanation
"""


