from __future__ import print_function
import torch

''' Tensors '''

x = torch.empty(5, 3)
# print(x)

x = torch.rand(5, 3)
# print(x)

x = torch.zeros(5, 3, dtype=torch.long)
# print(x)

x = torch.tensor([5.5, 3])
# print(x)

x = x.new_ones(5, 3, dtype=torch.double)      # new_* methods take in sizes
# print(x)

x = torch.randn_like(x, dtype=torch.float)    # override dtype!
# print(x)                                      # result has the same size

# print(x.size())

''' Operations '''
# print(x)

y = torch.rand(5, 3)
# print(y)
# print('\n\n')

# print(x + y)
# print(torch.add(x, y))

result = torch.empty(5, 3)
torch.add(x, y, out=result)
# print(result)

# adds x to y
# y.add_(x)
# print(y)

# NumPy-like indexing
# print(x[:, 1])

# Resize/ reshape tensor using torch.view

x = torch.randn(4, 4)
y = x.view(16)
z = x.view(-1, 8)  # the size -1 is inferred from other dimensions
# print(x.size(), y.size(), z.size())

# Tensor -> Int
x = torch.randn(1)
# print(x)
# print(x.item())

# Converting torch tensors to NumPy Arrays

a = torch.ones(5)
# print(a)

b = a.numpy()
# print(b)

# These arrays are linked
a.add_(1)
# print(a)
# print(b)

# Numpy -> Torch tensor
import numpy as np
a = np.ones(5)
b = torch.from_numpy(a)
np.add(a, 1, out=a)
# print(a)
# print(b)

# CUDA Tensors
# let us run this cell only if CUDA is available
# We will use ``torch.device`` objects to move tensors in and out of GPU

# let us run this cell only if CUDA is available
# We will use ``torch.device`` objects to move tensors in and out of GPU
if torch.cuda.is_available():
    device = torch.device("cuda")          # a CUDA device object
    y = torch.ones_like(x, device=device)  # directly create a tensor on GPU
    x = x.to(device)                       # or just use strings ``.to("cuda")``
    z = x + y
    print(z)
    print(z.to("cpu", torch.double))       # ``.to`` can also change dtype together!

# import torch
#
# " Data for training"
# x_train = torch.FloatTensor([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
# y_train = torch.FloatTensor([2, 4, 6, 8, 10, 12, 14, 16, 18, 20])
#
# " Data for evaluation of model"
# x_test = torch.FloatTensor([2, 4, 6, 8, 10, 12, 14, 16, 18, 20])
#
# model = torch.nn.Sequential(torch.nn.Linear(10, 3), torch.nn.ReLU(), torch.nn.Linear(3, 10), )
#
# loss_fn = torch.nn.MSELoss(reduction='sum')
#
# learning_rate = 1e-4
# for t in range(48500):
#     y_pred = model(x_train)
#     loss = loss_fn(y_pred, y_train)
#     if t % 1000 == 999:
#         print(t, loss.item())
#
#     model.zero_grad()
#     loss.backward()
#     with torch.no_grad():
#         for param in model.parameters():
#             param -= learning_rate * param.grad
#
# y_pred_to_validate = model(x_test)
# print(y_pred_to_validate.detach().numpy())