"""Check if states form mutually unbiased basis."""
from typing import Any, List, Union

import numpy as np

def is_mutually_unbiased_basis(vec_list: List[Union[np.ndarray, List[Union[float, Any]]]]) -> bool:
    r"""
    Check if list of vectors constitute a mutually unbiased basis [WikMUB]_.

    We say that two orthonormal bases

    .. math::
        \begin{equation}
            \mathcal{B}_0 = \left\{u_a: a \in \Sigma \right\} \subset \mathbb{C}^{\Sigma}
            \quad \text{and} \quad
            \mathcal{B}_1 = \left\{v_a: a \in \Sigma \right\} \subset \mathbb{C}^{\Sigma}
        \end{equation}

    are *mutually unbiased* if and only if
    :math:`\left|\langle u_a, v_b \rangle\right| = 1/\sqrt{\Sigma}` for all :math:`a, b \in \Sigma`.

    For :math:`n \in \mathbb{N}`, a set of orthonormal bases :math:`\left\{
    \mathcal{B}_0, \ldots, \mathcal{B}_{n-1} \right\}` are mutually unbiased bases if and only if
    every basis is mutually unbiased with every other basis in the set, i.e. :math:`\mathcal{B}_x`
    is mutually unbiased with :math:`\mathcal{B}_x^{\prime}` for all :math:`x \not= x^{\prime}` with
    :math:`x, x^{\prime} \in \Sigma`.

    Examples
    ==========

    MUB of dimension :math:`2`.

    For :math:`d=2`, the following constitutes a mutually unbiased basis:

    .. math::
        \begin{equation}
            \begin{aligned}
                M_0 &= \left\{ |0 \rangle, |1 \rangle \right\}, \\
                M_1 &= \left\{ \frac{|0 \rangle + |1 \rangle}{\sqrt{2}},
                \frac{|0 \rangle - |1 \rangle}{\sqrt{2}} \right\}, \\
                M_2 &= \left\{ \frac{|0 \rangle i|1 \rangle}{\sqrt{2}},
                \frac{|0 \rangle - i|1 \rangle}{\sqrt{2}} \right\}. \\
            \end{aligned}
        \end{equation}

    >>> import numpy as np
    >>> from toqito.states import basis
    >>> from toqito.state_props import is_mutually_unbiased_basis
    >>> e_0, e_1 = basis(2, 0), basis(2, 1)
    >>> mub_1 = [e_0, e_1]
    >>> mub_2 = [1 / np.sqrt(2) * (e_0 + e_1), 1 / np.sqrt(2) * (e_0 - e_1)]
    >>> mub_3 = [1 / np.sqrt(2) * (e_0 + 1j * e_1), 1 / np.sqrt(2) * (e_0 - 1j * e_1)]
    >>> mubs = [mub_1, mub_2, mub_3]
    >>> is_mutually_unbiased_basis(mubs)
    True

    Non non-MUB of dimension :math:`2`.

    >>> import numpy as np
    >>> from toqito.states import basis
    >>> from toqito.state_props import is_mutually_unbiased_basis
    >>> e_0, e_1 = basis(2, 0), basis(2, 1)
    >>> mub_1 = [e_0, e_1]
    >>> mub_2 = [1 / np.sqrt(2) * (e_0 + e_1), e_1]
    >>> mub_3 = [1 / np.sqrt(2) * (e_0 + 1j * e_1), e_0]
    >>> mubs = [mub_1, mub_2, mub_3]
    >>> is_mutually_unbiased_basis(mubs)
    False

    References
    ==========
    .. [WikMUB] Wikipedia: Mutually unbiased bases
        https://en.wikipedia.org/wiki/Mutually_unbiased_bases

    :param vec_list: The list of vectors to check.
    :return: :code:`True` if :code:`vec_list` constitutes a mutually unbiased basis, and
             :code:`False` otherwise.
    """
    if len(vec_list) <= 1:
        raise ValueError("There must be at least two bases provided as input.")

    dim = vec_list[0][0].shape[0]
    for i, _ in enumerate(vec_list):
        for j, _ in enumerate(vec_list):
            for k in range(dim):
                if i != j:
                    if not np.isclose(
                        np.abs(np.inner(vec_list[i][k].conj().T[0], vec_list[j][k].conj().T[0]))
                        ** 2,
                        1 / dim,
                    ):
                        return False
    return True


# Author: Sylvain Lapeyrade (sylla801)
# File: TSKS11-Lab2: Task 3

# ***************************** TASK 3 *****************************
# Calculate the Katz Centrality of each article with
#  lamdba = 0.85 * (1/|lamdba_max|), where lamdba_max is the largest
#  eigenvalue of the adjacency matrix.
# List the top five articles and their Katz score.

import numpy as np

# The calculations being too expensive, only one file
#  will be dealt with at the same time

network_number = 1  # Number of the file wanted

title_file = open(
    '/courses/TSKS11/ht2019/data_and_fcns/session2/titles/{}.txt'
    .format(network_number), 'r')
link_file = open(
    '/courses/TSKS11/ht2019/data_and_fcns/session2/links/{}.txt'
    .format(network_number), 'r')

title_list = []
titles = title_file.read().splitlines()  # Array of the titles
for title in titles:
    title_list.append(title)

degree = np.loadtxt(link_file, dtype=int)  # Loads all links
n = max(map(max, degree))  # Number of nodes

m = np.zeros((n, n), dtype=float)  # Matrix M initialized with 0
for j in range(len(degree)):  # Put 1 for every links
    m[degree[j][1] - 1][degree[j][0] - 1] = 1

identity_matrix = np.identity(n)  # Identity Matrix 3000x3000

# Compute the eigenvalues of m.
katz_eigenvalues = np.linalg.eigvals(m)

# Vector with only ones (3000, 1)
vector_I = np.ones((n, 1), dtype=int)

alpha = 0.85 * (1 / max(abs(katz_eigenvalues)))  # Alpha as instrcuted

# Katz centrality formula
katz_centrality = (1 / n) * np.matmul(np.linalg.inv(
    identity_matrix - (alpha * m)), vector_I)

# Sum of all katz centrality
total_katz_centrality = float(np.sum(katz_centrality, axis=0))

for i in range(n):
    katz_centrality[i] /= total_katz_centrality  # Normalisation

# Arguments of the fives largest katz centrality
five_argmax_katz = katz_centrality.argsort(axis=None)[-5:][::-1]

print("\n\nNetwork {}: \n".format(network_number))
print("Nodes with the highest Katz Centrality:")
for i in range(5):
    print(str(i + 1) + '.', "Katz-Centrality: {0:.6f}"
          .format(round(float(katz_centrality[five_argmax_katz[i]]), 6)),
          "| Title:", title_list[five_argmax_katz[i]])


#!/usr/bin/python3
"""
Program: 95% Confidence Interval of a Proportion Simulation
Author: Nathan Fox <nchristopherfox@gmail.com>
Date Created: 2019-05-28
Date Modified: 2019-05-28

This program conducts a simulation to confirm the modified Wald's method
for calculating a 95% Confidence Interval (CI) for a proportion. It takes a
population proportion parameter, then draws s trials for n simulations
and reports the percentage of simulations where the calculated CI contained
the population proportion parameter.

Modified Wald's Method for 95% CI for a Proportion was taken from pg 39 of
Intuitive Biostatistics by Harvey Motulsky, ISBN-13 978-0-19-064356-0
"""

import argparse
import numpy as np

def mod_wald(successes, trials):
    """Calculate Modified Wald's Method 95% Confidence Interval for Proportion.

    Calculates a 95% confidence interval sourced from Intuitive Biostatistics,
    see note in script docstring. This method assumes several things:

    1) The sample was taken from the same distribution as the population.
       i.e. This sample is representative.
    2) All observations are independently drawn.
    3) All observations are reported accurately.

    Args:
        successes: integer; number of trials that gave the desired outcome
        trials: integer; total number of trials

    Returns:
        A tuple with 2 elements. tuple[0] is the lower bound of the confidence
        interval. tuple[1] is the upper bound.
    """
    p_hat = (successes + 2) / (trials + 4)
    w = 2 * np.sqrt((p_hat * (1-p_hat)) / (trials + 4))
    return (p_hat-w, p_hat+w)

def simulate(x, n, sample_size):
    """Simulate calculation of 95% CI for proportions from binomial data.

    Simulates n experiments, drawing sample_size data points from a distribution
    with a favorable proportion of x. Calculates a confidence interval for
    each, then displays the number of confidence intervals that actually
    contained the population parameter.

    Args:
        x: float; population proportion for favorable outcome
        n: integer; number of simulations
        sample_size: integer; number of trials per simulation

    Returns:
        Numpy array of size(n, 3). Each row is a simulation.
        Column 0: Lower bound of CI
        Column 1: Upper bound of CI
        Column 3: Float form of boolean(CI contains population parameter)
    """
    if x > 1.0 or x < 0.0:
        raise ValueError('x must be in the closed interval [0, 1]!')
    results = np.zeros((n, 3))
    for i in range(n):
        s = np.random.uniform(size=sample_size)
        s = s < x
        results[i] = (mod_wald(successes=s.sum(), trials=sample_size) + (-1.0,))
        results[i, 2] = float((results[i, 0] <= x) and (results[i, 1] >= x))
    return results

def main():
    """Simulation to verify 95% Confidence Interval for Proportion.

    Prints n, number of simulations where the CI actually contained the
    population parameter, and the percentage version of the last value. To see
    command line arguments, run 'python conf_int_proportion_sim.py -h'.
    """
    parser = argparse.ArgumentParser(description=('Confirms 95% Confidence '
                                        'Interval by simulation. Sample size '
                                        'of 40 per simulation. Takes 1 '
                                        'parameter:\n\tP: Population '
                                        'percentage of favorable outcomes'))
    parser.add_argument('P', type=float,
                        help='Proportion of population with favorable outcome')
    parser.add_argument('-n', default=100, type=int,
                        help='Number of simulations to be run')
    parser.add_argument('-s', default=40, type=int,
                        help=('Number of trials in each simulation '
                              '(sample size)'))

    args=parser.parse_args()
    results = simulate(x=args.P, n=args.n, sample_size=args.s)
    print('\nResults\n====================\n')
    # print(results)
    print('# of Simulations:           {}'.format(args.n))
    print('# of CIs Including True P:  {}'.format(int(results[:,2].sum())))
    print('% of Sims Including True P: {}'.format(results[:,2].sum()/args.n))
    print()

if __name__=='__main__':
    main()


import numpy as np
from scipy.linalg import svd
from sklearn.decomposition import TruncatedSVD

if __name__ == '__main__':
    A = np.array([[1, 2, 3],
                  [3, 2, 5],
                  [5, 6, 1]])
    U, s, VT = svd(A)
    print(U)
    print(s)
    print(VT)
    print()

    Sigma = np.zeros(A.shape)
    for i in range(len(s)):
        Sigma[i][i] = s[i]
    print(Sigma)
    print(U.dot(Sigma).dot(VT))

    print(U[:, :2].dot(Sigma[:2, :2]).dot(VT[:2, :]))


import numpy as np
import matplotlib.pyplot as plt
import scipy as sp

'''
A 2-layer network, to model the XOR and XNOR operator.

This model is actually not good, so you should just do this as an exercise.
'''

# create the input vector X
X = np.zeros( (4,2) )
X[0,0] = 0; X[0,1] = 0
X[1,0] = 0; X[1,1] = 1
X[2,0] = 1; X[2,1] = 0
X[3,0] = 1; X[3,1] = 1
X = np.concatenate(  (np.ones((X.shape[0],1) ),X) ,axis=1)
print('X is \n',X,'\n')

# create the true solution vector Z
Z = np.zeros( (4,1) )
Z[0,0] = 0
Z[1,0] = 1
Z[2,0] = 1
Z[3,0] = 0
print('Z is \n',Z,'\n')

# chose random values for the weights
scale = 1

U = np.random.rand(3,2)*2*scale - scale
v = np.random.rand(3,1)*2*scale - scale

print('U initial is \n',U,'\n')
print('v initial is \n',v,'\n')

# gradient descent step
alpha = .2;

nIt = 100000;

# create an error matrix to plot the gradient descent results
E = np.zeros((nIt,1))

for i in range(0,nIt):

    Y = np.dot(X,U)
    A0 = sp.special.expit(Y)
    A = np.concatenate(  (np.ones((A0.shape[0],1) ),A0) ,axis=1)
    #print('Y is \n',Y,'\n')

    h = sp.special.expit(np.dot(A,v))

    #print('Zp is \n',Zp,'\n')
    d = Z - h

    D = np.diag(d[:,0])

    U = U + alpha*np.dot(np.dot(X.T,np.dot(D,np.multiply(A0,1-A0))), np.diag(v[1:,0]))

    v = v + alpha*np.dot(A.T,d)

    E[i] = np.dot(d.T,d)

print('U final initial is \n',U,'\n')
print('v final initial is \n',v,'\n')

xplot = list(range(1,nIt+1))
plt.semilogx(np.log(xplot),E, "-o", color="r")
plt.tight_layout()
plt.xlabel('# iterations')
plt.ylabel('error')
print('the final result with a .5 threshold is \n',np.round(h))


from math import log2 as lg, pow, exp
import numpy as np

def entropy(arr):
    ent = 0

    for p in arr:
        if p <= 0: continue
        ent -= p * lg(p)

    return ent

def get_py_array(px, yx_matrix):
    py_array = [round(sum([px[i] * yx_matrix[i][j] for i in range(len(yx_matrix))]), 4) for j in range(len(yx_matrix))]
    return py_array

def get_entropy_x(px):
    hx = entropy(px)
    return hx

def get_entropy_y(px, yx_matrix):
    py_array = get_py_array(px, yx_matrix)
    hy = entropy(py_array)
    return hy

def get_entropy_yx(px, yx_matrix):
    hyx = sum([sum(px[i] * entropy(yx_matrix[i]) for i in range(len(yx_matrix)))])

    return hyx

def get_entropy_xy(px, yx_matrix):
    # py_array = get_py_array(px, yx_matrix)
    return get_entropy_x(px) - get_entropy_y(px, yx_matrix) + get_entropy_yx(px, yx_matrix)
    # new_matrix = [[yx_matrix[i][j] for i in range(len(yx_matrix))] for j in range(len(yx_matrix))]
    # hxy = sum([sum(py_array[i] * entropy(new_matrix[i]) for i in range(len(new_matrix)))])
    #
    # return hxy

def print_all(p, m):
    print("p(y) array:", get_py_array(p, m))
    print("H(X) =", get_entropy_x(p))
    print("H(Y) =", get_entropy_y(p, m))
    print("H(Y|X) =", get_entropy_yx(p, m))
    print("H(X|Y) = ", get_entropy_xy(p, m))
    print(lg(3))

if __name__ == "__main__":
    px = [0.4, 0.2, 0.4]
    # yx_matrix = [[0.15, 0.25, 0.6],
    #              [0.35, 0.25, 0.4],
    #              [0.49, 0.5, 0.01]]

    yx_matrix = [[0.7, 0.2, 0.1],
                 [0.1, 0.7, 0.2],
                 [0.2, 0.1, 0.7]]

    pxx = [10/43, 9/43, 5/43, 6/43, 7/43, 4/43, 1/43, 1/43]
    print(entropy(pxx))

    print_all(px, yx_matrix)


from sympy import ( symbols, solve, diff, integrate, exp, sqrt, lambdify, Integral, pprint )
import matplotlib.pyplot as plt
import numpy as np

# The probability density function of a random variable on the interval [16, 25]

x = symbols( 'x' )
F = ( 3/122 ) * sqrt( x )

a, b = 16, 25

def expected_value( f, var, a, b ):
	return integrate( var * f, ( var, a, b ) )

def variance( f, var, a, b, mu ):
	return integrate( ( ( var **2 ) * f ), ( var, a, b ) ) - mu**2

def std_dev( var ):
	return sqrt( var )

# Find the expected value.
ev = expected_value( F, x, a, b )
round( ev, 2 )

# Find the variance.
variance = variance( F, x, a, b, ev )
round( variance, 2 )

# Find the standard deviation.
stdev = std_dev( variance )
round( stdev, 2 )

# Find the probability that the random variable has a value greater than the mean.
prob_gt_mean = integrate( F, ( x, ev, b ) )
round( prob_gt_mean, 2 )

# find the probability that the value of the random variable is within one standard deviation of the mean.
prob_within_1sd = integrate( F, ( x, ev - stdev, ev + stdev ) )
round( prob_within_1sd, 2 )


'''
Perform PCA analysis
'''
import numpy as np

def pca(samples, mean=None, max_variance=1, full_matrices=False):
    '''
    Perform PCA analysis on samples according to an amount of principal
    components.

    in: 1xC vector mean of all samples
        RxC matrix containing the samples
        int maximal variance --> limits the amount of returned
        eigenvectors/values
    out: Rx? matrix of eigenvectors/eigenvalues
        eigenvalues and eigenvectors of the covariance matrix such that their
        total variance is not lower than max_variance.
        1xC vector the average sample
    '''
    if not bool(np.sum(mean)):
        mean = np.mean(samples, axis=0)

    # create difference between samples and mean
    deviation_from_mean = samples - mean

    # perform singular value decomposition to get eigenvectors and eigenvalues
    eigenvectors, eigenvalues, variance = np.linalg.svd(
                                            deviation_from_mean.T,
                                            full_matrices=False
                                            )

    if not full_matrices:
        # only keep eigenvalues that add up to max_variance * total variance
        total_variance = np.sum(eigenvalues)
        for i in range(eigenvalues.size):
            if np.sum(eigenvalues[:i]) >= max_variance*total_variance:
                eigenvalues = eigenvalues[:i]
                eigenvectors = eigenvectors[:, :i]

    return eigenvalues, eigenvectors, mean


# -*- coding: utf-8 -*-
"""
Created on Sat Oct 02 17:12:25 2018

Polynomial Regression Model to help HR department of an organization find
if the previous salary quoted by the potential future employee is correct (truth or bluff)
based on the designation-wise salary data of the past employer

@author: Priyanka Sharma
"""

# Importing the libraries
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

# Importing the dataset
dataset = pd.read_csv('Position_Salaries.csv')
# Independant Variable
X = dataset.iloc[:, 1:2].values
# Dependant Variable
y = dataset.iloc[:, 2].values

# Splitting the dataset into the Training set and Test set
# Not required since we do not have much data so we cannot split
# Also, we need to use all data to make accurate prediction
"""from sklearn.cross_validation import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 0)"""

# Feature Scaling
# Not required as it is taken care of by the library
"""from sklearn.preprocessing import StandardScaler
sc_X = StandardScaler()
X_train = sc_X.fit_transform(X_train)
X_test = sc_X.transform(X_test)
sc_y = StandardScaler()
y_train = sc_y.fit_transform(y_train)"""

# Fitting Linear Regression to the dataset
# added for comparison with Polynomial Regression Model
from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(X,y)

# Fitting Polynomial Regression to the dataset
from sklearn.preprocessing import PolynomialFeatures
# below code transforms X into new matrix containing independent variables with different powers
# degree can be adjusted to see which model fits better
poly_reg = PolynomialFeatures(degree =4)
X_poly = poly_reg.fit_transform(X)
lin_reg_2 = LinearRegression()
lin_reg_2.fit(X_poly, y)

# Visualizing Linear Regression results
# added for comparison with Polynomial Regression Model
plt.scatter(X, y, color = 'red')
plt.plot(X, lin_reg.predict(X), color = 'blue')
plt.title('Truth or Bluff (Linear Regression)')
plt.xlabel('Position Level')
plt.ylabel('Salary')
plt.show()

# Visualizing Polynomial Regression results
plt.scatter(X, y, color = 'red')
plt.plot(X, lin_reg_2.predict(poly_reg.fit_transform(X)), color = 'blue')
plt.title('Truth or Bluff (Polynomial Regression)')
plt.xlabel('Position Level')
plt.ylabel('Salary')
plt.show()

# Visualizing Polynomial Regression results
# X_grid contains all the levels along with incremented levels with resolution of 0.1
X_grid = np.arange(min(X), max(X), 0.1)
X_grid = X_grid.reshape((len(X_grid), 1))
plt.scatter(X, y, color = 'red')
plt.plot(X_grid, lin_reg_2.predict(poly_reg.fit_transform(X_grid)), color = 'blue')
plt.title('Truth or Bluff (Polynomial Regression)')
plt.xlabel('Position Level')
plt.ylabel('Salary')
plt.show()

# Predicting a new result with Linear Regression
# added for comparison with Polynomial Regression Model
lin_reg.predict(6.5)

# Predicting a new result with Polynomial Regression
lin_reg_2.predict(poly_reg.fit_transform(6.5))


import numpy as np
from .errors import NotImplementedYet

# https://towardsdatascience.com/activation-functions-neural-networks-1cbd9f8d91d6

class ActivationFunction():
    '''All activation functions need to be subclasses of this class. This class can be used to define newer activation functions.
    '''

    def __init__(self, func_name: str) -> None:
        self.name = func_name

    def __str__(self) -> str:
        return "{} Activation Function Object".format(self.name)

    def forward(self, *args: object, **kwargs: dict):
        raise NotImplementedYet

    def backward(self, *args: object, **kwargs: dict):
        raise NotImplementedYet

class Relu(ActivationFunction):
    '''The Rectified Linear Unit Activation Function'''

    def __init__(self) -> None:
        super().__init__(func_name="relu")

    def forward(self, inputs: np.ndarray) -> np.ndarray:
        return np.maximum(inputs, 0)

class Sigmoid(ActivationFunction):
    '''Sigmoid Activation Function'''

    def __init__(self) -> None:
        super().__init__(func_name="sigmoid")

    def forward(self, inputs: np.ndarray) -> np.ndarray:
        return 1/(1+np.exp(-inputs))

class Softmax(ActivationFunction):
    '''Softmax Activation Function for multiclass classificiation'''

    def __init__(self) -> None:
        super().__init__(func_name="softmax")

    def forward(self, inputs: np.ndarray) -> np.ndarray:
        expo = np.exp(inputs)
        return expo / expo.sum(axis=-1)

class Tanh(ActivationFunction):
    '''TanH activation function'''

    def __init__(self) -> None:
        super().__init__(func_name="tanh")

    def forward(self, inputs: np.ndarray) -> np.ndarray:
        return np.tanh(inputs)

class Arctan(ActivationFunction):
    '''TanH activation function'''

    def __init__(self) -> None:
        super().__init__(func_name="arctan")

    def forward(self, inputs: np.ndarray) -> np.ndarray:
        return np.arctan(inputs)


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
MIT License

Copyright (c) 2022 Jongrae.K

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""

import numpy as np

def q2dcm(quat):

    q13 =quat[0:3]
    q4 = quat[3];

    q13x = np.array([   [ 0,          -q13[2],       q13[1]],
                        [ q13[2],      0,           -q13[0]],
                        [-q13[1],      q13[0],        0]])

    q13 = np.reshape(q13,(3,1))
    dcm = (q4**2-np.squeeze(q13.T@q13))*np.eye(3) + 2*q13@q13.T - 2*q4*q13x

    return dcm

def dcm2q(dcm):

    quat = np.zeros(4)

    a1 = (1 + dcm[0,0] - dcm[1,1] - dcm[2,2])/4
    a2 = (1 + dcm[1,1] - dcm[0,0] - dcm[2,2])/4
    a3 = (1 + dcm[2,2] - dcm[0,0] - dcm[1,1])/4
    a4 = (1 + dcm[0,0] + dcm[1,1] + dcm[2,2])/4

    a = np.array([a1, a2, a3, a4])
    a_idx = a.argmax()
    quat[a_idx] = np.sqrt(a.max())

    if a_idx==0:
        quat[1] = (dcm[0,1]+dcm[1,0])/(4*quat[0]);
        quat[2] = (dcm[0,2]+dcm[2,0])/(4*quat[0]);
        quat[3] = (dcm[1,2]-dcm[2,1])/(4*quat[0]);
    elif a_idx==1:
        quat[0] = (dcm[0,1]+dcm[1,0])/(4*quat[1]);
        quat[2] = (dcm[1,2]+dcm[2,1])/(4*quat[1]);
        quat[3] = (dcm[2,0]-dcm[0,2])/(4*quat[1]);
    elif a_idx==2:
        quat[0] = (dcm[0,2]+dcm[2,0])/(4*quat[2]);
        quat[1] = (dcm[1,2]+dcm[2,1])/(4*quat[2]);
        quat[3] = (dcm[0,1]-dcm[1,0])/(4*quat[2]);
    elif a_idx==3:
        quat[0] = (dcm[1,2]-dcm[2,1])/(4*quat[3]);
        quat[1] = (dcm[2,0]-dcm[0,2])/(4*quat[3]);
        quat[2] = (dcm[0,1]-dcm[1,0])/(4*quat[3]);

    if quat[3] < 0:
        quat = - quat

    return quat

# generate random quaternion
eig_ax = 2*(np.random.rand(3)-0.5)
eig_ax = eig_ax/np.linalg.norm(eig_ax) #random axis normalized

eig_ang = np.pi*2*(np.random.rand(1)-0.5) # angle between +/- pi [rad]

# random quaternion
q13 = eig_ax*np.sin(eig_ang/2)
q4 = np.cos(eig_ang/2)
quat = np.hstack((q13,q4))

# random quaternion to dcm and the dcm to quaternion
dcm = q2dcm(quat)
quat_from_dcm = dcm2q(dcm)

# compare quaternion
print(np.linalg.norm(quat - quat_from_dcm))

# dcm from the converted quaternion
dcm2 = q2dcm(quat_from_dcm);

# compare dcm
print(np.linalg.norm(dcm-dcm2))


from pyomo.environ import *
def defineOptimization():

    model = AbstractModel()

    # Set of products
    model.P = Set()

    # Initial inventory
    model.init_inventory = Param(model.P, within=NonNegativeReals)

    # Ordering cost
    model.K = Param(model.P, within=NonNegativeReals)

    # Ordering holding cost
    model.h = Param(model.P, within=NonNegativeReals)

    # Volume of each item
    model.v = Param(model.P, within=NonNegativeReals)

    # number of time intervals
    model.T = Param(within=NonNegativeReals)

    # supplier capacity
    model.C = Param(within=NonNegativeReals)

    # Set of time intervals
    model.t = RangeSet(1, model.T)

    # Demand
    model.demand = Param(model.t, model.P, within=NonNegativeReals)

    # Order size
    model.z = Var(model.t, model.P, within=NonNegativeReals)

    # Indicator if the order was made
    model.delta = Var(model.t, model.P, within=Boolean)

    # Minimize ordering cost
    def cost_rule(model):
        return sum(sum(model.K[j] * model.delta[t,j] +
                       model.h[j] * (model.init_inventory[j] + sum(model.z[i,j] - model.demand[i,j] for i in sequence(t)))
               for j in model.P) for t in model.t)
    model.cost = Objective(rule=cost_rule)

    # init_inventory[j] + sum z[i,j] >= sum demand[i,j] constraint
    def order_rule(model, t, j):
        return model.init_inventory[j] + sum(model.z[i,j] for i in sequence(t)) >= sum(model.demand[i,j] for i in sequence(t))
    model.order_constraint = Constraint(model.t, model.P, rule=order_rule)

    # z[t,j] <= delta[t,j] * sum(demand[i,j])
    def delta_rule(model, t, j):
        return model.z[t,j] <= model.delta[t,j] * sum(model.demand[i,j] for i in sequence(t,model.T))
    model.delta_constraint = Constraint(model.t, model.P, rule=delta_rule)

    # sum v[j] * z[t,j] <= C
    def capacity_rule(model,t):
        return sum(model.v[j] * model.z[t,j] for j in model.P) <= model.C
    model.capacity_constraint = Constraint(model.t, rule = capacity_rule)

    return model

#f:\MIPCL\mip\bin\mps_mipcl.exe capacitated_dynlotsizing.mps

#solver = SolverFactory("xpress", solver_io="lp", is_mip = True)
#solver = SolverFactory("gurobi", solver_io="lp", is_mip = True)
#result = solver.solve(instance)

#print("Running time (sec)")
#print(result.Solver.Time)

# print solution
#print("\n Time Product Order indicator Order size")
#for i in range(instance.T.value):
#  for j in instance.P.value:
#   if instance.delta[i+1,j].value > 0.1:
#      print("%d %s %f %f" % (i, j, instance.delta[i+1,j].value, instance.z[i+1,j].value))

#instance.pprint()

#result.write()


# Задача №13
import numpy as np
import math

"В условии задачи дана вычисленная векторная норма ||x||inf=max(xi), "
"поэтому посчитаем подчинённую матричную норму ||x||inf"

def matrix_norm(matrix, m,n):
    '''Фукция вычисления норм'''
    max_row_abs=[]
    for i in range(m):
        row_abs = [abs(matrix[i][j]) for j in range(n)]
        max_row_abs.append(max(row_abs))
    return max(max_row_abs)

def relative_error(matrix, f, norm_df):
    '''Функция вычисления погрешности'''
    m,n = matrix.shape
    m1,n1=f.shape

    #Вычиляем обратную матрицу с помощью numpy
    inverse_matrix_A=np.linalg.inv(matrix)
    m2,n2=inverse_matrix_A.shape

    # Вычисляем нормы
    norm_A=matrix_norm(matrix,m,n)
    norm_f=matrix_norm(f,m1,n1)
    norm_inv_mat_A=matrix_norm(inverse_matrix_A, m2,n2)

    # Вычисляем Мю
    m=norm_A*norm_inv_mat_A

    print(f'Оценка относительной погрешности:   {m*norm_df/norm_f}')

def run():
    A= np.array(([-7, -3, -8],[6, -6, -8],[-5, 1, -3]))
    f=np.array(([-7.1, 7.2, 6.7],))
    norm_df=0.6
    relative_error(A, f, norm_df)

run()


import numpy as np
import torch
from torch.nn import functional as F

def laplacian_aleatoric_uncertainty_loss_original(input, target, log_variance, reduction='mean'):
    '''
    References:
        MonoPair: Monocular 3D Object Detection Using Pairwise Spatial Relationships, CVPR'20
        Geometry and Uncertainty in Deep Learning for Computer Vision, University of Cambridge
    '''
    assert reduction in ['mean', 'sum']
    loss = 1.4142 * torch.exp(-0.5*log_variance) * torch.abs(input - target) + 0.5*log_variance
    return loss.mean() if reduction == 'mean' else loss.sum()
"""
def laplacian_aleatoric_uncertainty_loss(input, target, log_std, reduction='sum', reg_weight=None):
    '''
    References:
        MonoPair: Monocular 3D Object Detection Using Pairwise Spatial Relationships, CVPR'20
        Geometry and Uncertainty in Deep Learning for Computer Vision, University of Cambridge
    '''
    assert reduction in ['mean', 'sum']
    loss1 = 1.4142 * torch.exp(-log_std) * torch.abs(input - target)
    loss2 = log_std
    loss = loss1 + loss2
    #if reg_weight is not None:
    #    loss1 *= reg_weight
    #    loss2 *= reg_weight
    #    loss *= reg_weight
    loss1 = loss1.mean() if reduction == 'mean' else loss1.sum()
    loss2 = loss2.mean() if reduction == 'mean' else loss2.sum()
    loss = loss.mean() if reduction == 'mean' else loss.sum()

    return loss, loss1, loss2
"""
def laplacian_aleatoric_uncertainty_loss(input, target, log_std, FUNCTION, reduction='sum', reg_weight=None):
    '''
    References:
        MonoPair: Monocular 3D Object Detection Using Pairwise Spatial Relationships, CVPR'20
        Geometry and Uncertainty in Deep Learning for Computer Vision, University of Cambridge
    '''
    assert reduction in ['mean', 'sum']
    loss1 = 1.4142 * torch.exp(-log_std) * FUNCTION( input, target, reduce = False)
    loss2 = log_std
    loss = loss1 + loss2
    #if reg_weight is not None:
    #    loss1 *= reg_weight
    #    loss2 *= reg_weight
    #    loss *= reg_weight
    loss1 = loss1.mean() if reduction == 'mean' else loss1.sum()
    loss2 = loss2.mean() if reduction == 'mean' else loss2.sum()
    loss = loss.mean() if reduction == 'mean' else loss.sum()
    return loss, loss1, loss2

def gaussian_aleatoric_uncertainty_loss(input, target, log_variance, reduction='mean'):
    '''
    References:
        What Uncertainties Do We Need in Bayesian Deep Learning for Computer Vision?, Neuips'17
        Geometry and Uncertainty in Deep Learning for Computer Vision, University of Cambridge
    '''
    assert reduction in ['mean', 'sum']
    loss = 0.5 * torch.exp(-log_variance) * torch.abs(input - target)**2 + 0.5 * log_variance
    return loss.mean() if reduction == 'mean' else loss.sum()

if __name__ == '__main__':
    pass


import numpy as np

def birth(tau, M, c):
    """
    Get the change in number of molecules to each cell of matrix M
    by birth,
    the propensity in each cell is
    c
    """

    # Get the propensity
    P = c

    # Get the change (positive cause of birth)
    Z = np.random.poisson(lam=tau*P, size=M.shape)

    return Z

def die(tau, M, c):
    """
    Get the change in number of molecules to each cell of matrix M
    by death,
    the propensity in cell i is
    c * M_i
    """

    # Get the propensity
    P = c * M

    # Get the change (negative cause of death)
    Z = -1 * np.random.poisson(lam=tau*P)

    return Z

def react(tau, M_A, M_B, c):
    """
    Get the change in number of molecules to each cell of matrices
    M_A and M_B, respectively
    by the reaction of species A and B according to the equation:
    2A + B -> 3A
    where the propensity in cell i is
    c * M_A_i * (M_A_i - 1) * M_B_i
    """

    # Get the propensity
    P = c * M_A * (M_A - 1) * M_B

    # Get the change
    Z = np.random.poisson(lam=tau*P)

    # The change is plus one for A and minus one for B
    Z_A = 1 * Z
    Z_B = -1 * Z

    return Z_A, Z_B

def diffuse(tau, M, d):
    """
    Get the change in number of molecules to each cell of matrix M
    by diffusion in each of four directions,
    the propensity in cell i is
    d * M_i
    """

    # Get the matrix of propensity functions (for each cell)
    P  = d * M

    # Get the amount diffused in each of four directions
    Ds = np.random.poisson(lam=tau*P, size=(4,) + M.shape)

    # Zero matrix (for calculating difference vector)
    Z = np.zeros(shape=M.shape, dtype=np.int16)

    ### 1. Take from top of matrix and give to bottom of matrix
    # Make D equal to all of first of Ds except bottom row
    D = Ds[0]  # grab first direction (down)
    D = D[:-1,:]  # get top (index all except bottom row)
    Z[:-1,:] -= D  # subtract from top
    Z[1:,:] += D  # add to bottom

    ### 2. Take from bottom of matrix and give to top of matrix
    # Make D equal to all of second of Ds except top row
    D = Ds[1]  # grab second direction (up)
    D = D[1:,:]  # grab bottom (index all except top row)
    Z[1:,:] -= D  # subtract from bottom
    Z[:-1,:] += D  # add to top

    ### 3. Take from left of matrix and give to right of matrix
    D = Ds[2]  # grab third direction (right)
    D = D[:,:-1]  # grab left
    Z[:,:-1] -= D  # subtract from left
    Z[:,1:] += D  # add to right

    ### 4. Take from right of matrix and give to left of matrix
    D = Ds[3]  # grab fourth direction (left)
    D = D[:,1:]  # grab right
    Z[:,1:] -= D  # subtract from right
    Z[:,:-1] += D  # add to left

    return Z

# Initialization

def initialize_movie(N_t, m, n, A_init, B_init):
    """
    Initializes the 2D grid for a movie with N_t time points,
    m rows, and n columns. And with initial A and B set to
    A_init and B_init.

    Initializes the parameters with signed 16-bit integer, which
    can go from -32_768 to 32_767.
    """

    # Initialize the grid of cells for A and B populations
    shape = (N_t, m, n)  # index by time, row, column

    X_A = np.zeros(shape, dtype=np.int16)  # set A population at all times to zero
    X_A[0] += A_init  # add initial A population for time zero

    X_B = np.zeros(shape, dtype=np.int16)  # do the same as above for B...
    X_B[0] += B_init

    return X_A, X_B

# Calculation

def calculate_picture(tau, M_A, M_B, mu, beta, alpha, kappa, d_A, d_B):
    """
    Calculate the number of molecules in each cell of the A and B grids
    for time t+1.

    Note that unlike "calculate_movie" we cannot write directly to the array
    so we need to return it, this means the for loop will look different.
    """

    # Birth
    Z_A_birth = birth(tau, M=M_A, c=mu)
    Z_B_birth = birth(tau, M=M_B, c=beta)

    # Die
    Z_A_death = die(tau, M=M_A, c=alpha)

    # React
    Z_A_react, Z_B_react = react(tau, M_A, M_B, c=kappa)

    # Diffuse
    Z_A_diffusion = diffuse(tau, M=M_A, d=d_A)
    Z_B_diffusion = diffuse(tau, M=M_B, d=d_B)

    # Calculate next grid
    M_A_next = M_A + Z_A_birth + Z_A_death + Z_A_react + Z_A_diffusion
    M_B_next = M_B + Z_B_birth + Z_B_react + Z_B_diffusion

    return M_A_next, M_B_next

# Miscillaneous initialization if we don't care about the movie and only the final timepoint

def initialize_picture(m, n, A_init, B_init):
    """
    Initializes the 2D grid for a picture with,
    m rows, and n columns. And with initial A and B set to
    A_init and B_init.

    Initializes the parameters with signed 16-bit integer, which
    can go from -32_768 to 32_767.
    """

    # Initialize the grid of cells for A and B populations
    shape = (m, n)  # index by row, column

    X_A = np.zeros(shape, dtype=np.int16)  # set A population to zero
    X_A += A_init  # add initial A population for time zero

    X_B = np.zeros(shape, dtype=np.int16)  # do the same as above for B...
    X_B += B_init

    return X_A, X_B


import numpy as np

def h_fpp(H):
    """Evaluate the significance of an H score.

    The H test is an extension of the Z_m^2 or Rayleigh tests for
    uniformity on the circle. These tests estimate the Fourier coefficients
    of the distribution and compare them with the values predicted for
    a uniform distribution, but they require the user to specify the number
    of harmonics to use. The H test automatically selects the number of
    harmonics to use based on the data.

    Arguments
    ---------

    H : float
        The H value to evaluate

    Returns
    -------

    fpp : float
        The probability of an H score this large arising from sampling a
        uniform distribution.

    Reference
    ---------

    de Jager, O. C., Swanepoel, J. W. H, and Raubenheimer, B. C., "A
    powerful test for weak periodic signals of unknown light curve shape
    in sparse data", Astron. Astrophys. 221, 180-190, 1989.
    """
    # These values are obtained by fitting to simulations.
    a = 0.9999755
    b = 0.39802
    c = 1.210597
    d = 0.45901
    e = 0.0022900

    if H<=23:
        return a*np.exp(-b*H)
    elif H<50:
        return c*np.exp(-d*H+e*H**2)
    else:
        return 4e-8
        # This comes up too often to raise an exception
        raise ValueError("H=%g>50 not supported; false positive probability less than 4*10**(-8)" % H)

def h_test(events):
    """Apply the H test for uniformity on [0,1).

    The H test is an extension of the Z_m^2 or Rayleigh tests for
    uniformity on the circle. These tests estimate the Fourier coefficients
    of the distribution and compare them with the values predicted for
    a uniform distribution, but they require the user to specify the number
    of harmonics to use. The H test automatically selects the number of
    harmonics to use based on the data. The returned statistic, H, has mean
    and standard deviation approximately 2.51, but its significance should
    be evaluated with the routine h_fpp. This is done automatically in this
    routine.

    Arguments
    ---------

    events : array-like
        events should consist of an array of values to be interpreted as
        values modulo 1. These events will be tested for statistically
        significant deviations from uniformity.

    Returns
    -------

    H : float
        The raw score. Larger numbers indicate more non-uniformity.
    M : int
        The number of harmonics that give the most significant deviation
        from uniformity.
    fpp : float
        The probability of an H score this large arising from sampling a
        uniform distribution.

    Reference
    ---------

    de Jager, O. C., Swanepoel, J. W. H, and Raubenheimer, B. C., "A
    powerful test for weak periodic signals of unknown light curve shape
    in sparse data", Astron. Astrophys. 221, 180-190, 1989.
    """
    max_harmonic = 20
    ev = np.reshape(events, (-1,))
    cs = np.sum(np.exp(2.j*np.pi*np.arange(1,max_harmonic+1)*ev[:,None]),axis=0)/len(ev)
    Zm2 = 2*len(ev)*np.cumsum(np.abs(cs)**2)
    Hcand = (Zm2 - 4*np.arange(1,max_harmonic+1) + 4)
    M = np.argmax(Hcand)+1
    H = Hcand[M-1]
    fpp = h_fpp(H)
    return (H, M, fpp)


#%%
# building NN from scratch using numpy and calculus

import numpy as np

# follow along with https://towardsdatascience.com/how-to-build-a-simple-neural-network-from-scratch-with-python-9f011896d2f3

# the sample data is the result of XOR operation

#   A   B   A XOR B
#   0   0       0
#   1   0       1
#   0   1       1
#   1   1       1

# first is the network structure
# 1 x input layer
# 1 x hidden layer
# 1 x output layer

# activation function
# for the last layer, we will use a sigmoid function as it outputs values between 0 and 1
# for hidden layers, will use a tanh() function

# other functions for activation could be things like ReLU

#%%
# def a sigmoid function -- used as activation to the last layer
def sigmoid(x):
    return 1/(1 + np.exp(-x))

print("sigmoid of 5 is {}".format(sigmoid(5)))

# paramaters in this NN are W and B
# one hidden layer(W1 and B1)
# one output layer(W2 and B2)
# W1, W2
# B1, B2

np.random.seed(2)

# The 4 training examples by columns
X = np.array([[0, 0, 1, 1], [0, 1, 0, 1]])

print(X)
print(X.shape)
# x is (2,4) - that is 2 features and 4 samples

# The outputs of the XOR for every example in X
Y = np.array([[0, 1, 1, 0]])
print(Y.shape)
print(Y)
# y, the output, is 1,4, as in 1 label and 4 examples

for i in range(0,4):
    print("{} {} --> {}".format(
        X[0,i],
        X[1,i],
        Y[0,i]
    ))

# n0 -> number of input neurons, that is the number of features in a sample
#dimensions of X is: Sn x  n0
#   where Sn is number of samples
#   n0 number of features in the sample

# n1 is number of neurons in hidden layer
#   dimensions of W1 == n0 x n1
#   Lh = XW1 + B1
#   dimensins of XW1 is (Sn x n0) dot (n0 x n1) ==> (Sn x n1)
#

# n2 is number of neurons in the output layer
# dimensions of W2 == n1 x n2
#     Lout = LhW2 + B2
#     dimonsions of LhW1 is (Sn x n1) dot (n1 x n2) ==> (Sn x n2)

def initialise_parameters(n_x, n_h, n_y):
    # n_x - number of featurs in input
    # n_h - number of neurons in hidden layer
    # n_y - number of neurons in output
    # inour exmaple that will be 2, 2, 1
    print("input feautres {}\nhidden layer neurons {}\noutput layer neurons {}".format(
        n_x,
        n_h,
        n_y))

    # w1 is n_x by n_h, b1 must be n_h
    w1 = np.random.randn(n_h, n_x)
    b1 = np.zeros((n_h,1))
    print("w1: {} x {}:\n {}".format(n_x, n_h, w1))
    print("b1: {} x {}:\n {}".format(n_h, 1, b1))

    # w2 is n_h x n_y, b must be b_y
    w2 = np.random.randn(n_y, n_h)
    b2 = np.zeros((n_y, 1))
    print("w2: {} x {}:\n {}".format(n_h, n_y, w2))
    print("b2: {} x {}:\n {}".format(n_y, 1, b2))

    parameters = {
        "W1": w1,
        "b1": b1,
        "W2": w2,
        "b2": b2
    }
    return parameters

# build the forward propogation function
# takes inpout X and network parameters
def forward_prop(X, p):
    W1 = p["W1"]
    b1 = p["b1"]
    W2 = p["W2"]
    b2 = p["b2"]

    # general formula
    # An = ActivationFunction ( X.W + B)

    # first layer
    Z1 = np.dot(W1, X)  + b1
    A1 = np.tanh(Z1) # activation for layer 1 is tanh

    #second layer
    Z2 = np.dot(W2, Z1) + b2 # Z1 is input to Z2
    A2 = sigmoid(Z2) # activationfor layer 2 is sigmoid

    cache = {
        "A1": A1,
        "A2": A2
    }

    # returning the final output abd the cache
    return A2, cache

# cost function
# now have compute the loss function. We will use the Cross
# Entropy Loss function. Calculate_cost(A2, Y) takes as input the result of the
# NN A2 and the groundtruth matrix Y and returns the cross entropy cost:

def calculate_cost(A2, Y):
    cost = -np.sum(
        np.multiply(Y, np.log(A2)) +  np.multiply(1-Y, np.log(1-A2))
        )/m  # m is number of samples in X
    cost = np.squeeze(cost)
    return cost

# Now the most difficult part of the Neural Network algorithm, Back Propagation.
# The code here may seem a bit weird and difficult to understand but we will not
# dive into details of why it works here. This function will return the
# gradients of the Loss function with respect to the 4 parameters of our
# network(W1, W2, b1, b2):

def backward_prop(X, Y, cache, parameters):
    A1 = cache["A1"]
    A2 = cache["A2"]

    W2 = parameters["W2"]

    dZ2 = A2 - Y
    dW2 = np.dot(dZ2, A1.T)/m
    db2 = np.sum(dZ2, axis=1, keepdims=True)/m
    dZ1 = np.multiply(np.dot(W2.T, dZ2), 1-np.power(A1, 2))
    dW1 = np.dot(dZ1, X.T)/m
    db1 = np.sum(dZ1, axis=1, keepdims=True)/m

    grads = {
        "dW1": dW1,
        "db1": db1,
        "dW2": dW2,
        "db2": db2
    }

    return grads

# Nice, now we have the all the gradients of the Loss function, so we can
# procceed to the actual learning! We will use Gradient Descent algorithm to
# update our parameters and make our model learn with the learning rate passed
# as a parameter:

def update_parameters(parameters, grads, learning_rate):
    W1 = parameters["W1"]
    b1 = parameters["b1"]
    W2 = parameters["W2"]
    b2 = parameters["b2"]

    dW1 = grads["dW1"]
    db1 = grads["db1"]
    dW2 = grads["dW2"]
    db2 = grads["db2"]

    W1 = W1 - learning_rate*dW1
    b1 = b1 - learning_rate*db1
    W2 = W2 - learning_rate*dW2
    b2 = b2 - learning_rate*db2

    new_parameters = {
        "W1": W1,
        "W2": W2,
        "b1" : b1,
        "b2" : b2
    }

    return new_parameters

def model(X, Y, n_x, n_h, n_y, num_of_iters, learning_rate):
    parameters = initialise_parameters(n_x, n_h, n_y)

    for i in range(0, num_of_iters+1):
        a2, cache = forward_prop(X, parameters)

        cost = calculate_cost(a2, Y)

        grads = backward_prop(X, Y, cache, parameters)

        parameters = update_parameters(parameters, grads, learning_rate)

        if(i%100 == 0):
             print('Cost after iteration# {:d}: {:f}'.format(i, cost))

    return parameters

def predict(X, parameters):
    a2, cache = forward_prop(X, parameters)
    yhat = a2
    yhat = np.squeeze(yhat)
    if(yhat >= 0.5):
        y_predict = 1
    else:
        y_predict = 0

    return y_predict

np.random.seed(2)

# The 4 training examples by columns
X = np.array([[0, 0, 1, 1], [0, 1, 0, 1]])

# The outputs of the XOR for every example in X
Y = np.array([[0, 1, 1, 0]])

# No. of training examples
m = X.shape[1]
# Set the hyperparameters
n_x = 2     #No. of neurons in first layer
n_h = 2     #No. of neurons in hidden layer
n_y = 1     #No. of neurons in output layer
num_of_iters = 1000
learning_rate = .3

trained_parameters = model(X, Y, n_x, n_h, n_y, num_of_iters, learning_rate)
# Test 2X1 vector to calculate the XOR of its elements.
# You can try any of those: (0, 0), (0, 1), (1, 0), (1, 1)
# X_test = np.array([[1], [1]])
# y_predict = predict(X_test, trained_parameters)

def predict_and_print(Xin, trained_parameters):
    predict_y = predict(Xin, trained_parameters)
    # Print the result
    print('Neural Network prediction for example ({:d}, {:d}) is {:d}'.format(
    Xin[0][0], Xin[1][0], predict_y))
    return predict_y

X_test = np.array([[0],[0]])
y_predict = predict_and_print(X_test, trained_parameters)
X_test = np.array([[0],[1]])
y_predict = predict_and_print(X_test, trained_parameters)
X_test = np.array([[1],[0]])
y_predict = predict_and_print(X_test, trained_parameters)
X_test = np.array([[1], [1]])
y_predict = predict_and_print(X_test, trained_parameters)
# Print the result
# print('Neural Network prediction for example ({:d}, {:d}) is {:d}'.format(
#     X_test[0][0], X_test[1][0], y_predict))


#!/usr/bin/env python
# coding: utf-8

# # Principal Component Analysis
#
# ## 1. Libraries

# In[1]:

import matplotlib.pyplot as plt
import numpy as np

from IPython.display import display
from sklearn.decomposition import PCA

# ## 2. Data

# In[2]:

# Generate initial points (Gaussian, centered around 0,0)
np.random.seed(3347)
n = 100
x = 1*np.random.normal(size=n)
y = 2*np.random.normal(size=n)
X0 = np.vstack((x, y)).T

# Rotate points by 60 degrees via a rotation matrix
theta = np.radians(60.0)
rot = np.array([[ np.cos(theta), -np.sin(theta)],
                [ np.sin(theta),  np.cos(theta)]])
X1 = X0 @ rot.T

# ## 3. Perform PCA

# In[3]:

pca = PCA(n_components=2)
pca.fit(X1)

# ## 4. Plot the data

# In[4]:

fig, ax = plt.subplots(1, 1, figsize=(8,7))
ax.plot(X0[:,0], X0[:,1], color='gray', marker='.', linestyle='', markersize=10, alpha=0.4)
ax.plot(X1[:,0], X1[:,1], color='blue', marker='.', linestyle='', markersize=10, alpha=0.7)
ax.axis('equal')

txt = ''
i=0
for eigenvalue, eigenvector in zip(pca.explained_variance_, pca.components_):
    i += 1
    txt += f'  λ = {eigenvalue:.5f}, x = {eigenvector}\n'
    dx = eigenvalue * eigenvector[0]
    dy = eigenvalue * eigenvector[1]
    ax.arrow(0, 0, dx, dy, color='purple', width=0.2, head_width=1, alpha=0.5)
    ax.text(dx, dy, f'PCA{i}   ', color='purple', fontsize=14, ha='right', va='center')
ax.text(0, 0, txt, color='black', fontsize=12, transform=ax.transAxes)


### based on https://github.com/kylemcdonald/Parametric-t-SNE/blob/master/Parametric%20t-SNE%20(Keras).ipynb

import numpy as np
from tensorflow.keras import backend as K
from tensorflow.keras.losses import categorical_crossentropy
from tqdm.autonotebook import tqdm
import tensorflow as tf

def Hbeta(D, beta):
    """Computes the Gaussian kernel values given a vector of
    squared Euclidean distances, and the precision of the Gaussian kernel.
    The function also computes the perplexity (P) of the distribution."""
    P = np.exp(-D * beta)
    sumP = np.sum(P)
    H = np.log(sumP) + beta * np.sum(np.multiply(D, P)) / sumP
    P = P / sumP
    return H, P

def x2p(X, u=15, tol=1e-4, print_iter=500, max_tries=50, verbose=0):
    """
    % X2P Identifies appropriate sigma's to get kk NNs up to some tolerance
    %
    %   [P, beta] = x2p(xx, kk, tol)
    %
    % Identifies the required precision (= 1 / variance^2) to obtain a Gaussian
    % kernel with a certain uncertainty for every datapoint. The desired
    % uncertainty can be specified through the perplexity u (default = 15). The
    % desired perplexity is obtained up to some tolerance that can be specified
    % by tol (default = 1e-4).
    % The function returns the final Gaussian kernel in P, as well as the
    % employed precisions per instance in beta.
    %
    """

    # Initialize some variables
    n = X.shape[0]  # number of instances
    P = np.zeros((n, n))  # empty probability matrix
    beta = np.ones(n)  # empty precision vector
    logU = np.log(u)  # log of perplexity (= entropy)

    # Compute pairwise distances
    if verbose > 0:
        print("Computing pairwise distances...")
    sum_X = np.sum(np.square(X), axis=1)
    # note: translating sum_X' from matlab to numpy means using reshape to add a dimension
    D = sum_X + sum_X[:, None] + -2 * X.dot(X.T)

    # Run over all datapoints
    if verbose > 0:
        print("Computing P-values...")
    for i in range(n):
        if verbose > 1 and print_iter and i % print_iter == 0:
            print("Computed P-values {} of {} datapoints...".format(i, n))

        # Set minimum and maximum values for precision
        betamin = float("-inf")
        betamax = float("+inf")

        # Compute the Gaussian kernel and entropy for the current precision
        indices = np.concatenate((np.arange(0, i), np.arange(i + 1, n)))
        Di = D[i, indices]
        H, thisP = Hbeta(Di, beta[i])

        # Evaluate whether the perplexity is within tolerance
        Hdiff = H - logU
        tries = 0
        while abs(Hdiff) > tol and tries < max_tries:

            # If not, increase or decrease precision
            if Hdiff > 0:
                betamin = beta[i]
                if np.isinf(betamax):
                    beta[i] *= 2
                else:
                    beta[i] = (beta[i] + betamax) / 2
            else:
                betamax = beta[i]
                if np.isinf(betamin):
                    beta[i] /= 2
                else:
                    beta[i] = (beta[i] + betamin) / 2

            # Recompute the values
            H, thisP = Hbeta(Di, beta[i])
            Hdiff = H - logU
            tries += 1

        # Set the final row of P
        P[i, indices] = thisP

    if verbose > 0:
        print("Mean value of sigma: {}".format(np.mean(np.sqrt(1 / beta))))
        print("Minimum value of sigma: {}".format(np.min(np.sqrt(1 / beta))))
        print("Maximum value of sigma: {}".format(np.max(np.sqrt(1 / beta))))

    return P, beta

def compute_joint_probabilities(
    samples, batch_size=5000, d=2, perplexity=30, tol=1e-5, verbose=0
):
    """ This function computes the probababilities in X, split up into batches
    % Gaussians employed in the high-dimensional space have the specified
    % perplexity (default = 30). The number of degrees of freedom of the
    % Student-t distribution may be specified through v (default = d - 1).
    """
    v = d - 1

    # Initialize some variables
    n = samples.shape[0]
    batch_size = min(batch_size, n)

    # Precompute joint probabilities for all batches
    if verbose > 0:
        print("Precomputing P-values...")
    batch_count = int(n / batch_size)
    P = np.zeros((batch_count, batch_size, batch_size))
    # for each batch of data
    for i, start in enumerate(tqdm(range(0, n - batch_size + 1, batch_size))):
        # select batch
        curX = samples[start : start + batch_size]
        # compute affinities using fixed perplexity
        P[i], _ = x2p(curX, perplexity, tol, verbose=verbose)
        # make sure we don't have NaN's
        P[i][np.isnan(P[i])] = 0
        # make symmetric
        P[i] = P[i] + P[i].T  # / 2
        # obtain estimation of joint probabilities
        P[i] = P[i] / P[i].sum()
        P[i] = np.maximum(P[i], np.finfo(P[i].dtype).eps)

    return P

def z2p(z, d, n, eps=10e-15):
    """ Computes the low dimensional probability
    """
    v = d - 1
    sum_act = tf.math.reduce_sum(tf.math.square(z), axis=1)
    Q = K.reshape(sum_act, [-1, 1]) + -2 * tf.keras.backend.dot(z, tf.transpose(z))
    Q = (sum_act + Q) / v
    Q = tf.math.pow(1 + Q, -(v + 1) / 2)
    Q *= 1 - np.eye(n)
    Q /= tf.math.reduce_sum(Q)
    Q = tf.math.maximum(Q, eps)
    return Q

def tsne_loss(d, batch_size, eps=10e-15):
    # v = d - 1.0
    def loss(P, Z):
        """ KL divergence
        P is the joint probabilities for this batch (Keras loss functions call this y_true)
        Z is the low-dimensional output (Keras loss functions call this y_pred)
        """
        Q = z2p(Z, d, n=batch_size, eps=eps)
        return tf.math.reduce_sum(P * tf.math.log((P + eps) / (Q + eps)))

    return loss


import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import fsolve

Pc = 4
Pw = 8
c = 1/2
d = 1/2

def F(x,P=Pc,c=c):
    '''Cattle Profit Function'''
    return P*x - c*x**2

def AG(x, P=Pw):
    '''Wheat farm profit before crop damage'''
    return P*(x**0)   # to return an array of len(x)

def AGD(x,P=Pw,d=d):
    '''Wheat farm profit after damage'''
    return AG(x,P) - d*x**2

def copt(P=Pc,c=c):
    '''rancher private optimum'''
    return P/(2*c)

def topt(P=Pc,c=c, d=d):
    '''Social effient optimum'''
    return P/(2*(c+d))

CE, TE = copt(),topt()

def coaseplot1():
    xx = np.linspace(0,6,100)
    fig = plt.subplots(figsize=(12,8))
    plt.plot(xx, F(xx), label = 'Rancher Profit' )
    plt.plot(xx, AG(xx), '--', label = 'Farm w/ no cattle damage' )
    plt.plot(xx, AGD(xx), label = 'Farm w/ cattle damage')
    plt.plot(xx, F(xx) + AGD(xx),label='Sum of both activities')
    plt.scatter(copt(),F(copt()))
    plt.scatter(copt(), 0)
    plt.scatter(topt(),F(topt()) + AGD(topt()))
    plt.grid()
    plt.xlim(0,6)
    plt.ylim(0,14)
    plt.xlabel('x -- head of cattle', fontsize=18)
    plt.ylabel('Benefits/Profit', fontsize=18)
    plt.legend(fontsize=14);

def MC(x,c=1/2):
    '''Cattle MC'''
    return 2*c*x

def excost(x,d=1/2):
    return 2*d*x

def coaseplot2(Pw=Pw, Pc=Pc):
    xx = np.linspace(0,6,100)
    fig = plt.subplots(figsize=(12,9))
    plt.axhline(Pc);
    plt.plot(xx, MC(xx), label = 'Rancher PMC' )
    plt.plot(xx, MC(xx)+excost(xx), label = 'SMC')
    plt.fill_between(xx, MC(xx)+excost(xx),Pc*xx**0, where=((MC(xx)<=Pc*xx**0) & (xx>2)),
                     facecolor='green', alpha=0.2, label='DWL')

    plt.text(3,5,'DWL' , fontsize=15)
    plt.text(5,3.5,r'$SMB = P_C$', fontsize=15)
    plt.text(5,5.5, r'$PMC$', fontsize=15)
    plt.text(4.5,8.5, r'$SMC$', fontsize=15)
    #plt.scatter(topt(),G(topt()) + AGD(topt()))
    plt.grid()
    plt.xlim(0,6)
    plt.ylim(0,10)
    plt.yticks(np.arange(0, 10, 1))
    plt.xlabel('x -- head of cattle')
    plt.ylabel('Benefits/Profit')
    plt.legend();

# #### Code for land example

A=1

def FT(T, A=A):
    return A*np.sqrt(T)

def MVPT(P,T,A=A):
    return A*P/T**(1/2)

def LD(P,r,A=A):
    return (P*A/r)**2

A=1
Tbar  = 10  # Total land endowment
P = 5.5     # Price of output
cl = 3      # cost of clearing land

def req(P, cl, Tb=Tbar, N=2, A=A):
    '''equilibrium rental rate'''
    def landemand(r):
        return N*(A*P/r)**2 - Tb
    return fsolve(landemand, 1)[0]

def mopt(P,cl,A=A):
    '''Optimum land use for each i at the P*MPT = max(cl,r)'''
    r = req(P,cl)
    ru = max(cl, r)
    return (A*P/ru)**2

mopt(P,cl), MVPT(P, mopt(P,cl) )

def landmarket(P, cl, title, A=A):
    t = np.linspace(0.1,Tbar-0.1, 2*Tbar)
    fig = plt.subplots(figsize=(12,8))
    x0 = mopt(P,cl,A=A)
    plt.ylim(0,5)
    #plt.axhline(cl,linestyle=':')
    plt.axhline(max(cl,req(P,cl,A=A)),linestyle='--')
    plt.axhline(cl,linestyle=':')
    plt.plot(t,MVPT(P,t))
    plt.text(8, MVPT(P,8),r'$P \cdot F_T(T)$', fontsize=18)
    plt.text(1, MVPT(P,Tbar-1),r'$P \cdot F_T(\bar T - T)$', fontsize=18)
    plt.xlabel('T -- land use', fontsize=18)
    plt.ylabel('MVPT', fontsize=18)

    plt.scatter(x0, MVPT(P,x0))
    plt.scatter(Tbar-mopt(P,cl),MVPT(P,x0))
    plt.plot([x0,x0],[0,MVPT(P,x0)],':')
    plt.plot([Tbar-x0,Tbar-x0],[0,MVPT(P,x0)],':')
    plt.plot(t,MVPT(P,Tbar - t))
    plt.plot(t,MVPT(P,Tbar-t))
    plt.title(title, fontsize=18)
    plt.xlim(0,Tbar);


############################################

# This is the Python program to determine the
# Root of a user-defined function using the
# Newton-Raphson method/algorithm for numerical
# calculation of root of a given function.

############################################

#import the necessary libraries
import matplotlib.pyplot as plt
import numpy as np
from scipy.interpolate import spline

#Define the function prefixes using NumPy
sin = np.sin
cos = np.cos
tan = np.tan
pi  = np.pi
exp = np.exp
ln  = np.log
log = np.log10

#Define the derivative function
def df(x):
    h = 0.000000001
    top = f(x + h) - f(x)
    bottom = h
    slope = top / bottom
    return slope

fx = input("Enter the function but not a logarithmic function :")      #take function as input from the user
f = lambda x: eval(fx)                                                 # using lambda for defining function

#Code to determine the points between which the root lies

for x in range(0, 100):
    if f(x)*f(x+1) <= 0:
        print("The root lies between",int(x),"and",int(x+1))

# Newton Raphson Method

#Enter the initial approximation and Error as Input from User
p = float((input("Enter the initial approximation :")))
n = float((input("Input the number of iterations :")))
e = int((input("Input the decimal places up to which you want the answer :")))

#Define the Newton raphson function
def nr(x):
    return (x - (f(x) / df(x)))

# p - the initial point i.e. a value closer to the root
# n - number of iterations
# e - precision of the final answer

def iterate(p, n):
    x = 0
    for i in range(int(n)):
        if i == 0:                              #calculate first approximation
            x = nr(p)
        else:
            x = nr(iterate(x, n))               # Iterate the subsequent approximations
        n = n-1
    return x

a = iterate(p, n)
print("The root of the entered equation is :" , round(a, e))                              #Final Solution

#Code to plot the curve
x = np.linspace(-10,10,10)
xnew = np.linspace(x.min(), x.max(), 300)       #Interpolates extra values to make smooth curve
f_smooth = spline(x, f(x), xnew)
df_smooth = spline(x, df(x), xnew)

#Plot the curves
fig=plt.figure()
plt.plot(xnew, f_smooth, '-b', label = 'function')
plt.plot(xnew, df_smooth, '--g', label = 'derivative')
plt.plot(round(a,e), 0, 'ro', label = 'The root of the function')
plt.xlabel('x')
plt.ylabel('fx and dfx')
fig.savefig('Newton__Raphson_Exp.png')          #Export the image obtained
fig.suptitle('Function and Derivative')
plt.legend()

#Show the curve
plt.show()


# Import required stuff
from cvxpy import *
import numpy as np
import matplotlib.pyplot as plt

# In this problem, we need both y.txt and beta.txt, so read them from input files.
# Please note that the path has to be changed accordingly before running.
with open('/home/akilesh/Desktop/Akilesh_opt/y.txt') as f:
    y = []
    for line in f:
        line = line.split() # to deal with blank
        if line:            # lines (ie skip them)
            line = [float(i) for i in line]
            y.append(line)

with open('/home/akilesh/Desktop/Akilesh_opt/beta0.txt') as f:
    betaorg = []
    for line in f:
        line = line.split() # to deal with blank
        if line:            # lines (ie skip them)
            line = [float(i) for i in line]
            betaorg.append(line)

# Define 100 logarithmically spaced values from 10^1 to 10^-2.
lvals = np.logspace(-2, 1, 100)
mseList = []	# List for storing MSEs as we vary the lambda.
changepointsList = []	# List for storing changepoint as we vary the lambda.
lvalsList = []	# List for storing beta computed as we vary the lambda.

for val in lvals:
	# As in prev problem, beta is the variable.
	beta = Variable(1, 100)
	error = sum_squares(y - beta)
	error = error/2

	obj = Minimize(error + val*tv(beta))
	prob = Problem(obj)
	prob.solve()

# to compute mse for a particular lambda.
# Threshold is defined as 10^-8.
	mse = 0
	thresh = 0.00000001
	changepoints = 0

	# Iterate over points
	for i in range(0, 100):
		req = (beta[i].value - betaorg[i][0])
		a = abs(req).value
		# Compute MSE.
		mse = mse +  req ** 2
		# If abs. value is greater than thresh
		if(a > thresh):
			changepoints = changepoints + 1

	# Appending stuff to corresponding list
	mse = mse / 100
	mseList.append(mse)
	changepointsList.append(changepoints)
	lvalsList.append(val)

#print(mseList)
# For plotting - mse vs lambda.
plt.subplot(211)
plt.plot(lvalsList, mseList)
plt.xlabel('lambda ')
plt.ylabel('mse')
plt.title(' mse vs lambda ')

# For plotting - changepoints vs lambda.
plt.subplot(212)
plt.plot(lvalsList, changepointsList)
plt.xlabel('lambda ')
plt.ylabel(' changepoints')
plt.title(' changepoints vs lambda ')

plt.tight_layout()
plt.show()


import math
import numpy as np

# numerical error margin
EPS = 1e-12

def normal(points):
    '''Compute normal vector of two input vectors.

    Args:
        points: List of two lists of length 3
                representing two 3-dimensional points.

    Return:
        Normal vector of the two input points (vectors).
    '''

    p = points[0]
    q = points[1]

    n = np.array([p[1]*q[2] - p[2]*q[1],
                  p[2]*q[0] - p[0]*q[2],
                  p[0]*q[1] - p[1]*q[0]])
    return n

def circumsphere_3d_2(pointsf):
    '''Circumsphere of 2 points.

    Args:
        A list of the 2 input points.

    Returns:
        Pair of circumcenter and circumradius.
    '''

    points = np.array(pointsf)
    return list((points[0] + points[1]) / 2.0), \
           np.linalg.norm((points[0] - points[1]) / 2.0)

def circumsphere_3d_3(pointsf):
    '''Circumsphere of 3 points.

    Args:
        A list of the 3 input points.

    Returns:
        Pair of circumcenter and circumradius.
    '''

    points = np.array(pointsf)

    d1 = points[1] - points[0]
    d2 = points[2] - points[0]

    # compute normal vector
    n = normal([d1,d2])

    # Make system of 3 equations to solve for.
    # Third equation is: Circumcenter should be orthogonal to normal.
    genmat = np.array([d1, d2, n])
    normvec = np.array([np.linalg.norm(genmat[0])**2,
                        np.linalg.norm(genmat[1])**2,
                        0])
    # 0-based circumsphere is the solution to this equation system.
    cc = np.linalg.solve(2*genmat, normvec)

    return list(cc + points[0]), np.linalg.norm(cc)

def circumsphere_3d_4(pointsf):
    '''Circumsphere of 4 points.

    Args:
        A list of the 4 input points.

    Returns:
        Pair of circumcenter and circumradius.
    '''

    points = np.array(pointsf)

    # generator matrix D: Contains (as rows) the three direction vectors of
    # the tetrahedron spanned by the points with the first point as origin.
    genmat = np.array([points[i] - points[0] for i in range(1,4)])

    # vector n containing the square norms of the 3 vectors from genmat.
    normvec = np.array([np.linalg.norm(genmat[i])**2 for i in range(3)])

    # 0-based circumsphere is the solution x to 2Dx = n
    cc = np.linalg.solve(2*genmat, normvec)

    # Return translated circumcenter and
    return list(cc + points[0]), np.linalg.norm(cc)

def circumsphere_3d(points):
    npoints = len(points)
    if npoints == 0:
        return None, 0
    elif npoints == 1:
        return points[0], 0
    elif npoints == 2:
        return circumsphere_3d_2(points)
    elif npoints == 3:
        return circumsphere_3d_3(points)
    elif npoints == 4:
        return circumsphere_3d_4(points)
    else:
        raise ValueError("Cannot have {} points on a sphere.".format(npoints))

def miniexball_3d(pin, pon, pout):
    """Compute smallest enclosing ball of pin that has pon
    on its surface.

    Args:
        pin: List of points to be inside the ball
        pon: List of points to be on the ball
        pout: List of points to be outside the ball

    Returns:
        tuple: the center (x, y, z) of the enclosing ball
        int: the radius of the enclosing ball
    """

    if len(pon) == 4:
        cc, cr = circumsphere_3d(pon)
        valid = True
        for p in pin:
            if np.linalg.norm(np.array(p) - np.array(cc)) > cr + EPS:
                valid = False
        for p in pout:
            if np.linalg.norm(np.array(p) - np.array(cc)) < cr - EPS:
                valid = False
        if not valid:
            raise ValueError("No sphere including {} and excluding {} exists."
                    .format(pin, pout))
    elif pout != []:
        p = pout.pop()
        # compute smallest enclosing disk of pin-p, pon
        cc, cr = miniexball_3d(list(pin), list(pon), list(pout))
        # if p is inside the disk
        if cc is None or np.linalg.norm(np.array(p) - np.array(cc)) < cr:
            cc, cr = miniexball_3d(list(pin), list(pon + [p]), list(pout))
    elif pin != []:
        p = pin.pop()
        # compute smallest enclosing disk of pin-p, pon
        cc, cr = miniexball_3d(list(pin), list(pon), list(pout))
        # if p is outside the disk
        if cc is None or np.linalg.norm(np.array(p) - np.array(cc)) > cr:
            cc, cr = miniexball_3d(list(pin), list(pon + [p]), list(pout))
    else:
        cc, cr = circumsphere_3d(pon)
    return cc, cr


import numpy as np
import sys
from collections import deque

S_SIZE = 7

class AssociativityChecker:
    def __init__(self, A, n):
        # A is the Cayley Table of an operation on a n-element set.
        # A must be a n*n matrix, each entry a number between 0 and n-1.
        self.A = A
        self.n = n

    # Linear extension of the operation
    def op(s, u, v):
        w = np.zeros(s.n, dtype=np.int)
        for x in range(s.n):
            for y in range(s.n):
                exy = s.A[x,y]
                w[exy] += u[x]*v[y]
                w[exy] %= S_SIZE
        return w

    # Checks if the operation is associative using the algorithm of
    # Rajagopalan and Schulman.
    def is_associative_random(s,t):
        for _ in range(t):
            # random vectors with entries in the finite field Z/Z_{S_SIZE}*
            u = np.random.randint(1, S_SIZE+1, size=s.n)
            v = np.random.randint(1, S_SIZE+1, size=s.n)
            w = np.random.randint(1, S_SIZE+1, size=s.n)
            is_it = (s.op(s.op(u,v), w) == s.op(u, s.op(v,w))).all()
            if (not is_it): return False
        return True

    # Returns True if the operation is cancellative.
    # We just check the definition.
    def is_cancellative(s):
        for x in range(s.n):
            xs = np.zeros(s.n, dtype=bool)
            for y in range(s.n):
                xs[s.A[x,y]] = True
            if (not xs.all()): return False
        return True

    # Returns a set of generators of the operation. In particular, the set
    # has size at most lgn + 1 when the operation is cancellative.
    def generators(s):
        # set of generators
        gen = []
        # inlist: elements in the set generated by gen.
        inlist = []
        is_inlist = np.zeros(s.n, dtype=bool)
        # outlist: elements out of the set generated by gen.
        # here we use deque as a linked list.
        outlist = deque(range(0,s.n))

        # we just insert elements to gen until it generates the whole set
        while len(outlist) != 0:
            # we take an element x that is not in the set generated by
            # gen.
            x = outlist.popleft()
            while is_inlist[x] and len(outlist):
                x = outlist.popleft()
            if (is_inlist[x]): break
            gen.append(x)

            # we combine x with all the elements y already generated by
            # gen.
            new_elements = deque([x])
            while (len(new_elements) != 0):
                x = new_elements.popleft()
                if (is_inlist[x]): continue
                inlist.append(x)
                is_inlist[x] = True
                for y in inlist:
                    if (not is_inlist[s.A[x,y]]):
                        new_elements.append(s.A[x,y])
                    if (not is_inlist[s.A[y,x]]):
                        new_elements.append(s.A[y,x])
        return gen

    # True if the operation is associative.
    # O(n^2*lgn) if the operation is cancellative.
    def is_associative_deterministic(s):
        gs = s.generators()
        for x in range(s.n):
            for y in range(s.n):
                for g in gs:
                    if (s.A[s.A[x,g],y] != s.A[x,s.A[g,y]]):
                        return False
        return True


from numpy import exp, array, random, dot
#A single neuron, with 3 input connections and 1 output connection.

class NeuralNetwork():
    def __init__(self):
        random.seed(1)

        #A 3 x 1 matrix with random weights, with values in the range -1 to 1
        self.synaptic_weights = 2 * random.random((3, 1)) - 1

    # The Sigmoid function, which describes an S shaped curve.

    def __sigmoid(self, x):
        return 1 / (1 + exp(-x))

    # the gradient of the Sigmoid curve.
    def __sigmoid_derivative(self, x):
        return x * (1 - x)

    # Training the neural network through trial and error.
    def train(self, training_set_inputs, training_set_outputs, number_of_training_iterations):
        for iteration in xrange(number_of_training_iterations):
            # Pass the training set through our neural network (a single neuron).
            output = self.think(training_set_inputs)

            # Calculate the error
            error = training_set_outputs - output

            # Multiply the error by the input and again by the gradient of the Sigmoid curve.
            adjustment = dot(training_set_inputs.T, error * self.__sigmoid_derivative(output))

            # Adjust the weights.
            self.synaptic_weights += adjustment

    def think(self, inputs):
        return self.__sigmoid(dot(inputs, self.synaptic_weights))

if __name__ == "__main__":

    #Initialization
    neural_network = NeuralNetwork()

    print "Random starting synaptic weights: "
    print neural_network.synaptic_weights

    #Training sets.
    training_set_inputs = array([[0, 0, 1], [1, 1, 1], [1, 0, 1], [0, 1, 1]])
    training_set_outputs = array([[0, 1, 1, 0]]).T

    # Do it 10,000 times and makeadjustments each time.
    neural_network.train(training_set_inputs, training_set_outputs, 10000)

    print "New synaptic weights after training: "
    print neural_network.synaptic_weights

    # Test the neural network with a new situation.
    print "Considering new situation [1, 0, 0] -> ?: "
    print neural_network.think(array([1, 0, 0]))


import numpy as np
import scipy.sparse as sp
from sklearn import preprocessing
from utils import normalized_laplacian_matrix

class LaplacianEigenmaps:
    r"""An implementation of `"Laplacian Eigenmaps" <https://papers.nips.cc/paper/1961-laplacian-eigenmaps-and-spectral-techniques-for-embedding-and-clustering>`_
    from the NIPS '01 paper "Laplacian Eigenmaps and Spectral Techniques for Embedding and Clustering".
    The procedure extracts the eigenvectors corresponding to the largest eigenvalues
    of the graph Laplacian. These vectors are used as the node embedding.
    """

    def __init__(self, dimensions: int = 32, seed: int = None):

        self.dimensions = dimensions
        self.seed = seed

    def fit(self, graph: sp.csr_matrix):
        """
        Fitting a Laplacian EigenMaps model.
        """
        L_tilde = normalized_laplacian_matrix(graph)
        _, self._embedding = sp.linalg.eigsh(L_tilde, k=self.dimensions,
                                             which='SM', return_eigenvectors=True)

    def get_embedding(self, normalize=True) -> np.array:
        """Getting the node embedding."""
        embedding = self._embedding
        if normalize:
            embedding = preprocessing.normalize(embedding)
        return embedding


### module for basic operations with elltwo_normalized metric on Boolean feature vectors
from scipy.spatial.distance import euclidean as elltwo
from scipy.spatial.distance import pdist

### normalize a boolean feature vector to the Euclidean unit sphere
def normalize2(x):
    try:
        return (1./elltwo(x,0))*x
    except:
        x

### We treat the vectors as normalized to the unit sphere
def get_metric(params=None):
    met_func=lambda pt1,pt2: elltwo(normalize2(pt1),normalize2(pt2)) if params is None else lambda pt1,pt2: elltwo(normalize2(params*pt1),normalize2(params*pt2))
    matr_func=lambda points_list: pdist([normalize2(pt) for pt in points_list],metric='euclidean') if params is None else lambda points_list: pdist([normalize2(params*pt) for pt in points_list],metric='euclidean')
    return met_func,matr_func

### Construct a midpoint ON THE UNIT SPHERE
def midpoint(pt1,pt2):
    return normalize2(normalize2(pt1)+normalize2(pt2))

### MUST CONSIDER A MORE SYMMETRIC CONSTRUCTION, mapping x to normalize2(2*x-1),
### but then there are questions regarding the generation of midpoints.


import numpy as np
import matplotlib.pyplot as plt

class pca:
    def __init__(self,n_dim):
        self.n_dim = n_dim
    @staticmethod
    def variance(vector):
        """claculates the variance of a given vector"""
        return np.sum(np.power(vector - np.mean(vector) , 2))/len(vector)
    @staticmethod
    def covariance(vector1,vector2):
        """calculates the covariance between two vectors"""
        return np.sum((vector1 - np.mean(vector1)) * (vector2 - np.mean(vector2)) )/len(vector1)
    @staticmethod
    def substract_mean(data):
        """substacts the mean of data"""
        return data - np.mean(data,axis=0)
    @staticmethod
    def calcuate_cov_matrix(data):
        n_features = data.shape[1]
        cov_matrix = np.zeros(shape = (n_features,n_features))
        for i in range(n_features):
            for j in range(n_features):
                cov_matrix[i,j] = pca.covariance(data[:,i],data[:,j])
        return cov_matrix
    @staticmethod
    def eigenvalues_eigenvectors(cov):
        return np.linalg.eig(cov)
    @staticmethod
    def project_data_on_pc(data,pc):
        projected_data = []
        for c in pc:
            projected_data.append(np.dot(data,c))
        return np.array(projected_data).T

    def reduce_dim(self,data):
        data = pca.substract_mean(data)
        val,vect = pca.eigenvalues_eigenvectors(pca.calcuate_cov_matrix(data))
        ordered_vect = [x for x, _ in sorted(zip(vect.tolist(), val))]
        return pca.project_data_on_pc(data,ordered_vect[0:self.n_dim])

if __name__ == "__main__":
    # generate Data
    slope = 1
    intercept = 0
    interval = 5
    n_points = 100
    sigma = 0.5

    x = np.random.uniform(0,interval,n_points)
    y = slope * x + intercept + np.random.normal(2, sigma, n_points)
    data = np.column_stack((x,y))

    PCA = pca(n_dim=2)
    transformed_data = PCA.reduce_dim(data)

    fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(10, 10))
    fig.gca().set(xlim=(-5, 5), ylim=(-5, 5))

    axes[0].scatter(data[:,0],data[:,1])
    axes[1].scatter(PCA.reduce_dim(data)[:,0],PCA.reduce_dim(data)[:,1])
    plt.show()


import numpy as np
import matplotlib.pyplot as plt

v = np.array([2, 1]) # Cria o Vetor "v" com array NumPy.
s = np.array([-3, 2]) # Cria o Vetor "s" com array NumPy.
z = v + s # soma os vetores "v" e "s".
vectors = np.array([v, s, z]) # Adiciona os Vetores "v", "s" e "z" dentro de um terceiro Vetor.

print("v = {0}\n s = {1}\n v + s = {2}\n v + s + z = {3}".format(v, s, z, vectors))

# Cria um plot com quiver() para exibir os 3 vetores.
plt.quiver(0, 0, vectors[:,0], vectors[:,1], color=['r', 'b', 'g'], scale=10)
plt.axis('equal')
plt.xlabel('Coordenadas - X')
plt.ylabel('Coordenadas - Y')
plt.grid()
plt.savefig('../images/plot-03.png', format='png')
plt.show()


"""
Comparing models using Hierarchical modelling.
"""
from __future__ import division
import numpy as np
import pymc3 as pm
import matplotlib.pyplot as plt
plt.style.use('seaborn-darkgrid')

## specify the Data
y = np.repeat([0, 1], [3, 6])  # 3 tails 6 heads

with pm.Model() as model:
    # Hyperhyperprior:
    model_index = pm.DiscreteUniform('model_index', lower=0, upper=1)
    # Hyperprior:
    kappa_theta = 12
    mu_theta = pm.math.switch(pm.math.eq(model_index, 1), 0.25, 0.75)
    # Prior distribution:
    a_theta = mu_theta * kappa_theta
    b_theta = (1 - mu_theta) * kappa_theta
    theta = pm.Beta('theta', a_theta, b_theta) # theta distributed as beta density
    #likelihood
    y = pm.Bernoulli('y', theta, observed=y)
    trace = pm.sample(5000)

## Check the results.

## Print summary for each trace
#pm.summary(trace)

## Check for mixing and autocorrelation
#pm.autocorrplot(trace)

## Plot KDE and sampled values for each parameter.
pm.traceplot(trace)

## Get the posterior sample of model_index:
model_idx_sample = trace['model_index']
## Compute the proportion of model_index at each value:
p_M1 = sum(model_idx_sample == 1) / len(model_idx_sample)
p_M2 = 1 - p_M1

## Get the posterior sample of theta:
theta_sample = trace['theta']
## Extract theta values when model_index is 1:
theta_sample_M1 = theta_sample[model_idx_sample == 1]
## Extract theta values when model_index is 2:
theta_sample_M2 = theta_sample[model_idx_sample == 0]

## Plot histograms of sampled theta values for each model,
plt.figure()
plt.subplot(1, 2, 1)
plt.hist(theta_sample_M1, label='p(M1|D) = {:.3f}'.format(p_M1))
plt.xlabel(r'$\theta$')
plt.ylabel(r'$p(\theta|\mu=0.25,D)$')
plt.xlim(0, 1)
plt.legend(loc='upper right', framealpha=0.5)

plt.subplot(1, 2, 2)
plt.hist(theta_sample_M2, label='p(M2|D) = {:.3f}'.format(p_M2))
plt.xlabel(r'$\theta$')
plt.ylabel(r'$p(\theta|\mu=0.75,D)$')
plt.xlim(0, 1)
plt.legend(loc='upper right', framealpha=0.5)

plt.savefig('Figure_10.2.png')
plt.show()


import numpy as np

from optimizer import Optimizer

class Polyak(Optimizer):
    """
    Fatkhulin-Polyak adaptive gradient descent
    https://arxiv.org/pdf/2004.09875.pdf

    Arguments:
        lr (float, optional): an estimate of the inverse smoothness constant
    """
    def __init__(self, f_opt, lr_min=0, lr_max=np.inf, *args, **kwargs):
        super(Polyak, self).__init__(*args, **kwargs)
        self.f_opt = f_opt
        self.lr_min = lr_min
        self.lr_max = lr_max

    def step(self):
        self.grad = self.loss.gradient(self.x)
        self.estimate_new_stepsize()
        self.x -= self.lr * self.grad

    def estimate_new_stepsize(self):
        loss_gap = self.loss.value(self.x) - self.f_opt
        self.lr = loss_gap / self.loss.norm(self.grad)**2
        self.lr = min(self.lr, self.lr_max)
        self.lr = max(self.lr, self.lr_min)

    def init_run(self, *args, **kwargs):
        super(Polyak, self).init_run(*args, **kwargs)
        self.trace.lrs = []

    def update_trace(self):
        super(Polyak, self).update_trace()
        self.trace.lrs.append(self.lr)


import numpy as np
import util

from linear_model import LinearModel

def main(train_path, eval_path, pred_path):
    """Problem 1(e): Gaussian discriminant analysis (GDA)

    Args:
        train_path: Path to CSV file containing dataset for training.
        eval_path: Path to CSV file containing dataset for evaluation.
        pred_path: Path to save predictions.
    """
    # Load dataset
    x_train, y_train = util.load_dataset(train_path, add_intercept=False)
    x_eval, y_eval = util.load_dataset(eval_path, add_intercept=False)

    fn, ext = pred_path.split('.')
    plot_path = '.'.join([fn, 'png'])

    # Train a GDA classifier
    # Plot decision boundary on validation set
    # Use np.savetxt to save outputs from validation set to pred_path
    gda = GDA()
    gda.fit(x_train, y_train)
    y_pred = gda.predict(x_eval)

    util.plot(x_eval, y_pred, gda.theta, plot_path, correction=1.0)

class GDA(LinearModel):
    def fit(self, x, y):
        """Fit a GDA model to training set given by x and y.

        Args:
            x: Training example inputs. Shape (m, n).
            y: Training example labels. Shape (m,).

        Returns:
            theta: GDA model parameters.
        """
        # Find phi, mu_0, mu_1, and sigma
        # Write theta in terms of the parameters
        def sum_outer(x):
            m, n = x.shape
            return sum(np.outer(x[i, :], x[i, :]) for i in range(m))

        m, n = x.shape
        sum_y1 = np.sum(y)
        self.phi = sum_y1 / m
        self.mu_0 = np.sum(x[y==0], axis=0) / (m-sum_y1)
        self.mu_1 = np.sum(x[y==1], axis=0) / sum_y1

        mu_y = np.tile(self.mu_0, (m, 1))
        mu_y[y==1] = self.mu_1
        self.sigma = sum_outer(x - mu_y) / m
        sigma_i = np.linalg.inv(self.sigma)

        theta = np.dot(self.mu_0 - self.mu_1, sigma_i)
        theta_0 = 0.5*(-self.mu_0.T.dot(sigma_i).dot(self.mu_0) + self.mu_1.T.dot(sigma_i).dot(self.mu_1)) + np.log(1-self.phi) - np.log(self.phi)
        self.theta = np.insert(theta, 0, theta_0)

        if self.verbose:
            print('mu_0', self.mu_0)
            print('mu_1', self.mu_1)
            print('sigma\n', self.sigma)
            print('theta\n', self.theta)

    def predict(self, x):
        """Make a prediction given new inputs x.

        Args:
            x: Inputs of shape (m, n).

        Returns:
            Outputs of shape (m,).
        """
        x_mu0 = x - self.mu_0
        x_mu1 = x - self.mu_1

        sigma_i = np.linalg.inv(self.sigma)
        z0 = np.sum(np.dot(np.dot(x_mu0, sigma_i), x_mu0.T), axis=1)
        z1 = np.sum(np.dot(np.dot(x_mu1, sigma_i), x_mu1.T), axis=1)

        p_y0 = (1.0 - self.phi) * np.exp(-0.5 * z0)
        p_y1 = self.phi * np.exp(-0.5 * z1)

        return (p_y1 > p_y0).astype(int)

if __name__ == "__main__":
    main(train_path='../data/ds1_train.csv',
         eval_path='../data/ds1_valid.csv',
         pred_path='output/p01e_pred_1.txt')

    main(train_path='../data/ds2_train.csv',
         eval_path='../data/ds2_valid.csv',
         pred_path='output/p01e_pred_2.txt')


#%%
import numpy as np

#%%
def Custom_Convolve2D(image, kernel, padding=0, strides=1):
    # Cross Correlation
    # kernel = np.flipud(np.fliplr(kernel))
    ## Put your code here.
    # Gather Shapes of Kernel + Image + Padding
    xKernShape = kernel.shape[0]
    yKernShape = kernel.shape[1]
    xImgShape = image.shape[0]
    yImgShape = image.shape[1]

    # Shape of Output Convolution
    xOutput = int(((xImgShape - xKernShape + 2 * padding) / strides) + 1)
    yOutput = int(((yImgShape - yKernShape + 2 * padding) / strides) + 1)
    output = np.zeros((xOutput, yOutput))

    # Apply Equal Padding to All Sides
    if padding != 0:
        imagePadded = np.zeros((xImgShape + padding * 2, yImgShape + padding * 2))
        imagePadded[
            int(padding) : int(-1 * padding), int(padding) : int(-1 * padding)
        ] = image
    else:
        imagePadded = image

    # Iterate through image
    for y in range(imagePadded.shape[1]):
        # Exit Convolution
        if y > imagePadded.shape[1] - yKernShape:
            break
        # Only Convolve if y has gone down by the specified Strides
        if y % strides == 0:
            for x in range(imagePadded.shape[0]):
                # Go to next row once kernel is out of bounds
                if x > imagePadded.shape[0] - xKernShape:
                    break
                # Only Convolve if x has gone down by the specified Strides
                if x % strides == 0:
                    output[x // strides, y // strides] = (
                        kernel * imagePadded[x : x + xKernShape, y : y + yKernShape]
                    ).sum()

    return output

def Custom_MaxPlooling(image, pool_size=(2, 2), padding=0, strides=None):
    ## Put your code here.
    # Gather Shapes of pool + Image + Padding
    xPoolShape = pool_size[0]
    yPoolShape = pool_size[1]
    xImgShape = image.shape[0]
    yImgShape = image.shape[1]
    # Stride
    if strides is None:
        strides = max(yPoolShape, xPoolShape)

    # Shape of Output Convolution
    xOutput = int(((xImgShape - xPoolShape + 2 * padding) / strides) + 1)
    yOutput = int(((yImgShape - yPoolShape + 2 * padding) / strides) + 1)
    output = np.zeros((xOutput, yOutput))

    # Apply Equal Padding to All Sides
    if padding != 0:
        imagePadded = np.zeros((xImgShape + padding * 2, yImgShape + padding * 2))
        imagePadded[
            int(padding) : int(-1 * padding), int(padding) : int(-1 * padding)
        ] = image
    else:
        imagePadded = image

    # Iterate through image
    for y in range(imagePadded.shape[1]):
        # Exit Convolution
        if y > imagePadded.shape[1] - yPoolShape:
            break
        # Only Convolve if y has gone down by the specified Strides
        if y % strides == 0:
            for x in range(imagePadded.shape[0]):
                # Go to next row once kernel is out of bounds
                if x > imagePadded.shape[0] - xPoolShape:
                    break
                # Only Convolve if x has gone down by the specified Strides
                if x % strides == 0:
                    output[x // strides, y // strides] = (
                        imagePadded[x : x + xPoolShape, y : y + yPoolShape]
                    ).max()

    return output

def Custom_Flatten(img):
    img = (img - img.min()) / (img.max() - img.min())
    return img.flatten()

def Custom_Dense(data, units=10):
    n = data.shape[0]
    data = np.reshape(data, (n, 1))

    # Setting the range from 0 to 0.04 to stop exponent explotion
    w = np.random.uniform(0, 0.04, [units, n])
    b = np.random.uniform(0, 0.04, [units, 1])

    return 1 / (1 + np.exp(-1 * (w @ data + b)))

def Custom_softmax(data):
    data = np.exp(data)
    data = data / data.sum()

    return data

#%%
im = np.array(
    [
        [4, 9, 2, 5, 8, 3],
        [5, 6, 2, 4, 0, 3],
        [2, 4, 5, 4, 5, 2],
        [5, 6, 5, 4, 7, 8],
        [5, 7, 7, 9, 2, 1],
        [5, 8, 5, 3, 8, 4],
    ]
)

kernel = np.array([[1, 0, -1], [1, 0, -1], [1, 0, -1]])
ss = np.array([[3.1, 0.3, 1.2]])
# %%
print(Custom_Convolve2D(im, kernel, 1, 1))
print(Custom_softmax(ss))
# %%


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Fri Mar 29 18:30:25 2019

@author: francesco
"""

import math
import numpy as np

def expected_value(X_values, X_probabilities):
    """
    SUM(x*p)
    """
    if len(X_values) != len(X_probabilities):
        return -1
    else:
        mean = 0
        for i in range(len(X_values)):
            mean += X_values[i] * X_probabilities[i]
        return mean

def variance(X):
    """
    VAR(X) = SUM[(xi - E(X))^2 * pi]
    VAR(X) = SUM[xi^2 * pi] - E(X)^2
    """
    values = X[0]
    probabilities = X[1]
    variance = 0
    mean = expected_value(X[0], X[1])

    for i in range(len(values)):
        variance += math.pow((values[i] - mean), 2) * probabilities[i]

    # method 2
    variance2 = 0
    for i in range(len(values)):
        variance2 += math.pow(values[i], 2) * probabilities[i]
    variance2 -= mean*mean

    print(variance)
    print(variance2)

def covarianza(X_values, X_probabilities, Y_values, Y_probabilities):
    """
    cov(X,Y) = E[(X - E[X])(Y - E[Y])]
    """
    X_mean = expected_value(X_values, X_probabilities)
    Y_mean = expected_value(Y_values, Y_probabilities)

    part = 0
    for i in range(len(X_values)):
        part += (X_values[i] - X_mean) * (Y_values[i] - Y_mean)

    coVariance = part/(len(X_values)-1)
    print (coVariance)

#x[0] values x[1]probability
X = [2.1,2.5,3.6,4.0], [1/4, 1/4, 1/4, 1/4]
Y = [8,10,12,14], [1/4, 1/4, 1/4, 1/4]
covarianza(X[0],X[1],Y[0],Y[1])
variance(X)
mean = expected_value(X[0], X[1])
print(mean)


#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2018/8/16 15:23
# @Author  : zzy824
# @File    : word_2_vector_main.py

from __future__ import print_function
import numpy as np
from w2v_utils import *

words, word_to_vec_map = read_glove_vecs('data/glove.6B.50d.txt')

""" Cosine Similarity """
def cosine_similarity(u, v):
    """
    Cosine similarity reflects the degree of similariy between u and v

    Arguments:
        u -- a word vector of shape (n,)
        v -- a word vector of shape (n,)

    Returns:
        cosine_similarity -- the cosine similarity between u and v defined by the formula above.
    """

    distance = 0.0

    # Compute the dot product between u and v (≈1 line)
    dot = np.dot(u, v)
    # Compute the L2 norm of u (≈1 line)
    # todo: see how it work
    norm_u = np.linalg.norm(u)

    # Compute the L2 norm of v (≈1 line)
    norm_v = np.linalg.norm(v)
    # Compute the cosine similarity defined by formula (1) (≈1 line)
    cosine_similarity = dot / (norm_u * norm_v)

    return cosine_similarity

# # test case for cosine similarity
# father = word_to_vec_map["father"]
# mother = word_to_vec_map["mother"]
# ball = word_to_vec_map["ball"]
# crocodile = word_to_vec_map["crocodile"]
# france = word_to_vec_map["france"]
# italy = word_to_vec_map["italy"]
# paris = word_to_vec_map["paris"]
# rome = word_to_vec_map["rome"]
#
# print("cosine_similarity(father, mother) = ", cosine_similarity(father, mother))
# print("cosine_similarity(ball, crocodile) = ", cosine_similarity(ball, crocodile))
# print("cosine_similarity(france - paris, rome - italy) = ", cosine_similarity(france - paris, rome - italy))

""" Word analogy task """

def complete_analogy(word_a, word_b, word_c, word_to_vec_map):
    """
    Performs the word analogy task as explained above: a is to b as c is to ____.

    Arguments:
    word_a -- a word, string
    word_b -- a word, string
    word_c -- a word, string
    word_to_vec_map -- dictionary that maps words to their corresponding vectors.

    Returns:
    best_word --  the word such that v_b - v_a is close to v_best_word - v_c, as measured by cosine similarity
    """

    # convert words to lower case
    word_a, word_b, word_c = word_a.lower(), word_b.lower(), word_c.lower()

    # Get the word embeddings v_a, v_b and v_c (≈1-3 lines)
    e_a, e_b, e_c = word_to_vec_map[word_a], word_to_vec_map[word_b], word_to_vec_map[word_c]

    words = word_to_vec_map.keys()
    max_cosine_sim = -100  # Initialize max_cosine_sim to a large negative number
    best_word = None  # Initialize best_word with None, it will help keep track of the word to output

    # loop over the whole word vector set
    for w in words:
        # to avoid best_word being one of the input words, pass on them.
        if w in [word_a, word_b, word_c]:
            continue

        # Compute cosine similarity between the combined_vector and the current word (≈1 line)
        cosine_sim = cosine_similarity(e_b - e_a, word_to_vec_map[w] - e_c)

        # If the cosine_sim is more than the max_cosine_sim seen so far,
        # then: set the new max_cosine_sim to the current cosine_sim and the best_word to the current word (≈3 lines)
        if cosine_sim > max_cosine_sim:
            max_cosine_sim = cosine_sim
            best_word = w

    return best_word

# # test case for complete analogy
# triads_to_try = [('italy', 'italian', 'spain'), ('india', 'delhi', 'japan'), ('man', 'woman', 'boy'), ('small', 'smaller', 'large')]
# for triad in triads_to_try:
#     print('{} -> {} :: {} -> {}'.format( *triad, complete_analogy(*triad,word_to_vec_map)))

""" Debiasing word vectors """
g = word_to_vec_map['woman'] - word_to_vec_map['man']

# Neutralize bias for non-gender specific words
def neutralize(word, g, word_to_vec_map):
    """
    Removes the bias of "word" by projecting it on the space orthogonal to the bias axis.
    This function ensures that gender neutral words are zero in the gender subspace.

    Arguments:
        word -- string indicating the word to debias
        g -- numpy-array of shape (50,), corresponding to the bias axis (such as gender)
        word_to_vec_map -- dictionary mapping words to their corresponding vectors.

    Returns:
        e_debiased -- neutralized word vector representation of the input "word"
    """

    # Select word vector representation of "word". Use word_to_vec_map. (≈ 1 line)
    e = word_to_vec_map[word]

    # Compute e_biascomponent using the formula give above. (≈ 1 line)
    e_biascomponent = (np.dot(e,g) / np.square(np.linalg.norm(g))) * g

    # Neutralize e by substracting e_biascomponent from it
    # e_debiased should be equal to its orthogonal projection. (≈ 1 line)
    e_debiased = e - e_biascomponent

    return e_debiased

# # test case for neutralize
# e = "receptionist"
# print("cosine similarity between " + e + " and g, before neutralizing: ", cosine_similarity(word_to_vec_map["receptionist"], g))
#
# e_debiased = neutralize("receptionist", g, word_to_vec_map)
# print("cosine similarity between " + e + " and g, after neutralizing: ", cosine_similarity(e_debiased, g))

# Equalization algorithm for gender-specific words
def equalize(pair, bias_axis, word_to_vec_map):
    """
    Debias gender specific words by following the equalize method described in the figure above.

    Arguments:
    pair -- pair of strings of gender specific words to debias, e.g. ("actress", "actor")
    bias_axis -- numpy-array of shape (50,), vector corresponding to the bias axis, e.g. gender
    word_to_vec_map -- dictionary mapping words to their corresponding vectors

    Returns
    e_1 -- word vector corresponding to the first word
    e_2 -- word vector corresponding to the second word
    """

    # Step 1: Select word vector representation of "word". Use word_to_vec_map. (≈ 2 lines)
    w1, w2 = pair
    e_w1, e_w2 = word_to_vec_map[w1], word_to_vec_map[w2]

    # Step 2: Compute the mean of e_w1 and e_w2 (≈ 1 line)
    mu = (e_w1 + e_w2) / 2

    # Step 3: Compute the projections of mu over the bias axis and the orthogonal axis (≈ 2 lines)
    mu_B = np.dot(mu, bias_axis) / np.sum(bias_axis * bias_axis) * bias_axis
    mu_orth = mu - mu_B

    # Step 4: Use equations (7) and (8) to compute e_w1B and e_w2B (≈2 lines)
    e_w1B = np.dot(e_w1, bias_axis) / np.sum(bias_axis * bias_axis) * bias_axis
    e_w2B = np.dot(e_w2, bias_axis) / np.sum(bias_axis * bias_axis) * bias_axis

    # Step 5: Adjust the Bias part of e_w1B and e_w2B using the formulas (9) and (10) given above (≈2 lines)
    corrected_e_w1B = np.sqrt(np.abs(1 - np.sum(mu_orth * mu_orth))) * (e_w1B - mu_B) / np.linalg.norm(
        e_w1 - mu_orth - mu_B)
    corrected_e_w2B = np.sqrt(np.abs(1 - np.sum(mu_orth * mu_orth))) * (e_w2B - mu_B) / np.linalg.norm(
        e_w2 - mu_orth - mu_B)

    # Step 6: Debias by equalizing e1 and e2 to the sum of their corrected projections (≈2 lines)
    e1 = corrected_e_w1B + mu_orth
    e2 = corrected_e_w2B + mu_orth

    return e1, e2

# # test case for equalize
# print("cosine similarities before equalizing:")
# print("cosine_similarity(word_to_vec_map[\"man\"], gender) = ", cosine_similarity(word_to_vec_map["man"], g))
# print("cosine_similarity(word_to_vec_map[\"woman\"], gender) = ", cosine_similarity(word_to_vec_map["woman"], g))
# print()
# e1, e2 = equalize(("man", "woman"), g, word_to_vec_map)
# print("cosine similarities after equalizing:")
# print("cosine_similarity(e1, gender) = ", cosine_similarity(e1, g))
# print("cosine_similarity(e2, gender) = ", cosine_similarity(e2, g))

Lec5:Sequence_models/week2:NLP_and_Word Embedding/Word Vector Representation/word_2_vector_main.py