#!/bin/python3
import numpy as np
import random

cicli = 1000000
hidden_layer = 2
epsilon = 0.01

def fdt(x,deriv=False):
    if(deriv==True):
        return (1-np.tanh(x)**2)

    return np.tanh(x)

def error_func(output):
    e=0
    for i in range(len(output)):
        e += (output[i]-Y[i])**2
    return 0.5*e

#input data
X = np.array([[0,0,1],  #input, 00 01 10 11
            [0,1,1],
            [1,0,1],
            [1,1,1]])

#output data
Y = np.array([[0], #xi, output voluto 0 1 1 0
             [1],
             [1],
             [0]])

#np.random.seed(1) #per avere sempre lo stesso

#sinapsi tra input e Perceptron di output
syn0 = 0.1*(2*np.random.random((3,hidden_layer)) -1) #Sinapsi tra input e hidden layer
#print(syn0) #2x1
syn1 = 0.1*(2*np.random.random((hidden_layer,1)) -1) #Sinapsi tra hidden layer e Perceptron di output

# fase forward
l0 = X
l1 = fdt(np.dot(l0,syn0)) #output layer hidden, 4x2 4 esempi, 2 neuroni di output
l2 = fdt(np.dot(l1,syn1)) #output della rete (per ogni esempio)

for i in range(cicli):
    #print(str(int(100*i/cicli))+"---------------------------------------") #% sul numero totale di cicli

    old_error = error_func(l2)

    #FASE BACKWARD
    #calcolo delta_nu
    delta_nu = Y - l2

    sum = 0
    for nu in range(len(X)): #ciclo tra gli esempi, batch
        sum += epsilon*(delta_nu[nu]*fdt(np.dot(l1[nu],syn1),True))*l1[nu]
        #print(sum)
    syn1[:,0] += sum

    for j in range(hidden_layer): #loop tra i neuroni hidden
        sum=0
        for nu in range(len(X)): #loop tra gli esempi
            sum += epsilon*delta_nu[nu]*fdt(np.dot(l1[nu],syn1),True)*syn1[j]*fdt(np.dot(X[nu],syn0[:,j]),True)
        syn0[:,j] += sum


    #fase forward
    l1 = fdt(np.dot(l0,syn0)) #output layer hidden, 4x4 4 esempi, 4 neuroni di output
    l2 = fdt(np.dot(l1,syn1)) #output della rete (per ogni esempio)

    current_error = error_func(l2)

#    if (np.linalg.norm(sum) < 10**-8):
#        print("Very low variation in weight-space - exiting")
#        print("delta_w :"+str(np.linalg.norm(sum)))
#        break
    if (error_func(l2) < 0.001):
        print("Very low error - exiting")
        break
#    if (abs(current_error - old_error) < 10**-8):
#        print("Very low error variation - exiting")
#        break
    if(i % 10000) == 0:   # stampo errore ogni 10k
        for i in range(len(np.transpose(l2))):
            print(np.transpose(l2)[i])
        print(str(int(100*i/cicli))+"-------"+str(current_error))

print("Error: "+str(error_func(l2)))
for i in range(len(np.transpose(l2))):
    print(np.transpose(l2)[i])
