m1 = [[1.0, 2.0],
      [3.0, 4.0]]
m2 = np.array([[1.0, 2.0],
               [3.0, 4.0]],dtype=np.float32)
m3 = tf.constant([[1.0, 2.0],
                  [3.0, 4.0]])

print(type(m1))
print(type(m2))
print(type(m3))

t1 = tf.convert_to_tensor(m1, dtype=tf.float32)
t2 = tf.convert_to_tensor(m2, dtype=tf.float32)
t3 = tf.convert_to_tensor(m3, dtype=tf.float32)

print(type(t1))
print(type(t2))
print(type(t3))

"""
Explanation: Convertir arreglos de numpy en tensores
End of explanation
"""


m1 = tf.constant([1.,2.])

m2 = tf.constant([[1],[2]])

m3 = tf.constant([
    [[1,2],
     [3,4],
     [5,6]],
    [[7,8],
     [9,10],
     [11,12]]  ])

print(m1)
print(m2)
print(m3)

m_zeros = tf.zeros([1,2])
m_ones = tf.ones([3,3])
m_sevens = tf.ones([2,3,2])*7

print(m_zeros)
print(m_ones)
print(m_sevens)


"""
Explanation: Crear tensores directamente
End of explanation
"""


x = tf.constant([1.,2.])
y = tf.constant([5.,6.])

# Definir la operacion negacion sobre el tensor x
neg_op = tf.negative(x)
print(neg_op)

#tf.add(x, y)      # Add two tensors of the same type, x + y
#tf.subtract(x, y) # Subtract tensors of the same type, x - y 
#tf.multiply(x, y) # Multiply two tensors element-wise
#tf.pow(x, y)      # Take the element-wise power of x to y 
#tf.exp(x)         # Equivalent to pow(e, x), where e is Euler’s number (2.718...) 
#tf.sqrt(x)        # Equivalent to pow(x, 0.5)
#tf.div(x, y)      # Take the element-wise division of x and y
#tf.truediv(x, y)  # Same as tf.div, except casts the arguments as a float 
#tf.floordiv(x, y) # Same as truediv, except rounds down the final answer into an integer
#tf.mod(x, y)      # Takes the element-wise remainder from division

# Ejecutar las operaciones definidas e imprimir los resultados
with tf.Session() as sess:
    result = sess.run(neg_op)

# Imprimir el resultado computado
print(result)

"""
Explanation: Operaciones Básicas
Para ver todas las operaciones --> https://www.tensorflow.org/api_guides/python/math_ops
El código realmente corre hasta que se ejecuta una sesión. Esta es la manera que tiene tensorflow de desacoplar el código de ML del harware en el que correrá. Entonces lo que se hace es definir las operaciones que queremos computar y después, en una sesión (totalmente configurable, pero ese es otro tema) se ejecutan efectivamente esas líneas de código.
End of explanation
"""


import tensorflow as tf
sess = tf.InteractiveSession()

x = tf.constant([[1.,2.]])
neg_x = tf.negative(x)

result = neg_x.eval()
print(result)

sess.close()

"""
Explanation: Hacer una sesión interactiva
El levantar una sesión es computacionalmente costoso, por eso es preferible que se haga una sola vez. Cuando se están haciendo pruebas con código es comun levantar un sesión interactiva una sola vez, e invocar a la función eval() que reutiliza la misma sesión sin tener que estar creando sesiones nuevas cada vez que se quieran obtener resultados.
End of explanation
"""


import tensorflow as tf
sess = tf.InteractiveSession()

raw_data = [1., 2., 8., -1., 0., 5.5, 6., 13]
spike = tf.Variable(False)
spike.initializer.run()


for i in range(1, len(raw_data)):
    if raw_data[i] - raw_data[i-1] > 5:
        updater = tf.assign(spike, True)
        updater.eval()
    else:
        tf.assign(spike, False).eval()
    print("Spike", spike.eval())
    
sess.close()

"""
Explanation: Usando una variable
Un ejemplo sobre-simplificado en donde el booleano spike se activa cuando el dato nuevo excede en 5 unidades al dato anterior. Se imprime en cada iteracion el estado del booleano para verificar si hubi un Spike o no.
End of explanation
"""


import tensorflow as tf
sess = tf.InteractiveSession()

raw_data = [1., 2., 8., -1., 0., 5.5, 6., 13]
spikes = tf.Variable([False]*len(raw_data),name='spikes')
spikes.initializer.run()


saver = tf.train.Saver({"spikes": spikes})

for i in range(1, len(raw_data)):
    if raw_data[i] - raw_data[i-1] > 5:
        spikes_val = spikes.eval()
        spikes_val[i] = True
        updater = tf.assign(spikes,spikes_val)
        updater.eval()

save_path = saver.save(sess, "./checkpoints/spikes.ckpt")
print("spikes data saved in file: %s" % save_path)

spikes_val = spikes.eval()
print("SPIKES: {}".format(spikes_val))

sess.close()

"""
Explanation: Guardar variables
End of explanation
"""


import tensorflow as tf
sess = tf.InteractiveSession()
loaded_spikes = tf.Variable([False]*8, name='loaded_spikes')

saver = tf.train.Saver({"spikes": loaded_spikes})

saver.restore(sess, "./checkpoints/spikes.ckpt")
print(loaded_spikes.eval())

sess.close()

"""
Explanation: Cargar una variable guardada
End of explanation
"""


import tensorflow as tf
import numpy as np

raw_data = np.random.normal(10,1,100)

alpha = tf.constant(0.05)
curr_value = tf.placeholder(tf.float32)
prev_avg = tf.Variable(0.)

update_avg = alpha * curr_value + (1 - alpha) * prev_avg

init = tf.global_variables_initializer()

with tf.Session() as sess:
    sess.run(init)
    for i in range(len(raw_data)):
        curr_avg = sess.run(update_avg, feed_dict={curr_value:raw_data[i]})
        sess.run(tf.assign(prev_avg, curr_avg))
        print(raw_data[i], curr_avg)

"""
Explanation: Visualizando operaciones
Como ejemplo se usara el calculo de promedio ponderado que se calcula como:
$$ Avg_{t} = f(Avg_{t-1}, x_{t}) = (1 - a)Avg_{t-1} + ax_{t} $$
Primero se define el algoritmo y se imprime en consola...
End of explanation
"""


