#  Write Python scripts to implement basic
operations with TensorFlow 2 tensors, including
tensor creation, manipulation, and computation
graphs. Explore eager execution for dynamic
computation.



import tensorflow as tf # type: ignore
import numpy as np

def tensor_creation():
    scalar = tf.constant(5)
    vector = tf.constant([1,2,3])
    matrix = tf.constant([[1,2],[3,4]])
    tensor_3D = tf.constant([[[1],[2]], [[3],[4]]])

    print(f"scalar: {scalar}")
    print(f"vector: {vector}")
    print(f"matrix: {matrix}")
    print(f"3D tensor: {tensor_3D}")

    np_array = np.array([[10,20],[30,40]])
    tf_from_np = tf.convert_to_tensor(np_array)
    print(f"Tensor from numpy array: {tf_from_np}")

def tensor_manipulation():
    a = tf.ones((2,3))
    b = tf.zeros((2,3))
    c = tf.fill((2,3), 7)

    print(f"Ones: {a}")
    print(f"Zeros: {b}")
    print(f"Fill: {c}")

    reshaped = tf.reshape(a, (3,2))
    print(f"Rshaped tensor: {reshaped}")

    concated = tf.concat([a,b], axis=0)
    print(f"Concatenated tensor: {concated}")

    print(f"First row: {a[0]}")
    print(f"Element at (1,2): {a[1,2].numpy()}")


def tensor_operations():
    x = tf.constant([2.0,4.0,6.0])
    y = tf.constant([1.0,3.0,5.0])

    print(f"Add : {tf.add(x,y)}")
    print(f"subtract : {tf.subtract(x,y)}")
    print(f"multiply : {tf.multiply(x,y)}")
    print(f"divide : {tf.divide(x,y)}")
    print(f"Dot product : {tf.tensordot(x,y, axes=1)}")

@tf.function
def compute_graph(x,y):
    return tf.sqrt(tf.add(x ** 2, y ** 2))

def dynamic_sum(n):
    total = tf.constant(0)

    for i in range(n):
        total += i
        tf.print("Step", i, "current total: ", total)
    return total



def run_pipeline():
    tensor_creation()
    tensor_manipulation()
    tensor_operations()

    compute = compute_graph(3.0, 4.0)
    print(f"Compute hypotenuse: {compute}")

    result = dynamic_sum(5)
    print(f"Dynamic sum : {result}")

if __name__ == "__main__":
    run_pipeline()