import numpy as np

# feed-forward networks - называются сетями прямого распространения
# — они принимают входные данные и получают прогноз. Такое
# название они получили потому, что информация в них распространяется
# в прямом направлении. В этих примерах информацией являются все числа, не
# являющиеся весами и уникальные для каждого прогноза.


# Прогнозирование с несколькими выходами и одним входом
weights_num = np.array([0.1, 0.2, 0])


def neural_network_num(input, weights):
    """Прогнозирование с одним выходом Numpy"""
    pred_nums = input.dot(weights)
    return pred_nums


toes_num = np.array([8.5, 9.5, 9.9, 9.0]) #Текущее среднее число игр, сыгранных игроками
wlrec_num = np.array([0.65, 0.8, 0.8, 0.9]) #Текущая доля игр, окончившихся победой
nfans_num = np.array([1.2, 1.3, 0.5, 1.0]) # Число болельщиков (в миллионах)

print('Результаты прогнозирования с несколькими выходами и одним входом')
for i in range(0, 3):
    input_num = np.array([toes_num[i], wlrec_num[i], nfans_num[i]])
    pred_num = neural_network_num(input_num, weights_num)
    print(pred_num)


def ele_mul(number, vector):
    """Поэлементное умножение (реализация dot from Numpy)"""
    output = [0, 0, 0]
    assert (len(output) == len(vector))
    for i in range(len(vector)):
        output[i] = number * vector[i]

    return output


# Прогнозирование с несколькими входами и выходами
# Веса: травмы, победы, горечь от поражения
weights_sum = [[0.1, 0.1, -0.3],
                [0.1, 0.2, 0.0],
                [0.0, 1.3, 0.1]]


def w_sum(a, b):
    """Сумма произведений элементов векторов"""
    assert (len(a) == len(b))
    output = 0
    for i in range(len(a)):
        output += (a[i] * b[i])
    return output


def vect_mat_mul(vect, matrix):
    """Векторное умножение матрицы на число"""
    output = [0, 0, 0]
    assert (len(vect) == len(matrix))

    for i in range(len(vect)):
        output[i] = w_sum(vect, matrix[i])

    return output


def neural_network_sum(input, weights):
    """Вычисление для каждого выхода взвешенной суммы входов (вероятности события заложенного в весе)"""
    pred = vect_mat_mul(input, weights)
    return pred

toes = [8.5, 9.5, 9.9, 9.0] #Текущее среднее число игр, сыгранных игроками
wlrec = [0.65, 0.8, 0.8, 0.9] #Текущая доля игр, окончившихся победой
nfans = [1.2, 1.3, 0.5, 1.0] # Число болельщиков (в миллионах)

input = [toes[0], wlrec[0], nfans[0]]

print(weights_sum[0])
print(input)
pred = neural_network_sum(input, weights_sum)
print('Получение прогноза с несколькими входами и выходами')
print(f"прогноз вероятности травм {pred[0]}")
print(f"прогноз вероятности победы {pred[1]}")
print(f"прогноз вероятности огорчения {pred[2]}")

# игр % побед # болельщиков
# (8.5 *0.1) + (0.65*0.1) + (1.2 *-0.3) = 0.555 прогноз вероятности травм
# (8.5 *0.1) + (0.65*0.2) + (1.2 *0.0) = 0.98 прогноз вероятности победы
# (8.5 *0.0) + (0.65*1.3) + (1.2 *0.1) = 0.965 прогноз вероятности огорчения



# Прогнозирование на основе прогнозов (накладывание нейронных сетей друг на друга)

        # игр % побед # болельщиков
ih_wgt = [ [0.1, 0.2, -0.1], # hid[0]
            [-0.1,0.1, 0.9] , # hid[l]
            [0.1, 0.4, 0.1] ] # hid[2]

hp_wgt = [ [0.3, 1.1, -0.3], # травмы?
            [0.1, 0.2, 0.0] , # победа?
            [0.0, 1.3, 0.1] ] # печаль?

weights = [ih_wgt, hp_wgt]


def neural_network(input, weights):
    hid = vect_mat_mul(input,weights[0])
    pred = vect_mat_mul(hid,weights[1])
    return pred


toes = [8.5, 9.5, 9.9, 9.0]
wlrec = [0.65,0.8, 0.8, 0.9]
nfans = [1.2, 1.3, 0.5, 1.0]

input = [toes[0], wlrec[0], nfans[0]]  # В переменной input передается запись, соответствующая первой игре в сезоне


def neural_network(input, weights):
    hid = vect_mat_mul(input,weights[0])
    pred = vect_mat_mul(hid, weights[1])
    return pred

pred = neural_network(input, weights)
print(f'Прогнозирование на основе прогнозов: {pred}')

# Прогнозирование на основе прогнозов Numpy
# игр % побед болельщиков
ih_wgt = np.array([
        [0.1, 0.2, -0.1],
        [-0.1,0.1, 0.9],
        [0.1, 0.4, 0.1]]).T


hp_wgt = np.array([
    [0.3, 1.1, -0.3],
    [0.1, 0.2, 0.0],
    [0.0, 1.3, 0.1]]).T

weights = [ih_wgt, hp_wgt]

def neural_network(input, weights):
    hid = input.dot(weights[0])
    pred = hid.dot(weights[1])
    return pred

toes = np.array([8.5, 9.5, 9.9, 9.0])
wlrec = np.array([0.65,0.8, 0.8, 0.9])
nfans = np.array([1.2, 1.3, 0.5, 1.0])
input = np.array([toes[0],wlrec[0],nfans[0]])
pred = neural_network(input,weights)
print(f'Прогнозирование на основе прогнозов Numpy: {pred}')

# Работа с Numpy
a = np.array([0, 1, 2, 3]) # Вектор
b = np.array([4, 5, 6, 7]) # Еще один вектор
c = np.array([ [0,1,2,3],
                [4,5,6,7]])#------ Матрица
d = np.zeros((2,4)) # Матрица 2x4, заполненная нулями
e = np.random.rand(2,5) # Матрица 2 х 5, заполненная случайными числами от 0 до 1
print(a)
print(b)
print(c)
print(d)
print(e)

print(f'Каждый элемент вектора а будет умножен на 0.1: {a * 0.1}')
print(f'Каждый элемент матрицы с будет умножен на 0.2: {c * 0.2}')
print(f'Поэлементное умножение а на b: {a * b}')
print(f'Поэлементное умножение векторов, затем каждый элемент результата будет умножен на 0.2: {a * b * 0.2}')
print(f'Поэлементное умножение вектора а с каждой строкой матрицы с, поскольку с имеет то же число столбцов, что и а: {a * c}')
# print(f'Так как а и е имеют разное число столбцов, эта операция возбудит исключение «Value Error: operands could not be broadcast together with...» («Ошибка в значении: операнды не могут передаваться вместе с...»): {a * e}')
# Главное правило, касающееся поэлементных операций (+, -, *, /), — обе переменные
# должны иметь одинаковое число столбцов или одна из переменных
# должна иметь только один столбец.

a = np.zeros((1, 4))  # Вектор с длиной 4
b = np.zeros((4, 3))  # Матрица с 4 строками и 3 столбцами
c = a.dot(b)
print(c.shape)  # Выведет (1,3)
print(c)  # [[0. 0. 0.]] Вектор с длиной 3


a = np.zeros((2, 4))  # Матрица с 2 строками и 4 столбцами
b = np.zeros((4, 3))  # Матрица с 4 строками и 3 столбцами
c = a.dot(b)  # Вектор с длиной 4
print(c.shape)  # Выведет (2,3)
print(c)  # Матрица с 2 строками и 3 столбцами

e = np.zeros((2, 1))  # Матрица с 2 строками и 1 столбцом
f = np.zeros((1, 3))  # Матрица с 1 строкой и 3 столбцами
g = e.dot(f)  # Матрица с 1 строкой и 3 столбцами
print(g.shape)  # Выведет(2,3)
h = np.zeros((5, 4)).T  # Матрица с 4 строками и 5 столбцами
# Операция .Т повернет (транспонирует) матрицу, поменяв строки и столбцы местами
i = np.zeros((5, 6))  # Матрица с 5 строками и 6 столбцами
j = h.dot(i)  # Матрица с 6 строками и 5 столбцами
print(j.shape)  #  Выведет(4,6)

h = np.zeros((5, 4)) # Матрица с 5 строками и 4 столбцами
i = np.zeros((5, 6)) # Матрица с 5 строками и 6 столбцами
# j = h.dot(i)
# print(j.shape) # Сгенерирует ошибку
