import numpy as np
from scipy.stats import kstest, ks_2samp

np.random.seed(2020)

if __name__ == "__main__":

    # np.random.normal(center, spread, size)
    x = np.random.normal(0, 1, 10)
    y = np.random.normal(0, 1, 10)
    z = np.random.normal(1.1, 0.9, 10) # one of these things is not like the other
    print("X:", x) #> [-0.14235884,  2.05722174,  0.28326194,  1.32981198, -0.15462185, -0.06903086,  0.75518049,  0.82564665, -0.11306921, -2.36783759]
    print("Y:", y) #> [-0.16704943,  0.68539797,  0.02350011,  0.45620128,  0.27049278, -1.43500814,  0.88281715, -0.58008166, -0.5015653 ,  0.59095329]
    print("Z:", z) #> [0.44154537, 1.33557992, 0.32978398, 0.93122668, 0.76386234, 0.68422613, 0.36518051, 1.05938903, 1.209195  , 1.9333575 ]

    print("--------------------------")
    print("KS TESTS (AGAINST NORMAL DISTRIBUTION)...")
    print("X:", kstest(x, "norm")) #> KstestResult(statistic=0.33855972126320677, pvalue=0.1590112941560191)
    print("Y:", kstest(y, "norm")) #> KstestResult(statistic=0.18866753744918974, pvalue=0.8060547009565138)
    print("Z:", kstest(z, "norm")) #> KstestResult(statistic=0.6292184031091476, pvalue=0.0002318573221670418)

    print("--------------------------")
    print("TWO-SAMPLE KS TESTS...")
    print("X-Y:", ks_2samp(x, y)) #> KstestResult(statistic=0.3, pvalue=0.7869297884777761)
    print("X-Z:", ks_2samp(x, z)) #> KstestResult(statistic=0.6, pvalue=0.05244755244755244)
    print("Y-Z", ks_2samp(y, z)) #> KstestResult(statistic=0.6, pvalue=0.05244755244755244)

    #breakpoint()


'''
This file implements fisher projection of classification data onto K(< no.of.classes) dimensions
to further fit it with LDA.
Referenced from
https://github.com/probml/pmtk3/blob/master/toolbox/SupervisedModels/fisherLda/fisherLdaFit.m
Author: Srikar-Reddy-Jilugu(@always-newbie161)
'''
import superimport

import numpy as np

def fisher_lda_fit(X, y, kdims):
    """
    :param X: shape(nsamples, ndims)
    :param y: shape(nsamples, 1)
    :param kdims: int
    :return W: Linear Projection Matrix; ndarray of shape(ndims, kdims)
    """
    nclasses = np.max(y)  # no .of.classes
    nsamples, ndims = X.shape

    if nclasses == 2:
        # assuming y is from {1,2}
        ndx1 = np.where(y == 1)[0]
        ndx2 = np.where(y == 2)[0]
        mu1, mu2 = np.mean(X[ndx1, :]), np.mean(X[ndx2, :])
        S1, S2 = np.cov(X[ndx1, :]), np.cov(X[ndx2, :])
        Sw = S1 + S2
        W = np.linalg.inv(Sw) @ (mu2 - mu1)
    else:
        # assuming y is from {1,2,..nclasses}
        muC = np.zeros((nclasses, ndims))
        for c in range(0, nclasses):
            ndx = np.where(y == (c + 1))[0]
            muC[c, :] = (np.mean((X[ndx, :]), axis=0))

        mu_matrix = np.squeeze(muC[y-1, :], axis=1)
        Sw = (X - mu_matrix).T @ (X - mu_matrix)
        muX = np.mean(X, axis=0)
        Sb = (np.ones((nclasses, 1))*muX - muC).T @ (np.ones((nclasses, 1))*muX - muC)
        _, eigvecs = np.linalg.eig(np.linalg.pinv(Sw) @ Sb)
        W = eigvecs[:, :kdims]

    return W


import numpy

def map_feature(X1, X2, num_examples, degree=6):
    """
    Feature mapping function to polynomial features.
    Maps the features to quadratic features.
    Returns a new df with more features, comprising of
    x1, x2, x1^2, x2^2, x1*x2, x1*x2^2, etc...
    Args:
        X1, X2: vectors of original features
        features: int, the number of initial features
        degree: int, the polynomial degree
    Returns:
        mapped_features: a matrix with the new features
    """
    mapped_features = numpy.ones((num_examples))

    for idx_i in range(1, degree+1):
        for idx_j in range(idx_i + 1):
            polynomial_features = numpy.multiply(numpy.power(X1, idx_i - idx_j), numpy.power(X2, idx_j))
            mapped_features = numpy.c_[mapped_features, polynomial_features]

    return mapped_features


import numpy as np
import matplotlib.pyplot as plt

from sklearn.decomposition import PCA

def paired_comparison(x2D, y2D, debugPlot=False, dropFirst=None):
    assert x2D.shape[1] == y2D.shape[1], "must have same numebr of channels"
    nChannel = x2D.shape[1]

    expvarXX = np.sort(np.linalg.eigvals(np.cov(x2D.T)))[::-1]
    expvarYY = np.sort(np.linalg.eigvals(np.cov(y2D.T)))[::-1]

    pcaX = PCA(n_components=nChannel)
    pcaY = PCA(n_components=nChannel)
    pcaX.fit(x2D)
    pcaY.fit(y2D)

    rotX = pcaX.components_
    rotY = pcaY.components_

    covXY = np.cov(x2D.dot(rotY.T).T)
    covYX = np.cov(y2D.dot(rotX.T).T)

    expvarXY = np.diag(covXY).copy()
    expvarYX = np.diag(covYX).copy()

    # # Sensibility test 1: Covariance of (x transformed to PCA domain) should be a diagonal matrix with
    # # elements equal to the eigenvalues of covariance of x
    # covXX = np.cov(x2D.dot(rotX.T).T)
    # evals1 = np.diag(covXX)
    # evals2 = np.sort(np.linalg.eigvals(np.cov(x2D.T)))[::-1]
    # print(np.linalg.norm(evals1-evals2))

    # Sensibility test 2: The sum of explained variances should match before and after transform
    # print(np.sum(expvarXX) - np.sum(expvarXY))
    # print(np.sum(expvarYY) - np.sum(expvarYX))

    ratioXX = expvarXX / np.sum(expvarXX)
    ratioYY = expvarYY / np.sum(expvarYY)
    ratioXY = expvarXY / np.sum(expvarXY)
    ratioYX = expvarYX / np.sum(expvarYX)

    if debugPlot:
        fig, ax = plt.subplots(ncols=2, figsize=(8, 4))
        ax[0].plot(ratioXX, label='XX')
        ax[0].plot(ratioYY, label='XY')
        ax[1].plot(ratioXY, label='YY')
        ax[1].plot(ratioYX, label='YX')
        ax[0].legend()
        ax[1].legend()
        plt.show()

    if dropFirst is not None:
        ratioXX = ratioXX[dropFirst:]
        ratioXY = ratioXY[dropFirst:]
        ratioYY = ratioYY[dropFirst:]
        ratioYX = ratioYX[dropFirst:]

    rezXY = 1 - np.sum(np.abs(ratioXX - ratioXY)) / 2
    rezYX = 1 - np.sum(np.abs(ratioYY - ratioYX)) / 2

    return rezXY, rezYX, ratioXX, ratioXY, ratioYY, ratioYX


import superimport

import pyprobml_utils as pml

import matplotlib.pyplot as plt
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.decomposition import PCA
from scipy import linalg
from numpy import array
from numpy import mean
from numpy import cov
from numpy.linalg import eig
import numpy as np

# Dataset according to matlab code:

n = 100
np.random.seed(0)
a = np.random.multivariate_normal((1, 3), [[4.0, 0.01], [0.01, 0.1]], n)
b = np.random.multivariate_normal((3, 1), [[4.0, 0.01], [0.01, 0.1]], n)

X = np.vstack((a, b))

Y = np.hstack((np.zeros(n), np.ones(n)))

mu_a, mu_b = a.mean(axis=0).reshape(-1,1), b.mean(axis=0).reshape(-1,1)

xmax = np.max(X[:, 0])
xmin = np.min(X[:, 0])
ymax = np.max(X[:, 1])
ymin = np.min(X[:, 1])

nMale = 100
nFemale = 200

"""##FischerLDA
It is done in 2 ways:

a) Using sklearn.discriminant

b) According to the book (manual calculation))

# a)
"""

def flda_sklearn(X, Y):
    lda = LinearDiscriminantAnalysis()
    X_fit = lda.fit(X, Y)
    X_tran = lda.transform(X)
    w_sk = X_fit.coef_
    return X_tran, X_fit, w_sk

def Xproj_fish_sk_cal(w_sk):
    slope = w_sk[0, 1]/w_sk[0, 0]
    Xproj_fish_sk = X.dot(w_sk.T)
    return Xproj_fish_sk

"""# b)"""

def calculate_covariance_matrix(X, Y=None):
    if Y is None:
        Y = X
    n_samples = np.shape(X)[0]
    covariance_matrix = (1 / (n_samples-1)) * (X - X.mean(axis=0)).T.dot(Y - Y.mean(axis=0))

    return np.array(covariance_matrix, dtype=float)

def flda_manual(a, b):
    # Covariance matrices of the two datasets
    cov1 = calculate_covariance_matrix(a)
    cov2 = calculate_covariance_matrix(b)
    cov_tot = (cov1 + cov2)/2

    # Mean of the two datasets
    mean1 = a.mean(0)
    mean2 = b.mean(0)
    mean_diff = np.atleast_1d(mean1 - mean2)

    # The vector w, which when X is projected onto it best separates the data by class.
    # w = (mean1 - mean2) / (cov1 + cov2) [Formula in topic 9.2.6.1 in the book]

    w = -np.linalg.pinv(cov_tot).dot(mean_diff)

    return w

def Xproj_fish_man_cal(X, w):
    Xproj_fish = X.dot(w)
    Xproj_fish_male = Xproj_fish[:nMale]
    Xproj_fish_female = Xproj_fish[nMale:nFemale]
    return Xproj_fish, Xproj_fish_male, Xproj_fish_female

"""##PCA

It is done in 2 ways:

a) Using sklearn.decomposition

b) Manual principal component analysis

# a)
"""

def pca_sklearn(X, Y):
    pca = PCA(n_components=1)
    X_fit = pca.fit(X)
    X_tran = pca.transform(X)
    return X_fit, X_tran

def Xproj_pca_sk_cal(X_fit):
  weights = X_fit.components_
  Xproj_pca_sk = X.dot(weights.T)
  return Xproj_pca_sk

"""# b)"""

def pca_manual(X, Y):
    # calculate the mean of each column
    M = mean(X.T, axis=1)
    # center columns by subtracting column means
    C = M - X
    # calculate covariance matrix of centered matrix
    V = cov(C.T)
    # eigendecomposition of covariance matrix
    values, vectors = eig(V)
    # project data
    P = vectors.T.dot(C.T)

    return vectors

def Xproj_pca_man_cal(X, vectors):
  vector = vectors[:, 0]
  Xproj_pca = X.dot(vector)
  Xproj_pca_male = Xproj_pca[:nMale]
  Xproj_pca_female = Xproj_pca[nMale:nFemale]
  return Xproj_pca, Xproj_pca_male, Xproj_pca_female

"""Plot functions:"""

def plot_data(a, b):
    plt.figure()
    plt.plot(a[:,0], a[:,1], 'b.', b[:,0], b[:,1], 'r+')
    #plt.plot(mu_a, mu_b, 'black')
    plt.legend(['Male', 'Female', 'Means'])
    pml.savefig("fisher_lda_data.pdf")
    plt.show()

def plot_all_vectors(a, b, vectors, w):
    mu_a, mu_b = a.mean(axis=0).reshape(-1,1), b.mean(axis=0).reshape(-1,1)
    mid_point = (mu_a + mu_b)/2

    vector = vectors[:, 0]
    slope_pca = vector[1]/vector[0]
    c_pca = mid_point[1] - slope_pca*mid_point[0]

    slope = w[1]/w[0]
    c = mid_point[1] - slope*mid_point[0]

    x = np.linspace(xmin+1, xmax+1, 100)
    z = np.linspace(xmin+1, xmax+1, 100)

    plt.figure()
    plt.xlim(xmin, xmax)
    plt.ylim(ymin, ymax)
    plt.plot(a[:,0], a[:,1], 'b.', b[:,0], b[:,1], 'r+')
    plt.plot(x, slope*x + c)
    plt.plot(z, slope_pca*z + c_pca)
    #plt.plot(mu_a, mu_b, 'black')
    plt.legend(['Male', 'Female', 'FisherLDA vector', 'PCA vector'])
    #plt.legend(['Male', 'Female', 'FisherLDA vector', 'PCA vector', 'Means'])
    pml.savefig("fisher_lda_lines.pdf")
    plt.show()

def plot_flda_vectors(a, b, vectors, w):
    mu_a, mu_b = a.mean(axis=0).reshape(-1,1), b.mean(axis=0).reshape(-1,1)
    mid_point = (mu_a + mu_b)/2

    vector = vectors[:, 0]
    slope_pca = vector[1]/vector[0]
    c_pca = mid_point[1] - slope_pca*mid_point[0]

    slope = w[1]/w[0]
    c = mid_point[1] - slope*mid_point[0]

    x = np.linspace(xmin+1, xmax+1, 100)
    z = np.linspace(xmin+1, xmax+1, 100)

    plt.figure()
    plt.xlim(xmin, xmax)
    plt.ylim(ymin, ymax)
    plt.plot(a[:,0], a[:,1], 'b.', b[:,0], b[:,1], 'r+')
    plt.plot(x, slope*x + c)
    #plt.plot(z, slope_pca*z + c_pca)
    #plt.plot(mu_a, mu_b, 'black')
    plt.legend(['Male', 'Female', 'FisherLDA vector'])
    #plt.legend(['Male', 'Female', 'FisherLDA vector', 'PCA vector', 'Means'])
    pml.savefig("fisher_lda_lines_flda.pdf")
    plt.show()

def plot_pca_vectors(a, b, vectors, w):
    mu_a, mu_b = a.mean(axis=0).reshape(-1,1), b.mean(axis=0).reshape(-1,1)
    mid_point = (mu_a + mu_b)/2

    vector = vectors[:, 0]
    slope_pca = vector[1]/vector[0]
    c_pca = mid_point[1] - slope_pca*mid_point[0]

    slope = w[1]/w[0]
    c = mid_point[1] - slope*mid_point[0]

    x = np.linspace(xmin+1, xmax+1, 100)
    z = np.linspace(xmin+1, xmax+1, 100)

    plt.figure()
    plt.xlim(xmin, xmax)
    plt.ylim(ymin, ymax)
    plt.plot(a[:,0], a[:,1], 'b.', b[:,0], b[:,1], 'r+')
    #plt.plot(x, slope*x + c)
    plt.plot(z, slope_pca*z + c_pca)
    #plt.plot(mu_a, mu_b, 'black')
    plt.legend(['Male', 'Female', 'PCA vector'])
    #plt.legend(['Male', 'Female', 'FisherLDA vector', 'PCA vector', 'Means'])
    pml.savefig("fisher_lda_lines_pca.pdf")
    plt.show()

def plot_proj(name, argument):
    plt.figure()
    if name == 'pca':
        Xproj_pca_male = argument[:nMale]
        Xproj_pca_female = argument[nMale:nFemale]
        plt.hist(Xproj_pca_male, color='red', ec='black')
        plt.hist(Xproj_pca_female, color='blue', ec='black')
        plt.title('Projection of points onto PCA vector')
        pml.savefig("fisher_lda_pca.pdf")
        plt.show()
    else :
        Xproj_fish_male = argument[:nMale]
        Xproj_fish_female = argument[nMale:nFemale]
        plt.hist(Xproj_fish_male, color='red', ec='black')
        plt.hist(Xproj_fish_female, color='blue', ec='black')
        plt.title('Projection of points onto Fisher vector')
        pml.savefig("fisher_lda_flda.pdf")
        plt.show()

"""Data"""

plot_data(a, b)

"""FLDA"""

X_tran, X_fit, w_sk = flda_sklearn(X, Y)
w = flda_manual(a, b)

Xproj_fish_sk = Xproj_fish_sk_cal(w_sk)
Xproj_fish_man, Xproj_fish_male, Xproj_fish_female = Xproj_fish_man_cal(X, w)
Xproj_fish_man = Xproj_fish_man.reshape(-1, 1)
assert np.allclose(Xproj_fish_sk, Xproj_fish_man)

plot_proj('flda', Xproj_fish_man) # Or plot_proj('flda', X_tran) as both the methods give similar results

"""PCA"""

X_fit, X_tran = pca_sklearn(X, Y)
vectors = pca_manual(X, Y)

Xproj_pca_sk = Xproj_pca_sk_cal(X_fit)
Xproj_pca_man, Xproj_pca_male, Xproj_pca_female = Xproj_pca_man_cal(X, vectors)
Xproj_pca_man = Xproj_pca_man.reshape(-1, 1)
assert np.allclose(Xproj_pca_sk, Xproj_pca_man)

plot_proj('pca', Xproj_pca_man) # Or plot_proj('pca', X_tran) as both the methods give similar results

"""Plot of vectors"""

plot_all_vectors(a, b, vectors, w)
plot_pca_vectors(a, b, vectors, w)
plot_flda_vectors(a, b, vectors, w)


#!/usr/bin/env python2

# Author: juscodit@gmail.com

import matplotlib
import numpy as np
import matplotlib.pyplot as plt
import math
import sys
from math import pow
from math import exp

matplotlib.rcParams['xtick.direction'] = 'out'
matplotlib.rcParams['ytick.direction'] = 'out'

delta = 0.025

### ==============================
### Classical Newton for Rosenbrock function
### ==============================

# Rosenbrock function for point @p
def fr(p):
  return 100.0 * pow((p[1] - pow(p[0], 2)), 2) + pow((1.0 - p[0]), 2)

# gradient at point @p
def gr(p):
  x, y = p
  drx = 400.0 * pow(x, 3) - 400.0 * x * y + 2.0 * x - 2.0
  dry = 200.0 * y - 200.0 * pow(x, 2)
  return np.array([drx, dry])

# hessian matrix
def hr(p):
  x, y = p
  m11 = 1200.0 * pow(x, 2) - 400.0 * y + 2.0
  m12 = -400.0 * x
  m21 = -400.0 * x
  m22 = 200.0
  return np.matrix([[m11, m12], [m21, m22]])

# descent direction
def ddr(p):
  hessian = hr(p)
  heinv = np.linalg.inv(hessian)
  return np.squeeze(np.asarray(np.negative(np.dot(heinv, gr(p)))))

# step length at point @p with respect to descent direction @dd
def alpha_r(p, dd):
  a, r, c1 = 1, 0.3, 1e-4

  g = gr(p)
  p1 = p + a * dd
  while fr(p1) > fr(p) + c1 * a * np.dot(g, dd):
    a *= r
    p1 = p + a * dd

  return a

def classical_newton_r(p0):
  result = []
  result.append(p0)
  pk = p0

  i = 0
  while np.linalg.norm(gr(pk)) > 0.001:
    print "step %d [%f, %f]" % (i, pk[0], pk[1])
    i += 1
    dd = ddr(pk)
    a = alpha_r(pk, dd)
    pk = pk + a * dd
    result.append(pk)

  print "step %d [%f, %f]" % (i, pk[0], pk[1])

  return result

### ==============================
### quadratic approximation for Rosenbrock function at some point
### ==============================

x = np.arange(-1.2, 1.0, delta)
y = np.arange(-0.6, 1.2, delta)
X, Y = np.meshgrid(x, y)
Z = 100 * (Y - X * X) * (Y - X * X) + (1 - X) * (1 - X)

p = (-0.5, 0)
gp = gr(p)
hp = hr(p)
Zapp = fr(p) + (gp[0] * (X - p[0]) + gp[1] * (Y - p[1])) + \
       0.5 * (hp[0, 0] * (X - p[0]) * (X - p[0]) +
              hp[0, 1] * (X - p[0]) * (Y - p[1]) +
              hp[1, 0] * (Y - p[1]) * (X - p[0]) +
              hp[1, 1] * (Y - p[1]) * (Y - p[1]))

plt.figure()
plt.contour(X, Y, Z, [15, 9, 6, 4, 2, 1, 0.1])
plt.contour(X, Y, Zapp, [10, 8, 6, 4, 2, 1], colors='green', linewidths=0.7)
plt.plot(*p, marker='o', color='r')

plt.savefig("rosen3.png")

### ==============================
### Rosenbrock function
### ==============================

x = np.arange(-1.2, 1.0, delta)
y = np.arange(-0.6, 1.2, delta)
X, Y = np.meshgrid(x, y)
Z = 100 * (Y - X * X) * (Y - X * X) + (1 - X) * (1 - X)

## for initial point (0.6, 0.6)

plt.figure()
plt.contour(X, Y, Z, [15, 9, 6, 4, 2, 1, 0.1])

res = classical_newton_r((0.6, 0.6))

xp, yp = zip(*res)
plt.plot(xp, yp, '--', lw=3)

for r in res:
  plt.plot(*r, marker='o', color='r')

plt.savefig("rosen1.png")

## for initial point (-1.2, 1)

plt.figure()
plt.contour(X, Y, Z, [15, 9, 6, 4, 2, 1, 0.1])

res = classical_newton_r((-1.2, 1))

xp, yp = zip(*res)
plt.plot(xp, yp, ls='--', lw=3)

for r in res:
  plt.plot(*r, marker='o', color='r')

plt.savefig("rosen2.png")

### ==============================
### Classical Newton for log(exp(x) + exp(-x))
### ==============================

def gl(x):
  return (exp(x) - exp(-x)) / (exp(x) + exp(-x))

def hl(x):
  return 1 - pow(gl(x), 2)

def classical_newton_l(x0, niter):
  result = []
  hessian = []
  xk = x0
  i = 0
  while gl(xk) != 0 and i < niter:
    result.append(xk)
    hessian.append(hl(xk))
    xk = xk - gl(xk) / hl(xk)
    i += 1
  result.append(xk)
  hessian.append(hl(xk))
  return (result, hessian)

plt.figure()

x = np.arange(-2, 6, .01)
y = np.log(np.exp(x) + np.exp(-x))

(res, hessian) = classical_newton_l(1.1, 4)
print res
print hessian

for r in res:
  plt.plot(r, 0, marker='o', color='r')

tag = 'x0'
plt.annotate(tag, xy=(res[0], 0.05), xytext=(0.5, 0.2), arrowprops=dict(arrowstyle="->"))

tag = 'x1'
plt.annotate(tag, xy=(res[1], 0.05), xytext=(res[1], 0.2))

tag = 'x2'
plt.annotate(tag, xy=(res[2], 0.05), xytext=(1.5, 0.2), arrowprops=dict(arrowstyle="->"))

tag = 'x3'
plt.annotate(tag, xy=(res[3], 0.05), xytext=(res[3], 0.2))

tag = 'x4'
plt.annotate(tag, xy=(res[4], 0.05), xytext=(res[4], 0.2))

plt.plot(x, y, linewidth=2)

plt.savefig("log.png")

# plt.show()


'''Quantum Fourier Transform Simulator for Shor's Algorithm'''

import cirq
import numpy as np

# define the initial four qubits
q0 = cirq.GridQubit(0, 0) # |0〉
q1 = cirq.GridQubit(0, 1) # |0〉
q2 = cirq.GridQubit(1, 0) # |0〉
q3 = cirq.GridQubit(1, 1) # |0〉

def main():
    '''Simulates the Four-qubit QFT Circuit'''

    # create and append the quantum circuit
    circuit = cirq.Circuit()
    circuit.append(initialize_QFT(False)) # i.e., QFT
    circuit.append(initialize_QFT(True)) # i.e., QFT†
    print("\n ┼─────────────────────────────────┼\n",
    "│ Four-qubit QFT Circuit (N = 2⁴) │\n",
    "┼─────────────────────────────────┼\n\n")
    print(circuit)

    # simulate the circuit and print the resulting state vector
    result = cirq.Simulator().simulate(circuit)
    print("\n\n⦿  Final State Vector |ψ₄〉:\n\n")
    print(np.around(result.final_state_vector, 7))

def initialize_QFT(inv=False):
    '''Initializes the Four-qubit QFT Circuit'''

    if inv is True:
        sign = -1 # i.e., QFT†
    else:
        sign = 1 # i.e., QFT

    # GATE 1: apply Hadamard to q0
    yield cirq.H(q0)

    # GATE 2: apply controlled R₂ between q0 and q1
    yield cirq.CZPowGate(exponent=sign*0.5)(q0,q1)

    # GATE 3: apply controlled R₃ between q0 and q2
    yield cirq.CZPowGate(exponent=sign*0.25)(q0,q2)

    # GATE 4: apply controlled R₄ between q0 and q3
    yield cirq.CZPowGate(exponent=sign*0.125)(q0,q3)

    # GATE 5: apply Hadamard to q1
    yield cirq.H(q1)

    # GATE 6: apply controlled R₂ between q1 and q2
    yield cirq.CZPowGate(exponent=sign*0.5)(q1,q2)

    # GATE 7: apply controlled R₃ between q1 and q3
    yield cirq.CZPowGate(exponent=sign*0.25)(q1,q3)

    # GATE 8: apply Hadamard to q2
    yield cirq.H(q2)

    # GATE 9: apply controlled R₂ between q2 and q3
    yield cirq.CZPowGate(exponent=sign*0.5)(q2,q3)

    # GATE 10: apply Hadamard to q3
    yield cirq.H(q3)

    # GATE 11: apply swap between q1 and q2
    yield cirq.SWAP(q1,q2)

    # GATE 12: apply final swap between q0 and q3
    yield cirq.SWAP(q0,q3)

if __name__ == '__main__':
    main()


# Law of large numbers
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams["figure.figsize"] = (20,10)
from distributionLib import Dist
import os
import imageio
np.random.seed(404)

N = 1000

data = np.random.choice([0, 1], size=(N,1), p=[0.49, 0.51])
E_x = np.sum(data)/N

sampleLook = 801
temp = 0
yData = []
xData = []
sample = 100

plt.plot([0,sampleLook],[E_x,E_x],'r--',label='E(x)')
plt.ylim(E_x - E_x/20, E_x +E_x/20)
plt.xlim(0,sampleLook)
plt.title('Law of Large Numbers')
plt.xlabel('Number of samples')
plt.ylabel('Mean of sample Data')
##filenames = []
for ii in range(sampleLook):
    tSample = np.random.randint(0, N,size=(sample,1))
    temp += np.mean(data[tSample])
    if not ii%10:
        if not ii:
            plt.scatter(ii,temp/(ii+1),color='k',label='Sample Data mean')
            plt.legend()
        else:
            plt.scatter(ii,temp/(ii+1),color='k')
##        filename = f'{ii}.png'
##        filenames.append(filename)
        plt.pause(0.00001)
        plt.draw()
##        plt.savefig(filename)

##with imageio.get_writer('mygif.gif', mode='I') as writer:
##    for filename in filenames:
##        image = imageio.imread(filename)
##        writer.append_data(image)
##
### Remove files
##for filename in set(filenames):
##    os.remove(filename)
##


"""
itoProcess.py

Created by Luca Camerani at 31/08/2020, University of Milano-Bicocca.
(l.camerani@campus.unimib.it)
All rights reserved.

This file is part of the EcoFin-Library (https://github.com/LucaCamerani/EcoFin-Library),
and is released under the "BSD Open Source License".
"""

import math

import numpy as np

from EcoFin.math.stochasticProcess.process import Process
from EcoFin.utils.utils import listInterpreter

class ItoProcess():
    def __init__(self, T, dt, t=0, x0=0, n=None):
        self.T = T
        self.t = t
        self.dt = dt
        self.steps = len(self.getTimeVector())
        self.x0 = x0
        self.n = n
        if n is None: self.n = 1

        self.initSBM()

    def getStepsN(self):
        return math.trunc((self.T - self.t) / self.dt) + 1

    def initSBM(self):
        self.SBM = Process(self.T, self.dt, self.t, n=self.n).standardBrownianMotion(lineCorr=False)
        return self.SBM

    def getTimeVector(self):
        return np.arange(self.t, self.T + 1, self.dt)

    def driftBrownianMotion(self, a, b, scalarCorr=True):
        self.initSBM()
        output = []
        for i in range(0, self.n):
            dX = a * self.dt + b * self.SBM.increments[i]
            output.append(self.x0 + np.cumsum(dX))

        if scalarCorr: output = listInterpreter(output)
        return output

    def geometricBrownianMotion(self, mu, sigma, scalarCorr=True):
        self.initSBM()
        output = []
        for i in range(0, self.n):
            x = [self.x0]
            for k in range(1, self.getStepsN()):
                drift = (mu - .5 * sigma ** 2) * (self.getTimeVector() - self.t)[k]
                diffusion = sigma * self.SBM.process[i][k]
                temp = self.x0 * np.exp(drift + diffusion)
                x.append(temp)

            output.append(x)

        if scalarCorr: output = listInterpreter(output)
        return output

    def meanRevertingProcess(self, k, theta, sigma):
        """
        :param k: mean reversion force
        :type k:
        :param theta: long term value
        :type theta:
        :param sigma:
        :type sigma:
        :return:
        :rtype:
        """
        self.initSBM()
        longTerm = theta - self.x0
        sigmaBis = sigma * np.sqrt(2. / k)
        x = np.zeros(self.steps)
        for i in range(self.steps - 1):
            x[i + 1] = x[i] + k * (longTerm - x[i]) * self.dt + \
                       sigmaBis * self.SBM.increments[i]

        return self.x0 + x


"""Python implementation of a knapsack problem algorithm."""

import numpy as np

def knapsack(items, size):
    """Takes a list of items and a knapsack size.
    Each item should have a value and a weight packed in a tuple/list.
    Returns selected items and max accumulated value.

    >>> items = [(3, 4), (2, 3), (4, 2), (4, 3)]  # (value, weight)
    >>> size = 6
    >>> knapsack(items, size)
    (8.0, [(4, 3), (4, 2)])
    """

    memo = np.zeros([size + 1, len(items) + 1])
    for i in range(1, len(items) + 1):
        value_i, weight_i = items[i - 1]
        for x in range(size + 1):
            prev_item_value = memo[x, i-1]

            # If current size is smaller than item weight,
            # we place the value of the previous item for the given size x...
            if x < weight_i:
                memo[x, i] = prev_item_value
                continue

            # ...otherwise, we accumulate the current item value together with
            # a value of the previous item, if it fits the remaining size.
            # If it doesn't, then just the current item value is taken,
            # but only when it's bigger than the previous item value...
            prev_current_value = memo[x-weight_i, i-1] + value_i

            # That's why, we choose a higher value before assigning it to the current item.
            memo[x, i] = max(prev_item_value, prev_current_value)

    # Having evaluated values for all combinations of items and sizes (items * sizes),
    # we are now able to point the highest one, which is indexed by the last item and size.
    max_value = memo[size, len(items)]

    # We can localize selected items by taking advantage from the fact,
    # that every eligible item value has to be bigger than the previous one
    # on the same weight position. In other words, if values of consecutive items
    # are the same, then we know, that such a value belongs to the first item it occurred.
    knapsack_items = []
    item_idx = len(items) + 1
    while size:
        # Get index of max value in a row. If tie, then index of first occurrence is taken.
        item_idx = memo[size, :item_idx].argmax()
        if item_idx == 0:
            break
        item = items[item_idx - 1]
        knapsack_items.append(item)
        size -= item[1]  # Decrease size by a weight of a selected item.

    return max_value, knapsack_items

if __name__ == "__main__":
    import doctest
    doctest.testmod()


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

from sympy import *
x, y, z = symbols('x y z')
init_printing(use_unicode=True)
print(Eq(x, y))

print(solveset(Eq(x**2, 1), x))
print(solveset(Eq(x**2 - 1, 0), x))
print(solveset(x**2 - 1, x))

print(solveset(x**2 - x, x))
print(solveset(x - x, x, domain=S.Reals))
print(solveset(sin(x) - 1, x, domain=S.Reals))
print(solveset(exp(x), x))
# No solution exists
print(solveset(cos(x) - x, x))
# Not able to find solution
print(linsolve([x + y + z - 1, x + y + 2*z - 3 ], (x, y, z)))

print(linsolve(Matrix(([1, 1, 1, 1], [1, 1, 2, 3])), (x, y, z)))
M = Matrix(((1, 1, 1, 1), (1, 1, 2, 3)))
system = A, b = M[:, :-1], M[:, -1]
print(linsolve(system, x, y, z))
a, b, c, d = symbols('a, b, c, d', real=True)
print(nonlinsolve([a**2 + a, a - b], [a, b]))
print(nonlinsolve([x*y - 1, x - 2], x, y))
print(nonlinsolve([x**2 + 1, y**2 + 1], [x, y]))
system = [x**2 - 2*y**2 -2, x*y - 2]
vars = [x, y]
print(nonlinsolve(system, vars))
system = [exp(x) - sin(y), 1/y - 3]
print(nonlinsolve(system, vars))
print(nonlinsolve([x*y, x*y - x], [x, y]))
system = [a**2 + a*c, a - b]
print(nonlinsolve(system, [a, b]))

print(solve([x**2 - y**2/exp(x)], [x, y], dict=True))
print(solve([sin(x + y), cos(x - y)], [x, y]))
print(solveset(x**3 - 6*x**2 + 9*x, x))
print(roots(x**3 - 6*x**2 + 9*x, x))
print(solve(x*exp(x) - 1, x ))
f, g = symbols('f g', cls=Function)
print(f(x).diff(x))
diffeq = Eq(f(x).diff(x, x) - 2*f(x).diff(x) + f(x), sin(x))
print(diffeq)
print(dsolve(diffeq, f(x)))
print(dsolve(f(x).diff(x)*(1 - sin(f(x))) - 1, f(x)))


# %%
# Bulk Imports and setup
from IPython.display import display, Math, Latex
import matplotlib.pyplot as plt
import numpy as np

import sympy as sm
from sympy.utilities.lambdify import lambdify, implemented_function
x, y, lamda = sm.symbols('x, y, lamda')

def move_sympyplot_to_axes(p, ax):
    backend = p.backend(p)
    backend.ax = ax
    backend._process_series(backend.parent._series, ax, backend.parent)
    backend.ax.spines['right'].set_color('none')
    backend.ax.spines['bottom'].set_position('zero')
    backend.ax.spines['top'].set_color('none')
    plt.close(backend.fig)

# Defining the equations via sympy
dot_x = x-y   # f(x,y)
dot_y = -2*x**2+y  # g(x,y)
display(Math('\dot{x} = '+ sm.latex(dot_x)))
display(Math('\dot{y} = '+ sm.latex(dot_y)))

# %%
## A matrix from dot(X)=AX (if system is linear)
# We contruct the vector <f(x,y), g(x,y)>.
A_vector = sm.Matrix([dot_x, dot_y])
# Jacobian for the A_vector. In a linear system this gives the A matrix from dot(X)=AX
A_matrix = A_vector.jacobian(([x, y]))
display(Math('\mathbf{A} = '+ sm.latex(A_matrix)))
# %%
# Calculates Characteristic Polynomial
charpoly = A_matrix.charpoly()
charpoly
# %%
# Setting equations to zero and computing Fixed Points
dot_x_Equal, dot_y_Equal = sm.Eq(dot_x, 0), sm.Eq(dot_y, 0)
equilibria = sm.solve((dot_x_Equal, dot_y_Equal), x, y, dict=True)
# %%
# Calculates eigenvals and eigenvects for each eq
# The list is of the form [{eq}, [eigenthing1, eigenthing1]]
# Each eigenthing is [(eigenval, multiplicity, [eigenvec]), …]
eigen_things = [[eq, A_matrix.subs(eq).eigenvects()]
              for eq in equilibria]
# Showing equilibrium and eigenthings

for eq in equilibria:
    display(Math("\mathrm{Equilibrium}: (" + sm.latex(eq[x])+","+sm.latex(eq[y])+")"))
    # eigen_things[#eq][eigenthings][#pair][0=value, 1=mult, 2=eigenvect]
    for pair in eigen_things[0][1]:
            display(Math("\mathrm{Eigenvalue}:"+ sm.latex(pair[0]) +
                         "\quad \mathrm{Multiplicity}=" + sm.latex(pair[1])))
            display(Math("\mathrm{Eigenvector}: "+ sm.latex(pair[2][0])))
# %%
# Ploting  parameters
fig = plt.figure(figsize=(10,8))
ax = fig.add_subplot(1,1,1)
# ax.set_title("Sistema diferencial")

# Setting Plot Boundaries (needed of sympy)
x_min, y_min, y_max, x_max = -2, -2, 2, 2
# For plotting the trajectory
time, dt = 50, 0.1
x_0, y_0 = 0.5, 0.6

# Choose if normalization of vectors is applied
normalize = True

#################################
# Plot fixed points
for point in equilibria:
    ax.plot(point[x],point[y],"red", marker = "o", markersize = 10.0)

# To plot we translate from sympy to numpy
x_movement = lambdify((x, y), dot_x)
y_movement = lambdify((x, y), dot_y)

# define a grid and compute direction at each point
x_line = np.linspace(x_min, x_max, 20)
y_line = np.linspace(y_min, y_max, 20)

# Plot a singular trayectory
x_list= [x_0]
y_list = [y_0]
for i in range(time):
    x_list.append(x_list[i] + (x_movement(x_list[i], y_list[i])) * dt)
    y_list.append(y_list[i] + (y_movement(x_list[i], y_list[i])) * dt)

ax.plot(x_list, y_list, color="black")

# Quiverplot
# create a grid and set the dir vecs
X, Y = np.meshgrid(x_line, y_line)
x_dir, y_dir = x_movement(X,Y), y_movement(X,Y)
# prepare the colour and normalization factor
norm = np.sqrt(x_dir**2 + y_dir**2)

#Normalize
if normalize:
    x_dir /= norm
    y_dir /= norm

plt.quiver(X, Y,
           x_dir,
           y_dir,
           norm,
           pivot='mid')

# Plot Nullclines
p1 = sm.plot_implicit(dot_x_Equal, (x, x_min, x_max), (y, y_min, y_max), show= False)
p2 = sm.plot_implicit(dot_y_Equal, (x, x_min, x_max), (y, y_min, y_max), show= False)

move_sympyplot_to_axes(p1, ax)
move_sympyplot_to_axes(p2, ax)

plt.show()


import numpy as np
import matplotlib.pyplot as plt

# we set alpha, which is the discount rate as 0.5
alpha = 0.5

dict_action = {
    1: 'left',
    2: 'hold_still',
    3: 'right'
}

# reward defination

def left(s):
    if s == -10:
        s = -10
        reward = -11
    elif s == 1:
        s = 0
        reward = 99
    else:
        s = s - 1
        reward = -1
    return s, reward

def right(s):
    if s == 10:
        s = 10
        reward = -11
    elif s == -1:
        s = 0
        reward = 99
    else:
        s = s + 1
        reward = -1
    return s, reward

def hold_still(s):
    if s == 0:
        return s, 99
    else:
        return s, -1

def get_q_array(v): # after solving the Bellman equation, we get v(s) = reward + alpha * v(s')
    q_array = np.zeros([21, 3]) # -10 to 10
    coords = [k for k in range(-10, 11)]
    for i in range(21):
        for j in range(3): # j = 0, 1, 2 to match the dictionary, we have num = j + 1
            s = coords[i]
            num = j + 1
            if num == 1: # left
                s_prime, reward = left(s)
            elif num ==2: # hold_still
                s_prime, reward = hold_still(s)
            else: # right
                s_prime, reward = right(s)
            q_array[i, j] = reward + alpha * v[coords.index(s_prime)]
    return q_array

def get_policy(q_array):
    return np.argmax(q_array, axis=1) + 1 # 1: left, 2: hold_still, 3: right, find the max value in each row
def update_v(pi, q_array):
    v = np.zeros([21, 1])
    for i in range(21):
        v[i] = q_array[i, pi[i]-1]
    return v

def solve_for_v(pi):
    '''
    For this problem, the equation can be represented as the following format:
    v_pi_k = r_pi_k + alpha * P_pi_k * v_pi_k
    in matrix form
    R = (I - alpha*A)V
    V = (I - alpha*A)^(-1) * R

    A ∈ R^(21 * 21)
    alpha = 0.5
    I is a unit matrix
    '''
    # the equation for this problem is v_pi_k = r_pi_k + alpha * P_pi_k * v_pi_k
    # initialization
    A = np.zeros([21, 21])
    V = np.zeros([21, 1])
    R = np.zeros([21, 1])
    I = np.mat(np.identity(21))

    coords = [k for k in range(-10, 11)]

    for i in range(21):
        s = coords[i]
        action = pi[i]
        if action == 1: # left
            s_prime, reward = left(s)
        elif action == 2: # hold_still
            s_prime, reward = hold_still(s)
        else: # right
            s_prime, reward = right(s)
        R[i] = reward
        A[i, coords.index(s_prime)] = 1

    V = np.dot(np.linalg.inv(I - alpha * A), R)  # use broadcast mechanism
    return V

def main():
    # initialize
    q_array = np.zeros([21, 3])
    pi = np.ones([21, 1]) # initial policy

    diff_norm = np.float(100) # a very large number comparing to eps
    eps = np.float(10**-6)
    count = 0
    print('policy-based method bgeins!')

    while(diff_norm >= eps):
        v_pre = solve_for_v(pi)
        q_array = get_q_array(v_pre)
        pi = get_policy(q_array) # pi is policy, here it is updated. Actually this function can be merged to get_q_array, we divided it into two parts for readability
        v = solve_for_v(pi)
        diff_norm = np.linalg.norm(v - v_pre) # we use Euclidean distance as the loss evaluator
        count = count + 1

    print('done!')
    print('action value matrix: ')
    print(q_array)
    print('state values:')
    print(v)
    print('the optimal policy is: ')
    print(pi)
    print('time of iterations:', count, ' alpha =', alpha)

    fig = plt.figure()
    x = [k for k in range(-10, 11)]
    plt.scatter(x, pi)
    plt.title('policy_based_method')
    plt.ylabel('action')
    plt.xlabel('s')
    plt.show()

if __name__ == '__main__':
    main()


try:
    import numpy as _np
    from math import sqrt as _sqrt, log as _log
    from typing import Union, Tuple, Dict, List
    from univariate._base import BoundedInterval
except Exception as e:
    print(f"some modules are missing {e}")

class Uniform(BoundedInterval):
    """
    This class contains methods concerning the Continuous Uniform Distribution [#]_.

    .. math::
        \\text{Uniform}(x;a,b) = \\frac{1}{b-a}

    Args:

        a(float): lower limit of the distribution
        b(float): upper limit of the distribution where b > a

    Referene:
        .. [#] Weisstein, Eric W. "Uniform Distribution." From MathWorld--A Wolfram Web Resource. https://mathworld.wolfram.com/UniformDistribution.html
    """

    def __init__(self, a: float, b: float) -> None:
        self.a = a
        self.b = b

    def pdf(self, x: Union[List[float], _np.ndarray, float]) -> Union[float, _np.ndarray]:
        """
        Args:
            x (Union[List[float], numpy.ndarray, float]): random variable(s)

        Returns:
            Union[float, numpy.ndarray]: evauation of pdf at x
        """
        a = self.a
        b = self.b

        if isinstance(x, (_np.ndarray, List)):
            x0 = 1/(b-a)
            if not type(x) is _np.ndarray:
                x = _np.array(x)
            return _np.piecewise(x, [(a <= x) & (x <= b), (a > x) | (x > b)], [x0, 0.0])

        return 1 / (b - a) if a <= x and x <= b else 0.0

    def cdf(self, x: Union[List[float], _np.ndarray, float]) -> Union[float, _np.ndarray]:
        """
        Args:
            x (Union[List[float], numpy.ndarray, float]): data point(s) of interest

        Returns:
            Union[float, numpy.ndarray]: evaluation of cdf at x
        """
        a = self.a
        b = self.b

        def __generator(a: float, b: float, x: float) -> float:
            if x < a:
                return 0.0
            if a <= x and x <= b:
                return (x - a) / (b - a)
            if x > b:
                return 1.0

        if isinstance(x, (_np.ndarray, List)):
            if not type(x) is _np.ndarray:
                x = _np.array(x)
            # performance could be improved with np.piecewise
            return _np.vectorize(__generator)(a, b, x)

        return __generator(a, b, x)

    def mean(self) -> float:
        """
        Returns: Mean of the Uniform distribution.
        """
        return 1 / 2 * (self.a + self.b)

    def median(self) -> float:
        """
        Returns: Median of the Uniform distribution.
        """
        return 1 / 2 * (self.a + self.b)

    def mode(self) -> Tuple[int, int]:
        """
        Returns: Mode of the Uniform distribution.

        Note that the mode is any value in (a,b)
        """
        return (self.a, self.b)

    def var(self) -> float:
        """
        Returns: Variance of the Uniform distribution.
        """
        return 1 / 12 * pow(self.b - self.a, 2)

    def std(self) -> float:
        """
        Returns: Standard deviation of the Uniform distribution.
        """
        return _sqrt(1 / 12 * pow(self.b - self.a, 2))

    def skewness(self) -> float:
        """
        Returns: Skewness of the Uniform distribution.
        """
        return 0.0

    def kurtosis(self) -> float:
        """
        Returns: Kurtosis of the Uniform distribution.
        """
        return -6 / 5

    def entropy(self) -> float:
        """
        Returns: entropy of uniform Distirbution.

        Reference: Park, S.Y. & Bera, A.K.(2009). Maximum entropy autoregressive conditional heteroskedasticity model. Elsivier.
        link: http://wise.xmu.edu.cn/uploadfiles/paper-masterdownload/2009519932327055475115776.pdf
        """
        return _log(self.b-self.a)

    def summary(self) -> Dict[str, Union[float, Tuple[int, int]]]:
        """
        Returns:
            Dictionary of Uniform distirbution moments. This includes standard deviation.
        """
        return {
            'mean': self.mean(), 'median': self.median(), 'mode': self.mode(),
            'var': self.var(), 'std': self.std(), 'skewness': self.skewness(), 'kurtosis': self.kurtosis()
        }


import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
import pandas as pd
import numpy as np
features = ['age', 'workclass', 'fnlwgt', 'education', 'education-num', 'marital-status', 'occupation',
                           'relationship', 'race', 'sex', 'capital-gain', 'capital-loss', 'hours-per-week',
                           'native-country', '<=50k']
df = pd.read_csv('./adult-small.data',
                  names=features)
df.dropna()
df.drop_duplicates()
df = df[df['workclass'] != '?']
df = df[df['occupation'] != '?']
df = df[df['education'] != '?']
df = df[df['marital-status'] != '?']
df = df[df['relationship'] != '?']
df = df[df['race'] != '?']
df = df[df['sex'] != '?']
df = df[df['native-country'] != '?']
X = pd.get_dummies(df, columns=['workclass', 'education', 'marital-status', 'occupation', 'relationship', 'race', 'sex', 'native-country'])
X['<=50k'] = X['<=50k'].map({'<=50K':1, '>50K': 0})
y = X['<=50k']
X = X.drop(['<=50k'], axis=1)
n_samples = X.shape[0]

pca = PCA()
X_transformed = pca.fit_transform(X)
X_r = pca.fit_transform(X)

X_centered = X - np.mean(X, axis=0)
cov_matrix = np.dot(X_centered.T, X_centered) / n_samples
eigenvalues = pca.explained_variance_
for eigenvalue, eigenvector in zip(eigenvalues, pca.components_):
    print(eigenvalue)

#lda = LinearDiscriminantAnalysis(n_components=2)
#X_r2 = lda.fit(X, y).transform(X)

# Percentage of variance explained for each components
print('explained variance ratio (first two components): %s'
      % str(pca.explained_variance_ratio_))
print(pca.get_covariance())
plt.figure()
colors = ['navy', 'turquoise']
lw = 2

for color, i, target_name in zip(colors, [0, 1], ['no', 'yes']):
    plt.scatter(X_r[y == i, 0], X_r[y == i, 1], color=color, alpha=.8, lw=lw,
                label=target_name)
plt.legend(loc='best', shadow=False, scatterpoints=1)
plt.title('PCA of Second Dataset (Bank Data)')
plt.xlabel("feature 1 (transformed)")
plt.ylabel("feature 2 (transformed")

plt.clf()

loss_arr = []
fig, (ax1) = plt.subplots(1, 1, sharex=True)
for i in range(X.shape[1]):
    print("Doing {} components...".format(i+1))
    pca = PCA(n_components=i)
    X_transformed = pca.fit_transform(X)
    X_reconstructed = pca.inverse_transform(X_transformed)
    loss_arr.append(((X - X_reconstructed) ** 2).mean().sum())
ax1.plot(np.arange(X.shape[1]), loss_arr, label="reconstruction error")
ax1.set_ylabel("Avg RMSE")
plt.xlabel("n_components")
fig.suptitle("Reconstruction Error vs PCA Dimensionality Reduction DS1")
plt.savefig("pcaerr1.png")


import numpy as np

class AutoDiff():
    '''
    Class to use for automatic differentiation, forward mode

    Addresses the following need.

    1) The user has some function which maps some number of input variables, for example x and y, to a single
    output value, f(x,y).

    2) The user would like to calculate f(x,y) at a point (x,y) that they specify, as well as the partial
    derivatives df/dx and df/dy at that same point (x, y).

    By first defining their input variables as AutoDiff objects, and then using these objects in
    standard python calculations, including with numpy functions, the user is returned a AutoDiff object
    whose val attribute is the value of the function, and whose derv attribute is the derivative of the function.

    Which partial derivative is stored in the derv attribute of the final AutoDiff object depends on which
    input variable's derv attribute is initially set to 1, while the derv attributes of the other input variables
    are set to 0.

    Example:
    x = AutoDiff(3,1)
    y = AutoDiff(2,0)
    f = x**2 + y**2 + 4
    print(f.val, f.dev)
    (17, 6)
    '''

    def __init__(self, val, derv=1):
        self.val = float(val)
        self.derv = float(derv)

    # -----------Overloading Urnary functions ---------------------------------------------

    def __pos__(self):
        return AutoDiff(self.val, self.derv)

    def __neg__(self):
        return AutoDiff(-1*self.val, -1*self.derv)

    # -----------Add, Subtract, Multiple, Divide functions --------------------------------

    def __mul__(self, other):
        try:
            return AutoDiff(self.val * other.val, self.derv * other.val + other.derv*self.val)
        except:
            return AutoDiff(self.val * other, self.derv * other)

    def __rmul__(self, other):
        return AutoDiff(self.val * other, self.derv * other)

    def __truediv__(self, other):
        try:
            return AutoDiff(self.val/other.val, self.derv/other.val + ( -1*self.val/(other.val**2) ) *other.derv)
        except:
            return AutoDiff(self.val/other, self.derv/other)

    def __rtruediv__(self, other):
        return AutoDiff(other/self.val,  -1*other/(self.val**2) )

    def __add__(self, other):
        try:
            return AutoDiff(self.val + other.val, self.derv + other.derv)
        except:
            return AutoDiff(self.val + other, self.derv + 0)

    def __radd__(self, other):
        return AutoDiff(self.val + other, self.derv + 0)

    def __sub__(self, other):
        try:
            return AutoDiff(self.val - other.val, self.derv + -1*other.derv)
        except:
            return AutoDiff(self.val - other, self.derv + 0)

    def __rsub__(self, other):
        return AutoDiff(other - self.val, -1*self.derv + 0)

    # -----------Overloading Trigonometric functions ---------------------------------------------

    def sin(self):
        return AutoDiff(np.sin(self.val), np.cos(self.val)*self.derv)

    def cos(self):
        return AutoDiff(np.cos(self.val), -1*np.sin(self.val)*self.derv)

    def tan(self):
        return AutoDiff(np.tan(self.val), (1/(np.cos(self.val)**2))*self.derv)

    def arcsin(self):
        if (self.val < -1) or (self.val > 1):
            raise ValueError(' Input to arcsin() must be between -1 and 1 inclusive.')
        else:
            return AutoDiff(np.arcsin(self.val), 1/np.sqrt(1 - (self.val)**2)*self.derv)

    def arccos(self):
        if (self.val < -1) or (self.val > 1):
            raise ValueError(' Input to arccos() must be between -1 and 1 inclusive.')
        else:
            return AutoDiff(np.arccos(self.val), -1/np.sqrt(1 - (self.val)**2)*self.derv)

    def arctan(self):
        return AutoDiff(np.arctan(self.val), 1/(1 + (self.val)**2)*self.derv)

    # -----------Overloading Hyperbolic functions ---------------------------------------------

    def sinh(self):
        return AutoDiff(np.sinh(self.val), np.cosh(self.val)*self.derv)

    def cosh(self):
        return AutoDiff(np.cosh(self.val), np.sinh(self.val)*self.derv)

    def tanh(self):
        return AutoDiff(np.tanh(self.val), (1/np.cosh(self.val))**2*self.derv)

    def arcsinh(self):
        return AutoDiff(np.arcsinh(self.val), 1/np.sqrt(1 +(self.val)**2)*self.derv)

    def arccosh(self):
        return AutoDiff(np.arccosh(self.val), 1/np.sqrt((self.val)**2 - 1) *self.derv)

    def arctanh(self):
        return AutoDiff(np.arctanh(self.val), 1/(1 - (self.val)**2) *self.derv)

    # -----------Overloading Power functions ---------------------------------------------

    def __pow__(self, other):
        try:
            return AutoDiff(self.val**other.val, other.val*self.val**(other.val - 1)*self.derv  +  self.val**other.val*np.log(self.val)*other.derv )
        except:
            return AutoDiff(self.val**other, other*self.val**(other - 1)*self.derv)

    def __rpow__(self, other):
        return AutoDiff(other**self.val, other**self.val*np.log(other)*self.derv)

    def exp(self):
        return AutoDiff(np.exp(self.val), np.exp(self.val)*self.derv)

    # -----------Overloading log functions ---------------------------------------------

    def log(self):
        return AutoDiff(np.log(self.val), (1/self.val)*self.derv)

    def log10(self):
        return AutoDiff(np.log10(self.val), (1/(np.log(10)*self.val))*self.derv)

    def log2(self):
        return AutoDiff(np.log2(self.val), (1 / (np.log(2) * self.val)) * self.derv)

    # -----------Overloading the Square Root function ------------------------------------------

    def sqrt(self):
        return AutoDiff(np.sqrt(self.val), (1/(2*np.sqrt(self.val) )*self.derv) )

    # -----------Overloading the logistic function ---------------------------------------------

    def logist(self):
        return AutoDiff( 1/(1 + np.exp(-1*(self.val))), (1/(1 + np.exp(-1*(self.val)))) * (1 - (1/(1 + np.exp(-1*(self.val))))) *self.derv)

    #-----------Overloading the Comparison Operators >, <, >=, <=, ==. != -------------------------

    def __lt__(self, other):
        try:
            if self.val < other.val:
                return True
            else:
                return False
        except:
            if self.val < other:
                return True
            else:
                return False

    def __gt__(self, other):
        try:
            if self.val > other.val:
                return True
            else:
                return False
        except:
            if self.val > other:
                return True
            else:
                return False

    def __le__(self, other):
        try:
            if self.val <= other.val:
                return True
            else:
                return False
        except:
            if self.val <= other:
                return True
            else:
                return False

    def __ge__(self, other):
        try:
            if self.val >= other.val:
                return True
            else:
                return False
        except:
            if self.val >= other:
                return True
            else:
                return False

    def __eq__(self, other):
        try:
            if self.val == other.val:
                return True
            else:
                return False
        except:
            if self.val == other:
                return True
            else:
                return False

    def __ne__(self, other):
        try:
            if self.val != other.val:
                return True
            else:
                return False
        except:
            if self.val != other:
                return True
            else:
                return False


from collections import namedtuple
import numpy

class Perceptron:
  Sample = namedtuple('Sample', 'x y')

  def __init__(self, learning_rate=0.5):
    self.learning_rate = learning_rate

  def train(self, training_data):
    '''
    training_data example: namedtuple('Labeled_Sample', 'x y')
    for each sample in training_data: sample.x is data, sample.y is label
    '''
    self.dim = len(training_data[0].x)
    self.initWeights(self.dim)
    for sample in training_data:
      y_prime = numpy.dot(self.weight_vector, sample.x)
      if self.sign(y_prime) != self.sign(sample.y) or y_prime == 0:
        adjustment = [self.learning_rate * sample.y * x for x in sample.x]
        self.weight_vector = [a + b for (a,b) in zip(self.weight_vector, adjustment)]

  def classify(self, sampleX):
    '''
    sampleX includes ONLY the sample.x (data) component of a sample,
    and NOT the label which would be sample.y
    '''
    dotprod = numpy.dot(self.weight_vector, sampleX)
    if dotprod > 0:
      return 1
    else:
      return 0

  def initWeights(self, dim):
    self.weight_vector = [0] * dim

  def setLearningRate(self, number):
    self.learning_rate = number

  def sign(self, number):
    if number < 0:
      return -1
    if number > 1:
      return 1
    return 0

def testTraining():
    p = Perceptron()
    training_data = [
      p.Sample(x=[1,1,0,1,1],y=1),
      p.Sample(x=[0,0,1,1,0],y=-1),
      p.Sample(x=[0,1,1,0,0],y=1),
      p.Sample(x=[1,0,0,1,0],y=-1),
      p.Sample(x=[1,0,1,0,1],y=1),
      p.Sample(x=[1,0,1,1,0],y=-1)
    ]
    p.train(training_data)
    print "Testing training result:\n"
    print "Expect: \n", p.weight_vector, " to equal\n[0,1,0,-.5,.5]"

    print "\nTesting classification result:\n"
    print "Expect ", training_data[0].x, " to be classified as 1. Really: ", p.classify(training_data[0].x)
    print "Expect ", training_data[1].x, " to be classified as 0. Really: ", p.classify(training_data[1].x)
    print "Expect ", training_data[2].x, " to be classified as 1. Really: ", p.classify(training_data[2].x)
    print "Expect ", training_data[3].x, " to be classified as 0. Really: ", p.classify(training_data[3].x)
    print "Expect ", training_data[4].x, " to be classified as 1. Really: ", p.classify(training_data[4].x)
    print "Expect ", training_data[5].x, " to be classified as 0. Really: ", p.classify(training_data[5].x)

def testSign():
    print "Expect ", self.sign(-5), " to equal -1"
    print "Expect ", self.sign(0), " to equal 0"
    print "Expect ", self.sign(5), " to equal 1"

if __name__ == "__main__":
  testTraining()


# -*- coding: utf-8 -*-
"""
Created on Sun Dec  3 11:43:10 2017

@author: chris
"""

import numpy as np

def f(x,mu,sigma):
    return 1/(np.sqrt(2*np.pi)*sigma) * np.exp(-(x - mu)**2 / (2*sigma**2))

def mixture(x,p,mu,sigma):
    result = p[0]*f(x,mu[0],sigma[0]) # compatible with vector and scalar
    for h in range(1,len(p)):
        result += p[h]*f(x,mu[h],sigma[h])
    return result

def EM(y,p,mu,sigma):
    n = len(y)
    c = len(p)
    w = np.zeros([c,n])
    re_p = np.zeros(c)
    re_mu = np.zeros(c)
    re_sigma = np.zeros(c)
    re_logL = 0
    # E-step
    for j in range(n):
        tmp = mixture(y[j],p,mu,sigma)
        for i in range(c):
            w[i,j] = p[i] * f(y[j],mu[i],sigma[i]) / tmp
            re_logL += w[i,j] * (np.log(p[i]*f(y[j],mu[i],sigma[i])))
            # w[i,j] = p[i] * f(y[j],mu[i],sigma[i]) / mixture(y[j],p,mu,sigma)
            # use tmp to reduce computational complexity
    # M-step
    for i in range(c):
        re_p[i] = np.mean(w[i])
        re_mu[i] = sum(w[i]*y) / sum(w[i])
        re_sigma[i] = np.sqrt(sum(w[i] * (y-re_mu[i])**2) / sum(w[i]))
    return re_p,re_mu,re_sigma,re_logL

## simulation
np.random.seed(0)
N = 400
#Y = np.append(np.append( np.random.normal(loc=-2,scale=1,size=N//4) , np.random.normal(loc=2,scale=1,size=N//2)) , np.random.normal(loc=6,scale=1,size=N//4))
Y = np.append( np.random.beta(4,1,size=N//2) , np.random.beta(1,4,size=N//2))

## hyper-parameter
g = 5

## EM control parameter
cache_size = 1000
tol = 1e-4

## init
em_p = np.zeros([cache_size,g])
em_mu = np.zeros([cache_size,g])
em_sigma = np.zeros([cache_size,g])
em_l = np.zeros([cache_size])
em_la = np.zeros([cache_size])

em_p[0] = np.ones(g)/g
em_mu[0] = np.random.normal(loc=np.mean(Y),scale=np.std(Y),size=g)
em_sigma[0] = np.ones(g) * np.std(Y)
em_p[0],em_mu[0],em_sigma[0],em_l[0] = EM(Y,em_p[0],em_mu[0],em_sigma[0]) ## calculate logL via EM

t=0
while (t+1 < cache_size):
    em_p[t+1],em_mu[t+1],em_sigma[t+1],em_l[t+1] = EM(Y,em_p[t],em_mu[t],em_sigma[t])
    if (t>0):
        a = (em_l[t+1] - em_l[t]) / (em_l[t] - em_l[t-1])
        em_la[t+1] = em_l[t] + (em_l[t+1]-em_l[t])/(1-a)
        if (abs(em_la[t+1] - em_la[t]) < tol or np.isnan(em_la[t+1])):
            break;
    t += 1

from matplotlib import pyplot as plt
from scipy import stats

# example 1
n,bins,patches = plt.hist(Y,bins=30,normed=1,color=[0.4,0.8,0.8])
plt.plot(bins,mixture(bins,em_p[t-1],em_mu[t-1],em_sigma[t-1]),ls='--',color=[0.4,0.2,0.9])
plt.plot(bins,mixture(bins,[0.25,0.5,0.25],[-2,2,6],[1,1,1]),ls='--',color=[0.8,0.3,0.2])
plt.legend(('mixture model','real distribution'))
plt.title("g=%d , convergence after %d iterations"%(g,t))
plt.savefig(filename='E://workspace//ex1g%d.png'%g,dpi=100)

# example 2
n,bins,patches = plt.hist(Y,bins=30,normed=1,color=[0.4,0.8,0.8])
plt.plot(bins,mixture(bins,em_p[t-1],em_mu[t-1],em_sigma[t-1]),ls='--',color=[0.4,0.2,0.9])
plt.plot(bins,0.5*stats.beta.pdf(bins, 1, 4) + 0.5*stats.beta.pdf(bins, 4, 1), 'r-')
#plt.plot(bins,0.5*stats.beta.pdf(bins, 4, 1), 'g-')
plt.legend(('mixture model','real distribution'))
plt.title("g=%d , convergence after %d iterations"%(g,t))
plt.savefig(filename='E://workspace//ex2g%d.png'%g,dpi=100)


# -*- coding: utf-8 -*-
"""
Created on Wed Feb 27 08:55:58 2019

@author: Ham

HackerRanch Challenge: Linear Algebra

The NumPy module also comes with a number of built-in routines
for linear algebra calculations.
These can be found in the sub-module linalg.

linalg.det

The linalg.det tool computes the determinant of an array.

print numpy.linalg.det([[1 , 2], [2, 1]])       #Output : -3.0

linalg.eig

The linalg.eig computes the eigenvalues and right eigenvectors of a square array.

vals, vecs = numpy.linalg.eig([[1 , 2], [2, 1]])
print vals                  #Output : [ 3. -1.]
print vecs                  #Output : [[ 0.70710678 -0.70710678]
                            #          [ 0.70710678  0.70710678]]

linalg.inv

The linalg.inv tool computes the (multiplicative) inverse of a matrix.

print numpy.linalg.inv([[1 , 2], [2, 1]])   #Output : [[-0.33333333  0.66666667]
                                            #          [ 0.66666667 -0.33333333]]

Other routines can be found here

Task

You are given a square matrix A with dimensions NxN.
Your task is to find the determinant.

Input Format

The first line contains the integer N.
The next N lines contains the N space separated elements of array A.

Output Format

Print the determinant of A.

Sample Input

2
1.1 1.1
1.1 1.1

Sample Output

0.0

"""

import io
import numpy

STDIN_SIO = io.StringIO("""
3
1 2 3
4 5 6
1 2 1
""".strip())

if __name__ == '__main__':
    #a = [list(map(float, input().split())) for _ in range(int(input()))]
    a = [list(map(float, STDIN_SIO.readline().strip().split()))
         for _ in range(int(STDIN_SIO.readline()))]
    print(a)
    print(round(numpy.linalg.det(a), 2))


## =========================================================================
## @author Leonardo Florez-Valencia (florez-l@javeriana.edu.co)
## =========================================================================

import numpy, sys
import matplotlib.pyplot as plotter
from matplotlib import cm

# Check input arguments
if len( sys.argv ) < 2:
  print( "Usage:", sys.argv[ 0 ], "input.csv" )
  sys.exit( 1 )
# end if

# Load data
print( "reading" )
X = numpy.genfromtxt( sys.argv[ 1 ], delimiter = ',' )
print( "done: ", X.shape )

# PCA
m = X.mean( axis = 0 )
c = X - m
S = ( c.T @ c ) / float( X.shape[ 0 ] )
[ v, R ] = numpy.linalg.eig( S )
idx = v.argsort( )[ : : -1 ]
v = v[ idx ]
R = R[ : , idx ]
retention = v / v.sum( )

# Projection onto PCA
pX = ( ( X - m ) @ R )[ : , : 1 ]

# ---> Usar pX como los datos de entrada para la RN, SVM, Arbol, Bagg...

pX = numpy.append( pX, numpy.zeros( ( pX.shape[ 0 ], 1 ) ), axis = 1 )

# Backprojection from PCA
rX = ( pX @ R.T ) + m

print( "mean:\n", m )
print( "covariance:\n", S )
print( "values:\n", v )
print( "vectors:\n", R )
print( "retention:\n", retention )
print( "inverse:\n", numpy.linalg.inv( R ) )
print( "transpose:\n", R.T )
print( "inverse - transpose:\n", numpy.linalg.inv( R ) - R.T )
print( "norm e0:\n", numpy.linalg.norm( R, axis = 0 ) )
print( "norm e1:\n", numpy.linalg.norm( R, axis = 1 ) )
print( "Frobenius norm:\n", numpy.linalg.norm( R ) )

# Show initial configuration
fig, ax1 = plotter.subplots( )
ax1.axis( 'equal' )
data_X, data_Y = zip( *X )
data_pX, data_pY = zip( *pX )
data_rX, data_rY = zip( *rX )
plotter.scatter(
    data_X, data_Y, c = "#ff0000", marker = "x"
    )
plotter.scatter(
    data_pX, data_pY, c = "#00ff00", marker = "o"
    )
plotter.scatter(
    data_rX, data_rY, c = "#0000ff", marker = "+"
    )
plotter.quiver(
    [ m[ 0 ], m[ 0 ] ], [ m[ 1 ], m[ 1 ] ],
    [ R[ 0 ][ 0 ], R[ 0 ][ 1 ] ],
    [ R[ 1 ][ 0 ], R[ 1 ][ 1 ] ]
    )
plotter.show( )

# eof - $RCSfile$


#!/opt/local/bin/python3
# -*- coding: utf-8 -*-
# Copyright (c) 2020-2021 Cody R. Drisko. All rights reserved.
# Licensed under the MIT License. See the LICENSE file in the project root for more information.
#
# Name: validate.py - Version 1.0.0
# Author: crdrisko
# Date: 10/29/2020-09:20:01
# Description: Validate the results of the finiteDifferencesExample using numpy's gradient and scipy's cumtrapz

import numpy as np
from scipy import integrate

x = np.linspace(0, 10000.1, 100002)
y = (4 * x**2) + (2 * x) - 7

derivative1st = np.gradient(y, x)
integral2nd = integrate.cumtrapz(derivative1st, x, initial=0.0)

integral1st = integrate.cumtrapz(y, x, initial=0.0)
derivative2nd = np.gradient(integral1st, x)

counter1 = 0
counter2 = 0

tolerance = 2 * (x[1] - x[0])

for i in range(1, np.size(x) - 1):
    if y[i] - (integral2nd[i] - 7) <= tolerance:
        counter1 += 1

    if y[i] - derivative2nd[i] - 2 <= tolerance:
        counter2 += 1

print("The calculation of ∫(dy / dx) dx resulted in %d out of %d elements within %0.1f of the exact result."
    % (counter1, np.size(y) - 2, tolerance))

print("The calculation of d(∫y dx) / dx resulted in %d out of %d elements within %0.1f of the exact result."
    % (counter2, np.size(y) - 2, tolerance))


import numpy as np
from caid.cad_geometry import cad_nurbs
from matplotlib import pyplot as plt
from numpy import pi, array

# ... create a 3D curve
def make_curve():
    T = np.array([0., 0., 0., 0.5, 0.75, 0.75, 1., 1., 1.])
    C = [[ 6.0, 0.0, 6.0],
         [-5.5, 0.2, 5.5],
         [-5.0, 1.3,-5.0],
         [ 4.5, 2.,-4.5],
         [ 4.0, 2.5, 4.0],
         [-3.5, 2.75, 3.5],]

    crv = cad_nurbs([T], C)
    return crv
# ...

# ... custom plot curve
def curve_plot(crv, text="P", color="r" \
               , xmin=None, xmax=None \
               , ymin=None, ymax=None \
               , n_points=100):

    frame1 = plt.gca()
    frame1.axes.get_xaxis().set_visible(False)
    frame1.axes.get_yaxis().set_visible(False)
    frame1.set_frame_on(False)

    if (xmin is not None) and (xmax is not None):
        plt.xlim(xmin, xmax)

    if (ymin is not None) and (ymax is not None):
        plt.ylim(ymin, ymax)

    t = np.linspace(0.0,1.0,n_points)
    C = crv.points
    P = crv(t)
    x = P[:,0]
    y = P[:,1]

    plt.plot(x,y, color)

    i = 0
    for [xc,yc,zc] in C:
        label = "$"+text+"_{%s}$" % (str(i+1))
        plt.text(xc-0.05,yc+0.05, label, fontsize=14, color="k")
        plt.plot(xc,yc, "ok")
        i += 1
    # control polygon
    plt.plot(C[...,0], C[...,1], "-.k")
# ...

# ... create a 3D curve
crv0 = make_curve()
crv1 = make_curve()
# ...

# ... refine the curve by inserting the knot 0.5
crv1.refine(0, [0.5])
# ...

# ... raising the spline degree
crv1.elevate(0,1)
# ...

# ... define the matplotlib grid
xmin = -6.5 ; xmax = 7.
ymin = -0.5 ; ymax = 3.25
# ...

curve_plot(crv0, text="P", color="b")
plt.savefig("curve_ex1_original.png")
plt.clf()

curve_plot(crv1, text="Q", color="r")
plt.savefig("curve_ex1_refined.png")
plt.clf()


import numpy as np
import random
import cmath
import math
import copy
import time

#generate two large odd numbers of a specific bit size
def generateLargeOdd(bitSize):
    a = random.getrandbits(128) | 1
    #print(a)
    return a

#Primality check using Fermat Theorem to identify base 2 pseudoprimes
def checkPseudoprimeFermat(num):
    base = 2
    if (checkPrimeModularExponentiation(num-1, base, num)) == 1:
        return True #returns true if prime
    return False #returns false if composite

#Primality check using Modular Exponentiation to the base 2
def checkPrimeModularExponentiation(num2, base, num):
    res = 1
    base%=num
    if (base == 0) :
        res = 0
        return res
    while (num2 > 0) :
        #print("base:", base)
        if ((int(num2) & 1) == 1) :
            res = (res * base) % num
        base = (base * base) % num
        num2 = int(num2) >> 1 # num2 = num/2
    return res #if res is 1 or n-1 it is prime

#Helper function for Miller Rabin test
def millerHelper(d, n):
    a = 2 + random.randint(1, n - 4)
    #print("base:", a)
    x = checkPrimeModularExponentiation(d, a, n)
    if (x == 1 or x == n - 1):
        return True
    while (d != n - 1):
        x = (x * x) % n
        d *= 2
        if (x == 1):
            return False
        if (x == n - 1):
            return True
    return False

#Primality check using Miller Rabin test
def checkPrimeMillerRabin(n):
    k = 4 #no. of iterations
    if (n <= 1 or n == 4):
        return False
    if (n <= 3):
        return True
    d = n - 1
    while (d % 2 == 0):
        d //= 2
    for i in range(k): #Iterating k times
        if not millerHelper(d, n):
            return False
    return True

#Primality check using Trial Division
def checkPrimeTrialDivision(a):
    for i in range(2, math.ceil(pow(a, 0.5))):
        if a%i == 0:
            return False
    return True

#Find relative prime of a number
def relativePrime(p,q):
    phi = (p-1)*(q-1)
    for e in range(3, phi, 2):
        if phi%e != 0:
            return e

#Extended Euclid
def egcd(a, b):
    s = 0; old_s = 1
    t = 1; old_t = 0
    r = b; old_r = a
    while r != 0:
        quotient = old_r // r
        old_r, r = r, old_r - quotient * r
        old_s, s = s, old_s - quotient * s
        old_t, t = t, old_t - quotient * t
    return old_r, old_s, old_t # return gcd, x, y

#To find modular multiplicative inverse of e
def modularMultiplicativeInverse(e, p, q):
    phi = (p-1)*(q-1)
    gcd, x, y = egcd(e, phi)
    if x < 0:
        x += phi
    return x

def rsaEncrypt(M, e, n):
    C = []
    for m in M:
        C_inner = []
        for i in m:
            ex = checkPrimeModularExponentiation(e, i, n)
            #ex = (pow(m, e))%n
            C_inner.append(ex)
        C.append(C_inner)
    return C

def rsaDecrypt(C, d, n):
    MD = []
    for c in C:
        MD_inner = []
        for i in c:
            de = checkPrimeModularExponentiation(d, i, n)
            #de = (pow(c, d))%n
            MD_inner.append(chr(de))
        MD_in = "".join(MD_inner)
        MD.append(MD_in)
    return MD

def rsaHelper(M):
    count = 0
    #a = 6967
    M_original = copy.deepcopy(M)
    M_ascii = []
    for i in range(len(M)):
        M[i] = str(M[i])
        ma = [ord(c) for c in M[i]]
        M_ascii.append(ma)
    largePrimes = []
    while(count<2):
        oddNum = generateLargeOdd(128)
        if count==1 and oddNum == largePrimes[0]: continue
        if checkPrimeModularExponentiation(oddNum-1, 2, oddNum)!=1:
            continue
        if not checkPseudoprimeFermat(oddNum):
            if not checkPrimeMillerRabin(oddNum):
                continue #continue searching if number is composite
        #if not checkPrimeTrialDivisionLoop(oddNum):
            #continue
        count+=1
        largePrimes.append(oddNum)

    a, b = largePrimes[0], largePrimes[1]
    #print("The two large prime numbers: ", a, b)
    #print("Original message M: ", M)
    n = a*b
    e = relativePrime(a, b)
    #print("e: ",e)
    d = modularMultiplicativeInverse(e, a, b)
    #print("d: ", d)
    C = rsaEncrypt(M_ascii, e, n)
    #print("CypherText C: ", C)
    MD = rsaDecrypt(C, d, n)
    #print("Decrypted message MD: ",MD)

    return MD

if __name__ == '__main__':

    VC = [5,7, 8, 10]
    #VC = [0j, (2+1.1102230246251565e-16j), (1.4997597826618576e-32-2.4492935982947064e-16j), (2+4.440892098500626e-16j)]
    VCD = rsaHelper(VC)
    print(np.allclose(VC, VCD))


import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import scipy
import seaborn as sns

def PDF(n):
    from scipy.stats import kde
    density = kde.gaussian_kde(df[n])
    xgrid = np.linspace(df[n].min(), df[n].max(), 100)
    plt.hist(df[n], bins=10, density=True)
    plt.plot(xgrid, density(xgrid), 'r-')
    plt.show()

def BoxWithWhiskers (n):
    #sns.boxplot(data=df[n], orient='h')
    plt.boxplot(df[n])
    plt.show()

def MaximumLikelihood (n):
    from scipy.stats import lognorm
    x = np.linspace(df[n].min(), df[n].max(), 100)
    parameters = scipy.stats.lognorm.fit(df[n])
    plt.hist(df[n], bins=10, density=True)
    pdf = scipy.stats.lognorm.pdf(x, parameters[0], parameters[1],parameters[2])
    plt.plot(x, pdf, 'r--')
    plt.show()

def log(x, m, s):
    return np.exp(-((np.log(x)-m)**2)/(2*s**2))/(x*s*(2*np.pi)**0.5)

def LeastSquaresMethod (n):
    value, binsedge, _ = plt.hist(df[n], bins=10, density=True)
    ydata = [value[i] for i in range(len(value)-1)]
    xdata = [(binsedge[i] + binsedge[i + 1]) / 2 for i in range(len(value) - 1)]
    popt, pcov = scipy.optimize.curve_fit(log, xdata, ydata)
    mu, sigma = popt
    #print('mu = ',mu)
    x = np.linspace(df[n].min(), df[n].max(), 100)
    x1 = x.tolist()
    x2 = [log(x2,mu,sigma) for x2 in x1]
    plt.plot(x, x2, 'r--')
    plt.hist(df[n], bins=5, density=True)
    plt.show()

def QQPlotLognorm (n, m, a, b):
    from scipy.stats import lognorm
    # Calculation of quantiles
    parameters = scipy.stats.lognorm.fit(df[n])
    percs = np.linspace(0, 100, m)
    qn_first = np.percentile(df[n], percs)
    qn_lognorm = scipy.stats.lognorm.ppf(percs / 100.0, *parameters)

    # Building a quantile biplot
    plt.figure(figsize=(10, 10))
    plt.plot(qn_first, qn_lognorm, ls="", marker="o", markersize=6)
    x = np.linspace(np.min(df[n]), np.max(df[n]))
    plt.plot(x, x, color="k", ls="--")
    plt.xlim(a, b)
    plt.ylim(a, b)
    plt.xlabel(f'Empirical distribution')
    plt.ylabel('Theoretical (lognormal) distribution')
    plt.show()

def QQPlotGamma (n, m, a, b):
    from scipy.stats import gamma
    # Calculation of quantiles
    parameters = scipy.stats.gamma.fit(df[n])
    percs = np.linspace(0, 100, m)
    qn_first = np.percentile(df[n], percs)
    qn_gamma = scipy.stats.gamma.ppf(percs / 100.0, *parameters)

    # Building a quantile biplot
    plt.figure(figsize=(10, 10))
    plt.plot(qn_first, qn_gamma, ls="", marker="o", markersize=6)
    x = np.linspace(np.min(df[n]), np.max(df[n]))
    plt.plot(x, x, color="k", ls="--")
    plt.xlim(a, b)
    plt.ylim(a, b)
    plt.xlabel(f'Empirical distribution')
    plt.ylabel('Theoretical (gamma) distribution')
    plt.show()

def QQPlotExp (n, m, a, b):
    from scipy.stats import expon
    # Calculation of quantiles
    parameters = scipy.stats.expon.fit(df[n])
    percs = np.linspace(0, 100, m)
    qn_first = np.percentile(df[n], percs)
    qn_expon = scipy.stats.expon.ppf(percs / 100.0, *parameters)

    # Building a quantile biplot
    plt.figure(figsize=(10, 10))
    plt.plot(qn_first, qn_expon, ls="", marker="o", markersize=6)
    x = np.linspace(np.min(df[n]), np.max(df[n]))
    plt.plot(x, x, color="k", ls="--")
    plt.xlim(a, b)
    plt.ylim(a, b)
    plt.xlabel(f'Empirical distribution')
    plt.ylabel('Theoretical (gamma) distribution')
    plt.show()

def QQPlotBeta (n, m, a, b):
    from scipy.stats import beta
    # Calculation of quantiles
    parameters = scipy.stats.beta.fit(df[n])
    percs = np.linspace(0, 100, m)
    qn_first = np.percentile(df[n], percs)
    qn_beta = scipy.stats.beta.ppf(percs / 100.0, *parameters)

    # Building a quantile biplot
    plt.figure(figsize=(10, 10))
    plt.plot(qn_first, qn_beta, ls="", marker="o", markersize=6)
    x = np.linspace(np.min(df[n]), np.max(df[n]))
    plt.plot(x, x, color="k", ls="--")
    plt.xlim(a, b)
    plt.ylim(a, b)
    plt.xlabel(f'Empirical distribution')
    plt.ylabel('Theoretical (gamma) distribution')
    plt.show()

def QQPlotPareto (n, m, a, b):
    from scipy.stats import pareto
    # Calculation of quantiles
    parameters = scipy.stats.pareto.fit(df[n])
    percs = np.linspace(0, 100, m)
    qn_first = np.percentile(df[n], percs)
    qn_pareto = scipy.stats.pareto.ppf(percs / 100.0, *parameters)

    # Building a quantile biplot
    plt.figure(figsize=(10, 10))
    plt.plot(qn_first, qn_pareto, ls="", marker="o", markersize=6)
    x = np.linspace(np.min(df[n]), np.max(df[n]))
    plt.plot(x, x, color="k", ls="--")
    plt.xlim(a, b)
    plt.ylim(a, b)
    plt.xlabel(f'Empirical distribution')
    plt.ylabel('Theoretical (Pareto) distribution')
    plt.show()

def TestLognorm (n):
    from scipy.stats import lognorm
    # Calculation of the Kolmogorov-Smirnov test and chi-square
    parameters = scipy.stats.lognorm.fit(df[n])
    ks = scipy.stats.kstest(df[n], 'lognorm', parameters, N=100)
    cm = scipy.stats.cramervonmises(df[n], 'lognorm', parameters)
    print(ks)
    print(cm)

def TestGamma (n):
    # Calculation of the Kolmogorov-Smirnov test and chi-square
    parameters = scipy.stats.gamma.fit(df[n])
    ks = scipy.stats.kstest(df[n], 'gamma', parameters, N=100)
    cm = scipy.stats.cramervonmises(df[n], 'gamma', parameters)
    print(ks)
    print(cm)

def TestExp (n):
    # Calculation of the Kolmogorov-Smirnov test and chi-square
    parameters = scipy.stats.expon.fit(df[n])
    ks = scipy.stats.kstest(df[n], 'expon', parameters, N=100)
    cm = scipy.stats.cramervonmises(df[n], 'expon', parameters)
    print(ks)
    print(cm)

def TestBeta (n):
    from scipy.stats import beta
    # Calculation of the Kolmogorov-Smirnov test and chi-square
    parameters = scipy.stats.beta.fit(df[n])
    ks = scipy.stats.kstest(df[n], 'beta', parameters, N=100)
    cm = scipy.stats.cramervonmises(df[n], 'beta', parameters)
    print(ks)
    print(cm)

def TestPareto (n):
    from scipy.stats import pareto
    parameters = scipy.stats.pareto.fit(df[n])
    ks = scipy.stats.kstest(df[n], 'pareto', parameters, N=100)
    cm = scipy.stats.cramervonmises(df[n], 'pareto', parameters)
    print(ks)
    #print(cm)

def TestVeibulo (n):
    from scipy.stats import exponweib
    parameters = scipy.stats.exponweib.fit(df[n])
    ks = scipy.stats.kstest(df[n], 'exponweib', parameters, N=100)
    cm = scipy.stats.cramervonmises(df[n], 'exponweib', parameters)
    print(ks)
    print(cm)

path_to_file = 'avocado.csv'
data = pd.read_csv(r'C:\Users\Ксения\Documents\Учеба\ИТМО\1 семестр\Методы\Лаб 1\Код Лаб 1\avocado.csv')
df = pd.DataFrame(data,columns= ['AveragePrice', 'Total Volume', '4046'])

#PDF('Total Volume')
#BoxWithWhiskers('Total Volume')
#MaximumLikelihood('4046')
#LeastSquaresMethod('4046')

#QQPlotLognorm('AveragePrice', 21, 0, 4)
#QQPlotLognorm('Total Volume', 1000, 0, 10000000)
#QQPlotLognorm('4046', 100, 0, 10000)

#TestLognorm('AveragePrice')
#TestLognorm('Total Volume')
#TestLognorm('4046')

#QQPlotGamma('AveragePrice', 21, 0, 4)
#QQPlotGamma('Total Volume', 1000, 0, 100000000)
#QQPlotGamma('4046', 100, 0, 100000000)
"""
TestGamma('AveragePrice')
TestGamma('Total Volume')
TestGamma('4046')

QQPlotExp('AveragePrice', 21, 0, 4)
QQPlotExp('Total Volume', 1000, 0, 10000000)
QQPlotExp('4046', 1000, 0, 4000)
TestExp('AveragePrice')
TestExp('Total Volume')
TestExp('4046')

QQPlotBeta('AveragePrice', 21, 0, 4)
QQPlotBeta('Total Volume', 1000, 0, 10000000)
QQPlotBeta('4046', 100, 0, 10000)
"""
#TestBeta('AveragePrice')
#TestBeta('Total Volume')
#TestBeta('4046')

QQPlotPareto('Total Volume', 1000, 0, 10000)
TestPareto('Total Volume')

#TestVeibulo('Total Volume')


'''
linear_algebra.py by Dominic Reichl, @domreichl
module for math_for_machine_learning.py that introduces vectors and matrices

key concepts:
1.1. vectors: magnitude, direction, addition
1.2. vector multiplication: scalar, dot product, cross product
2.1. matrices: addition, subtraction, negation, transposition, multiplication, inverse
2.2. systems of equations
3.1. eigenvectors and eigenvalues
'''

'''
PART 1: VECTORS

A vector is a numberic element that has magnitude and direction.
Vectors describe spatial lines and planes; they enable calculations
that explore multi-dimensional relationships.
'''

def vector_properties(x, y):
    '''
        Takes the elements of a vector with 2 dimensions as input.
        Plots the vector and returns its magnitude and direction.
    '''

    print('-'*30 + '\nVector Properties\n' + '-'*30)

    import numpy as np # for building an array
    import matplotlib.pyplot as plt # for plotting the vector
    from math import sqrt, atan, degrees, log # for calculating vector properties

    v = np.array([x, y]) # define vector as a 2d array
    print('Vector:', v)

    # calculate vector magnitude
    vMag = sqrt(x**2 + y**2) # raw equation
    vMagAlt = np.linalg.norm(v) # numpy equation
    vMag == vMagAlt # True
    print('Magnitude:', vMag)

    # calculate vector direction (vector angle as inverse tangent)
    if x > 0: vAtan = degrees(atan(y/x))
    elif x < 0 and y > 0: vAtan = degrees(atan(y/x)) + 180
    elif x < 0 and y < 0: vAtan = degrees(atan(y/x)) - 180
    vAtanAlt = np.degrees(np.arctan2(y, x)) # numpy equation (saves 2 lines of code)
    vAtan == vAtanAlt # True
    print('Direction:', vAtan)

    # plot vector as arrow in a coordinate system (quiver plot)
    plt.grid()
    plt.quiver([0],[0], *v, angles='xy', scale_units='xy', scale=1, color='green')
    plt.xlim(-10, 10); plt.ylim(-10, 10)
    plt.show()

    return vMag, vAtan

def vector_addition(v1, v2):
    '''
        Takes as input two 2d vectors as lists.
        Plots the vectors and their sum, then returns the latter.
    '''

    print('-'*30 + '\nVector Addition\n' + '-'*30)

    import numpy as np # for building arrays
    import matplotlib.pyplot as plt # for plotting vectors

    v1 = np.array(v1) # first vector as array
    v2 = np.array(v2) # second vector as array
    vSum = v1 + v2 # vector addition
    vectors = np.array([v1, v2, vSum])
    print('Vector 1:', v1)
    print('Vector 2:', v2)
    print('Vector sum:', vSum)

    # plot vectors as arrows (quiver plot)
    plt.grid()
    plt.quiver([0],[0], vectors[:,0], vectors[:,1], angles='xy', scale_units='xy', scale=1, color=['g', 'b', 'r'])
    plt.xlim(-10, 10); plt.ylim(-10, 10)
    plt.show()

    return vSum

def vector_multiplication(x, y):
    '''
        Takes as input 2d or 3d vector(s) as list(s) and/or number(s) as integer(s).
        Returns scalar, dot, or cross product as output.
    '''

    print('-'*30 + '\nVector Multiplication\n' + '-'*30)

    import numpy as np

    if type(x) == list and type(y) == int:
        product = np.array(x) * y # scalar product
        print('Vector:', np.array(x))
        print('Multiplier:', y)
        print('Scalar product:', product)
        return product
    elif type(x) == int and type(y) == list:
        product = x * np.array(y) # scalar product
        print('Multiplier:', y)
        print('Vector:', np.array(y))
        print('Scalar product:', product)
        return product
    elif type(x) == list and type(y) == list:
        v1 = np.array(x)
        v2 = np.array(y)
        print('Vector 1:', v1)
        print('Vector 2:', v2)
        if len(x) == 2:
            product = v1 @ v2 # dot product; alternative: np.dot(v1,v2)
            print('Dot product:', product)
            return product
        elif len(x) == 3:
            product = np.cross(v1,v2) # cross product
            print('Cross product:', product)
            return product

'''
PART 2: MATRICES

A matrix is an array of numbers that are arranged in rows and columns.
For example, a 2x3 matrix has two rows and three columns.
'''

def matrix_operations(A, B, operation='add'):
    '''
        Takes as input 2 matrices as nested lists and an operation.
        Possible operations:
            - addition ('add'; default)
            - subtraction ('sub')
            - negation ('neg')
            - transposition ('t')
            - multiplication ('mul')
            - division ('div'; inverse multiplication)
        Returns result of operation.
    '''

    print('-'*30 + '\nMatrix Operation\n' + '-'*30)

    import numpy as np

    A = np.matrix(A) # .matrix allows multiplication with * and inverse with .I
    B = np.matrix(B)

    if operation == 'add': return print(A+B) # sum of matrices
    if operation == 'sub': return print(A-B) # difference of matrices
    if operation == 'neg': return print(-A,-B) # negation of matrices
    if operation == 'transpose': return print(A.T,B.T) # transposition of matrices
    if operation == 'mul': # matrix multiplication if allowed by shapes
        if np.shape(A)[0] == np.shape(B)[1] and np.shape(A)[1] == np.shape(B)[0]:
            return print(A*B) # dot product; alternatives: A@B; np.dot(A,B)
        else:
            return print('These matrices cannot be multiplied.')
    if operation == 'div': # matrix inverse multiplication if allowed by shapes
        if np.shape(A)[0] == np.shape(B)[0] and np.shape(A)[1] == np.shape(B)[1]:
            return print(A*B.I) # dot product; alternative: A@np.linalg.inv(B)
        else:
            return print('These matrices cannot be divided.')

def solve_system_of_equations(xy, results):
    '''
        Takes as input 2 matrices as nested lists representing equations.
        For example, 'xy' might be [[2,4],[6,2]] for equations
        2x+4y and 6x+2y, and 'results' might be [[18,34]].
        Returns values for x and y that solve the equation system.
    '''

    print('\n' + '-'*30 + '\nSolving System of Equations\n' + '-'*30)

    import numpy as np

    xy = np.matrix(xy)
    results = np.matrix(results)

    XY = xy.I @ results # inverse multiplication
    x = float(XY[0][0])
    y = float(XY[1][0])

    print('x is', x, '\ny is', y)
    return x,y # return values for x and y as floats in a tuple

'''
PART 3: EIGENVECTORS AND EIGENVALUES

An eigenvalue is a scalar multiplier that produces an eigenvector
via linear transformation of a non-zero vector.
'''

def eigenvalues_eigenvectors(A):
    '''
        Takes as input a matrix as a nested list.
        Returns eigenvalues and eigenvectors.
    '''

    print('\n' + '-'*30 + '\nEigenvectors, Eigenvalues\n' + '-'*30)

    import numpy as np

    A = np.array(A) # turn list into array
    eVals, eVecs = np.linalg.eig(A) # get eigenvalues and eigenvectors

    print('Matrix:\n', A)
    print('Lambdas:', eVals[0], 'and', eVals[1])
    print('Vectors:', eVecs[:,0], 'and', eVecs[:,1])

    return eVals, eVecs


import numpy as np

class univariate_Linear_Regression:
    # initialize slope and intercept
    def __init__(self):
        self.m = 0.0  # slope
        self.b = 0.0  # intercept

# sum of square deviation for single variable
    def ss_x(self, x):
        return sum((x-np.mean(x))**2)

    # sum of square deviation for two variables x and y
    def ss_xy(self, x, y):
        x_mean = np.mean(x)			# mean of x
        y_mean = np.mean(y)			# mean of y
        return sum((x-x_mean)*(y-y_mean))

    # Train our regression model based on shape and size
    def train(self, x, y):

        # verify the features and labels are of same size
        assert(len(x) == len(y))

        # calculate the slope
        ss_x = self.ss_x(x)
        ss_xy = self.ss_xy(x, y)
        self.m = ss_xy/ss_x

        # calculate the intercept
        self.b = (np.mean(y)) - (self.m)*(np.mean(x))

    # return the predicted values based on feature and weights
    def predict(self, x):
        predictions = np.zeros(len(x))
        for i in range(len(x)):
            predictions[i] = self.m * x[i] + self.b         # Y = mx + b
        return predictions

# Dataset to train model
x = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
y = np.array([1.1, 1.9, 2.8, 4, 5.2, 5.8, 6.9, 8.1, 9, 9.9])

# Initialize our model
reg = univariate_Linear_Regression()

# Train our model with the data
reg.train(x, y)

# Make a prediction
print(reg.predict(x))


import numpy as np
import matplotlib.pyplot as plt
import random
import scipy.stats as stat
from scipy.optimize import curve_fit

def p(x,norm):                           #probability density function (PDF) for the small angle Rutherford distribution
    p = (norm*(2*x))/(1+x**2)**2
    return p

def c_inv(t):                            #inverse cumulative pdf c^-1(t)=x
    c=np.sqrt(t/(1-t))
    return c

N=10**6                             #number of extractions

#random sampling of Rutherford distribution
x=[]
for i in range (0, N):
    n=random.random()           #extract 10^6 random numb. between 0 and 1
    x.append(c_inv(n))
x=np.array(x)                #calculate the inverse of the cumulative in the random point previosly generated

#fit histogram with Rutherford ditribution
a=0
b=20
n_bins=1000                         #number of bins

hist, bins=np.histogram(x, bins=n_bins , density=False, range=(a,b))
z=np.array([0.5 * (bins[i] + bins[i+1]) for i in range(len(bins)-1)]) #bins center
fit, cov=curve_fit(p, xdata=z, ydata=hist, p0=(10**4))
norm_fit = fit
norm_err = np.sqrt(np.diag(cov))

#compute chi square
chi2, p_value = stat.chisquare(hist, p(z,norm_fit))
dof=n_bins-1
chi2_red=chi2/dof

#plot the rutherford formula histogram and its best fit curve
y=np.linspace(0,100, 10000)
plt.figure()
plt.hist(x, color='orange', bins=n_bins , histtype='step', range=(a,b))
plt.plot(y, p(y, norm_fit), 'k')
plt.xlabel('$x$', fontsize=12)
plt.ylabel('$Counts$', fontsize=12)
plt.yscale('log')
plt.xlim(0, 15, 3)
plt.ylim(1, 3*10**4)
plt.xticks(np.arange(0,16, 3), fontsize=10)
plt.yticks(fontsize=10)
props = dict(boxstyle='round', facecolor='orange', alpha=0.5)
plt.text(7, 5*10**3, '$\\chi^2$/ d.o.f = %.2f / %u = %.2f' %(chi2, dof, chi2_red), fontsize=10, bbox=props)


import numpy as np

def jacobi(A, B, x_init, epsilon=0.001, max_iterations=500):
    D = np.diag(np.diag(A))
    LU = A - D
    x = x_init
    for i in range(max_iterations):
        D_inv = np.diag(1 / np.diag(D)) #inverse diagonal
        x_new = np.dot(D_inv, B - np.dot(LU, x))
        if np.linalg.norm(x_new - x) < epsilon:
            return x_new
        x = x_new
        print(i, x)

a = np.array([
    [11, 2.6, 0.4, -3.3],
    [-4.1, 22, 2.2, -3.5],
    [-1.2, -15.7, 33, -0.1],
    [-7.7, -6.3, 4.8, 44]
])
b = np.array([51.27, -64.66, -104.96, -27.12])

x_init = np.zeros(len(b)) #starting vector
print("a:",a,"\n b:" ,b, "\n")
x = jacobi(a, b, x_init)
print("\nfinal:", x)


import numpy as np
import math

class BaseRegression(object):

    def __init__(self, n_iter, lr):
        self.n_iter = n_iter
        self.lr = lr
        self.w = []
        self.errors = []

    def init_weight(self, n_feat):
        bound = 1 / n_feat
        self.w = np.random.uniform(-bound, bound, (n_feat, ))

    def fit(self, X, y):

        X = np.insert(X, 0, 1, axis=1)
        self.init_weight(n_feat=X.shape[1])

        for it in range(self.n_iter):
            y_pred = X.dot(self.w)

            mse = np.mean(0.5 * (y_pred - y)**2)
            self.errors.append(mse)

            grad = (y_pred - y).dot(X)
            self.w -= self.lr * grad

    def predict(self, X):
        X = np.insert(X, 0, 1, axis=1)
        y_pred = X.dot(self.w)
        return y_pred

class LinearRegression(BaseRegression):

    def __init__(self, n_iter=1000, lr=0.01):
        super(LinearRegression, self).__init__(n_iter, lr)

    def fit(self, X, y):
        super(LinearRegression, self).fit(X, y)


import numpy as np

def standardize(data: np.array) -> np.array:
    """Function performing (x - mean) / sd standarization

    Args:
        data (np.array): _description_

    Returns:
        np.array: _description_
    """
    mean_ = data.mean(axis=0)
    sd_ = data.std(axis=0)

    return (data - mean_) / sd_


import numpy as np
#import numba

def fft(f, x=None, **kwargs):
    """
    customized fourier transform of function f
    g = int dt f(t) * exp(- i * freq * t)

    Return:
        freq: frequencies where f are evaluated
        g: the fourier transform of f
    """
    nx = len(f)

    if x is None:
        x = np.arange(nx)

    dx = x[1] - x[0]

    g = np.fft.fft(f, **kwargs)
    g = np.fft.fftshift(g)
    g *= dx

    freq = 2. * np.pi * np.fft.fftshift(np.fft.fftfreq(nx, d=dx))

    return freq, g * np.exp(-1j * freq * x[0])

def ifft(f, x=None):
    """
    customized fourier transform of function f
    g = int dt f(t) * exp(i * freq * t)
    return:
        freq: frequencies where f are evaluated
        g: the fourier transform of f
    """
    nx = len(f)

    if x is None:
        x = np.arange(nx)

    dx = x[1] - x[0]

    g = np.fft.ifft(f)
    g = np.fft.ifftshift(g)
    g = g * dx /2./np.pi * len(x)
    freq = 2. * np.pi * np.fft.ifftshift(np.fft.fftfreq(nx, d=dx))

    return freq, g * np.exp(1j * freq * x[0])

def fft2(f, dx=1, dy=1):
    """
    customized FFT for 2D function
    input:
        f: 2d array,
            input array
    return:
        freq: 1d array
            frequencies
        g: 2d array
            fourier transform of f
    """
    nx, ny = f.shape

    g = np.fft.fft2(f)
    g = np.fft.fftshift(g)

    g = g * dx * dy

    freqx = 2. * np.pi * np.fft.fftshift(np.fft.fftfreq(nx, d=dx))
    freqy = 2. * np.pi * np.fft.fftshift(np.fft.fftfreq(nx, d=dy))

    return freqx, freqy, g

def dft(x, f, k):
    '''
    Discrete Fourier transfrom at specified momentum
    '''

    dx = (x[1] - x[0]).real

    g = np.zeros(len(k), dtype=np.complex128)

    for i in range(len(k)):
        g[i] = np.sum(f * np.exp(-1j * k[i] * x)) * dx

    return g

def dft2(x, y, f, kx, ky):
    '''
    Discrete Fourier transfrom at specified momentum
    '''

    dx = x[1] - x[0]
    dy = y[1] - y[0]

    X, Y = np.meshgrid(x, y)
    g = np.zeros((len(kx), len(ky)), dtype=complex)

    for i in range(len(kx)):
        for j in range(len(ky)):
            g[i, j] = np.sum(f * np.exp(-1j * kx[i] * X - 1j * ky[j] * Y)) * dx * dy

    return g


from sympy import *
import sys
sys.path.insert(1, '..')
from tait_bryan_R_utils import *
from rodrigues_R_utils import *
from quaternion_R_utils import *

xsl, ysl, zsl = symbols('xsl ysl zsl')
xtg, ytg, ztg = symbols('xtg ytg ztg')
px, py, pz = symbols('px py pz')
#om, fi, ka = symbols('om fi ka')
#sx, sy, sz = symbols('sx sy sz')
q0, q1, q2, q3 = symbols('q0 q1 q2 q3')
vxx, vxy, vxz = symbols('vxx vxy vxz')
vyx, vyy, vyz = symbols('vyx vyy vyz')
scale_x, scale_y = symbols('scale_x scale_y')

position_symbols = [px, py, pz]
#orientation_symbols = [om, fi, ka]
#orientation_symbols = [sx, sy, sz]
orientation_symbols = [q0, q1, q2, q3]
scale_symbols = [scale_x, scale_y]
all_symbols = position_symbols + orientation_symbols + scale_symbols

point_source_local = Matrix([xsl * scale_x, ysl * scale_y, zsl, 1]).vec()
#RT_wc = matrix44FromTaitBryan(px, py, pz, om, fi, ka)[:-1,:]
#RT_wc = matrix44FromRodrigues(px, py, pz, sx, sy, sz)[:-1,:]
RT_wc = matrix44FromQuaternion(px, py, pz, q0, q1, q2, q3)[:-1,:]
point_source_global = RT_wc * point_source_local
point_on_line_target_global = Matrix([xtg, ytg, ztg])

vxt = Matrix([[vxx, vxy, vxz]]).transpose()
vyt = Matrix([[vyx, vyy, vyz]]).transpose()

target_value = Matrix([0,0]).vec()
model_function = Matrix([vxt.dot(point_source_global - point_on_line_target_global),vyt.dot(point_source_global - point_on_line_target_global)]).vec()
delta = target_value - model_function
delta_jacobian=delta.jacobian(all_symbols)

print(delta)
print(delta_jacobian)

with open("rectangular_object_with_unknown_width_height_quaternion_wc_jacobian.h",'w') as f_cpp:
    f_cpp.write("inline void rectangular_object_with_unknown_width_height_quaternion_wc(Eigen::Matrix<double, 2, 1> &delta, double px, double py, double pz, double q0, double q1, double q2, double q3, double xsl, double ysl, double zsl, double xtg, double ytg, double ztg, double vxx, double vxy, double vxz, double vyx, double vyy, double vyz, double scale_x, double scale_y)\n")
    f_cpp.write("{")
    f_cpp.write("delta.coeffRef(0,0) = %s;\n"%(ccode(delta[0,0])))
    f_cpp.write("delta.coeffRef(1,0) = %s;\n"%(ccode(delta[1,0])))
    f_cpp.write("}")
    f_cpp.write("\n")
    f_cpp.write("inline void rectangular_object_with_unknown_width_height_quaternion_wc_jacobian(Eigen::Matrix<double, 2, 9> &j, double px, double py, double pz, double q0, double q1, double q2, double q3, double xsl, double ysl, double zsl, double xtg, double ytg, double ztg, double vxx, double vxy, double vxz, double vyx, double vyy, double vyz, double scale_x, double scale_y)\n")
    f_cpp.write("{")
    for i in range (2):
        for j in range (9):
            f_cpp.write("j.coeffRef(%d,%d) = %s;\n"%(i,j, ccode(delta_jacobian[i,j])))
    f_cpp.write("}")


# %%
import numpy as np
import matplotlib.pyplot as plt

def grad_gauss_wrt_mu(z, mu, inv_cvm):
    return inv_cvm @ (z - mu)

def grad_gauss_wrt_cvm(z, mu, inv_cvm):
    normalized_z = z - mu
    return -0.5 * (inv_cvm - (inv_cvm @ np.outer(normalized_z, normalized_z) @ inv_cvm))

def search_gradient(f, x0, n_samples, max_iterations, step_size):
    """
    Args:
        f: function to minimize
        x0: initial x value
        n_samples: number of samples to sample from gaussian distribution
        max_iterations: number of iterations of cross entropy method to perform
        step_size: step size for parameter updates

    Returns:
        x_hist: history of x values as gradient descent updates x
        f_hist: history of f(x) values as gradient descent updates x
        iterations: number of iterations to reach tolerance
    """

    x_hist = []
    x_hist.append(x0)

    f_hist = []
    f_hist.append(f(x0))

    iterations = 0

    # Set initial mean and covariance matrix
    mu = x0
    cvm = np.eye(mu.size)

    while True:
        iterations += 1
        inv_cvm = np.linalg.inv(cvm)
        samples = np.random.multivariate_normal(mu, cvm, n_samples)
        sample_means = np.array([grad_gauss_wrt_mu(z, mu, inv_cvm)*f(z) for z in samples])
        sample_cvms = np.array([grad_gauss_wrt_cvm(z, mu, inv_cvm)*f(z) for z in samples])
        grad_J_wrt_mu = np.mean(sample_means, axis=0)
        grad_J_wrt_cvm = np.mean(sample_cvms, axis=0)

        mu -= step_size * grad_J_wrt_mu
        cvm -= step_size * grad_J_wrt_cvm

        x_hist.append(mu)
        f_hist.append(f(mu))

        if iterations > max_iterations:
            break

    return x_hist, f_hist, iterations

if __name__ == '__main__':

    def f(x: np.ndarray) -> float:
        """
        """
        Q = np.array([[2, 0, 0],
                      [0, 4, 0],
                      [0, 0, 6]])
        b = np.array([2, 2, 2])
        return 0.5 * (x.T @ Q @ x) - (b.T @ x)

    # random seed
    np.random.seed(8)

    x0 = x_init = np.full(3, 2.)
    n_samples = 100
    iterations = 400
    step_size = 0.001

    x_hist, f_hist, iterations = search_gradient(f, x0, n_samples, iterations, step_size)

    x_hist = np.array(x_hist)
    f_hist = np.array(f_hist)
    min_idx = np.argmin(f_hist)

    print(f'min x value: {x_hist[min_idx]}')
    print(f'min f value: {f_hist[min_idx]}')
    print(f'iterations: {iterations-1}')

    # plot f_hist
    plt.plot(f_hist)
    plt.xlabel('iteration')
    plt.ylabel('$f(x)$')
    plt.title('Search gradient')
    plt.show()


import matplotlib.pyplot as plt
from numpy import sinh, cosh, ones, sqrt

plt.style.use('seaborn')

def AlmgrenChrissTrajectory(q ,h_start ,h_end ,lam ,eta ,sigma ,theta=None):
    # This function computes the optimal trading trajectory and its first order derivative according to the
    # Almgren-Chriss model
    # INPUTS:
    #  q        [vector] (1 x k_) grid of volume times
    #  h_start [scalar]  initial holdings
    #  h_end   [scalar]  final holdings
    #  lam   [scalar] parameter for the mean-variance trade-off
    #  eta      [scalar] parameter for the slippage component of the underlying price dynamics
    #  sigma    [scalar] diffusion coefficient of the underlying price dynamics
    #  theta    [scalar] drift coefficient of the underlying price dynamics
    #  OPS:
    #  h        [vector] (1 x k_) Almgren-Chriss trading trajectory at each point of the time grid q
    #  h_speed  [vector] (1 x k_) Almgren-Chriss trading trajectory first order derivative at each point of the time grid q
    #  NOTE: If not specified, theta is by default set equal to
    #        2*lam@sigma***hq_end (value that ensures monotonicity of the trajectories)
    #        If lam=0, then the VWAP trading trajectory is computed.

    # For details on the exercise, see here .
    ## Code
    if lam ! =0:
        coeff 2 = 2 *la m *sigm a* *2
        if theta is None:
            thet a =coeff 2 *h_end
        coeff 1 =sqrt(la m /eta ) *sigma
        coef f =sinh(coeff 1 *(q[-1 ] -q[0]))
        h_ q =(h_star t -thet a /coeff2 ) *sinh(coeff 1 *(q[-1 ] -q) ) /coef f +(h_en d -thet a /coeff2 ) *sinh \
            (coeff 1 *( q -q[0]) ) /coef f +thet a /coeff2
        hspeed_ q =-coeff 1 *(h_star t -thet a /coeff2 ) *cosh(coeff 1 *(q[-1 ] -q) ) /coef f +coeff 1 * \
                    (h_en d -thet a /coeff2 ) *cosh(coeff 1 *( q -q[0]) ) /coeff
    else:
        h_ q = q *(h_en d -h_start ) /(q[-1 ] -q[0] ) +(h_star t *q[-1 ] -h_en d *q[0] ) /(q[-1 ] -q[0])
        hspeed_ q =(h_en d -h_start ) /(q[-1 ] -q[0] ) *ones((1 ,len(q)))

    return h_q ,hspeed_q


import cmath
import copy
import numpy
import math
import scipy.linalg

class HarmonicOscillator(object):
    def __init__(self, m, w, order, shift):
        self.m = m
        self.w = w
        self.order = order
        # self.norm = (self.w / math.pi) ** 0.25 # not necessary but we just include...
        self.norm = 1.0 # not necessary but we just include...
        self.xavg = shift
        # self.eshift = self.xavg**2 * self.w**2 / 2.0
#-------------------------
    def update_shift(self,shift): # X : lattice configuration
        self.xavg = shift.copy()
#-------------------------
    def value(self,X): # X : lattice configuration
        result = numpy.prod(self.norm * numpy.exp(- (self.m * self.w / 2.0) * (X-self.xavg) * (X-self.xavg)))
        return result
#-------------------------
    def gradient(self,X): # grad / value
        # grad = (-self.w * (X-self.xavg)) * self.value(X)
        grad = - self.m * self.w * (X-self.xavg)
        return grad
#-------------------------
    def laplacian(self,X): # laplacian / value
        # lap = self.w * self.w * (X-self.xavg) * (X-self.xavg) * self.value(X) - self.w * self.value(X)
        lap = self.m * self.m * self.w * self.w * (X-self.xavg) * (X-self.xavg) - self.w * self.m
        return lap
#-------------------------
    def local_energy(self, X):

        nsites = X.shape[0]

        ke   = - 0.5 * numpy.sum(self.laplacian(X)) / self.m
        pot  = 0.5 * self.m * self.w * self.w * numpy.sum(X * X)

        eloc = ke+pot - 0.5 * self.w * nsites # No zero-point energy

        return eloc

class HarmonicOscillatorMomentum(object):
    def __init__(self, m, w, order, shift):
        self.m = m
        self.w = w
        self.order = order
        self.norm = 1.0 # not necessary but we just include...
        self.pavg = shift
#-------------------------
    def value(self,P): # P : lattice momentum
        result = numpy.prod(self.norm * numpy.exp(- (1.0 / (2.0 * self.m * self.w)) * (P-self.pavg) * (P-self.pavg)))
        return result
#-------------------------
    def gradient(self,P): # grad / value
        grad = - (1.0/(self.m * self.w)) * (P-self.pavg)
        return grad
#-------------------------
    def laplacian(self,P): # laplacian / value
        lap = (1.0/(self.m * self.w))**2 * (P-self.pavg) * (P-self.pavg) - (1.0/(self.m * self.w))
        return lap
#-------------------------
    def local_energy(self, P):

        nsites = P.shape[0]

        ke   = (1.0/(2.0*self.m)) * numpy.sum (P * P)
        pot  = - 0.5 * self.m * self.w * self.w * numpy.sum(self.laplacian(P))
        eloc = ke+pot - 0.5 * self.w * nsites # No zero-point energy

        return eloc