from sklearn.decomposition import PCA
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
import numpy as np
import random
import seaborn as sns
from six.moves import input

def euclidian(x1,x2):
	return (np.sum((x1-x2)**2))

def getclass(datapoint, centroids):
	distances = np.array([euclidian(datapoint, centroid) for centroid in centroids])
	return np.argmin(distances)

def compute_centroids(data, centroids):
	classes = np.zeros(data.shape[0])
	# assign each data point to closest centroid
	for i in range(len(data)):
		classes[i] = getclass(data[i], centroids)

	new_centroids = np.ndarray(centroids.shape)

	for i in range(len(centroids)):
		new_centroids[i] = np.mean(data[classes==i],axis=0)

	return new_centroids, classes

def plotme(data, target, centroids=[], legends=False, iter=None,clear=False):
	if clear:
		plt.clf() # Draw new figure each time

	# Ref: http://www.dummies.com/programming/big-data/data-science/how-to-visualize-the-clusters-in-a-k-means-unsupervised-learning-model/
	class_1 = data[target==0]
	class_2 = data[target==1]
	class_3 = data[target==2]

	if legends:
		c1 = plt.scatter(class_1[:,0], class_1[:,1],c='m',
			    marker='+')
		c2 = plt.scatter(class_2[:,0], class_2[:,1],c='m',
			    marker='o')
		c3 = plt.scatter(class_3[:,0], class_3[:,1],c='m',
			    marker='*')
		plt.legend([c1, c2, c3], ['Setosa', 'Versicolor',
	    			'Virginica'])
		plt.title('Iris dataset with 3 clusters and known outcomes')

	else:
		c1 = plt.scatter(class_1[:,0], class_1[:,1],c='r',
			    marker='o')
		c2 = plt.scatter(class_2[:,0], class_2[:,1],c='g',
			    marker='o')
		c3 = plt.scatter(class_3[:,0], class_3[:,1],c='b',
			    marker='o')

		if centroids!=[]:
			plt.scatter(centroids[:,0],centroids[:,1],c='k',marker='x')

		plt.title('Iris dataset with 3 clusters and unknown outcomes : Iter '+str(iter))

	plt.pause(1)

def kmeans(data, targets, k=2, iters=10,plotdata=None):

	# good initial centroids found by inspection
	centroid_indices = np.array([136,130,0])
	#centroid_indices = np.array([63, 53, 48])

	# bad centroids
	# centroid_indices = np.array([19,45,63])

	# Comment the above line and uncoment the line below to
	# chooses k random different points from the dataset as intitial centroids

	# centroid_indices = random.sample(range(data.shape[0]),k)
	print(centroid_indices)
	centroids =  np.array([data[i] for i in centroid_indices])

	for it in range(iters):
		centroids, classes = compute_centroids(data, centroids)
		plotme(plotdata,classes, centroids, iter=it+1)

	plotme(plotdata, targets, centroids, legends=True, clear = True)
	plt.savefig("classes-known.jpg")
	plt.pause(4)
	plotme(plotdata, classes, iter="Final", clear = True)
	plt.savefig("classes-unknown.jpg")
	input("Press any key to exit:")

if __name__ == '__main__':

	iris = load_iris()

	# Reduce iris data from 4 dimensions to 2 for ease of plotting

	pca = PCA(n_components=2).fit(iris.data)
	pca_2d = pca.transform(iris.data)

	plt.ion() # Interactive on

	#You can change the first argument to iris.data and remove the centroid argument in every plotme call
	# to compute centroid on the original data, and plot on the pca reduced data
	kmeans(pca_2d, iris.target, k=len(np.unique(iris.target)), plotdata=pca_2d)


import numpy as np
from sklearn.preprocessing import OneHotEncoder

def encode_labels(y):
    encoder = OneHotEncoder(sparse=False)
    return encoder.fit_transform(y)

def sigmoid(z):
    return 1.0 / (1.0 + np.exp(-z))

def sigmoid_gradient(z):
    return np.multiply(sigmoid(z), (1.0 - sigmoid(z)))


"""Numeric routines

Author: Dr.-Ing. Mauricio Fernández

This module offers a collection of tensor routines using numpy.
"""

# %% Import

import numpy as np

import tensor_routines as tr

# %% Products

def td(a: np.ndarray, b: np.ndarray, n: int) -> np.ndarray:
    """Tensor dot.

    Shortcut for `np.tensordot(a, b, n)`.

    Parameters
    ----------
    a : np.ndarray
        Tensor
    b : np.ndarray
        Tensor
    n : int
        Number of neighbour axes to be contracted

    Returns
    -------
    np.ndarray
        Result

    Examples
    --------
    Contract 2 neighbour axes

    >>> a = np.random.rand(2, 3, 4, 5)
    ... b = np.random.rand(4, 5, 6, 7)
    ... c = td(a, b, 2) # c_ijkl = a_ijmn b_mnkl
    ... print(c.shape)
    (2, 3, 6, 7)
    """
    return np.tensordot(a, b, n)

def sp(a: np.ndarray, b: np.ndarray) -> float:
    """Scalar product.

    Compute the scalar product (full contraction) of
    tensors with equal shape.

    Parameters
    ----------
    a : np.ndarray
        Tensor
    b : np.ndarray
        Tensor with b.shape == a.shape

    Returns
    -------
    float
        a_ijkl... b_ijkl...

    Examples
    --------
    >>> a = np.random.rand(2, 3, 4)
    ... b = np.random.rand(2, 3, 4)
    ... c = sp(a, b) # c = a_ijk b_ijk
    """
    return td(a, b, a.ndim)

def nf(a: np.ndarray) -> float:
    """Frobenius norm.

    Frobenius norm of a real-valued tensor (square
    root of the sum of all squared tensor components)
    by calling `np.linalg.norm(a)`.

    Parameters
    ----------
    a : np.ndarray
        Tensor

    Returns
    -------
    float
        Frobenius norm

    Examples
    --------
    >>> a = np.random.rand(2, 3, 4)
    ... nf(a) - np.sqrt(np.sum(a**2))
    """
    return np.linalg.norm(a)

def tp(*args) -> np.ndarray:
    """Tensor product.

    Recursively calls `np.tensordot(a, b, 0)` for argument list
    `args = [a0, a1, a2, ...]`, yielding, e.g.,
        tp(a0, a1, a2) = tp(tp(a0, a1), a2)

    Parameters
    ----------
    args : sequence
        Sequence of tensors

    Returns
    -------
    np.ndarray
        Tensor product

    Examples
    --------

    >>> a = np.random.rand(2, 3, 4)
    ... b = np.random.rand(7, 8, 9)
    ... c = tp(a, b) # c_ijkmno = a_ijk b_mno
    ... c.shape == (2, 3, 4, 7, 8, 9)
    """
    temp = args[0]
    for i in range(1, len(args)):
        temp = np.tensordot(temp, args[i], 0)
    return temp

def lm(a: np.ndarray, b: np.ndarray) -> np.ndarray:
    """Linear map.

    Compute linear map by contracting in order
    all axes of b with the last axes of a, e.g.,
    a_ijklm and b_nop, c = lm(a, b) with
    c_ij = a_ijmno b_mno.

    Parameters
    ----------
    a : np.ndarray
        Tensor
    b : np.ndarray
        Tensor with a.ndim >= b.ndim and matching commong axes

    Returns
    -------
    np.ndarray
        Linear map

    Examples
    --------
    >>> a = np.random.rand(2, 3, 4, 5, 6)
    ... b = np.random.rand(4, 5, 6)
    ... c = lm(a, b)
    ... c.shape == (2, 3)
    True
    """
    return td(a, b, b.ndim)

def rp(q: np.ndarray, a: np.ndarray) -> np.ndarray:
    """Rayleight product.

    Compute Rayleight product `b = rp(q, a)` defined as
        b_{i_1 i_2 ...} = q_{i_1 j_1} q_{i_2 j_2} ... a_{j_1 j_2 ...}
    where the second order tensor q in R^{n_{out} x n_{in}}
    is used to map
        a in R^{n_{in} x n_{in} x ...}
    to
        b in R^{n_{out} x n_{out} x ...}

    Parameters
    ----------
    q : np.ndarray
        Second-order tensor
    a : np.ndarray
        Tensor with constant axis dimension, e.g., a.shape = (4, 4, 4, 4)

    Returns
    -------
    np.ndarray
        Rayleigh product
    """
    n = a.ndim
    p = [n-1] + list(range(n-1))  # index permutation per Q
    for _ in range(n):
        a = np.transpose(td(a, np.transpose(q), 1), p)
    return a

# %% Transpositions

def tt(a: np.ndarray, t: list) -> np.ndarray:
    """Tensor dimension transposition.

    Shortcut for np.transpose.

    Parameters
    ----------
    a : np.ndarray
        Any numpy array
    t : list
        List of dimension transpositions with a.ndim == len(t)

    Returns
    -------
    np.ndarray
        Transposed array

    Examples
    --------
    >>> a = np.random.rand(2, 3, 4, 5)
    ... tt(a, (2, 0, 3, 1)).shape == (4, 2, 5, 3)
    True

    Personal note
    -------------
    np transposes dimensions, NOT indices. It can be
    considered as the index transpose of the result `at`
    (see examples below).

    Pattern:
        a in R(d_0, d_1, ...)
        t = (t_0, t_1, ...)
        d_t = (d_{t_0}, d_{t_1}, ...)
        i_t = (i_{t_0}, i_{t_1}, ...)
        at = tr(a, t) in R(*d_t)
        at_{*i_t} = a_{i_0 i_1 ...}

    Example 1:
        a in R(d_0, d_1, d_2, d_3)
        at = tr(a, (3, 1, 0, 2)) in R(d_3, d_1, d_0, d_2)
        at_{i_0 i_1 i_2 i_3} = a_{i_2 i_1 i_3 i_0}
        at_{i_3 i_1 i_0 i_2} = a_{i_0 i_1 i_2 i_3} <- rename to
            get dimension transposition

    Example 2:
        a in R(d_0, d_1, d_2, d_3)
        at = tr(a, (1, 3, 2, 0)) in R(d_1, d_3, d_2, d_0)
        at_{i_0 i_1 i_2 i_3} = a_{i_3 i_0 i_2 i_1}
        at_{i_1 i_3 i_2 i_0} = a_{i_0 i_1 i_2 i_3} <-

    With respect to Mathematica (index transposition):
    a in R(d_0, d_1, d_2, d_3)
    tr(a, (3, 1, 0, 2)) == TensorTranspose(a, {2, 1, 3, 0} + 1)
    tr(a, (1, 3, 2, 0)) == TensorTranspose(a, {3, 0, 2, 1} + 1)
    TensorTranspose(tr(a, t), t+1) == a
    """
    return np.transpose(a, t)

def tt_r(a: np.ndarray) -> np.ndarray:
    """Right index pair transposition

    Transpose right index pair of fourth-order tensor a.

    Parameters
    ----------
    a : np.ndarray
        Fourth-order tensor, a_ijkl

    Returns
    -------
    np.ndarray
        Fourth-order tensor, a_jikl
    """
    return tt(a, (0, 1, 3, 2))

def tt_l(a: np.ndarray) -> np.ndarray:
    """Left index pair transposition.

    Transpose left index pair of fourth-order tensor a.

    Parameters
    ----------
    a : np.ndarray
        Fourth-order tensor, a_ijkl

    Returns
    -------
    np.ndarray
        Fourth-order tensor, a_ijlk
    """
    return tt(a, (1, 0, 2, 3))

def tt_m(a: np.ndarray) -> np.ndarray:
    """Major transposition

    Parameters
    ----------
    a : np.ndarray
        Fourth-order tensor, a_ijkl

    Returns
    -------
    np.ndarray
        Fourth-order tensor, a_klij
    """
    return tt(a, (2, 3, 0, 1))

# %% Symmetrizations

def sym_2(a: np.ndarray) -> np.ndarray:
    """Symmetric part of second-order tensor.

    Parameters
    ----------
    a : np.ndarray
        Second-order tensor

    Returns
    -------
    np.ndarray
        Symmetric part
    """
    return (a + a.T)/2

def sym_r(a: np.ndarray) -> np.ndarray:
    """Symmetrize right index pair.

    Symmetrize a fourth-order tensor a_ijkl with respect to
    its right index pair.

    Parameters
    ----------
    a : np.ndarray
        Fourth-order tensor, a_ijkl

    Returns
    -------
    np.ndarray
        Fourth-order tensor, symmetric with respect to kl
    """
    return (a + tt_r(a))/2

def sym_l(a: np.ndarray) -> np.ndarray:
    """Symmetrize left index pair.

    Symmetrize a fourth-order tensor a_ijkl with respect to
    its left index pair.

    Parameters
    ----------
    a : np.ndarray
        Fourth-order tensor, a_ijkl

    Returns
    -------
    np.ndarray
        Fourth-order tensor, symmetric with respect to ij
    """
    return (a + tt_l(a))/2

def sym_lr(a: np.ndarray) -> np.ndarray:
    """Symmetrize left and right index pair.

    Symmetrize a fourth-order tensor a_ijkl with respect to
    its left and right index pair.

    Parameters
    ----------
    a : np.ndarray
        Fourth-order tensor, a_ijkl

    Returns
    -------
    np.ndarray
        Fourth-order tensor, symmetric with respect to ij and kl
    """
    return sym_r(sym_l(a))

def sym_m(a: np.ndarray) -> np.ndarray:
    """Symmetrize major index pair.

    Parameters
    ----------
    a : np.ndarray
        Fourth-order tensor, a_ijkl

    Returns
    -------
    np.ndarray
        Fourth-order tensor fulfilling a_ijkl = a_klij.
    """
    return (a + tt_m(a))/2

def sym_mm(a: np.ndarray) -> np.ndarray:
    """Symmetrize minor and major index pairs of given fourth-order tensor.

    Parameters
    ----------
    a : np.ndarray
        Fourth-order tensor, a_ijkl.

    Returns
    -------
    np.ndarray
        Fourth-order tensor fulfilling a_ijkl = a_jikl = a_klij.
    """
    return sym_m(sym_lr(a))

# %% Isotropic

def iso_ev(a: np.ndarray) -> np.ndarray:
    """Project eigenvalues of isotropic tensor.

    Compute for a given second- or fourth-order tensor the
    eigenvalues of the isotropic part of the given tensor `a`
        * For second-order tensors for `a = lambda ID_2 + aniso_part`,
            `lambda` is computed.
        * For fourth-order tensors
            `a = sum_{i=1}^3 lambda_i P_ISOi + aniso_part`,
            the lambda_i are computed.

    Parameters
    ----------
    a : np.ndarray
        Isotropic tensor.
        * Second order: a.shape == (3, 3)
        * Fourth order: a.shape == (3, 3, 3, 3)

    Returns
    -------
    np.ndarray
        * Second order: lambda
        * Fourth order: 1D-array with [lambda_1,2,3]

    Examples
    --------
    >>> iso_ev(7*np.eye(3)) == 7
    True
    >>> (iso_ev(13*P_ISO_1 + 20*P_ISO_2) == np.array([13, 20])).all()
    True
    """
    if a.shape == (3, 3, 3, 3):
        # Fourth-order
        return np.array([sp(a, P)/sp(P, P) for P in P_ISO])
    else:
        # Second-order
        return sp(a, ID_2)/3

def iso_t(lam: np.ndarray) -> np.ndarray:
    """Isotropic tensor.

    Create an isotropic second- or fourth-order tensor from the
    given eigenvalue(s).

    Parameters
    ----------
    lam : float or np.ndarray
        * Second order: float corresponding to lambda for lambda ID_2
        * Fourth order: 1D-array = [lambda_1,2|3] for
            sum_{i=1}^3 lambda_i P_ISO_i
            * [lambda_1, lambda_2] for only two first isotropic projects
            * [lambda_1,2,3] for all isotropic projectors

    Returns
    -------
    np.ndarray
        Isotropic tensor of second or fourth order.

    Examples
    --------
    >>> (iso_t(10) == 10*np.eye(3)).all()
    True
    >>> (iso_t([3, 7]) == 3*P_ISO_1 + 7*P_ISO_2).all()
    True
    """
    if isinstance(lam, (list, np.ndarray)):
        # Fourth-order
        if len(lam) == 2:
            return lam[0]*P_ISO_1 + lam[1]*P_ISO_2
        else:
            return lam[0]*P_ISO_1 + lam[1]*P_ISO_2 + lam[2]*P_ISO_3
    else:
        # Second-order
        return lam*ID_2

def iso_proj(a: np.ndarray) -> np.ndarray:
    """Isotropic projection.

    Compute the isotropic projection/part of the second-
    or fourth-order tensor `a`.

    Parameters
    ----------
    a : np.ndarray
        Second- or fourth-order tensor

    Returns
    -------
    np.ndarray
        Isotropic projection/part of `a`
    """
    return iso_t(iso_ev(a))

def iso_inv(a: np.ndarray) -> np.ndarray:
    """Inverse of isotropic tensor.

    Compute the inverse or pseudo-inverse of the isotropic
    second- or fourth-order tensor `a`.

    Parameters
    ----------
    a : np.ndarray
        Isotropic second- or fourth-order tensor

    Returns
    -------
    np.ndarray
        Inverse or pseudo-inverse

    Examples
    --------
    >>> ls = np.array([11, 7, 0])
    ... a = tn.iso_t(ls)
    ... ai = tn.iso_inv(a)
    ... tol = 1e-13
    ... assert nf(td(a, ai, 2) - tn.ID_S)/nf(tn.ID_S) < tol
    True
    """
    if a.shape == (3, 3, 3, 3):
        # Fourth order
        return iso_t([1/la for la in iso_ev(a) if la != 0])
    else:
        # Second order
        return iso_t(1/iso_ev(a))

# %% Voigt notation (not normalized)

def vn(a: np.ndarray) -> np.ndarray:
    """Voigt notation (based on convention of tr.VN_CONVENTION)

    Parameters
    ----------
    a : np.ndarray
        Second- or fourth-order tensor

    Returns
    -------
    np.ndarray
        Corresponding first- or second-order output in Voigt notation
    """
    if a.shape == (3, 3):
        out = np.array([
            a[tr.VN_CONVENTION[0][0], tr.VN_CONVENTION[0][1]],
            a[tr.VN_CONVENTION[1][0], tr.VN_CONVENTION[1][1]],
            a[tr.VN_CONVENTION[2][0], tr.VN_CONVENTION[2][1]],
            a[tr.VN_CONVENTION[3][0], tr.VN_CONVENTION[3][1]],
            a[tr.VN_CONVENTION[4][0], tr.VN_CONVENTION[4][1]],
            a[tr.VN_CONVENTION[5][0], tr.VN_CONVENTION[5][1]]
        ])
    else:
        out = np.eye(6)
        for i_1 in range(6):
            for i_2 in range(6):
                out[i_1, i_2] = a[
                    tr.VN_CONVENTION[i_1][0],
                    tr.VN_CONVENTION[i_1][1],
                    tr.VN_CONVENTION[i_2][0],
                    tr.VN_CONVENTION[i_2][1]
                ]
    return out

def vn_inv(a: np.ndarray) -> np.ndarray:
    """Inverse Voigt notation.

    Reconstruct symmetric second-order or minor symmetric fourth-order
    tensor.

    Parameters
    ----------
    a : np.ndarray
        6D vector or 6x6 matrix in Voigt notation

    Returns
    -------
    np.ndarray
        Symmetric second-order tensor or minor symmetric fourth-order
        tensor.
    """
    if a.shape == (6,):
        out = np.zeros(shape=[3, 3])
        for i in range(3):
            out[
                tr.VN_CONVENTION[i][0],
                tr.VN_CONVENTION[i][1]
            ] = a[i]
        for i in range(3, 6):
            out[
                tr.VN_CONVENTION[i][0],
                tr.VN_CONVENTION[i][1]
            ] = a[i]
            out[
                tr.VN_CONVENTION[i][1],
                tr.VN_CONVENTION[i][0]
            ] = a[i]
    else:
        out = np.zeros((3, 3, 3, 3))
        for i_1 in range(6):
            for i_2 in range(6):
                temp = a[i_1, i_2]
                out[
                    tr.VN_CONVENTION[i_1][0],
                    tr.VN_CONVENTION[i_1][1],
                    tr.VN_CONVENTION[i_2][0],
                    tr.VN_CONVENTION[i_2][1],
                ] = temp
                out[
                    tr.VN_CONVENTION[i_1][1],
                    tr.VN_CONVENTION[i_1][0],
                    tr.VN_CONVENTION[i_2][0],
                    tr.VN_CONVENTION[i_2][1],
                ] = temp
                out[
                    tr.VN_CONVENTION[i_1][0],
                    tr.VN_CONVENTION[i_1][1],
                    tr.VN_CONVENTION[i_2][1],
                    tr.VN_CONVENTION[i_2][0],
                ] = temp
                out[
                    tr.VN_CONVENTION[i_1][1],
                    tr.VN_CONVENTION[i_1][0],
                    tr.VN_CONVENTION[i_2][1],
                    tr.VN_CONVENTION[i_2][0],
                ] = temp
    return out

# %% Normalized Voigt notation (NOT Voigt notation)

def nvn(a: np.ndarray) -> np.ndarray:
    """Normalized Voigt notation.

    Return the symmetric second- or minor symmetric fourth-order tensor
    `a` in normalized Voigt notation according to the convention defined
    in the module by the constant NVN_CONVENTION.

    The norm of the returned objects corresponds to the actual Frobenius
    norm of the original tensors. For fourth-order tensors, the inverse
    of the returned matrix corresponds to the `nvn` of the inverse of `a`
    (with respect to the linear map on symmetric second-order tensors).

    Parameters
    ----------
    a : np.ndarray
        * Second order: a.shape == (3, 3) and symmetric
        * Fourth order: a.shape == (3, 3, 3, 3) and minor symmetric,
            i.e., a == sym_lr(a)

    Returns
    -------
    np.ndarray
        * Second order: 6D vector representation
        * Fourth order: 6x6 matrix representation
    """
    if a.shape == (3, 3):
        out = np.array([
            a[tr.VN_CONVENTION[0][0], tr.VN_CONVENTION[0][1]],
            a[tr.VN_CONVENTION[1][0], tr.VN_CONVENTION[1][1]],
            a[tr.VN_CONVENTION[2][0], tr.VN_CONVENTION[2][1]],
            a[tr.VN_CONVENTION[3][0], tr.VN_CONVENTION[3][1]]*SR2,
            a[tr.VN_CONVENTION[4][0], tr.VN_CONVENTION[4][1]]*SR2,
            a[tr.VN_CONVENTION[5][0], tr.VN_CONVENTION[5][1]]*SR2
        ])
    else:
        out = np.eye(6)
        for i_1 in range(6):
            for i_2 in range(6):
                out[i_1, i_2] = a[
                    tr.VN_CONVENTION[i_1][0],
                    tr.VN_CONVENTION[i_1][1],
                    tr.VN_CONVENTION[i_2][0],
                    tr.VN_CONVENTION[i_2][1]
                ]
                if i_1 > 2:
                    out[i_1, i_2] *= SR2
                if i_2 > 2:
                    out[i_1, i_2] *= SR2
    return out

def nvn_inv(a: np.ndarray) -> np.ndarray:
    """Inverse of normalized Voigt notation.

    Reconstruct symmetric second- or minor symmetric fourth-order
    tensor from `nvn` representation.

    Parameters
    ----------
    a : np.ndarray
        * 6D vector
        * 6x6 second-order tensor

    Returns
    -------
    np.ndarray
        * symmetric 3x3 second-order tensor
        * minor symmetric 3x3x3x3 fourth-order tensor
    """
    if a.shape == (6,):
        out = np.zeros(shape=[3, 3])
        for i in range(3):
            out[
                tr.VN_CONVENTION[i][0],
                tr.VN_CONVENTION[i][1]
            ] = a[i]
        for i in range(3, 6):
            out[
                tr.VN_CONVENTION[i][0],
                tr.VN_CONVENTION[i][1]
            ] = a[i]/SR2
            out[
                tr.VN_CONVENTION[i][1],
                tr.VN_CONVENTION[i][0]
            ] = a[i]/SR2
    else:
        out = np.zeros((3, 3, 3, 3))
        for i_1 in range(6):
            for i_2 in range(6):
                temp = a[i_1, i_2]
                if i_1 > 2:
                    temp /= SR2
                if i_2 > 2:
                    temp /= SR2
                out[
                    tr.VN_CONVENTION[i_1][0],
                    tr.VN_CONVENTION[i_1][1],
                    tr.VN_CONVENTION[i_2][0],
                    tr.VN_CONVENTION[i_2][1],
                ] = temp
                out[
                    tr.VN_CONVENTION[i_1][1],
                    tr.VN_CONVENTION[i_1][0],
                    tr.VN_CONVENTION[i_2][0],
                    tr.VN_CONVENTION[i_2][1],
                ] = temp
                out[
                    tr.VN_CONVENTION[i_1][0],
                    tr.VN_CONVENTION[i_1][1],
                    tr.VN_CONVENTION[i_2][1],
                    tr.VN_CONVENTION[i_2][0],
                ] = temp
                out[
                    tr.VN_CONVENTION[i_1][1],
                    tr.VN_CONVENTION[i_1][0],
                    tr.VN_CONVENTION[i_2][1],
                    tr.VN_CONVENTION[i_2][0],
                ] = temp
    return out

def inv_nvn(a: np.ndarray) -> np.ndarray:
    """Compute inverse through nvn.

    Compute inverse of minor symmetric fourth-order tensor
    through `nvn`. The inverse applies only on symmetric
    second-order tensors.

    Parameters
    ----------
    a : np.ndarray
        Fourth-order minor symmetrc tensor with a.shape == (3, 3, 3, 3)

    Returns
    -------
    np.ndarray
        Inverse of `a` w.r.t. symmetric second-order tensors
    """
    return nvn_inv(np.linalg.inv(nvn(a)))

# %% T1

def nv_polar(phi: float) -> np.ndarray:
    """Normal vector based on polar angle

    Parameters
    ----------
    phi : float
        Polar angle

    Returns
    -------
    np.ndarray
        2D normal vector
    """
    x = np.cos(phi)
    y = np.sin(phi)
    return np.array([x, y])

def nv_spherical(theta: float, phi: float) -> np.ndarray:
    """Normal vector based on spherical angles

    Parameters
    ----------
    theta : float
        Angle in [0, pi], start from z axis [0, 0, 1]
    phi : float
        Angle in x-y-plance, in [0, 2*pi]

    Returns
    -------
    np.ndarray
        Normal vector in 3D
    """
    x = np.sin(theta)*np.cos(phi)
    y = np.sin(theta)*np.sin(phi)
    z = np.cos(theta)
    return np.array([x, y, z])

# %% T4: stiffness/compliance tensors of important symmetry groups

def stiffness_component_dict(components=None) -> dict:
    """Generate stiffness component dictionary

    Generate dictionary based on the 21 values passed
    and based on the active Voigt notation (see
    VN_CONSTANT in module)

    Parameters
    ----------
    components : float, optional
        The 21 components, by default None

    Returns
    -------
    dict
        Dictionary with index and component as key-value pairs.
    """
    out = {}
    counter = 0
    for i_1 in range(6):
        for i_2 in range(i_1, 6):
            k = tr.VN_CONVENTION[i_1] + tr.VN_CONVENTION[i_2]
            k = np.array(k) + 1
            k = "".join(str(k_) for k_ in k)
            if not isinstance(components, type(None)):
                out[k] = components[counter]
                counter += 1
            else:
                out[k] = 0
    return out

def stiffness_tric(
    components: np.ndarray = None,
    components_d: dict = None
) -> np.ndarray:
    """Generate triclinic fourth-order stiffness tensor.

    Parameters
    ----------
    components : np.ndarray
        21 components of triclinic tensor, see
        stiffness_component_dict
    components_d : dictionary
        dictionary with 21 components
        of triclinic tensor, see
        stiffness_component_dict

    Returns
    -------
    np.ndarray
        Fourth-order triclinic tensor with minor
        and major symmetries
    """
    out = np.zeros(shape=[3, 3, 3, 3])
    if not isinstance(components, type(None)):
        components_d = stiffness_component_dict(components)
    for k, v in components_d.items():
        i = [int(s)-1 for s in k]
        out[i[0], i[1], i[2], i[3]] = v
        # tt_l
        out[i[1], i[0], i[2], i[3]] = v
        # tt_r
        out[i[0], i[1], i[3], i[2]] = v
        out[i[1], i[0], i[3], i[2]] = v  # + tt_l
        # tt_m
        out[i[2], i[3], i[0], i[1]] = v
        out[i[3], i[2], i[0], i[1]] = v  # + tt_l
        out[i[2], i[3], i[1], i[0]] = v  # + tt_r
        out[i[3], i[2], i[1], i[0]] = v  # + tt_l + tt_r
    return out

def stiffness_hex(
    t_1111: float,
    t_1122: float,
    t_1133: float,
    t_3333: float,
    t_2323: float
) -> np.ndarray:
    """Generate hexagonal fourht-order tensor

    Generate hexagonal/transversaly isotropic fourth order
    tensor with symmetry axis e_3.

    Parameters
    ----------
    t_1111 : float
    t_1122 : float
    t_1133 : float
    t_3333 : float
    t_2323 : float

    Returns
    -------
    np.ndarray
        Hexagonal fourth-order tensor with shape (3, 3, 3, 3)
    """
    # Isotropy axis is e_3
    components_d = {
        "1111": t_1111,
        "1122": t_1122,
        "1133": t_1133,
        "1123": 0,
        "1113": 0,
        "1112": 0,
        "2222": t_1111,
        "2233": t_1133,
        "2223": 0,
        "2213": 0,
        "2212": 0,
        "3333": t_3333,
        "3323": 0,
        "3313": 0,
        "3312": 0,
        "2323": t_2323,
        "2313": 0,
        "2312": 0,
        "1313": t_2323,
        "1312": 0,
        "1212": (t_1111 - t_1122)/2
    }
    out = stiffness_tric(components_d=components_d)
    return out

def stiffness_cub_l(l_1: float, l_2: float, l_3: float) -> np.ndarray:
    """Generate cubic stiffness based on its 3 eigenvalues

    Parameters
    ----------
    l_1 : float
        First eigenvalue (1x)
    l_2 : float
        Second eigenvalue (2x)
    l_3 : float
        Third eigenvalue (3x)

    Returns
    -------
    np.ndarray
        Cubic stiffness, fourth-order tensor
    """
    return td(np.array([l_1, l_2, l_3]), P_CUB, 1)

def stiffness_cub_get_l(stiffness: np.ndarray) -> np.ndarray:
    """Get eigenvalues of cubic stiffness

    Parameters
    ----------
    stiffness : np.ndarray
        Cubic stiffness, fourth-order tensor

    Returns
    -------
    np.ndarray
        The 3 cubic eigenvalues
    """
    return np.array([sp(stiffness, P)/sp(P, P) for P in P_CUB])

def stiffness_cub(
    c_1111: float,
    c_1122: float,
    c_2323: float
) -> np.ndarray:
    """Generate cubic stiffness based on free components.

    Parameters
    ----------
    c_1111 : float
        Free component
    c_1122 : float
        Free component
    c_2323 : float
        Free component

    Returns
    -------
    np.ndarray
        Stiffness, fourth-order tensor
    """
    l_1 = c_1111 + 2*c_1122
    l_2 = c_1111 - c_1122
    l_3 = 2*c_2323
    return stiffness_cub_l(l_1, l_2, l_3)

def stiffness_iso_l(l_1: float, l_2: float) -> np.ndarray:
    """Generate an isotropic stiffness based on eigenvalues.

    Parameters
    ----------
    l_1 : float
        First eigenvalue (=3K)
    l_2 : float
        Second eigenvalue (=2G)

    Returns
    -------
    np.ndarray
        Stiffness, fourth-order tensor
    """
    return iso_t([l_1, l_2])

def stiffness_iso(E: float, nu: float) -> np.ndarray:
    """Generate an isotropic stiffness based on Young's modulus E
    and Poisson's ration nu.

    Parameters
    ----------
    E : float
        Young's modulus
    nu : float
        Poisson's ratio

    Returns
    -------
    np.ndarray
        Stiffness, fourth-order tensor
    """
    l_1 = E/(1-2*nu)
    l_2 = E/(1+nu)
    return iso_t([l_1, l_2])

# %% Rotations

def rotation_matrix(n: np.ndarray, phi: float) -> np.ndarray:
    """Rotation matrix.

    Generate rotation matrix with rotation axis `n` and
    rotation angle `phi`.

    Parameters
    ----------
    n : np.ndarray
        Direction of ration axis (does not have to be normalized)
    phi : float
        Rotation angle in [0, np.pi)

    Returns
    -------
    np.ndarray
        Rotation matrix
    """
    n = np.array(n)
    n = n/nf(n)
    n_0 = np.cos(phi)*ID_2
    n_1 = -np.sin(phi)*lm(PT, n)
    n_2 = (1 - np.cos(phi))*tp(n, n)
    return n_0 + n_1 + n_2

# %% Constants

# Scalars
SR2 = np.sqrt(2)

# Identity on vectors
ID_2 = np.eye(3)

# Permutation tensor
PT = np.zeros((3, 3, 3))
PT[0, 1, 2] = 1
PT[1, 2, 0] = 1
PT[2, 0, 1] = 1
PT[1, 0, 2] = -1
PT[2, 1, 0] = -1
PT[0, 2, 1] = -1

# Fourth-order
ITI = tp(ID_2, ID_2)
ID_4 = tt(ITI, (0, 2, 1, 3))
ID_S = sym_r(ID_4)
ID_A = ID_4 - ID_S
P_ISO_1 = ITI/3
P_ISO_2 = ID_S - P_ISO_1
P_ISO_3 = ID_A
P_ISO = np.array([P_ISO_1, P_ISO_2, P_ISO_3])
P_CUB_1 = P_ISO_1
D_CUB = np.zeros(shape=[3]*4)
for ii in range(3):
    D_CUB[ii, ii, ii, ii] = 1
P_CUB_2 = D_CUB - P_CUB_1
P_CUB_3 = ID_S - (P_CUB_1 + P_CUB_2)
P_CUB = np.array([P_CUB_1, P_CUB_2, P_CUB_3])

# nvn variants
ID_S_NVN = nvn(ID_S)


import math
import sympy as sy
from sympy.abc import t

# 构造非线性方程中的f(x)
def f():
    fx = t ** 6 - 5 * t ** 5 + 3 * t ** 4 + t ** 3 - 7 * t ** 2 + 7 * t - 20
    return fx

# 构造f(x)的导数
def f_diff():
    fx_diff = sy.diff(f(), t)
    return fx_diff

# 计算f(x)
def cal_fx(x):
    return f().evalf(subs={t: x})

# 计算f(x)的导数
def cal_fx_diff(x):
    return f_diff().evalf(subs={t: x})

# 计算φ(x)
def phi(x):
    return x - cal_fx(x)

# 计算φ(x)的导数
def phi_diff(x):
    return 1 - cal_fx_diff(x)

# 符号函数，返回输入的正负
def sgn(x):
    if x >= 0:
        return 1
    else:
        return -1

# 简单迭代法，采用松弛加速
def iter(x, epsilon, k):
    x0 = x
    for i in range(k):
        temp = x
        x = (phi(x) - phi_diff(x0) * x) / (1 - phi_diff(x0))
        error = math.fabs(x - temp)
        if error < epsilon:
            return [x, error, i + 1]

# 牛顿法
def Newton(x, epsilon, k):
    for i in range(k):
        temp = x
        x = x - cal_fx(x) / cal_fx_diff(x)
        error = math.fabs(x - temp)
        if error < epsilon:
            return [x, error, i + 1]

# 弦割法
def Secant(x0, x1, epsilon, k):
    for i in range(k):
        x2 = x1 - (x1 - x0) * (cal_fx(x1) / cal_fx(x0)) / (cal_fx(x1) / cal_fx(x0) - 1)
        error = math.fabs(x2 - x1)
        x1, x0 = x2, x1
        if error < epsilon:
            return [x2, error, i + 1]

if __name__ == "__main__":
    epsilon: float = 1e-8  # 误差
    k = 100  # 最大迭代次数
    a = -1  # 区间左端点
    b = 5  # 区间右端点

    # 二分法
    while b - a >= 1:
        mid = (a + b) / 2
        if sgn(cal_fx(mid)) * sgn(cal_fx(a)) == -1:
            b = mid
        else:
            a = mid

    x0 = (a + b) / 2

    print("经过二分法，区间为[%s, %s]\n" % (a, b))

    [x, error, times] = iter(x0, epsilon, k)
    print("采用简单迭代法求解非线性方程：")
    print("x = %s" % x)
    print("误差：%s" % error)
    print("迭代次数：%s" % times)

    [x, error, times] = Newton(x0, epsilon, k)
    print("\n采用牛顿法求解非线性方程：")
    print("x = %s" % x)
    print("误差：%s" % error)
    print("迭代次数：%s" % times)

    [x, error, times] = Secant(b, a, epsilon, k)
    print("\n采用弦割法求解非线性方程：")
    print("x = %s" % x)
    print("误差：%s" % error)
    print("迭代次数：%s" % times)


import numpy as np
# Code Under Test
def entropy(ps):
    if any([(p < 0.0) or (p > 1.0) for p in ps]):
        raise ValueError("Bad input.")
    if sum(ps) > 1:
        raise ValueError("Bad input.")
    items = ps * np.log(ps)
    new_items = []
    for item in items:
        if np.isnan(item):
            new_items.append(0)
        else:
            new_items.append(item)
    return np.abs(-np.sum(new_items))


from Cryptoalpha import *
from sympy import *
from random import randint

def encrypt(E,p,a):
    return a.MtoS(matrix_mod(E*a.StoM(p),len(a.alphabet)))
    """Apply matrix E to string p mod 26 and return an encrypted string, relative to Cryptoalpha a """
def decrypt(D,c,a):
    return a.MtoS(matrix_mod(D*a.StoM(c),len(a.alphabet)))
    ####"""Apply matrix D to string c mod 26 and return a decrypted string, relative to Cryptoalpha a """
def get_decryption_matrix(P,C,a):
    """ Knowing two digraphs in string P are encoded as string C, determine a unique decryption matrix, relative to Cryptoalpha a """
    pdione = P[:2] #first plain digraph - letters string
    pditwo = P[2:] #second plain digraph - letters string
    cdione = C[:2] #first cipher digraph - letters string
    cditwo = C[2:] #second cipher digraph - letters string
    pdioneN = a.stoa(pdione) #make letters into numbers list
    pditwoN = a.stoa(pditwo) #make letters into numbers list
    cdioneN = a.stoa(cdione) #make letters into numbers list
    cditwoN = a.stoa(cditwo) #make letters into numbers list
    #ROUND TWO CODE
    plainmatrixnums = Matrix([[pdioneN[0],pditwoN[0]],[pdioneN[1],pditwoN[1]]]) #plaintext number matrix P
    #print("plainmatrix: ")
    #pprint(plainmatrixnums)
    ciphermatrixnums = Matrix([[cdioneN[0],cditwoN[0]],[cdioneN[1],cditwoN[1]]])#ciphertext number matrix C
    #print("ciphermatrix: ")
    #pprint(ciphermatrixnums)
    #find inverse of the cipher matrix
    cipherinverse = ciphermatrixnums.inv_mod(len(a.alphabet))
    #print("CipherInv: ")
    #pprint(cipherinverse)
    d = plainmatrixnums*cipherinverse #multiply plain with the cipher inverse to get d
    #print("D")
    #pprint(d)
    dModed = Matrix([
        [(d[0,0]%len(a.alphabet)), (d[0,1]%len(a.alphabet))],
        [(d[1,0]%len(a.alphabet)), (d[1,1]%len(a.alphabet))]
    ])
    #pprint(dModed)
    return dModed
def get_random_invertible_matrix(m):
    """ return a random 2x2 matrix M with gcd(det(M),m)= 1 """
    d = 2
    M = Matrix([[0,0],[0,0]])
    while d>1:
        #make a random 2x2 matrix, M, and let d = gcd(M, m)
        M = Matrix([[randint(0,m),randint(0,m)],[randint(0,m),randint(0,m)]])
        #range is 1 to m-1 -> your modulus value is the top
        d = gcd(det(M),m)
        #this must be last line; if the gcd is not 1 then you don't have an invertible matrix
    return M


"""Unvec operation."""
from typing import List, Optional

import numpy as np

def unvec(vector: np.ndarray, shape: Optional[List[int]] = None) -> np.ndarray:
    r"""
    Perform the unvec operation on a vector to obtain a matrix [Rigetti2020]_.

    Takes a column vector and transforms it into a :code:`shape[0]`-by-:code`shape[1]` matrix.
    This operation is the inverse of :code:`vec` operation in :code:`toqito`.

    For instance, for the following column vector

    .. math::
        u = \begin{pmatrix} 1 \\ 3 \\ 2 \\ 4 \end{pmatrix},

    it holds that

    .. math::
        \text{unvec}(u) =
        begin{pmatrix}
            1 & 2 \\
            3 & 4
        \end{pmatrix}

    More formally, the vec operation is defined by

    .. math::
        \text{unvec}(e_a \otimes e_b) = E_{a,b}

    for all :math:`a` and :math:`b` where

    .. math::
        E_{a,b}(c,d) = \begin{cases}
                          1 & \text{if} \ (c,d) = (a,b) \\
                          0 & \text{otherwise}
                        \end{cases}

    for all :math:`c` and :math:`d` and where

    .. math::
        e_a(b) = \begin{cases}
                     1 & \text{if} \ a = b \\
                     0 & \text{if} \ a \not= b
                 \end{cases}

    for all :math:`a` and :math:`b`.

    This function has been adapted from [Rigetti20]_.

    Examples
    ==========

    Consider the following vector

    .. math::
        u = \begin{pmatrix} 1 \\ 3 \\ 2 \\ 4 \end{pmatrix}

    Performing the :math:`\text{unvec}` operation on :math:`u` yields

    .. math::
        \text{unvec}(u) = \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix}

    >>> from toqito.matrix_ops import unvec
    >>> import numpy as np
    >>> u = np.array([1, 2, 3, 4])
    >>> unvec(u)
    [[1 2]
     [3 4]]

    See Also
    ========
    vec

    References
    ==========
    .. [Rigetti2020] Forest Benchmarking (Rigetti).
        https://github.com/rigetti/forest-benchmarking

    :param vector: A (:code:`shape[0] * shape[1]`)-by-1 numpy array.
    :param shape: The shape of the output matrix; by default, the matrix is assumed to be square.
    :return: Returns a :code:`shape[0]`-by-:code:`shape[1]` matrix.
    """
    vector = np.asarray(vector)
    if shape is None:
        dim = int(np.sqrt(vector.size))
        shape = dim, dim
    mat = vector.reshape(*shape).T
    return mat


import numpy
from numpy import array, absolute

t= [1, 2, 3]
print ('\nValue at first location:', t[0])
print ('Value at third location:', t[2])
print ('Value located using negative index:', t[-2])

print ('Comparison of a concatenated list with an array ')
r= [4, 5, 6]
print ('r=', r)
print ('t=', t )
result1= t + r
print ('Concatenation example using t and r lists.', result1)
result2=array([t,r])
print ('\nComparison of concatenation with array([t,r]).')
print (result2)

# Note the different results produced by indexing for a list and an array.
print ('\nThe second element of the preceding list and array')
print (result1[1])
print (result2[1])

print ('\nSlicing to produce an element, a column and a row of the array')
print (result2[0,1])
print (result2[:,1])  # Note the horizontal output of a column.
print (result2[1,:])

print ('\nExample 1 Section 2.3 Lial showing array addition from page 72.')
m= array([[10,12,5],[15, 20, 8]])
n= array([[45, 35, 20],[65, 40, 35]])
q= m + n
print ('\nArrays in order: m , n and m+n')
print (m)
print ('\n', n)   # Note that context determines the purpose of "n".
print ('\n', q)

print ('\nSubtraction of n from m equals q')
q= m - n
print (q)

s= 2.0
print ('\nMultiplication of q by a scalar', s)
q= s*q
print (q)

print ('\nFunctions can be applied to an array such as absolute value')
q= absolute(q)
print (q)


import numpy as np
from scipy.optimize import curve_fit
from typing import Callable, List, Tuple, TypeVar

def auto_correlation(data: List[float], lag: int) -> float:
    """Calculates auto-correlation of `data` with `lag` time delay

    Args:
        data (List[float]): data
        lag (int): lag of comparision

    Returns:
        float: auto-correlation of `data[lag:]` with `data[:-lag]
    """

    if lag == 0:
        return 1
    else:
        correlation = np.corrcoef(data[:len(data) - lag], data[lag:])[0, 1]
        return 0 if np.isnan(correlation) else correlation

def _exp_model(x: float, char_length: float) -> float:
    """Model of `f(x) = exp(- x / l)`

    Args:
        x (float): input
        char_length (float): characteristic length (l)

    Returns:
        float: output of function
    """

    return np.exp(-x / char_length)

def exp_characteristic_length(x_s: List[float], y_s: List[float]) -> Tuple[float, float]:
    """Finds characteristic length of `exp(-x / l)` function.

    Args:
        x_s (List[float]): inputs
        y_s (List[float]): outputs

    Returns:
        Tuple[float, float]: characteristic length and its error
    """

    try:
        x_length = x_s[-1] - x_s[0]
        x_length = 1 if x_length == 0 else x_length
        # should normalized x
        fit_para, fit_error = curve_fit(_exp_model, x_s / x_length, y_s, p0=(.5,))
        # because of normalization
        fit_para = fit_para[0] * x_length
        fit_error = np.sqrt(fit_error[0]) * x_length
    except:
        fit_para, fit_error = 0, 0

    return fit_para, fit_error

X = TypeVar('X')

def bootstrap_error(data: List[X], function: Callable[[List[X]], float], size: int = 100) -> float:
    """Calculates bootstrap error of `data`.

    Args:
        data (List[X]): list of any data
        function (Callable[[List[X]], float]): function gives the value that we want to calculate its error
        size (int, optional): number of batches. Defaults to 100.

    Returns:
        float: bootstrap error
    """

    ensemble_values = np.zeros(size)

    for i in range(size):
        # select `len(data)` values from data randomly
        random_numbers = np.random.randint(0, len(data), len(data))
        # get function output of this batch
        ensemble_values[i] = function(data[random_numbers])

    # return standard deviation of function output of batches
    return np.std(ensemble_values)


import numpy as np

def parse_input(lines):
    for l in lines:
        if 'increment' in l:
            yield ('increment', int(l.split()[-1]))
        elif 'stack' in l:
            yield ('stack',)
        elif 'cut' in l:
            yield ('cut', int(l.split()[-1]))
        else:
            raise Exception("Invalid line")

def modinv(a, m):
    def egcd(a, b):
        if a == 0:
            return (b, 0, 1)
        else:
            g, y, x = egcd(b % a, a)
            return (g, x - (b // a) * y, y)

    g, x, _ = egcd(a, m)
    if g != 1:
        raise Exception('modular inverse does not exist')
    else:
        return x % m

def reverse_deal(D, i):
    return D - 1 - i

def reverse_cut(D, i, N):
    return (i + N + D) % D

def reverse_increment(D, i, N):
    return modinv(N, D) * i % D

def reverse_shuffle(inputs, D, pos):
    for inp in inputs:
        if inp[0] == 'stack':
            pos = reverse_deal(D, pos)
        elif inp[0] == 'increment':
            pos = reverse_increment(D, pos, inp[1])
        elif inp[0] == 'cut':
            pos = reverse_cut(D, pos, inp[1])
        else:
            raise Exception("Invalid line")
    return pos

if __name__ == "__main__":
    with open('22.txt') as f:
        lines = f.readlines()
    N = 10007

    cards = np.arange(0, N)
    inputs = list(parse_input(lines))
    for i in inputs:
        if i[0] == 'stack':
            cards = np.flipud(cards)
        elif i[0] == 'increment':
            n = i[1]
            new = np.zeros(N, dtype=int)
            for idx in range(N):
                new[(idx * n) % len(cards)] = cards[idx]
            cards = new
        elif i[0] == 'cut':
            cards = np.roll(cards, -i[1])
        else:
            raise Exception("Invalid line")
    print("Part 1: {}".format(np.where(cards == 2019)[0][0]))

    # https://www.reddit.com/r/adventofcode/comments/ee0rqi/2019_day_22_solutions/fbnifwk?utm_source=share&utm_medium=web2x
    D = 119315717514047  # deck size
    X = 2020
    Y = reverse_shuffle(inputs[::-1], D, X)
    Z = reverse_shuffle(inputs[::-1], D, Y)
    A = (Y - Z) * modinv(X - Y + D, D) % D
    B = (Y - A * X) % D
    n = 101741582076661
    result = (pow(A, n, D) * X + (pow(A, n, D) - 1) * modinv(A - 1, D) * B) % D
    print("Part 2: {}".format(result))


############### THIS PROGRAM COMPUTES AN EXAMPLE OF LOGISTIC REGRESSSION ##################
# A dataset is read into X1,X2 which contains results for two student exams,
# and Y which contains the labels 1 and 0 for admission/rejection in a grad school.

import numpy as np
import os
import time
import pandas as pd
import matplotlib.pyplot as plt
from sklearn import linear_model
import scipy.optimize as opt
from matplotlib import rc

start=time.time()

path=os.getcwd() + '/Desktop/python_alb/ML/ng3/ex2data1.txt'
data=pd.read_csv(path, header=None, names=['Test1', 'Test2', 'Label'])
data.insert(0,'Ones',1) # add a column of ones for B

X0=np.array(data['Ones'])
X1=np.array(data['Test1'])
X2=np.array(data['Test2'])
Y=np.array(data['Label'])
Y=np.reshape(Y,(Y.shape[0],1))
X=np.stack((X0,X1,X2)) # stacks X0,X1,X2 features in rows of new array for matrix product

####### Visualizing the data ########
plt.close('all')
Tpass=data[data['Label'].isin([1])] # creates dataframe with values labeled by y=1
Tfail=data[data['Label'].isin([0])] # same with y=0

fig1, ax = plt.subplots(figsize=(12,8))
ax.scatter(Tpass['Test1'],Tpass['Test2'], c='b', label='Pass')
ax.scatter(Tfail['Test1'],Tfail['Test2'], c='r', label='Fail')
ax.axis([0, max(X1)+10,0, max(X2)+10])
plt.show()
plt.grid('on')
plt.legend()
plt.title('Students admitted')
plt.xlabel('Grade in Test 1')
plt.ylabel('Grade in Test 2')
end=time.time()

####### Define functions for Logistic Regression #######
def sigmoid(z):
    "sigmoid activation function for logistic regression"
    sigmoid=1/(1+np.exp(-z))
    return sigmoid

def Logistic_Loss(W,X,Y):
    "Calculates logistic loss from data, weights and labels"
    loss=-np.multiply(Y,np.log(sigmoid(np.dot(W,X))))-np.multiply((1-Y),np.log(1-sigmoid(np.dot(W,X))))
    return loss

def Logistic_Cost(W,X,Y):
    "Averages Logistic loss over samples"
    cost=np.mean(Logistic_Loss(W,X,Y))
    return cost

def Gradient(W,X,Y):
    "Computes gradient for a gradient descent step"
    gradient=1/Y.shape[0]*np.dot(sigmoid(np.dot(W,X))-Y.T,X.T)
    return gradient

####### Initialize learning rate and weight matrix, set number of iterations #######
alpha=0.001
W=np.zeros([1,X.shape[0]])
#W=np.array([-25,0,0])
iters=1000

def Gradient_Descent(W,X,Y,alpha,iters):
    Wnew=W.copy()
    Cost=np.zeros(iters)
    Witer=np.zeros([iters,3])
    for i in range(iters):
        Wnew=Wnew-alpha*Gradient(Wnew,X,Y)
        Cost[i]=Logistic_Cost(Wnew,X,Y)
        Witer[i]=Wnew
    return Wnew, Cost, Witer

plt.figure(2)

for k in range(4):
    Wnew, Cost, Witer = Gradient_Descent(W,X,Y,alpha*10**-k,iters)
    learn=u'\u03B1 = '+ str(alpha*10**-k)
    plt.plot(Cost, '.', label=learn, markersize=3)

plt.show()
plt.legend()
plt.grid('on')
plt.xlabel('N of iterations')
plt.ylabel('Cost Function')
plt.title('Cost function evolution')

plt.figure(3)
plt.plot(Witer)
plt.xlabel('iteration')
plt.ylabel('Weights')
plt.grid('on')
plt.title('Weight evolution')
plt.show()

Xnew=X.T
#Wnew=np.array([-25,0.222222222,0.222222222]) #why does setting this completely change accuracy?????
W_optimization = opt.fmin_tnc(func=Logistic_Cost, x0=Wnew, fprime=Gradient, args=(X, Y))
min_cost = Logistic_Cost(W_optimization[0], X, Y)
W_opt=np.reshape(W_optimization[0], (1,3))
def Predict_Admission(X,W_opt):
    probability=sigmoid(np.dot(W_opt,X))
    size=np.size(probability)
    Admission_result=np.zeros(size)
    print(probability)
    for l in range(size):
        if probability[0,l]>0.5:
            Admission_result[l]=1
        else:
            Admission_result[l]=0
    return Admission_result
    #return probability
Cand_grades=np.array([[1],[10],[10]])
Admission_result=Predict_Admission(X,W_opt)

theta_min = np.matrix(W_optimization[0])
correct = [1 if ((a == 1 and b == 1) or (a == 0 and b == 0)) else 0 for (a, b) in zip(Admission_result, Y)]
accuracy = (sum(map(int, correct)) % len(correct))
print('accuracy = {0}%'.format(accuracy))

plt.figure(4)
x_sigm=np.linspace(-100,100,1000)
y_sigm=sigmoid(x_sigm)
plt.plot(x_sigm,y_sigm,'b')
plt.xlabel('x')
plt.ylabel('f(x)')
plt.grid('on')
plt.title('Sigmoid Function')
plt.show()

print("This process ran in " + str(end-start) + " seconds")


# Code:
import numpy as np

# Use index slicing and show how to get the subarray consisting of the last 2 rows, and 3rd and 4th columns
given_array = [1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15], [16, 17, 18, 19, 20]
np_array = np.asarray(given_array).reshape(4, 5)
print("Full array:")
print(np_array)
print("Subarray:")
print(np_array[2:4, 2:4])

# Show how to create a one-dimensional array that sums the columns
col_sums_array = np.asarray(np_array.sum(axis=0)).reshape(1, 5)
print("One dimensional array that sums the columns:")
print(col_sums_array)

# Output:
# Full array:
# [[ 1  2  3  4  5]
#  [ 6  7  8  9 10]
#  [11 12 13 14 15]
#  [16 17 18 19 20]]
# Subarray:
# [[13 14]
#  [18 19]]
# One dimensional array that sums the columns:
# [[34 38 42 46 50]]


import numpy as np

def compute_gae(rewards, values, gamma: float = 0.99, lam: float = 0.95):
    """
    Calculates the unnormalized generalized advantage estimation function.
    Check out https://arxiv.org/abs/1506.02438.

    Arguments:
    rewards : ndarray of size (N,)
     - rewards earned during a trajectory
    values : ndarray of size(N+1,)
     - calculated values of states during a trajectory
    gamma / lam: float
     - discounting constants described in paper

    Returns:
    adv : ndarray of size
     - generalized advantage estimation
    """
    assert len(values) == len(rewards) + 1

    N = len(rewards)
    adv = np.zeros((N,))
    gae = 0

    for step in reversed(range(len(rewards))):
        delta = rewards[step] + gamma * values[step+1] - values[step]
        gae = delta + gamma * lam * gae
        adv[step] = gae

    return adv

def compute_rtg(rewards, gamma: float = 0.99):
    """
    Calculates the unnormalized sum of rewards after a point in a trajectory.
    Here we add a discounting factor that puts less emphasis on results that
    happen further in the future.

    Arguments:
    rewards : ndarray of size (N,)
     - rewards earned during a trajectory
    gamma : float
    - discounting factor (closer to one implies more weight for future events)

    Returns:
    returns : ndarray
    - returns of action, or the discounted future rewards
    """
    N = len(rewards)
    returns = np.zeros((N,))

    for step in reversed(range(N)):
        future_rewards = gamma * (returns[step+1] if step+1 < N else 0)
        returns[step] = rewards[step] + future_rewards
    return returns


#Learn how to apply L2 normalization to data
import numpy as np
import sklearn.preprocessing

#x' = x / sqrt(sigma_{i=1}^n((x_i)^2))

arr = np.array([[4, 1, 2, 2],
       [3, 4, 0, 0],
       [7, 5, 9, 2]])

#apply L2 normalization to each row
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!each row!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

normalizer = sklearn.preprocessing.Normalizer()

print(normalizer.fit_transform(arr))
# [[0.8        0.2        0.4        0.4       ]
#  [0.6        0.8        0.         0.        ]
#  [0.55513611 0.39652579 0.71374643 0.15861032]]


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sat Sep 11 14:22:08 2021
@author: jaimewalter
"""

## Visualize a cube that rotates in 3D, and its 2D orthographic projections. Made with linear transformations.

import numpy as np
import math
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection, Line3DCollection
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
from matplotlib.widgets import Slider, Button

# Assign angles of rotation
phi = 0
theta = 0
psi = 0

# Create function that generates coordinates of the vertices of the cube after rotating.
def rotate(phi, theta, psi):
    # Vertex coordinates of original cube
    vertices = np.array([[-1, -1, -1],
                      [1, -1, -1 ],
                      [1, 1, -1],
                      [-1, 1, -1],
                      [-1, -1, 1],
                      [1, -1, 1 ],
                      [1, 1, 1],
                      [-1, 1, 1]])

    # Rotation matrices
    XrollMatrix = np.mat([[1, 0, 0], \
                          [0, math.cos(phi), math.sin(phi)], \
                          [0, -math.sin(phi), math.cos(phi)]])

    YpitchMatrix = np.mat([[math.cos(theta), 0, -math.sin(theta)], \
                          [0, 1, 0], \
                          [math.sin(theta), 0, math.cos(theta)]])

    ZyawMatrix = np.mat([[math.cos(psi), math.sin(psi), 0], \
                          [-math.sin(psi), math.cos(psi), 0], \
                          [0, 0, 1]])

    # Generate vertex coords of rotated cube
    Z = np.zeros((8,3))
    for i in range(8): Z[i,:] = np.dot(vertices[i,:],XrollMatrix)
    for i in range(8): Z[i,:] = np.dot(Z[i,:],YpitchMatrix)
    for i in range(8): Z[i,:] = np.dot(Z[i,:],ZyawMatrix)

    # Vertices in each face of the cube
    faces = [[Z[0],Z[1],Z[2],Z[3]],
      [Z[4],Z[5],Z[6],Z[7]],
      [Z[0],Z[1],Z[5],Z[4]],
      [Z[2],Z[3],Z[7],Z[6]],
      [Z[1],Z[2],Z[6],Z[5]],
      [Z[4],Z[7],Z[3],Z[0]]]

    # Projection matrices
    ortXY = np.mat([[1, 0, 0], \
                   [0, 1, 0]])
    ortXZ = np.mat([[1, 0, 0], \
                    [0, 0, 1]])
    ortYZ = np.mat([[0, 1, 0], \
                    [0, 0, 1]])

    # Generate new coordinates and faces
    XY = np.zeros((8,2))
    for i in range(8): XY[i,:] = np.dot(ortXY, Z[i,:])
    #XY = np.delete(XY, 2, 1)

    XZ = np.zeros((8,2))
    for i in range(8): XZ[i,:] = np.dot(ortXZ, Z[i,:])
    #XZ = np.delete(XZ, 1, 1)

    YZ = np.zeros((8,2))
    for i in range(8): YZ[i,:] = np.dot(ortYZ, Z[i,:])
    #YZ = np.delete(YZ, 0, 1)

    facesXY = [[XY[7],XY[4],XY[5],XY[6]],
      [XY[1],XY[2],XY[3],XY[0]],
      [XY[4],XY[0],XY[3],XY[7]],
      [XY[5],XY[1],XY[2],XY[6]],
      [XY[7],XY[3],XY[2],XY[6]],
      [XY[0],XY[4],XY[5],XY[1]]]
    facesXZ = [[XZ[7],XZ[4],XZ[5],XZ[6]],
      [XZ[1],XZ[2],XZ[3],XZ[0]],
      [XZ[4],XZ[0],XZ[3],XZ[7]],
      [XZ[5],XZ[1],XZ[2],XZ[6]],
      [XZ[7],XZ[3],XZ[2],XZ[6]],
      [XZ[0],XZ[4],XZ[5],XZ[1]]]
    facesYZ = [[YZ[7],YZ[4],YZ[5],YZ[6]],
      [YZ[1],YZ[2],YZ[3],YZ[0]],
      [YZ[4],YZ[0],YZ[3],YZ[7]],
      [YZ[5],YZ[1],YZ[2],YZ[6]],
      [YZ[7],YZ[3],YZ[2],YZ[6]],
      [YZ[0],YZ[4],YZ[5],YZ[1]]]

    # Return all important values
    return Z, XY, XZ, YZ, facesXY, facesXZ, facesYZ, faces

# Get values from rotate()
results = rotate(phi, theta, psi)
Z = results[0]
XY = results[1]
XZ = results[2]
YZ = results[3]
facesXY = results[4]
facesXZ = results[5]
facesYZ = results[6]
faces = results[7]

 ## Make the plot
fig = plt.figure()

ax = fig.add_subplot(221, projection='3d', proj_type = "ortho", azim = -45, elev = 35, title="Isometric")
ax.set_box_aspect([1,1,1])

ax2 = fig.add_subplot(222, title="Top")
ax2.set_aspect('equal', adjustable='box')
ax3 = fig.add_subplot(224, title="Front")
ax3.set_aspect('equal', adjustable='box')
ax4 = fig.add_subplot(223, title="Side")
ax4.set_aspect('equal', adjustable='box')

plt.subplots_adjust(bottom=0.3)
plt.subplots_adjust(wspace = 0.5, hspace = 0.5)

plt.show()

# Define the function for updating the plot
def plot(Z, XY, XZ, YZ, facesXY, facesXZ, facesYZ, faces):
    # Clear previous points
    ax.clear()
    ax2.clear()
    ax3.clear()
    ax4.clear()

    ax.set_box_aspect([1,1,1])

    ax.set_title("Isometric")

    ax.set_xlabel('X')
    ax.set_ylabel('Y')
    ax.set_zlabel('Z')

    ax.set_xlim([-1.5, 1.5])
    ax.set_ylim([-1.5, 1.5])
    ax.set_zlim([-1.5, 1.5])

    ax.locator_params(axis='x', nbins=3)
    ax.locator_params(axis='y', nbins=3)
    ax.locator_params(axis='z', nbins=3)

    ax2.set_xlim([-2, 2])
    ax2.set_ylim([-2, 2])
    ax3.set_xlim([-2, 2])
    ax3.set_ylim([-2, 2])
    ax4.set_xlim([-2, 2])
    ax4.set_ylim([-2, 2])

    ax2.set_title("Top")
    ax3.set_title("Front")
    ax4.set_title("Side")

    ax2.set_xlabel('X')
    ax2.set_ylabel('Y')
    ax3.set_xlabel('X')
    ax3.set_ylabel('Z')
    ax4.set_xlabel('Y')
    ax4.set_ylabel('Z')

    # Plot new graphs
    ax.scatter3D(Z[:, 0], Z[:, 1], Z[:, 2])

    ax.add_collection3d(Poly3DCollection(faces, facecolors='cyan', linewidths=1, edgecolors='blue', alpha=.25))

    ax2.scatter(XY[:, 0], XY[:, 1])
    ax3.scatter(XZ[:, 0], XZ[:, 1])
    ax4.scatter(YZ[:, 0], YZ[:, 1])

    for i in range(6):
        poly = Polygon(facesXY[i], facecolor='cyan', edgecolor='blue', alpha = 0.25)
        ax2.add_patch(poly)
    for i in range(6):
        poly = Polygon(facesXZ[i], facecolor='cyan', edgecolor='blue', alpha = 0.25)
        ax3.add_patch(poly)
    for i in range(6):
        poly = Polygon(facesYZ[i], facecolor='cyan', edgecolor='blue', alpha = 0.25)
        ax4.add_patch(poly)

# Make the first plot
plot(Z, XY, XZ, YZ, facesXY, facesXZ, facesYZ, faces)

#Create sliders
axphi = plt.axes([0.19, 0.15, 0.65, 0.03], facecolor='lightgoldenrodyellow')
phi_slider = Slider(
    ax=axphi,
    label='Phi [rad]',
    valmin=0,
    valmax=1.57,
    valinit=phi,
)

axtheta = plt.axes([0.19, 0.1, 0.65, 0.03], facecolor='lightgoldenrodyellow')
theta_slider = Slider(
    ax=axtheta,
    label='Theta [rad]',
    valmin=0,
    valmax=1.57,
    valinit=theta,
)

axpsi = plt.axes([0.19, 0.05, 0.65, 0.03], facecolor='lightgoldenrodyellow')
psi_slider = Slider(
    ax=axpsi,
    label='Psi [rad]',
    valmin=0,
    valmax=1.57,
    valinit=psi,
)

# The function to be called anytime a slider's value changes
def update(val):
    phi = phi_slider.val
    theta = theta_slider.val
    psi = psi_slider.val
    results = rotate(phi, theta, psi)
    Z = results[0]
    XY = results[1]
    XZ = results[2]
    YZ = results[3]
    facesXY = results[4]
    facesXZ = results[5]
    facesYZ = results[6]
    faces = results[7]
    plot(Z, XY, XZ, YZ, facesXY, facesXZ, facesYZ, faces)

# Register the update function with each slider
phi_slider.on_changed(update)
theta_slider.on_changed(update)
psi_slider.on_changed(update)


"""

Parameter estimation using MCMC - Probability Model
=============================================================================

In the following we learn the mean and covariance of a univariate gaussian distribution from data.

"""

#%% md
#
# Initially we have to import the necessary modules.

#%%

import numpy as np
import matplotlib.pyplot as plt

from UQpy.inference import DistributionModel, BayesParameterEstimation
from sklearn.neighbors import KernelDensity  # for the plots
from UQpy.distributions import JointIndependent, Uniform, Lognormal, Normal

def pdf_from_kde(domain, samples1d):
    bandwidth = 1.06 * np.std(samples1d) * samples1d.size ** (-1 / 5)
    kde = KernelDensity(bandwidth=bandwidth).fit(samples1d.reshape((-1, 1)))
    log_dens = kde.score_samples(domain)
    return np.exp(log_dens)

#%% md
#
# First, for the sake of this example, we generate fake data from a gaussian distribution with mean 10 and standard
# deviation 1.

#%%

np.random.seed(100)
mu, sigma = 10, 1  # true mean and standard deviation
data_1 = np.random.normal(mu, sigma, 100).reshape((-1, 1))
np.random.seed()

# plot the data and true distribution
count, bins, ignored = plt.hist(data_1, 30, density=True)
plt.plot(bins, 1 / (sigma * np.sqrt(2 * np.pi)) * np.exp(- (bins - mu) ** 2 / (2 * sigma ** 2)),
         linewidth=2, color='r')
plt.title('data as histogram and true distribution to be estimated')
plt.show()

#%% md
#
# In a Bayesian setting, the definition of a prior pdf is a key point. The prior for the parameters must be defined in
# the model. Note that if no prior is given, an improper, uninformative, prior is chosen, :math:`p(\theta)=1` for all
# :math:`\theta`.

#%%

p0 = Uniform(loc=0., scale=15)
p1 = Lognormal(s=1., loc=0., scale=1.)
prior = JointIndependent(marginals=[p0, p1])

candidate_model = DistributionModel(distributions=Normal(loc=None, scale=None), n_parameters=2, prior=prior)

# Learn the unknown parameters using MCMC
from UQpy.sampling import MetropolisHastings

mh1 = MetropolisHastings(jump=10, burn_length=10, seed=[1.0, 0.2], random_state=123)

bayes_estimator = BayesParameterEstimation(inference_model=candidate_model,
                                           data=data_1,
                                           nsamples=500,
                                           sampling_class=mh1)

# print results
s = bayes_estimator.sampler.samples
plt.scatter(s[:, 0], s[:, 1])
plt.scatter(10, 1, marker='+', label='true parameter')
plt.title('MCMC samples')
plt.legend()
plt.show()

fig, ax = plt.subplots(1, 2, figsize=(10, 4))

domain = np.linspace(0, 15, 200)[:, np.newaxis]
pdf = pdf_from_kde(domain, s[:, 0])
ax[0].plot(domain, p0.pdf(domain), label='prior')
ax[0].plot(domain, pdf, label='posterior')
ax[0].set_title('posterior pdf of theta=mu')
ax[0].legend()

domain = np.linspace(0, 2, 200)[:, np.newaxis]
pdf = pdf_from_kde(domain, s[:, 1])
ax[1].plot(domain, p1.pdf(domain), label='prior')
ax[1].plot(domain, pdf, label='posterior')
ax[1].set_title('posterior pdf of theta=sigma')
ax[1].legend()

plt.show()


import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
import matplotlib.patches as mpatches

def x(t):
	return t

def y(t):
	return np.sin(2*t)

def draw_vector(v0, v1, ax=None, color='r', label=''):
	ax = ax or plt.gca()
	arrowprops=dict(arrowstyle='->',
		linewidth=3,
		shrinkA=0, shrinkB=0, color=color)
	ax.annotate('', v1, v0, arrowprops=arrowprops, label=label)

t_range = np.linspace(-10, 10, 100)
xval = x(t_range)
yval = y(t_range)

xval = xval.reshape((-1, 1))
yval = yval.reshape((-1, 1))
data = np.hstack((xval, yval))

pca = PCA(n_components=2, svd_solver='full')
pca.fit(data)
print(pca.explained_variance_/sum(pca.explained_variance_))
fig, ax = plt.subplots()
plt.scatter(xval, yval)

i=0

for length, vector in zip(pca.explained_variance_, pca.components_):
	v = vector * 1 * np.sqrt(length)

	if i==0:
		color = 'r'
		label = 'PC1'
		draw_vector(pca.mean_, pca.mean_ + v, color=color, label=label)
		draw_vector(pca.mpatchesean_, pca.mean_ - v, color=color)

	else:
		color = 'g'
		label = 'PC2'
		a2 = draw_vector(pca.mean_, pca.mean_ + 0.5*v, color=color, label=label)
		draw_vector(pca.mean_, pca.mean_ - 0.5*v, color=color)

	i+=1

red_patch = mpatches.Patch(color='r', label='First Principal Component')
blue_patch = mpatches.Patch(color='g', label='Second Principal Component')

plt.legend(handles=[red_patch, blue_patch])

plt.axis('equal')
plt.show()
plt.savefig(os.path.join('figures', "sinuid_PCA.pdf"))
plt.close()
print("Done with generating a plot where PCA is not informative (Figure 1)")


import numpy as np
np.set_printoptions(precision=4, suppress=True)

from BN import *

gra = [[],[0],[0],[1,2]]
ev = (1,1,1,1)

p1 = Node( np.array([.5]), gra[0] )# cloudy
print( "p1 false %.4e p1 true %.4e" % (p1.computeProb(ev)[0] , p1.computeProb(ev)[1]))

p2 = Node( np.array([.5,.1]), gra[1] )# sprinkler

p3 = Node( np.array([.2,.8]), gra[2] )# rain

p4 = Node( np.array([[.0,.9],[.9,.99]]), gra[3] )# wetgrass
print( "p2 = 1, p3 = 1, p4 false %.4e p4 true %.4e" % (p4.computeProb(ev)[0] , p4.computeProb(ev)[1]))

prob = [p1,p2,p3,p4]

bn = BN(gra, prob)

jp = []
for e1 in [0,1]:
	for e2 in [0,1]:
		for e3 in [0,1]:
			for e4 in [0,1]:
				jp.append(bn.computeJointProb((e1, e2, e3, e4)))

print("sum joint %.3f (1)" % sum(jp))

### Tests to joint Prob
ev = (0,0,0,0)
print( "joint %.4g (0.2)" % bn.computeJointProb(ev) )

ev = (1,1,1,1)
print( "joint %.4g (0.0396)" % bn.computeJointProb(ev) )

### Tests to post Prob
# P(e1|e4=1)
ev = (-1,[],[],1)
print("ev : ")
print(ev)
print( "post : %.4g (0.5758)" % bn.computePostProb(ev)  )

# P(e4|e1=1)
ev = (1,[],[],-1)
print("ev : ")
print(ev)
print( "post : %.4g (0.7452)" % bn.computePostProb(ev)  )

# P(e1|e2=0,e3=0)
ev = (-1,0,0,[])
print("ev : ")
print(ev)
print( "post : %.4g (0.3103)" % bn.computePostProb(ev)  )