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

class Exponential(SemiInfinite):
    """
    This class contans methods for evaluating Exponential Distirbution [#]_ [#]_.

    .. math:: \\text{Exponential}(x;\\lambda) = \\lambda e^{-\\lambda x}

    Args:

        - rate (float): rate parameter (:math:`\\lambda`) where rate > 0
        - x (float): random variable where x > 0

    References:
        .. [#] Weisstein, Eric W. "Exponential Distribution." From MathWorld--A Wolfram Web Resource. https://mathworld.wolfram.com/ExponentialDistribution.html
        .. [#] Wikipedia contributors. (2020, December 17). Exponential distribution. https://en.wikipedia.org/w/index.php?title=Exponential_distribution&oldid=994779060
    """

    def __init__(self, rate: float):
        if rate < 0:
            raise ValueError(f'lambda parameter should be greater than 0.')

        self.rate = rate

    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]: evaluation of pdf at x
        """
        rate = self.rate

        if isinstance(x, (_np.ndarray, List)):
            if not type(x) is _np.ndarray:
                x = _np.array(x)
            return _np.piecewise(x, [x >= 0, x < 0], [lambda x: rate*_np.exp(-(rate*(x))), lambda x: 0.0])

        return rate*_exp(-rate*x) if x >= 0 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
        """
        rate = self.rate

        if isinstance(x, (_np.ndarray, List)):
            if not type(x) is _np.ndarray:
                x = _np.array(x)
            return _np.piecewise(x, [x > 0, x <= 0], [lambda x: 1 - _np.exp(-rate*x), lambda x: 0.0])

        return 1 - _exp(-rate*x) if x > 0 else 0.0

    def mean(self) -> float:
        """
        Returns: Mean of the Exponential distribution
        """
        return 1 / self.rate

    def median(self) -> float:
        """
        Returns: Median of the Exponential distribution
        """
        return _log(2) / self.rate

    def mode(self) -> float:
        """
        Returns: Mode of the Exponential distribution
        """
        return 0.0

    def var(self) -> float:
        """
        Returns: Variance of the Exponential distribution
        """
        return 1 / pow(self.rate, 2)

    def std(self) -> float:
        """
        Returns: Standard deviation of the Exponential distribution
        """
        return _sqrt(self.var())

    def skewness(self) -> float:
        """
        Returns: Skewness of the Exponential distribution
        """
        return 2.0

    def kurtosis(self) -> float:
        """
        Returns: Kurtosis of the Exponential distribution
        """
        return 6.0

    def entorpy(self) -> float:
        """
        Returns: differential entropy of the Exponential distribution

        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 1 - _log(self.rate)

    def summary(self) -> Dict[str, Union[float, int]]:
        """
        Returns:
            Dictionary of Exponential 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()
        }


# -*- coding: utf-8 -*-

# Raivo Laanemets
# rlaanemt@ut.ee

import sys
import random
import numpy as np
import time
import math
import scitools

# From lab description.

# The constant lambda.

def c_lam():
    return 1.0

# The term omega_i for trapezoid formula.

def c_omega_tr(i, n, h):
    if i == 1 or i == n:
        return h / 2.0
    else:
        return h

# The term omega_i for Simpson's formula.

def c_omega_Sim(i, n, h):
    if i == 1 or i == n:
        return h / 3.0
    elif i >= 2 and i <= n - 1 and i % 2 == 0:
        return 4.0 * h / 3.0
    else:
        return 2.0 * h / 3.0

# The constant h.

def c_h(a, b, n):
    return (b - a) / float(n - 1)

# The term si used during the approximation calculation.

def c_s(a, j, h):
    return a + (j - 1) * h

# Integral equation bounds.

def c_a():
    return -math.pi

def c_b():
    return math.pi

# Function K.

def f_K(t, s):
    return 3.0 / (6.4 * math.pi * np.cos((t + s) / 2.0) ** 2 - 10 * math.pi)

# Function f.

def f_f(t):
    return 25.0 - 16.0 * np.sin(t) ** 2

# A helper function to calculate
# yn (see last line of solve function).
# This is taken from equation 7.

def f_yn(t, a, n, h, Z, q_f_w):
    sum = 0.0
    for j in xrange(1, n + 1):
        sum += q_f_w(j, n, h) * f_K(t, c_s(a, j, h)) * Z[j - 1]

    return f_f(t) + c_lam() * sum

# Solves the integral equation with functions
# and bounds given above. Parameter q_f_w defines
# the function for term omega_i in the equations.

def solve(n, q_f_w):
    a = c_a()
    b = c_b()
    h = c_h(a, b, n)
    zmat = [] # the matrix of zi coefficients
    bvec = [] # the right side vector (f(si) values)
    for i in xrange(1, n + 1): # i = 1,...,n
        s_i = c_s(a, i, h)
        f_s_i = f_f(s_i) # right side of the equation 5
        bvec.append(f_s_i)
        rvec = [] # the vector of zj coefficients in the current row
        for j in xrange(1, n + 1): # j = 1,...,n
            rvec.append(-c_lam() * q_f_w(j, n, h) * f_K(s_i, c_s(a, j, h)))
        # Add zi
        rvec[i - 1] += 1.0
        zmat.append(rvec)

    # Prepare for solving for zi's.
    A = np.matrix(zmat)
    b = np.matrix(bvec)
    b.shape = (n, 1)

    # Uses built-in solver.
    Z = np.linalg.solve(A, b)
    Z.shape = (1, n)

    # Returns the approximated function yn(t).
    return lambda t: f_yn(t, a, n, h, Z.getA1(), q_f_w)

# I assume that this was the exact solution.
def sol(t):
    return 17.0 / 2.0 + 128.0 / 17.0 * np.cos(2.0 * t)

def main(argv):
    # The approximation using trapezoid formula.
    yn_tr = solve(6, c_omega_tr)

    # The approximation using Simpson's formula.
    yn_Sim = solve(9, c_omega_Sim)

    # A set of points used in plot.
    ts = np.linspace(c_a(), c_b(), 50)

    # Plot showing the approximations and the exact solution.
    scitools.easyviz.plot(
        ts, yn_tr(ts), 'b+-',
        ts, yn_Sim(ts), 'go-',
        ts, sol(ts), 'ro-',
        legend=('approx. w. trapezoid', 'approx. w. Simpson', 'exact'),
        xlabel='t',
        ylabel='y(t)'
    )

if __name__ == "__main__":
    main(sys.argv[1:])


# bar3d.py
# -------------------------------------------------------------------------
# Create a 3D "Lego plot" to illustrate the Central Limit Theorem.
# -------------------------------------------------------------------------
"""
Description
-----------

This script demonstrates the creation of a 3D bar chart and it illustrates how
the distribution of a sum of random numbers differs from the distribution those
numbers are drawn from.

The script will bin num_points random (x,y) pairs in a 2D histogram, then
display a 3D bar chart of this data.  Each x and y coordinate is the sum
of num_to_sum numbers drawn from the uniform random distribution on [0,1).

Adjust num_to_sum and use different random number generators to explore the
Central Limit Theorem.  (The Central Limit Theorem implies that the
distribution of the *sum* of many random numbers should approach a normal
distribution.)
"""
# %% Setup
import numpy as np
import matplotlib.pyplot as plt

# Create a random number generator.
rng = np.random.default_rng()  # create a random number generator object
rand = rng.random              # assign its uniform distribution method to rand

# Import the 3D plotter.
from mpl_toolkits.mplot3d import Axes3D

# %% Generate data and histogram.
# Generate random (x,y) pairs to bin.  Each coordinate is the sum of N numbers
# drawn from the uniform distribution on [0,1).
num_points = 10000
num_to_sum = 2
num_bins = 20

x_vals = rand((num_points, num_to_sum)).sum(1)
y_vals = rand((num_points, num_to_sum)).sum(1)

# Use histogram2d to get counts and bin locations.
counts, x_bins, y_bins = np.histogram2d(x_vals, y_vals, bins=num_bins)

# %% Transform data for plotting 3D bar chart.
# For 3D bar chart, we need starting (x,y,z) coordinates for each bar and
# (dx,dy,dz) to specify its shape.  The following code uses meshgrid, diff, and
# flatten to construct these inputs from the output of histogram2d.
x_start, y_start = np.meshgrid(x_bins[:-1], y_bins[:-1])
z_start = np.zeros_like(x_start)

x_length, y_length = np.meshgrid(np.diff(x_bins), np.diff(y_bins))
z_length = counts

x = x_start.flatten()
y = y_start.flatten()
z = z_start.flatten()
dx = x_length.flatten()
dy = y_length.flatten()
dz = z_length.flatten()

# %% Plot transformed data.
fig = plt.figure()
ax3d = Axes3D(fig)
ax3d.bar3d(x, y, z, dx, dy, dz)

plt.show()


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Jul 26 14:30:54 2018

@author: deepayanbhadra
"""
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import scipy as sp
import numpy.linalg as nla
from math import pi
from scipy.spatial.distance import pdist, squareform, cdist
from sklearn.cluster import KMeans
import hmwk3
from hmwk2 import grad2d, div2d

# Q1(c) Richardson Iteration

def richardson(A,b,x,t):
    resids = 1
    all_res = []
    while resids > 10e-6:
        x = x+t*(b-A(x))
        resids = nla.norm(b-A(x),'fro')
        all_res = np.append(all_res,resids)
    return all_res,x

# Q1(d) Conjugate gradient

def conjgrad(A,b,x):
    tol = 10e-6;
    xk = x; rk = b-A(xk); pk = rk # Initial values
    res = [];
    while nla.norm(rk)>tol:
        grad = A(pk);
        alpk = np.dot(rk.flatten(),rk.flatten())/(np.dot(pk.flatten(),
                                                     grad.flatten()))
        xk = xk+alpk*pk;
        rkk = rk-alpk*grad;
        betak = np.dot(rkk.flatten(),rkk.flatten())/(np.dot(rk.flatten(),
                                                           rk.flatten()))
        pk = rkk+betak*pk;
        rk = rkk;
        res = np.append(res,nla.norm(rk,'fro'))
        # Storing all the residuals

    return x,resids

# Q1(f) to compute exact solutions using FFT

def l2denoise(b,mu):
    kernel = np.zeros((b.shape[0],b.shape[1]))
    kernel[0,0] = 1
    kernel[0,1] = -1
    Dx = np.fft.fft2(kernel)
    kernel = np.zeros((b.shape[0],b.shape[1]))
    kernel[0,0] = 1
    kernel[1,0] = -1
    Dy = np.fft.fft2(kernel)
    dd = np.divide(1,(mu*(np.conj(Dx)*Dx+np.conj(Dy)*Dy)+1))
    x = np.real(np.fft.ifft2(dd*np.fft.fft2(b)))
    return x

 # Q2(a) Two-moons dataset

def make_moons(n):
    """Create a 'two moons' dataset with n feature vectors,
        and 2 features per vector."""

    assert n%2==0, 'n must be even'
    # create upper moon
    theta = np.linspace(-pi / 2, pi / 2, n//2)
    # create lower moon
    x = np.r_[np.sin(theta) - pi / 4, np.sin(theta)]
    y = np.r_[np.cos(theta), -np.cos(theta) + .5]
    data = np.c_[x, y]
    # Add some noise
    data = data + 0.03 * np.random.standard_normal(data.shape)

    # create labels
    labels = np.r_[np.ones((n//2, 1)), -np.ones((n//2, 1))]
    labels = labels.ravel().astype(np.int32)

    return data,labels

if __name__ == "__main__":

    # Q1(b): Quadratic Image Denoising Model
    img= mpimg.imread('lena512.bmp')
    img = img.astype(float)
    b = img/max(img.flatten()) # Scaling to [0,1]
    mu = 2
    x0 = np.random.randn(*b.shape)
    f = lambda x:mu*div2d(grad2d(x))+x-b
    output = f(x0) # Evaluating A at x0

    # Q1(c) Richardson Iteration

    t = 0.05
    all_res,x = richardson(f,b,x0,t)
    plt.xlabel('# of iterations')
    plt.ylabel('Residual norm')
    plt.plot(all_res)
    print("# of iterations to convergence is",all_res.size)
    plt.imshow(b) # Noisy image
    plt.imshow(x.astype(float)) # De-noised image

    # Q1(d) Conjugate gradient

    [x,res] = conjgrad(f,b,x0)
    plt.xlabel('# of iterations')
    plt.ylabel('Residual norm')
    plt.plot(all_res)
    print("# of iterations to convergence is",all_res.size)
    plt.imshow(b) # Noisy image
    plt.imshow(x.astype(float)) # De-noised image

    # Q1(f) to compute exact solutions using FFT
    print('The norm of the gradient of the objective function is')
    nla.norm(f(l2denoise(b,mu)))

    # Q2(a) Building a dataset for Spectral Clustering

    data,l = make_moons(100)
    plt.scatter(data[l>0,0],data[l>0,1],c='r')
    plt.scatter(data[l<0,0],data[l<0,1],c='b')
    plt.show()
    S = np.zeros((100,100))
    sig = 0.09
    B = pdist(data)
    C = squareform(B)
    S = np.exp(-C/sig)

    # Q2(b) to compute the diagonal normalization matrix
    S_sum = np.sum(S,axis=1,keepdims = True)
    D = np.diagflat(S_sum)
    temp = np.power(S_sum,-0.5)
    S_hat = np.diagflat(temp).dot(S).dot(np.diagflat(temp))

    # Q2(c) to compute the EVD of normalized S.
    E,V = nla.eig(S_hat)
    idx = E.argsort()[::-1]
    E = E[idx]
    V = V[:,idx]

    U2 = np.stack([V[:,1],V[:,2]],axis=1)
    plt.scatter(U2[l==1,0],U2[l==1,1],c='r')
    plt.scatter(U2[l==-1,0],U2[l==-1,1],c='b')
    plt.show()

    # Q2(d) k-means clustering

    kmeans = KMeans(n_clusters = 2)
    y_kmeans = kmeans.fit_predict(U2)
    plt.scatter(U2[y_kmeans == 0, 0], U2[y_kmeans == 0, 1], s = 100, c = 'red', label = 'Cluster 1')
    plt.scatter(U2[y_kmeans == 1, 0], U2[y_kmeans == 1, 1], s = 100, c = 'blue', label = 'Cluster 2')
    plt.scatter(kmeans.cluster_centers_[:, 0], kmeans.cluster_centers_[:, 1], s = 300, c = 'yellow', label = 'Centroids')

    # Q3(a) Nystrom approximation of S

    data,l = make_moons(100000)
    sig = 0.09
    idx = np.random.choice(100000,size = 200, replace=False) # Random Sampling of 200 columns
    idx_c = np.setdiff1d(np.arange(100000),idx)
    temp1 = cdist(data,data[idx,:]) # Pairwise distance between two sets of observations
    C = np.exp(-temp1/sig)
    W = C[idx,:]

    # Q3(b)

    Z = C[200:,:]
    W_m = np.sum(W,axis=1,keepdims = True)+ np.sum(np.transpose(Z),
                                                     axis=1,keepdims = True)
    M_e = np.sum(Z,axis=1,keepdims = True)+ np.transpose(np.sum(Z,
                                                     axis=0,keepdims = True)
                                                    .dot(nla.inv(W))
                                                    .dot(np.transpose(Z)))
    diagD = np.concatenate([W_m,M_e],axis=0)
    temp = np.multiply(np.repeat(diagD,200,axis=1),C)
    C_n = np.dot(temp,np.diagflat(diagD[idx]))
    W_n = C_n[idx,:]
    M_n = C_n[idx_c,:]

    # Q3(c)

    W_hat = W_n + sp.linalg.sqrtm(nla.inv(W_n)).dot(np.transpose(M_n)).dot(M_n).dot(sp.linalg.sqrtm(nla.inv(W_n)))
    # Orthogonalization matrix

    D_w,U_w = nla.eig(W_hat)
    D_w,U_w = np.real(D_w), np.real(U_w)

    # Eigen-decomposition W_hat = V*D*V'

    U = np.matmul(C_n,np.matmul(sp.linalg.sqrtm(nla.inv(W_n)),U_w))*np.power(np.expand_dims(D_w,axis=-1),-0.5).T

    idx = np.argsort(D_w)[::-1]
    U_w = U_w[idx]
    U = U[:,idx]

    U = U/np.expand_dims(U[:,0],axis=-1)
    U2 = np.expand_dims(U[:,1],axis=-1)
    plt.scatter(U2[l==1],U2[l==1],c='r')
    plt.scatter(U2[l==-1],U2[l==-1],c='b')
    plt.show()
    # Approximate eigen-vectors

    # Q3(d) k-means clustering

    kmeans = KMeans(n_clusters = 2)
    U2 = U[:,-3:-1]
    y_kmeans = kmeans.fit_predict(U2)
    plt.scatter(U2[y_kmeans == 0, 0], U2[y_kmeans == 0, 1], s = 100, c = 'red', label = 'Cluster 1')
    plt.scatter(U2[y_kmeans == 1, 0], U2[y_kmeans == 1, 1], s = 100, c = 'blue', label = 'Cluster 2')
    plt.scatter(kmeans.cluster_centers_[:, 0], kmeans.cluster_centers_[:, 1], s = 300, c = 'yellow', label = 'Centroids')


"""
affine_scaling_algorithm.py

Returns the minimizer of the function
"""

import numpy as np
np.set_printoptions(formatter={'float': lambda x: "{0:0.3f}".format(x)});

def affine_scaling_algorithm(A, b, C, options):

    m, n = A.shape;
    u = np.random.rand(n, 1);
    v = b - A @ u;
    condition = (v != np.zeros((m, 1)));

    # Searching for strictly interior feasible solution
    if (condition.all()):
        C_new = np.concatenate((np.zeros((n, 1)), np.array([[1]])), axis = 0);
        A_new = np.concatenate((A, v), axis = 1);
        b_new = b;
        feasible_solution = np.concatenate((u, np.array([[1]])), axis = 0);
        u, report = affine_scaling_optimization(A_new, b_new, C_new, feasible_solution, options);
        u = np.delete(u, n).reshape((n, 1));

    # Searching for optimum solution
    X, report = affine_scaling_optimization(A, b, C, u, options);

    return (X, report);

def affine_scaling_optimization(A, b, C, X0, options):

    report = {};
    m, n = A.shape;
    N_iter_max = options['N_iter_max'];
    tolerance_x = options['tolerance_x'];
    tolerance_y = options['tolerance_y'];
    alpha = options['alpha']; # step size
    progress_x = np.zeros((N_iter_max + 1, X0.size));
    progress_y = np.zeros((N_iter_max + 1, 1));
    progress_x[0] = X0.T;
    progress_y[0] = C.T @ X0;
    X_old = X0;

    for iter_no in range(1, N_iter_max + 1):
        D = np.diag(X_old.flatten());
        A_bar = A @ D;
        # Orthogonal projector onto nullspace of A_bar
        P_bar = np.eye(n) - A_bar.T @ np.linalg.inv(A_bar @ A_bar.T) @ A_bar;
        d = - D @ P_bar @ D @ C; # directional vector (negative gradiant, -C, projected onto N(A))

        if ((d != np.zeros((n ,1))).any()):
            mask = (d < 0);
            r = np.min(-X_old[mask] / d[mask]);
        else:
            print('d = 0, exit ..');
            break;

        X = X_old + alpha * r * d;
        progress_x[iter_no] = X.T;
        progress_y[iter_no] = C.T @ X;

        if (np.linalg.norm(X - X_old) < tolerance_x * np.linalg.norm(X_old)):
            print('Tolerance in X is reached in %d iterations, exit..' % (iter_no));
            break;

        if (np.abs(progress_y[iter_no] - progress_y[iter_no - 1]) < tolerance_y * np.abs(progress_y[iter_no - 1])):
            print('Tolerance in Y is reached in %d iterations, exit..' % (iter_no));
            break;

        X_old = X;

    report = {'N_iter_max' : N_iter_max, 'iter_no' : iter_no, 'X0' : X0, 'X' : X, 'progress_x' : progress_x, 'progress_y' : progress_y};
    return (X, report);


from numpy import array, diag, mean, shape, sign, zeros
from numpy.linalg import det, eig, inv, norm
from numpy.linalg import solve as npsolve
from numpy.random import rand
from scipy.linalg import lu, svd
import matplotlib.pyplot as plt

def evectors(matrix):
    """ Returns the eigenvectors of a given matrix.
    Args
    ---
    `matrix : np.array` A numpy matrix

    Returns
    ---
    `vecs : np.array` A numpy matrix containing the eigenvectors
    """
    vals_vecs = eig(matrix)
    return vals_vecs[1]

def evalues(matrix):
    """ Returns the eigenvalues of a given matrix.
    Args
    ---
    `matrix : np.array` A numpy matrix

    Returns
    ---
    `vals : np.array` A numpy vector containing the eigenvalues
    """
    vals_vecs = eig(matrix)
    return vals_vecs[0]

def inverse(matrix):
    """ Returns the inverse of a given matrix.
    Args
    ---
    `matrix : np.array` A numpy matrix

    Returns
    ---
    `inv : np.array` The inverse matrix
    """
    invMatrix = inv(matrix)
    return invMatrix

def covarianceMatrix(A, B):
    """ Returns the covariance matrix of two given matrices `A` and `B`.
    Args
    ---
    `A : np.array` A `m` x `n` numpy matrix
    `B : np.array` A `m` x `n` numpy matrix

    Returns
    ---
    `cMatrix : np.array` The covariance matrix
    """
    N = len(A[0])
    C = (A @ B.T) / N
    return C

def SVDecomp(matrix):
    """ Performs a singlular value decomposition a given matrix.
    Args
    ---
    `matrix : np.array` An `m` x `n` numpy matrix

    Returns
    ---
    `U : np.array` An `m` x `m` orthonormal matrix whose columns are the
    eigenvectors of `matrix @ matrix.T`

    `S : np.array` An `m` x `n` matrix with the singular values (square roots
    of the non-zero eigenvalues of `matrix @ matrix.T`) along diagonal

    `V : np.array` An `n` x `n` orthonormal matrix whose columns are the
    eigenvectors of `matrix.T @ matrix`
    """
    U, S, V = svd(matrix)
    S = diag(S)
    V = V.T
    return U, S, V

def LUDecomp(matrix):
    """ Performs an LU decomposition a given matrix.
    Args
    ---
    `matrix : np.array` A numpy matrix

    Returns
    ---
    `P : np.array` The permutation matrix

    `L : np.array` Lower triangular matrix

    `U : np.array` Upper triangular matrix
    """
    P, L, U = lu(matrix)
    P = P.T
    return P, L, U

def polarDecomp(matrix):
    """ Performs a polar decomposition on a given matrix and breaks down the matrix into its rotating and stretching components.
    Args
    ---
    `matrix : np.array` A numpy matrix

    Returns
    ---
    `rotate : np.array` The rotation matrix

    `stretch : np.array` The stretch matrix
    """
    U,S,V = SVDecomp(matrix)

    rotate = U@V.T
    stretch = U@S@U.T

    return rotate, stretch

def random(height, width):
    """ Returns a random matrix of a given size.
    Args
    ---
    `height : int` The height of the returned matrix

    `width : int` The width of the returned matrix

    Returns
    ---
    `randomMatrix : np.array` A random matrix of the desired height and width
    """
    randomMatrix = rand(height, width)
    return randomMatrix

def solve(A, b):
    """ Solve for `x` a system of linear equations in the form of `Ax=b`.
    Args
    ---
    `A : np.array` The left hand matrix

    `b : np.array` The right hand vector

    Returns
    ---
    `x : np.array` The solution vector
    """
    x = npsolve(A,b)
    return x

def solveMany(A, B):
    """ Solves for many `x`s in a system of linear equations in the form of `Ax=b` where multiple `b`'s are given. Uses LU decomposition to bring time down from `O(n^3/3)` to `O(n^2)`.
    Args
    ---
    `A : np.array` The left hand matrix

    `B : np.array` A matrix whose columns are composed of all the right hand vectors

    Returns
    ---
    `X : np.array` A matrix whose columns are composed of all the solution vectors that correspond with their respective column in `B`
    """
    P,L,U = LUDecomp(A)
    N = len(B[0])
    X = zeros(shape(B))

    for i in range(N):
        c = solve(L, P @ B[:,i])
        x_i = solve(U, c)
        X[:,i] = x_i

    return X

def perturb(A, b, delta_b):
    """ Perturbs the system `Ax=b` by `delta_b`.
    Args
    ---
    `A : np.array` The left hand matrix

    `b : np.array` The right hand vector

    `delta_b : np.array` The perturbing vector

    Returns
    ---
    `relError : float` The relative error to the solution caused by `delta_b`

    `relPerturbation : float` The relative perturbation of `b` caused by `delta_b`
    """
    x1 = solve(A, b)
    x2 = solve(A, b + delta_b)
    delta_x = x2 - x1

    relError = norm(delta_x)/norm(x1)
    relPerturbation = norm(delta_b)/norm(b)

    return relError, relPerturbation

def optimalFit(X, Y, plot=False):
    """ Given two sets of points, finds the optimal shift and rotation to fit the points in matrix `X` onto `Y`.
    Args
    ---
    `X : np.array` An `m` x `n` matrix that represents the set of points to be shifted and rotated

    `Y : np.array` An `m` x `n` matrix that represents the desired set of points to be shifted onto

    `plot : bool` If set to true and data is 2-dimensional will plot the points and ideal transformation

    Returns
    ---
    `X_Translation : np.array` An `m` x `1` vector that is the optimal translation of `X` onto `Y`

    `X_Rotation : np.array` An `m` x `m` matrix that is the optimal rotation of `X` onto `Y`

    `X_Translated_Rotated : np.array` `X` after the optimal shift and rotation has been applied
    """
    M = len(X[:,0])
    N = len(X[0])

    # Find center of mass of X and Y
    X_Center = array([mean(X[i]) for i in range(M)])
    Y_Center = array([mean(Y[i]) for i in range(M)])

    # Optimal shift of X
    X_Translation = Y_Center - X_Center

    # Shift X to optimal position
    X_Translated = zeros(shape(X))
    for i in range(N):
        X_Translated[:,i] = X[:,i] + X_Translation

    # Find optimal rotation of X
    C = Y @ X_Translated.T
    SVD = SVDecomp(C)
    U,V = SVD[0], SVD[2]
    X_Rotation = U @ V.T

    # Rotate X to optimal position
    X_Translated_Rotated = zeros(shape(X))
    for i in range(N):
        X_Translated_Rotated[:,i] = X_Rotation @ X_Translated[:,i]

    if plot and (M == 2):
        # Plot original points
        subplt1 = plt.subplot(1, 2, 1)
        hl1, = plt.plot(X[0], X[1], '.', color="red", markersize=7)
        hl2, = plt.plot(Y[0], Y[1], '.', color="blue", markersize=7)
        plt.legend([hl1, hl2], ['X', 'Y'])
        plt.title("Original Points")\

        # Plot tranformed points
        plt.subplot(1, 2, 2, sharex=subplt1, sharey=subplt1)
        hl3, = plt.plot(X_Translated_Rotated[0], X_Translated_Rotated[1], '.',
                        color="red", markersize=7)
        hl4, = plt.plot(Y[0], Y[1], '.', color="blue", markersize=7)
        plt.legend([hl3, hl4], ['X_Translated_Rotated', 'Y'])
        plt.title("Optimal Transformation")
        plt.show()

    return X_Translation, X_Rotation, X_Translated_Rotated


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

# cubicFit matrix
A = [
    [1, -5/2, (-5/2)**2, (-5/2)**3],
    [1, -3/2, (-3/2)**2, (-3/2)**3],
    [1, -1/2, (-1/2)**2, (-1/2)**3],
    [1,  1/2, ( 1/2)**2, ( 1/2)**3]
]

print(np.linalg.inv(A))
print()

# Taylor series expansion for 4-point upwind-biased stencil
# for face approximation (a la cubicFit)
A = [
    [1, -5/2, 25/8, -125/48],
    [1, -3/2, 9/8, -27/48],
    [1, -1/2, 1/8, -1/48],
    [1, 1/2, 1/8, 1/48]
]

print(np.linalg.inv(A))
print()

# Taylor series coefficients for 5-point upwind-biased stencil
# for gradient calculation
A = [
    [1, -3, (-3)**2/2, (-3)**3/6, (-3)**4/24],
    [1, -2, (-2)**2/2, (-2)**3/6, (-2)**4/24],
    [1, -1, (-1)**2/2, (-1)**3/6, (-1)**4/24],
    [1,  0, 0        , 0        , 0],
    [1,  1, 1**2/2   , 1**3/6   , 1**4/24]
]

print(np.linalg.inv(A))
print()

A = [
    [-2 - -3, 1/2*((-2)**2 - (-3)**2), 1/3*((-2)**3 - (-3)**3), 1/4*((-2)**4 - (-3)**4)],
    [-1 - -2, 1/2*((-1)**2 - (-2)**2), 1/3*((-1)**3 - (-2)**3), 1/4*((-1)**4 - (-2)**4)],
    [ 0 - -1, 1/2*(  0 **2 - (-1)**2), 1/3*(  0 **3 - (-1)**3), 1/4*(  0 **4 - (-1)**4)],
    [ 1 -  0, 1/2*(  1 **2 -   0 **2), 1/3*(  1 **3 -   0 **3), 1/4*(  1 **4 -   0 **4)]
]

print(np.array(A))
print(np.linalg.inv(A))


import datetime
import time
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
import streamlit as st
from pandas_datareader import data as wb
from scipy.stats import norm
import statistics as stat
import yfinance as yf

#<----------SETTING THE PAGE PARAMETERS----------->
st.set_page_config(
    page_title = "Monte Carlo Simulator",
    page_icon="random",
    layout="centered",
    initial_sidebar_state="expanded",
)

#<----------HEADING PAGE TITLE AND DESCRIPTION------------>
st.title('Brownian Monte Carlo Simulator')
st.write("""
Created By: Dmitriy Rogozhnikov

[LinkedIn](https://www.linkedin.com/in/dmitriy-rogozhnikov/)

[GitHub](https://github.com/DmitriyRogo)
""")

with st.beta_expander('Monte Carlo - About', expanded=True):
    st.write("""
    The Monte Carlo is a widely used tool to solve a variety of problems ranging from numerical integration to optimization
    of financial portfolios.

    It's an incredible tool that used across various industries.

    The purpose of this application is calculate the probable outcomes of a given
    security using the Monte Carlo method.

    We will manipulate the number of scenarios and days we are looking to illustrate given our equity.

    This is a basic Monte Carlo simulator that utilizes Brownian motion to estimate probable rates of return.

    Brownian motion has two main driving components.

    1. Drift - The different directions that rates of return have had in the past.

    2. Volatility - Utilizing historical volatility and multiplying it by a standard variable.

    Using these components we can compute the daily return of any given security.

    We will run a number of simulations to simulate future trading days and the impact it will have on the portfolio.
    """)

#<----------MONTE CARLO SIDEBAR INPUTS----------->
st.sidebar.title("Settings")

#<----------SELECTING A VALID TICKER FOR THE MONTE CARLO SIMULATION---------->
ticker = st.sidebar.text_input("Input a Ticker", value="SPY")

#<----------SELECTING A STARTING DATE FOR CALCULATING THE VOLATILITY AND DRIFT COMPONENTS------>
st.sidebar.write("""
The start date is our basis for how far back we want to collect historical data to compute our volatility and drift.

The end date will always be today's date.
""")
startDate = st.sidebar.date_input("Historical Start Date", datetime.date(2010,1,1))

#<----------SELECTING NUMBER OF DAYS WE ARE LOOKING TO FORECAST----------->
intDays = st.sidebar.number_input("Number of Future Days to Simulate", min_value=5, max_value=None, value=50) + 1

#<----------SELECTING THE NUMBER OF SIMULATIONS TO RUN-------------------->
intTrials = st.sidebar.number_input("Total Number of Simulations to Run", min_value=5, max_value=None, value=100)

#<----------SETTING THE NUMBER OF TOTAL SHARES INVESTED WITHIN THE FUND----------->
numShares = st.sidebar.number_input("Number of " + ticker + " Shares Held", min_value=0, max_value=None, value=10)

#<----------FULL NAME OF FUND----------->
fullName = yf.Ticker(ticker).info['longName']

#<--------IMPORTING DATA FROM YAHOO FINANCE------------>
data = pd.DataFrame()
data[ticker] = wb.DataReader(ticker, data_source = 'yahoo',
start = startDate)['Close']

#<-------COMPUTING LOG RETURN-------->
log_return = np.log(1 + data.pct_change())
simple_return = (data/data.shift(1)-1)

#<-------CALCULATING DRIFT------>
u = log_return.mean()
var = log_return.var()
drift = u - (0.5 * var)
stdev = log_return.std()
Z = norm.ppf(np.random.rand(intDays, intTrials))
daily_returns = np.exp(drift.values + stdev.values * Z)

#<----WILL ADD FEATURE FOR ADVANCED SETTINGS TO MANIPULATE STANDARD DEVIATION AND MEAN------>
# st.sidebar.subheader("Advanced Settings")
# newstdev = st.sidebar.number_input("Standard Deviation", value=stdev.item(), format="%.4f")

#<-------CALCULATING STOCK PRICE-------->
price_paths = np.zeros_like(daily_returns)
price_paths[0] = data.iloc[-1]
for t in range(1, intDays):
    price_paths[t] = price_paths[t-1]*daily_returns[t]
    endValue = numShares * price_paths[t]

with st.beta_expander('Monte Carlo - Results', expanded=True):
    st.write("""
    Standard Deviation: {}

    Mean: {}

    Variance: {}

    Drift: {}
    """.format(stdev.item(), u.item(), var.item(), drift.item()), format="%.4f")

    #<-----PLOT HISTORICAL DATA------>
    st.subheader("Historical Closing Price for " + fullName)
    tickerFigure = plt.figure(figsize=(7,3))
    plt.plot(data)
    plt.xlabel("Date")
    plt.ylabel(ticker + " Price (USD)")
    st.pyplot(tickerFigure)

    #<-----PLOTTING HISTORICAL RETURNS HISTOGRAM----->
    st.subheader("Historical Frequency of Daily Returns")
    tickerHisto = plt.figure(figsize=(7,3))
    sns.distplot(log_return.iloc[1:])
    plt.xlabel("Daily Return")
    plt.ylabel("Frequency")
    st.pyplot(tickerHisto)

    #<-----PLOTTING MONTE CARLO CHART RESULTS------>
    st.subheader("Monte Carlo Results for " + fullName)
    mcFigure = plt.figure(figsize=(7,4))
    plt.plot(price_paths)
    plt.xlabel("# of Days Into Future")
    plt.ylabel(ticker + " Price (USD)")
    st.pyplot(mcFigure)

    #<-----PLOTTING MONTE CARLO HISTOGRAM RESULTS----->
    st.subheader("Density of Terminal Monte Carlo Values")
    mcHisto = plt.figure(figsize=(7,3))
    sns.distplot(pd.DataFrame(price_paths).iloc[-1])
    plt.xlabel("Price After {} Days".format(intDays-1))
    st.pyplot(mcHisto)

    #Plotting Portfolio Value Results
    portMax = max(endValue)
    portMedian = stat.median(endValue)
    portMin = min(endValue)

    st.subheader("Portfolio Results")
    st.write("Maximum Ending Portfolio Value: ${:,.2f}".format(portMax))
    st.write("Median Ending Portfolio Value: ${:,.2f}".format(portMedian))
    st.write("Minimum Ending Portfolio Value: ${:,.2f}".format(portMin))


# Stanisław Zakrzewski 29/01/2022 - normal distribution

import math
from scipy.integrate import quad

def probability_for_normal_distribution(normal_distribution, z):
    return quad(normal_distribution, -math.inf, z)[0]

def z_score(x, mean, standard_deviation):
    return (x - mean) / standard_deviation

def normal_distribution_function(mean, standard_deviation):
    return lambda x: 1 / math.sqrt(2 * math.pi * standard_deviation ** 2) * math.exp(
        -0.5 * ((x - mean) / standard_deviation) ** 2)

def normal_distribution(x, mean, standard_deviation):
    """
    Calculates Percentile, Probability between two values and Probability density function

    Parameters
    -------
        x: Real-number value of x is used to calculate probabilities and probability density function.
        mean: The mean is a value that represents the middle of a set of numbers.
            Usually, the mean refers to to the arithmetic mean or arithmetic average.
        standard_deviation: The standard deviation is a statistic that measures
            the data variability. It is derived from the square root of the distances
            between each value in the population and the population's mean squared.

    Returns
    -------
        P( X ≤ x )
        P( X > x )
        P(-|x| ≤ X ≤ |x| )
        Probability density( x )
    """

    nd = normal_distribution_function(mean, standard_deviation)
    zs = z_score(x, mean, standard_deviation)
    p1 = probability_for_normal_distribution(nd, zs)
    p2 = 1 - p1
    p3 = abs(abs(p1) - abs(p2))
    pd = nd(x)

    return p1, p2, p3, pd

# Example
print(normal_distribution(1, 0, 1))


'''
Integre numericamente a equação associada à dinâmica de um cometa na vizinhança do Sol. Use método de passo variável
com RK2 corrigido por RK4. Use GM=1.

Condições iniciais:

   1. x=0; y=4; vx=-1/2; vy=0 (órbita circular)
   2. x=0; y=4; vx=-1/4; vy=0 (órbita elíptica 1)
   3. x=0; y=4; vx=-0.65; vy=0 (órbita elíptica 2)
   4. x=0; y=4; vx=-sqrt(2)/2; vy=0 (órbita parabólica)
   5. x=0; y=4; vx= -1; vy=0 (órbita hiperbólica)

'''
import numpy as np
import matplotlib.pyplot as plt

def rk2heun(x0, y0, vx0, vy0, tf, dt, gm):
    fvx = lambda x, y: -(gm * x) / (((x ** 2) + (y ** 2)) ** (3 / 2))
    fvy = lambda x, y: -(gm * y) / (((x ** 2) + (y ** 2)) ** (3 / 2))
    posx = [x0]
    posy = [y0]
    velx = [vx0]
    vely = [vy0]
    for t in np.arange(0, tf, dt):
        k1x = vx0
        k1y = vy0
        k1vx = fvx(x0, y0)
        k1vy = fvy(x0, y0)
        xaux = x0 + k1x * dt
        yaux = y0 + k1y * dt
        vxaux = vx0 + k1vx * dt
        vyaux = vy0 + k1vy * dt
        k2x = vxaux
        k2y = vyaux
        k2vx = fvx(xaux, yaux)
        k2vy = fvy(xaux, yaux)
        x0 += (k1x + k2x)*(dt/2)
        y0 += (k1y + k2y)*(dt/2)
        vx0 += (k1vx + k2vx)*(dt/2)
        vy0 += (k1vy + k2vy)*(dt/2)
        posx.append(x0)
        posy.append(y0)
        velx.append(vx0)
        vely.append(vy0)
    return posx, posy, velx, vely

def passovarRK(x0, y0, vx0, vy0, tf, dt, gm, tol):
    fvx = lambda x, y: -(gm * x) / (((x ** 2) + (y ** 2)) ** (3 / 2))
    fvy = lambda x, y: -(gm * y) / (((x ** 2) + (y ** 2)) ** (3 / 2))
    posx = [x0]
    posy = [y0]
    velx = [vx0]
    vely = [vy0]
    cont = 0
    t = 0
    while t <= tf:
        if cont % 5 == 0:
            x02, x04 = x0, x0
            y02, y04 = y0, y0
            vx02, vx04 = vx0, vx0
            vy02, vy04 = vy0, vy0
            # RK2 Heun
            k1x2 = vx02
            k1y2 = vy02
            k1vx2 = fvx(x02, y02)
            k1vy2 = fvy(x02, y02)
            xaux2 = x02 + k1x2 * dt
            yaux2 = y02 + k1y2 * dt
            vxaux2 = vx02 + k1vx2 * dt
            vyaux2 = vy02 + k1vy2 * dt
            k2x2 = vxaux2
            k2y2 = vyaux2
            k2vx2 = fvx(xaux2, yaux2)
            k2vy2 = fvy(xaux2, yaux2)
            x02 += (k1x2 + k2x2) * (dt / 2)
            y02 += (k1y2 + k2y2) * (dt / 2)
            vx02 += (k1vx2 + k2vx2) * (dt / 2)
            vy02 += (k1vy2 + k2vy2) * (dt / 2)

            # RK4
            k1x4 = vx04
            k1y4 = vy04
            k1vx4 = fvx(x04, y04)
            k1vy4 = fvy(x04, y04)
            xaux4 = x04 + k1x4 * (dt / 2)
            yaux4 = y04 + k1y4 * (dt / 2)
            vxaux4 = vx04 + k1vx4 * (dt / 2)
            vyaux4 = vy04 + k1vy4 * (dt / 2)
            k2x4 = vxaux4
            k2y4 = vyaux4
            k2vx4 = fvx(xaux4, yaux4)
            k2vy4 = fvy(xaux4, yaux4)
            xaux4 = x0 + k2x4 * (dt / 2)
            yaux4 = y0 + k2y4 * (dt / 2)
            vxaux4 = vx0 + k2vx4 * (dt / 2)
            vyaux4 = vy0 + k2vy4 * (dt / 2)
            k3x4 = vxaux4
            k3y4 = vyaux4
            k3vx4 = fvx(xaux4, yaux4)
            k3vy4 = fvy(xaux4, yaux4)
            xaux4 = x0 + k3x4 * dt
            yaux4 = y0 + k3y4 * dt
            vxaux4 = vx0 + k3vx4 * dt
            vyaux4 = vy0 + k3vy4 * dt
            k4x4 = vxaux4
            k4y4 = vyaux4
            k4vx4 = fvx(xaux4, yaux4)
            k4vy4 = fvy(xaux4, yaux4)
            x04 += (k1x4 + 2 * k2x4 + 2 * k3x4 + k4x4) * (dt / 6)
            y04 += (k1y4 + 2 * k2y4 + 2 * k3y4 + k4y4) * (dt / 6)
            vx04 += (k1vx4 + 2 * k2vx4 + 2 * k3vx4 + k4vx4) * (dt / 6)
            vy04 += (k1vy4 + 2 * k2vy4 + 2 * k3vy4 + k4vy4) * (dt / 6)

            errx = ((x02 - x04)**2)**(1/2)
            erry = ((y02 - y04) ** 2) ** (1 / 2)
            errvx = ((vx02 - vx04)**2)**(1/2)
            errvy = ((vy02 - vy04) ** 2) ** (1 / 2)
            erro = max(errx, erry, errvx, errvy)
            dtnovo= dt * (tol/erro)**(1/3)
            if dtnovo > 2*dt:
                dt = 2 * dt
            if dtnovo < dt/2:
                dt = dt/2
        k1x = vx0
        k1y = vy0
        k1vx = fvx(x0, y0)
        k1vy = fvy(x0, y0)
        xaux = x0 + k1x * dt
        yaux = y0 + k1y * dt
        vxaux = vx0 + k1vx * dt
        vyaux = vy0 + k1vy * dt
        k2x = vxaux
        k2y = vyaux
        k2vx = fvx(xaux, yaux)
        k2vy = fvy(xaux, yaux)
        x0 += (k1x + k2x) * (dt / 2)
        y0 += (k1y + k2y) * (dt / 2)
        vx0 += (k1vx + k2vx) * (dt / 2)
        vy0 += (k1vy + k2vy) * (dt / 2)
        posx.append(x0)
        posy.append(y0)
        velx.append(vx0)
        vely.append(vy0)
        cont += 1
        t += dt
    return posx, posy, velx, vely

x0 = 0
y0 = 4
vx0 = [-1/2, -1/4, -0.65, -(2**(1/2))/2, -1]
vy0 = 0
gm = 1

tf = 51
dt = 0.1

tol = 1e-6

for vx in vx0:
    plt.plot(passovarRK(x0, y0, vx, vy0, tf, dt, gm, tol)[0], passovarRK(x0, y0, vx, vy0, tf, dt, gm, tol)[1], label=r'$v_{0x}=$'+f'{vx:.3f}')
plt.xlabel("x")
plt.ylabel("y")
plt.title('Órbitas em diferentes condições')
plt.legend()
plt.gca().set_aspect('equal', adjustable='box')
plt.grid()

plt.savefig("doiscorpos.png", dpi=300)


# Least squares interpolation

import numpy as np
from qr_factor import qr_factor
from triangular import triangular

def least_squares(x, y, d, X):
    '''
    Least squares polynomial

    Input:
      - x : x-axis known values
      - y : y-axis known values
      - d : degree of polynomial you want to use
      - X : query points x axis
    Output:
      - Y : values of the least squares polynomial at the query points
    '''
    n = d + 1
    # Build the Vandermonde matrix
    A = np.ones((len(x), n))
    for i in range(1, n):
        A[:,i] = x * A[:,i-1]
    # QR factorization
    (Q, R) = qr_factor(np.asmatrix(A))
    # Least squares vector is vector z such that T*z = g1
    g = Q.T * np.asmatrix(y).T
    g1 = g[0:n]
    T = R[0:n,0:n]
    p = triangular(T, g1, 0)
    # Evaluation of polynomial in query points
    Y = np.polyval(np.flip(p, 0), X)
    return Y


#!/usr/bin/env python3

import random
import numpy as np
import sympy

mod_space = 29

'''
Generate Encryption Key
'''
# In --> size of matrix (n x n)
# Out --> List of lists [[1,2,3],[4,5,6],[7,8,9]]

def generate_encryption_key(size):
    determinant = 0
    # Need to make sure encryption key is invertible, IE det(key) != 0
    while determinant == 0:
        matrix = []
        for i in range(size):  # Repeat i times based on input size
            row = []
            for k in range(size):
                # Add Random integer from 0 - mod space that we are working in
                number = random.randint(0, mod_space)
                row.append(number)
            matrix.append(row)  # Add row to matrix

        # Convert list of lists into numpy array, which acts as a matrix
        encryption_key = np.array(matrix)
        try:
            determinant = sympy.Matrix(encryption_key.tolist()).inv_mod(29).det()
        except:
            pass
        # If matrix is invertible, end function and return matrix
        #print(determinant)
        #determinant = int(np.linalg.det(encryption_key))
    return encryption_key

'''
Find Modular Inverse
'''
# In --> number, modspace (default is 29 for our case)
# Out --> modular inverse of number

def modular_inverse(num):

    for i in range(mod_space):  # Loop through possibile inverses in modspace
        if (num * i) % mod_space == 1:  # If i is an inverse for the number in modspace, return the number
            return i
    return False  # If inverse does not exist, return False

'''
Generate Decryption Key
'''
# In --> Encryption Key (matrix form)
# Out --> Decryption Key

def generate_decryption_key(encryption_key):

    # Take the prod of these 2 vars
    key_inv = np.linalg.inv(encryption_key)  # Inverse of encryption key
    # Determinant of encryption key
    det_key = int(np.linalg.det(encryption_key))

    #print((key_inv * (det_key) * modular_inverse(det_key)) % 29)

    # How to get multiplicative inverse of det(key) % 29
    # If key = [[1,2],[3,4]] ,  det(key) % 29 == 27      and
    ##                         inverse(det(key) % 29) == 14
    ##
    ##
    # How do we get from 27 to 14?
    ##
    # (det_key_mod * x) % 29 = inv --> solve for x
    # x == 14 in our example

    det_key_mod = int(det_key % 29)  # Determinant of encryption key mod 29
    # Find modular inverse of above var using function defined above
    det_key_mod_inv = int(modular_inverse(det_key_mod))

    #print(det_key_mod, det_key_mod_inv)

    # Final decryption key for [[1,2],[3,4]] is [[27,1],[16,14]]
    # decryption_key = inv(det(key)mod29) * (det(key) * inv(key)) % 29
    decryption_key = (key_inv * det_key)
    #decryption_key = np.around(decryption_key)
    #decryption_key = decryption_key.astype(int)
    decryption_key = (det_key_mod_inv * decryption_key) % 29
    decryption_key = np.around(decryption_key, 0)
    #print(decryption_key)
    return decryption_key

def generate_sympy_decryption_key(encryption_key):
    encryption_key = sympy.Matrix(encryption_key.tolist())
    #key_inverse = encryption_key ** -1
    #key_determinant = encryption_key.det()
    decryption_key = np.array(encryption_key.inv_mod(29))

    #key_determinant_mod = key_determinant % 29
    return decryption_key

#x =  np.array([[1,2],[3,4]])
# print(x)
#x = generate_encryption_key(4)
#generate_sympy_decryption_key(x)
#print(x)
#res = generate_decryption_key(x)


"""

https://en.wikipedia.org/wiki/Eigendecomposition_of_a_matrix
https://en.wikipedia.org/wiki/Singular_value_decomposition

"""

import numpy as np

"""

There are a few ways of factoring a matrix into a form of
M = A*B*C

For a square matrix, if the matrix is diagonalizable, then
it can be eigendecomposed into the following form
M = E*S*inv(E)
where E are the eigenvectors of the matrix M and S is the eigenvalues of the matrix
S is a scaling matrix but E can be a non-rotation matrix, the determinant of E is not 1/-1 usually
This implies that the vectors in the eigendecomposition are not necessarily orthogonal
S can be any complex number scale though
This mainly exists for square matrices, for non-square it sometimes doesn't

Another way is to use SVD decomposition
M = U*E*V
where S and D are a rotation matrix (determinant is 1/-1) and V is a scaling matrix
This implies that S and D are orthonormal and represent rotations/flips
U and V and are not necessarily inverse of each other though
E are positive and non-negative
This decomposition always exist for square and non-square matrices, its a pseudo-inverse for non-square matrices

"""

def test_matrix_factorize(n, s, iters):
    for i in range(iters):
        M = np.random.rand(n, n) * s
        ew, ev = np.linalg.eig(M)
        ER = ev
        ES = np.diag(ew)
        EN = np.dot(ER, np.dot(ES, np.linalg.inv(ER)))

        U, E, V = np.linalg.svd(M)
        E = np.diag(E)
        UEV = np.dot(U, np.dot(E, V))

        print("Matrix {}x{}".format(n, n))
        print(M)
        print()
        print("Eigendecomposition det={}".format(np.linalg.det(ER)))
        print("Eigenvectors det={}".format(np.linalg.det(ER)))
        print(ER)
        print("Eigenvalues det={}".format(np.linalg.det(ES)))
        print(ES)
        print()

        print("SVD decomposition")
        print("U det={}".format(np.linalg.det(U)))
        print(U)
        print("E det={}".format(np.linalg.det(E)))
        print(E)
        print("V det={}".format(np.linalg.det(V)))
        print(V)
        print()

        assert(np.allclose(M, EN))
        assert(np.allclose(M, UEV))

for i in range(1, 100):
    test_matrix_factorize(i, 1e4, 1)


# -*- coding: utf-8 -*-
"""
Created on Sun Dec 10 20:28:24 2017

@author: tzave
"""

import numpy as np
import matplotlib.pyplot as plt

def NewtonRaphson(x0, f, df, tol, N=None):
    i = 1
    p0 = x0
    while (True if N is None else (i < N)):
        p = p0 - f(p0)/df(p0)
        if np.abs(p-p0) < tol:
            break
        i = i + 1
        p0 = p
    return {'p': p, 'iterations': i}

def RegulaFalsi(a, b, f, tol, N=None):
    i = 1
    p0 = a
    p1 = b
    q0 = f(a)
    q1 = f(b)
    while (True if N is None else (i < N)):
        p = p1 - q1 * (p1 - p0)/(q1 - q0)
        if np.abs(p - p1) < tol:
            break
        i = i + 1
        p0 = p1
        q0 = q1
        p1 = p
        q1 = f(p)
    return {'p': p, 'iterations': i}

def Bisection(a, b, f, tol, N=None):
    i = 1
    c_old = 0
    while (True if N is None else (i < N)):
        c = (a + b) / 2
        if np.isclose(f(c), 0) or (i > 1 and (np.abs(c-c_old) < tol)):
            break
        i = i + 1
        if f(a) * f(c) < 0:
            b = c
        else:
            a = c
        c_old = c
    return {'p': c, 'iterations': i}

def f(x):
    return np.power(x, 4) + 2 * np.power(x, 3) - 5 *\
          np.power(x, 2) - 7 * x - 5

def df(x):
    return 4 * np.power(x, 3) + 6 * \
          np.power(x, 2) - 10 * x - 7

x0 = 2
roots = np.zeros(3)

result = NewtonRaphson(x0, f, df, 1e-5)
roots[0] = result['p']
iterations = result['iterations']
print('Newton Raphson Method: Root ', roots[0],
      ' found after ', iterations, ' iterations')

result = RegulaFalsi(1.5, 3, f, 1e-5)
roots[1] = result['p']
iterations = result['iterations']
print('RegulaFalsi Method: Root ', roots[1],
      ' found after ', iterations, ' iterations')

result = Bisection(1.5, 3, f, 1e-5)
roots[2] = result['p']
iterations = result['iterations']
print('Bisection Method: Root ', roots[2],
      ' found after ', iterations, ' iterations')

x = np.arange(1.5, 3, 0.1)
plt.close('all')
plt.figure(1)
plt.plot(x, f(x),
         roots[0], f(roots[0]), '*r',
         roots[1], f(roots[1]), '*b',
         roots[2], f(roots[2]), '*k')
plt.grid()

x = np.arange(1.5, 3, 0.01)
plt.figure(2)
plt.plot(x, df(x), label = 'df')
plt.legend()
plt.grid()


"""

"""
from sympy import pi,sin,cos,tan
from sympy.matrices import Matrix,eye
import sympy as sp
from itertools import combinations
# ~ from scipy.spatial import Delaunay, ConvexHull
import numpy as np
import sympy.core as sc
import sympy.matrices as sm
from sympy.core.basic import Basic

__all__ = [
    "pprint",
    "deg2rad",
    "ishtm",
    "isorthonormal",
    "isrot",
    "rad2deg",
    "sympy2float",
    "sympy_matrix_to_numpy_float",
    "issympyobject",
    "hcoords"
]

def pprint(*args,**kwargs):
    return sp.pprint(*args,**kwargs)

def deg2rad(theta, evalf=True):
    """
    Convert degrees to radians

    Parameters
    ----------

    theta : float, int, symbolic

    Returns
    -------

    theta_rad : symbolic
    """
    if evalf:
        theta_rad = ( theta*(pi/180) ).evalf()
    else:
        theta_rad = theta*(pi/180)
    return theta_rad

def rad2deg(theta, evalf=True):
    """
    Convert radians to degrees

    Parameters
    ----------

    theta : float, int, symbolic

    Returns
    -------

    theta_deg : symbolic
    """
    if evalf:
        theta_deg = ( theta*(180/pi) ).evalf()
    else:
        theta_deg = theta*(180/pi)
    return theta_deg

def issympyobject(obj):
    """
    Determine if input (obj) is a sympy object.

    Examples
    --------
    >>> from sympy import symbols
    >>> x = symbols("x")
    >>> issympyobject(x)
    True
    """
    if isinstance( obj, tuple(sc.all_classes ) ):
        return True
    elif isinstance(obj, Basic):
        return True
    elif isinstance(obj, sm.MatrixBase):
        return True
    else:
        return False

def ishtm(H):
    """
    Is H a homogeneous transformation matrix ?
    """
    nrow,ncol = H.shape
    if nrow == ncol == 4:
        if isrot(H[:3,:3]) and H[3,3]==1 and not any(H[3,:3]):
            return True
    return False

def isrot(R):
    """
    Is R a rotation matrix ?

    Parameters
    ----------

    R : `sympy.matrices.dense.MutableDenseMatrix`

    Returns
    -------

    False or True

    """
    nrow,ncol = R.shape
    if (nrow == ncol == 3) and isorthonormal(R):
        return True
    return False

def isorthonormal(R):
    """
    Check if R is orthonormal

    Parameters
    ----------

    R : `sympy.matrices.dense.MutableDenseMatrix`

    Returns
    -------

    False or True

    """
    _,ncol = R.shape
    for i,j in combinations(range(ncol), 2):
        if ( R[:,i].dot(R[:,j]) ).simplify() != 0:
            return False
    for i in range(ncol):
        if R[:,i].norm().simplify() != 1:
            return False
    return True

def hcoords(v):
    return v.col_join(Matrix([1]))

def sympy_matrix_to_numpy_float(H):
    """
    Convert SymPy Matrix (numerical) to NumPy array

    Parameters
    ----------

    H : `sympy.matrices.dense.MutableDenseMatrix`

    Returns
    -------

    Hf : array

    """
    Hf = np.array(H).astype("float64")
    return Hf

def sympy2float(sympy_object):
    """
    Convert a SymPy object to float object
    """
    if isinstance(sympy_object, Matrix):
        float_object = sympy_matrix_to_numpy_float(sympy_object)
    else:
        float_object = sympy_object
    return float_object

if __name__=="__main__":
    H = Matrix([[1,1,0,5],[1,0,0,4],[0,0,1,0],[0,0,0,1]])
    print(ishtm(H))

import numpy,from scipy,import sympy,from sympy


""" Given a vacabulary file, interploate the zipf \alpha """
import numpy as np
import sys

def powerLaw(y, x):
    """
    'When the frequency of an event varies as power of some attribute of that
    event the frequency is said to follow a power law.' (wikipedia)
    This is represented by the following equation, where c and alpha are
    constants:
    y = c . x ^ alpha
    Args
    --------
    y: array with frequency of events >0
    x: numpy array with attribute of events >0
    Output
    --------
    (c, alpha)
    c: the maximum frequency of any event
    alpha: defined by (Newman, 2005 for details):
        alpha = 1 + n * sum(ln( xi / xmin )) ^ -1
    """
    c = 0
    alpha = .0

    if len(y) and len(y)==len(x):
        c = max(y)
        xmin = float(min(x))
        alpha = 1 + len(x) * pow(sum(np.log(x/xmin)),-1)

    return (c, alpha)

def read_word_frequency(fname):
    freq = {}
    with open(fname, 'r') as f:
        for l in f:
            entry = l.split()
            if len(entry) != 2: # some edge cases like ' ' or just empty token.
                continue
            freq[entry[0]] = int(entry[1])
    return freq

if __name__=="__main__":
    # vocab.txt
    freq = read_word_frequency(sys.argv[1])
    y = sorted(freq.values(),reverse=True)
    x = np.array(range(1,len(y)+1))
    c, alpha = powerLaw(y, x)
    print(c, alpha)

    """
    from scipy import stats
    slope, intercept, r_value, p_value, std_err = stats.linregress(rank[30:-100],freq[30:-100])
    print(slope, intercept, std_err)
    """


# -*- coding: utf-8 -*-
"""
numpy provides n-dimensional array object; many functions to manipulate these
it doesn't know of computational graphs, deep learning or gradients

we can use it to fit 2-layer network to random dat,
a by manually implementing forward and backward passes
"""

import numpy

## initializing dimensions
BatchSize, InDimension, HiddenDimension, OutDimension = 64, 1000, 100, 10

## randomly initialize input and output data
in_data = numpy.random.randn(BatchSize, InDimension)
out_data = numpy.random.randn(BatchSize, OutDimension)

## randomly initialize weights
weight01 = numpy.random.randn(InDimension, HiddenDimension)
weight02 = numpy.random.randn(HiddenDimension, OutDimension)

learning_rate = 1e-6

def check_loss(prediction, value):
    ## compute and print loss
    loss = numpy.square(prediction - value).sum()
    print(tensor, loss)

def calculate_gradients(hid, hidden_relu, out_data_prediction):
    ## backdrop to compute gradients of weight01,02 wrt loss
    gradient_out_data_prediction = 2.0 * (out_data_prediction - out_data)
    gradient_weight02 = hidden_relu.T.dot(gradient_out_data_prediction)
    gradient_hid_relu = gradient_out_data_prediction.dot(weight02.T)
    gradient_hid = gradient_hid_relu.copy()
    gradient_hid[hid < 0] = 0
    gradient_weight01 = in_data.T.dot(gradient_hid)
    return gradient_weight01, gradient_weight02

def recalculate_weight(weight, gradient_weight):
    weight -= learning_rate * gradient_weight
    return weight

for tensor in range(500):
    ## forward pass: compute predicted out_data
    hid = in_data.dot(weight01)
    hidden_relu = numpy.maximum(hid, 0)
    out_data_prediction = hidden_relu.dot(weight02)

    check_loss(out_data_prediction, out_data)

    gradient_weight01, gradient_weight02 = calculate_gradients(hid, hidden_relu, out_data_prediction)

    ## update weights
    weight01 = recalculate_weight(weight01, gradient_weight01)
    weight02 = recalculate_weight(weight02, gradient_weight02)


import numpy as np
import pulp

# create the LP object, set up as a maximization problem
prob = pulp.LpProblem('Giapetto', pulp.LpMaximize)

# set up decision variables
soldiers = pulp.LpVariable('soldiers', lowBound=0, cat='Integer')
trains = pulp.LpVariable('trains', lowBound=0, cat='Integer')

# model weekly production costs
raw_material_costs = 10 * soldiers + 9 * trains
variable_costs = 14 * soldiers + 10 * trains

# model weekly revenues from toy sales
revenues = 27 * soldiers + 21 * trains

# use weekly profit as the objective function to maximize
profit = revenues - (raw_material_costs + variable_costs)
prob += profit  # here's where we actually add it to the obj function

# add constraints for available labor hours
carpentry_hours = soldiers + trains
prob += (carpentry_hours <= 80)

finishing_hours = 2*soldiers + trains
prob += (finishing_hours <= 100)

# add constraint representing demand for soldiers
prob += (soldiers <= 40)

# solve the LP using the default solver
optimization_result = prob.solve()

# make sure we got an optimal solution
assert optimization_result == pulp.LpStatusOptimal

# display the results
for var in (soldiers, trains):
    print('Optimal weekly number of {} to produce: {:1.0f}'.format(var.name, var.value()))

from matplotlib import pyplot as plt
from matplotlib.path import Path
from matplotlib.patches import PathPatch

# use seaborn to change the default graphics to something nicer
# and set a nice color palette
import seaborn as sns
sns.set_palette('Set1')

# create the plot object
fig, ax = plt.subplots(figsize=(8, 8))
s = np.linspace(0, 100)

# add carpentry constraint: trains <= 80 - soldiers
plt.plot(s, 80 - s, lw=3, label='carpentry')
plt.fill_between(s, 0, 80 - s, alpha=0.1)

# add finishing constraint: trains <= 100 - 2*soldiers
plt.plot(s, 100 - 2 * s, lw=3, label='finishing')
plt.fill_between(s, 0, 100 - 2 * s, alpha=0.1)

# add demains constraint: soldiers <= 40
plt.plot(40 * np.ones_like(s), s, lw=3, label='demand')
plt.fill_betweenx(s, 0, 40, alpha=0.1)

# add non-negativity constraints
plt.plot(np.zeros_like(s), s, lw=3, label='t non-negative')
plt.plot(s, np.zeros_like(s), lw=3, label='s non-negative')

# highlight the feasible region
path = Path([
    (0., 0.),
    (0., 80.),
    (20., 60.),
    (40., 20.),
    (40., 0.),
    (0., 0.),
])
patch = PathPatch(path, label='feasible region', alpha=0.5)
ax.add_patch(patch)

# labels and stuff
plt.xlabel('soldiers', fontsize=16)
plt.ylabel('trains', fontsize=16)
plt.xlim(-0.5, 100)
plt.ylim(-0.5, 100)
plt.legend(fontsize=14)
plt.show()


#!/usr/bin/env python3

import numpy as np
import scipy.linalg
import numpy.linalg as la

"""
def vector_lengths(a):
    return np.sqrt(np.sum(np.square(a), axis=1))

def vector_angles(X, Y):
    denominator=vector_lengths(X)*vector_lengths(Y)
    dotprod=np.sum(X*Y, axis=1)
    div=dotprod/denominator
    return np.degrees(np.arccos(np.clip(div,-1.0,1.0)))
"""
#mettere np.degrees dentro questa funzione per passare l'esercizio su TMC
def vector_angles(X, Y):
    ip = np.sum(X*Y, axis=1) #inner product
    Xlen = scipy.linalg.norm(X, 2, axis=1)
    Ylen = scipy.linalg.norm(Y, 2, axis=1)
    cosine = ip/(Xlen*Ylen)
    cosine = np.clip(cosine, -1.0, 1.0) #force cosine between -1 and 1
    radians = np.arccos(cosine)
    return radians

def main():
    np.random.seed(0)
    a=np.random.randint(0,10, (4,4))
    b=np.random.randint(0,10, (4,4))
    print(a)
    print(b)
    print(np.round(np.degrees(vector_angles(a,b)), 2))

if __name__ == "__main__":
    main()


'''
Collated by Ching-Shih Tsou 鄒慶士 博士 (Ph.D.) Distinguished Prof. at the Department of Mechanical Engineering/Director at the Center of Artificial Intelligence & Data Science (機械工程系特聘教授兼人工智慧暨資料科學研究中心主任), MCUT (明志科技大學) Prof. at the Institute of Information & Decision Sciences (資訊與決策科學研究所教授), NTUB (國立臺北商業大學) the Chinese Academy of R Software (CARS) (中華R軟體學會創會理事長) the Data Science and Business Applications Association of Taiwan (DSBA) (臺灣資料科學與商業應用協會創會理事長) the Chinese Association for Quality Assessment and Evaluation (CAQAE) (中華品質評鑑協會常務監事) the Chinese Society of Quality (CSQ) (中華民國品質學會大數據品質應用委員會主任委員
Notes: This code is provided without warranty.
'''

#### Real Values Signals 實數值訊號
# CT signal
import numpy as np
import matplotlib.pyplot as plt

t = np.linspace(-0.02, 0.05, 1000)
y = 325 * np.sin(2 * np.pi * 50 * t)  # y = Amp * sin(2pi*f*t + 0)
# 高階繪圖(high-level plotting)
plt.plot(t, y)  # (time, amplitude) --DFT(FFT)--> (freq, amplitude or power)
# 低階繪圖(low-level plotting)
plt.xlabel('time')
plt.ylabel('y=x(t) amplitude')
plt.title(r'Plot of CT signal $y=x(t)=325 \sin(2\pi 50 t)$')
plt.xlim([-0.02, 0.05])
plt.show()

# DT signal (Stem Plot or histogram plot)
n = np.arange(50)  # 樣本點數
dt = 0.07 / 50  # (end - start)/# points
x = np.sin(2 * np.pi * 50 * n * dt)
# 高階繪圖(high-level plotting)
plt.stem(n, x)
# 低階繪圖(low-level plotting)
plt.xlabel('n')
plt.ylabel('x[n]')
plt.title(r'Plot of DT signal $x[n] = 325 \sin(2\pi 50 n \Delta t)$')

#### Complex Valued Signals 複數值訊號
# x(t)=exp(j100πt), j is the complex notation in Python
# Real part and complex part

t = np.linspace(-0.02, 0.05, 1000)
plt.subplot(2, 1, 1)  # 圖面切分：兩rows，一column，繪1st子圖
plt.plot(t, np.exp(2j * np.pi * 50 * t).real)
plt.xlabel('t')
plt.ylabel('Re x(t)')
plt.title(r'Real part of  $x(t)=e^{j 100 \pi t}$')
plt.xlim([-0.02, 0.05])
plt.subplot(2, 1, 2)  # 圖面切分：兩rows，一column，繪2nd子圖
plt.plot(t, np.exp(2j * np.pi * 50 * t).imag)
plt.xlabel('t')
plt.ylabel('Im x(t)')
plt.title(r'Imaginary part of  $x(t)=e^{j 100 \pi t}$')
plt.xlim([-0.02, 0.05])
plt.subplots_adjust(wspace=0, hspace=0.5)
plt.show()

# 複數訊號計算
np.exp(2j * np.pi * 50 * t)

# Magnitude and phase 複數訊號的振幅與相位角
t = np.linspace(-0.02, 0.05, 1000)
plt.subplot(2, 1, 1)
plt.plot(t, np.abs(np.exp(2j * np.pi * 50 * t)))  # np.abs(np.exp(2j * np.pi * 50 * t)) --> 計算振幅
plt.xlabel(r'$t$')
plt.ylabel(r'$|x(t)|$')
plt.title(r'Absolute value  of  $x(t)=e^{j 100 \pi t}$')
plt.xlim([-0.02, 0.05])
plt.subplot(2, 1, 2)
plt.plot(t, np.angle(np.exp(2j * np.pi * 50 * t)) * 360 / (2 * np.pi))
plt.xlabel('$t$')
plt.ylabel(r'$\angle x(t)$')
plt.title(r'Phase of  $x(t)=e^{j 100 \pi t}$')
plt.xlim([-0.02, 0.05])
plt.subplots_adjust(wspace=0, hspace=0.5)
plt.show()

#### References:
# https://staff.fnwi.uva.nl/r.vandenboomgaard/SP20162017/SystemsSignals/plottingsignals.html


#!/usr/bin/env python3
"""
Normalize
"""

import numpy as np

def normalization_constants(X):
    """
    normalizing
    """
    m = X.shape[0]
    mean = np.sum(X / m, axis=0)
    std = np.sqrt(np.sum(((X - mean) ** 2), axis=0) / m)
    return mean, std


#Mass Spring Damper system Parameter File
import numpy as np
import control as cnt
import sys
sys.path.append('..') #add parent directory
import massSpringParam as P

Ts = P.Ts
beta = P.beta
tau_max = P.tau_max
m = P.m
k = P.k
b = P.b

#tuning parameters
#tr=1.6    #previous homework was done on the basis of tr=1.6 and step input is taken from this homework onwards
tr = 1.5
zeta = 0.7

#State Space Equations
# xdot = A*x + B*u
# y = C*x
A = np.array([[0.0, 1.0],
              [-P.k/P.m, -P.b/P.m]])
B = np.array([[0.0],
              [1.0/P.m]])
C = np.array([[1.0, 0.0]])

#gain calculation
wn = 2.2/tr #natural frequency
des_char_poly = [1, 2*zeta*wn, wn**2]
des_poles = np.roots(des_char_poly)

#Compute the gains if the system is controllable
if np.linalg.matrix_rank(cnt.ctrb(A, B)) != 2:
    print("The system is not controllable")
else:
    #.A just turns K matrix into a numpy array
    K = (cnt.acker(A, B, des_poles)).A
    kr = -1.0/(C @ np.linalg.inv(A - B @ K) @ B)

print('K: ', K)
print('kr: ', kr)


import numpy as np

class KMeans:
    def __init__(self, n_clusters, max_iter=100):
        self.n_clusters = n_clusters
        self.max_iter = max_iter
        self.centroid = []
        self.next_centroid = []
        self.labels_ = []

    def init_centroid(self,data,n_clusters):
        idx = np.sort(np.random.choice(len(data), n_clusters, replace=False))
        self.centroid = np.array(data)[idx].tolist()

        return self.centroid

    def euclidean_dst(self,x,y):
        distance = 0
        for i in range(len(x)):
            distance += (x[i] - y[i]) ** 2

        return np.sqrt(distance)

    def get_cluster(self, n_cluster, instances, centroid):
        distance = list(self.euclidean_dst(instances,centroid[i]) for i in range(n_cluster))

        return distance.index(min(distance))

    def find_cluster_member(self,label,cluster):
        indices = list(i for i, x in enumerate(label) if x == cluster)

        return indices

    def calculate_new_centroid(self,data,indices):
        sum_point = []
        for i in range(len(data[0])):
            total = 0
            for j in range(len(indices)):
                total += data[indices[j]][i]
            sum_point.append(total)

        for i in range(len(sum_point)):
            sum_point[i] /= len(indices)

        return sum_point

    def fit(self, data):
        self.centroid = self.init_centroid(data,self.n_clusters)
        convergance = False
        iteration = 0

        while not convergance:
            if (iteration > 0) :
                self.centroid = self.next_centroid

            self.next_centroid = []
            self.labels_ = []

            self.labels_ = list(self.get_cluster(self.n_clusters,data[i],self.centroid) for i in range(len(data)))
            self.next_centroid = list(self.calculate_new_centroid(data,self.find_cluster_member(self.labels_,i)) for i in range(self.n_clusters))

            iteration += 1
            convergance = (self.centroid == self.next_centroid or iteration >=100)

    def predict(self, instances):
        return self.get_cluster(self.n_clusters, instances, self.centroid)


# --------------
import pandas as pd
import numpy as np
import math
class complex_numbers:
    def __init__(self, real,imag):
        self.real = real
        self.imag = imag
    def __repr__(self):
        if self.real == 0.0 and self.imag == 0.0:
            return "0.00"
        if self.real == 0:
            return "%.2fi" % self.imag
        if self.imag == 0:
            return "%.2f" % self.real
        return "%.2f %s %.2fi" % (self.real, "+" if self.imag >= 0 else                  "-", abs(self.imag))
    def __add__(self, other):
        x = self.real + other.real
        y = self.imag + other.imag
        return complex_numbers(x,y)
    def __sub__(self, other):
        x = self.real - other.real
        y = self.imag - other.imag
        return complex_numbers(x,y)
    def __mul__(self, other):
        x = (self.real*other.real)-(self.imag*other.imag)
        y = (self.imag*other.real) + (self.real*other.imag)
        return complex_numbers(x,y)
    def __truediv__(self, other):
        sr, si, ore, oi = self.real, self.imag,                       other.real, other.imag # short forms
        r = float(ore**2 + oi**2)
        return complex_numbers((sr*ore+si*oi)/r, (si*ore-sr*oi)/r)
    def absolute(self):
        x  = self.real**2 + self.imag**2
        return math.sqrt(x)
    def argument(self):
        x  = math.atan(self.real/self.imag)
        x = 90-((x*180)/3.1415926535)
        return (x)
    def conjugate(self):
        x = self.real
        y = self.imag
        return complex_numbers(x,-y)

comp_1=complex_numbers(3,5)
comp_2=complex_numbers(4,4)
comp_sum=comp_1+comp_2
comp_diff=comp_1-comp_2
comp_prod=comp_1*comp_2
comp_quot=comp_1/comp_2
comp_abs = comp_1.absolute()
comp_conj = comp_1.conjugate()
comp_arg = comp_1.argument()
print(comp_1)
print(comp_2)
print(comp_sum)
print(comp_diff)
print(comp_prod)
print(comp_quot)
print(comp_abs)
print(comp_conj)
print(comp_arg)

#Code starts here


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

# # Example of performing linear least squares fitting

# ### First we import numpy and matplotlib as usual

# In[ ]:

get_ipython().run_line_magic('matplotlib', 'inline')
import matplotlib.pyplot as plt
import numpy as np

# ## Now let's generate some random data about a trend line

# In[ ]:

#Set a random number seed
np.random.seed(119)

#Set number of data points
npoints = 50

#Set x
x = np.linspace(0,10.,npoints)

#Set slope, intercept, and scatter rms
m = 2.0
b = 1.0
sigma = 2.0

#Generate y points
y = m*x + b + np.random.normal(scale=sigma, size=npoints) #scale=sigma is the rms value of a gaussian, so we're generating gaussian noise
y_err = np.full(npoints, sigma) #This means there are 50 two values to fill the array(?)

# ### Let's just plot the data first to make sure the plot works

# In[ ]:

f = plt.figure(figsize=(7,7))
plt.errorbar(x,y,sigma,fmt='o')
plt.xlabel('x')
plt.ylabel('y')

# ### Method #1, polyfit()

# In[ ]:

m_fit, b_fit = np.poly1d(np.polyfit(x, y, 1, w = 1./y_err)) #weight with uncertainties
print(m_fit, b_fit)

y_fit = m_fit * x * b_fit

# ### Plot result

# In[ ]:

f = plt.figure(figsize=(7,7))
plt.errorbar(x,y, yerr=y_err, fmt='o', label='data')
plt.plot(x,y_fit,label='fit')
plt.xlabel('x')
plt.ylabel('y')
plt.legend(loc=2, frameon=False)

# In[ ]:


import numpy as np

class LinearRegressor:

    def __init__(self):
        self.theta = None

    def train(self,X,y,learning_rate=1e-3, num_iters=100,verbose=False):

        """
        Train a linear model using gradient descent.

        Inputs:
        - X: 1-dimensional array of length N of training data.
        - y: 1-dimensional array of length N with values in the reals.
        - learning_rate: (float) learning rate for optimization.
        - num_iters: (integer) number of steps to take when optimizing
        - verbose: (boolean) If true, print progress during optimization.

        Outputs:
        A list containing the value of the loss function at each training iteration.
        """

        J_history = []

        # Initialize self.theta
        if self.theta is None:
            # lazily initialize theta
            self.theta = np.zeros((X.shape[1],))

        # Run gradient descent to find theta
        for i in range(num_iters):
            # evaluate loss and gradient
            loss, grad = self.loss(X, y)

            # add loss to J_history
            J_history.append(loss)

            #########################################################################
            # TODO:                                                                 #
            # Update the parameters using the gradient and the learning rate.       #
            #    One line of code expected                                          #
            #########################################################################

            #########################################################################
            #                       END OF YOUR CODE                                #
            #########################################################################

            # print loss every 1000 iterations
            if verbose and i % 1000 == 0:
                print ('iteration %d / %d: loss %f' % (i, num_iters, loss))

        return J_history

    def loss(self, X, y):
        """
        Compute the loss function and its derivative.
        Subclasses will override this.

        Inputs:
        - X: vector of length N with real values
        - y: 1-dimensional array of length N with real values.

        Returns: A tuple containing:
        - loss as a single float
        - gradient with respect to self.theta; an array of the same shape as theta
        """
        pass

    def predict(self, X):
        """
        Use the trained weights of this linear classifier to predict labels for
        data points.

        Inputs:
        - X: vector of length N of training data.

        Returns:
        - y_pred: Predicted output for the data in X. y_pred is a 1-dimensional
        array of length N, and each element is a real number.
        """
        y_pred = np.zeros(X.shape[0])
        ###########################################################################
        # TODO:                                                                   #
        # Implement this method. Store the predicted outputs in y_pred.           #
        #    One line of code expected                                            #
        ###########################################################################

        ###########################################################################
        #                           END OF YOUR CODE                              #
        ###########################################################################
        return y_pred

class LinearReg_SquaredLoss(LinearRegressor):
    "A subclass of Linear Regressors that uses the squared error loss function """

    """
    Function that returns loss and gradient of loss with respect to (X, y) and
    self.theta
        - loss J is a single float
        - gradient with respect to self.theta is an array of the same shape as theta

    """

    def loss (self,X,y):
        J = 0
        grad = np.zeros((2,))
        ###########################################################################
        # TODO:                                                                   #
        # Calculate J (loss) and grad (gradient) wrt to X,y, and self.theta.      #
        #   2-4 lines of code expected                                            #
        ###########################################################################

        ###########################################################################
        #                           END OF YOUR CODE                              #
        ###########################################################################
        return J, grad


# ------*------ coding: utf-8 ------*------
# @Time    : 2022/2/24 19:48
# @Author  : 冰糖雪狸 (NekoSilverfox)
# @Project : Statistica-model
# @File    : main.py
# @Software: PyCharm
# @Github  ：https://github.com/NekoSilverFox
# -----------------------------------------

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import scipy.stats
import math

TITLE_FONT_SIZE = 36
LABEL_FONT_SIZE = 36
TICK_FONT_SIZE = 32
FONT = 'Times New Roman'

def irnpoi(mu: int, size: int) -> np.ndarray:
    """
    【2.4.1】泊松分布（算法 1）
    :param mu: int 类型
    :param size: 数组大小
    :return: 具有泊松分布的 numpy.ndarry
    """
    if mu < 0 or size <= 0:
        raise ValueError

    arr_irnpoi = []

    for i in range(size):
        if mu < 88:
            i_uniform = np.random.uniform(low=0, high=1)
            p_t = math.exp(-mu)
            m = 1
            while (i_uniform - p_t) >= 0:
                i_uniform -= p_t
                p_t *= (mu / m)
                m += 1
            arr_irnpoi.append(m - 1)
        else:
            m = np.random.normal(loc=mu, scale=mu, size=1)
            arr_irnpoi.append(m)

    return np.array(arr_irnpoi)

def irnpsn(mu: int, size: int) -> np.ndarray:
    """
    【2.4.2】泊松分布（算法 2）
    :param mu: int 类型
    :param size: 数组大小
    :return: 具有泊松分布的 numpy.ndarry
    """
    if mu < 0 or size <= 0:
        raise ValueError

    arr_irnpsn = []

    for i in range(size):
        if mu < 88:
            i_uniform = np.random.uniform(low=0, high=1)
            p_t = i_uniform
            m = 1
            while p_t >= math.exp(-mu):
                i_uniform = np.random.uniform(low=0, high=1)
                p_t *= i_uniform
                m += 1
            arr_irnpsn.append(m - 1)
        else:
            m = np.random.normal(loc=mu, scale=mu, size=1)
            arr_irnpsn.append(m)

    return np.array(arr_irnpsn)

def get_poisson_chi_square(arr_source: np.ndarray, source_mu: int) -> int:
    """
    数组相对于标准泊松分布的卡方值
    :param source_mu: 参数泊松分布数组的 mu
    :param arr_source: 泊松分布数组
    :return: 相对于泊松分布的卡方值
    """
    if arr_source is None:
        raise ValueError

    N = arr_source.size  # 元素数量
    n = arr_source.max() - arr_source.min()  # 分的区间数
    chi_square = 0  # 卡方值
    arr_p_exp = get_arr_exp_hist_poisson(mu=source_mu, skip=n)
    arr_p_obs = get_hist_arr(arr=arr_source, arr_cut_value=n) / N

    for k in range(n):
        chi_square += (arr_p_obs[k] - arr_p_exp[k]) ** 2 / arr_p_exp[k]

    chi_square *= N

    print('原数组:', arr_source, ' (Min:', arr_source.min(), ' Max:', arr_source.max(), ')')
    print('元素数量 N:', N)
    print('区间数 n:', n)
    print('标准泊松频率数组:', arr_p_exp, ' shape:', arr_p_exp.shape, 'p_sum:', arr_p_exp.sum())
    print('随机泊松频率数组:', arr_p_obs, ' shape:', arr_p_obs.shape, 'p_sum:', arr_p_obs.sum())
    print('[EXP] Chi-square:', chi_square)
    print('[OBS] Chi-square:', scipy.stats.chi2.isf(q=0.05, df=n - 1))

    return chi_square

def get_arr_exp_hist_poisson(mu: int, skip: int) -> np.ndarray:
    """
    生成标准泊松分布频率的数组
    :param skip: 分割为几个区间
    :param mu: 参数 int 类型
    :return: ndarry 类型的数组
    """
    if mu < 0 or skip < 0:
        raise ValueError

    arr_poisson = []
    for k in range(skip):
        val = ((math.e ** -mu) * (mu ** k)) / math.factorial(k)
        arr_poisson.append(val)

    return np.array(arr_poisson)

def get_hist_arr(arr: np.ndarray, arr_cut_value: np.ndarray) -> np.ndarray:
    """
    根据传入的数值数组进行分隔并统计分布数量
    :param arr: 数组
    :param arr_cut_value: 要分隔的区间数组；比如：[0, 3, 5, 8] 则统计：(0, 3], (3, 5], (5, 8]
    :return: 统计结束后的数组
    """
    if arr is None or arr_cut_value is None:
        raise ValueError

    return pd.cut(x=arr, bins=arr_cut_value).value_counts().values

def plot_poisson_hist(arr_poisson: np.ndarray, save_path: str):
    """
    绘制直方图并保存
    :param arr_poisson: 具有泊松分布的数组
    :param save_path: 图像存储路径
    :return:
    """
    plt.figure(figsize=(18, 10), dpi=100)
    plt.hist(x=arr_poisson, bins=10, density=True)
    plt.grid(linestyle='--', alpha=0.5)
    plt.title('Закон Пуассона \n'
              'λ=4 Size=100 Section=' + arr_poisson.max().__str__(),
              fontdict={'family': FONT, 'size': TITLE_FONT_SIZE})
    plt.xlabel('x', fontdict={'family': FONT, 'size': LABEL_FONT_SIZE})
    plt.ylabel('P(x)', fontdict={'family': FONT, 'size': LABEL_FONT_SIZE})
    plt.xticks(fontproperties=FONT, size=TICK_FONT_SIZE)
    plt.yticks(fontproperties=FONT, size=TICK_FONT_SIZE)
    plt.xlim(0, 15)
    plt.savefig(save_path)
    plt.show()

if __name__ == '__main__':

    print('>' * 50, '\nirnpoi(mu=4, size=100):')
    arr_poi = irnpoi(mu=4, size=100)
    chi_square_poi = get_poisson_chi_square(arr_source=arr_poi, source_mu=4)
    plot_poisson_hist(arr_poisson=arr_poi,
                      save_path='./result/1_irnpoi.png')
    print('-' * 50)

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

    print('\n\n', '>' * 50, '\nirnpoi(mu=4, size=100):')
    arr_psn = irnpsn(mu=4, size=100)
    chi_square_psn = get_poisson_chi_square(arr_source=arr_psn, source_mu=4)
    print('-' * 50)
    plot_poisson_hist(arr_poisson=arr_psn,
                      save_path='./result/2_irnpsn.png')

    pass


import numpy as np

def GetFocalLength_Maya(imageWidth_px, focalLength_px, show_angle=False):
    """
    Returns focal length of camera in Maya [metric is pixel].
    imageWidth_px  : width of image
    focalLength_px : its correspond focal length
    show_angle     : show angle of view [deg]
    """

    ## Maya's default camera is based on 135 film (36mm x 24mm)
    focalLength_film = float(focalLength_px) / imageWidth_px * 36.0

    ## show Angle-of-View in Maya
    if (show_angle):
        AoVx_radian = np.atan( (imageWidth_px/2.0) / focalLength_px ) * 2.0
        AoVx_degree = round(np.rad2deg(AoVx_radian), 2)
        print("angle of view = "+str(AoVx_degree))

    return focalLength_film

def GetCameraTransform(rotX, rotY, dist):
    """
    Returns 6-tuple of pose information in Maya.
    The returned camera pose always looks the origin.
    This function only supports tilt (as rotX) and pan (as rotY).
    rotX: tilt value [deg]
    rotY: pan  value [deg]
    dist: distance from origin
    """

    rx = -rotX
    ry = -rotY
    rz = 0 # no Z-rotation

    deg2rad = np.pi / 180.0

    cX = np.cos(-rotX * deg2rad)
    sX = np.sin(-rotX * deg2rad)
    cY = np.cos(-rotY * deg2rad)
    sY = np.sin(-rotY * deg2rad)

    tx = +dist * cX * sY
    ty = -dist * sX
    tz = +dist * cX * cY

    return (rx,ry,rz, tx,ty,tz)

def GetCameraTransforms(rotX, rotYs, dist):
    """
    Returns the list of 6-tuple of pose information.
    It can set multiple pan (rotY) values.
    """

    trs = []
    for rotY in rotYs:
        tr = GetCameraTransform(rotX, rotY, dist)
        trs.append(tr)

    return trs

def ConvertArUcoRotation(rx_ArUco, ry_ArUco, rz_ArUco):
    """
    Converts ArUco marker rotation (Z+ up) to Maya rotation (Y+ up).
    """

    # cos/sin
    c = np.cos( np.deg2rad([rx_ArUco, ry_ArUco, rz_ArUco]) )
    s = np.sin( np.deg2rad([rx_ArUco, ry_ArUco, rz_ArUco]) )

    # construct 3x3 rotation matrices (in GLM)
    rotZ = [[ c[2], -s[2],  0.0], [ s[2], c[2],   0.0], [  0.0,  0.0,  1.0]]
    rotY = [[ c[1],   0.0, s[1]], [  0.0, +1.0,   0.0], [-s[1],  0.0, c[1]]]
    rotX = [[ +1.0,   0.0,  0.0], [  0.0, c[0], -s[0]], [  0.0, s[0], c[0]]]
    rotZYX = np.dot(np.dot(rotX, rotY), rotZ)

    # convert matrix from (X+,Y+,Z+) to inv(X+,Z+,Y-)
    rot_Maya = [[0.0, 0.0, 0.0],[0.0, 0.0, 0.0],[0.0, 0.0, 0.0]]

    rot_Maya[0][0] = +rotZYX[0][0]
    rot_Maya[0][1] = +rotZYX[1][0]
    rot_Maya[0][2] = -rotZYX[2][0]

    rot_Maya[1][0] = +rotZYX[0][2]
    rot_Maya[1][1] = +rotZYX[1][2]
    rot_Maya[1][2] = -rotZYX[2][2]

    rot_Maya[2][0] = +rotZYX[0][1]
    rot_Maya[2][1] = +rotZYX[1][1]
    rot_Maya[2][2] = -rotZYX[2][1]

    # convert to euler representation
    rx_GLM = np.atan2(rot_Maya[2][1], rot_Maya[2][2])
    rx = -np.rad2deg(rx_GLM)

    C2 = np.sqrt(rot_Maya[0][0]*rot_Maya[0][0]+rot_Maya[1][0]*rot_Maya[1][0])
    ry_GLM = np.atan2(-rot_Maya[2][0], C2)
    ry = -np.rad2deg(ry_GLM)

    S1 = np.sin(rx_GLM)
    C1 = np.cos(rx_GLM)
    rz_GLM = np.atan2(S1*rot_Maya[0][2] - C1*rot_Maya[0][1], C1*rot_Maya[1][1] - S1*rot_Maya[1][2] )
    rz = +np.rad2deg(rz_GLM) # [CAUTION] use "-rz"

    return (rx, ry, rz)

def ParseArUcoCameraPose(imgFile):
    """
    Parses ArUco marker pose (Z+ up) from filename to Maya pose (Y+ up).
    """

    import re

    m = re.search('(?<=rx)[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?', imgFile)
    rx_ArUco = float(m.group(0))
    m = re.search('(?<=ry)[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?', imgFile)
    ry_ArUco = float(m.group(0))
    m = re.search('(?<=rz)[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?', imgFile)
    rz_ArUco = float(m.group(0))

    m = re.search('(?<=tx)[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?', imgFile)
    tx_ArUco = float(m.group(0))
    m = re.search('(?<=ty)[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?', imgFile)
    ty_ArUco = float(m.group(0))
    m = re.search('(?<=tz)[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?', imgFile)
    tz_ArUco = float(m.group(0))

    ## convert rotation ArUco/Z+up to Maya/Y+up
    (rx, ry, rz) = ConvertArUcoRotation(rx_ArUco, ry_ArUco, rz_ArUco)

    ## Z+up (ArUco, 1.0=[m]) -> Y+up (Maya, 1.0=[cm])
    tx = +100.0 * tx_ArUco
    ty = +100.0 * tz_ArUco
    tz = -100.0 * ty_ArUco

    return (rx, ry, rz, tx, ty, tz)


"""Test capabilities for optimization.
This module contains a host of models and functions often used for testing optimization
algorithms.
"""
import sys

import numpy as np
import pandas as pd
from scipy.optimize import rosen

def ackley(x, a=20, b=0.2, c=2 * np.pi):
    r"""Ackley function.

    .. math::
        f(x) = -a \exp{\left(-b \sqrt{\frac{1}{p} \sum_{i=1}^p x_i^2}\right)}
        \exp{\left(\frac{1}{p} \sum_{i=1}^p \cos(c x_i)\right)} + a + \exp(1)

    Parameters
    ----------
    x : array_like
        Input domain with dimension :math:`p`, which
        is usually evaluated on the hypercube
        :math:`x_i \in [-32.768, 32.768]` for all :math:`i = 1, \dots, p`.
    a : float, optional
        The default value is 20.
    b : float, optional
        The default value is 0.2.
    c : float, optional
        The default value is :math:`2\pi`.

    Returns
    -------
    float
         Output domain

    Notes
    -----
    This function was proposed by David Ackley in [A1987]_ and used in [B1996]_
    and [M2005]_. It is characterized by an almost flat outer region and a central hole
    or peak where modulations become increasingly influential. The function has
    its global minimum :math:`f(x) = 0` at :math:`x = \begin{pmatrix}0 & \dots & 0
    \end{pmatrix}^T`.

    .. figure:: ../../docs/_static/images/fig-ackley.png
       :align: center

    References
    ----------
    .. [A1987] Ackley, D. H. (1987).
       *A connectionist machine for genetic hillclimbing*. Kluwer Academic Publishers.
    .. [B1996] Back, T. (1996).
       *Evolutionary algorithms in theory and practice:
       Evolution strategies, evolutionary programming, genetic algorithms*.
       Oxford University Press.
    .. [M2005] Molga, M., and Smutnicki, C. (2005).
       *Test functions for optimization needs*.
       Retrieved June 2020, from
       http://www.zsd.ict.pwr.wroc.pl/files/docs/functions.pdf.

    Examples
    --------
    >>> from temfpy.optimization import ackley
    >>> import numpy as np
    >>>
    >>> x = [0, 0]
    >>> y = ackley(x)
    >>> np.testing.assert_almost_equal(y, 0)
    """
    rslt = (
        a + np.exp(1) - (a * (np.exp(-b * np.sqrt(1 / len(x) * np.sum(np.square(x))))))
    )
    rslt -= np.exp(1 / len(x) * np.sum(np.cos(np.multiply(c, x))))

    return rslt

def rastrigin(x, a=10):
    r"""Rastrigin function.

    .. math::
        f(x) = a p + \sum_{i=1}^p \left(x_i^2 - 10 \cos(2\pi x_i)\right)

    Parameters
    ----------
    x : array_like
        Input domain with dimension :math:`p`.
        It is usually evaluated on the hypercube
        :math:`x_i\in [-5.12, 5.12]` for all :math:`i = 1, \dots, p`.
    a : float, optional
        The default value is 10.

    Returns
    -------
    float
         Output domain

    Notes
    -----
    The function was first proposed by Leonard Rastrigin in [R1974]_.
    It produces frequent local minima, as it is highly multimodal.
    However, the location of the minima are regularly distributed.
    The function has its global minimum :math:`f(x) = 0` at
    :math:`x = \begin{pmatrix}0 & \dots & 0 \end{pmatrix}^T`.

    .. figure:: ../../docs/_static/images/fig-rastrigin.png
       :align: center

    References
    ----------
    .. [R1974] Rastrigin, L. A. (1974).
       *Systems of extremal control*.
       Moscow, Russia.

    Examples
    --------
    >>> from temfpy.optimization import rastrigin
    >>> import numpy as np
    >>>
    >>> x = [0, 0]
    >>> y = rastrigin(x)
    >>> np.testing.assert_almost_equal(y, 0)
    """
    rslt = a * len(x) + np.sum(
        np.multiply(x, x) - 10 * np.cos(2 * np.multiply(np.pi, x)),
    )

    return rslt

def rosenbrock(x):
    r"""Rosenbrock function.

    .. math::
        f(x) = \sum^{p-1}_{i = 1} \left[100(x_{i+1}-x_i^2)^2 + (1-x_i^2) \right]

    Parameters
    ----------
    x : array_like
        Input domain with dimension :math:`p > 1`.

    Returns
    -------
    float
         Output domain

    Notes
    -----
    The function was first proposed by Howard H. Rosenbrock in [R1960]_ and
    is often referred to as Rosenbrock's valley or
    Rosenbrock's Banana function due to its shape.
    The function has its global minimum at
    :math:`x = \begin{pmatrix}1 & \dots & 1 \end{pmatrix}^T`.

    .. figure:: ../../docs/_static/images/fig-rosenbrock.png
       :align: center

    References
    ----------
    .. [R1960] Rosenbrock, H. H. (1960).
       An automatic method for finding the greatest
       or least value of a function.
       *The Computer Journal*, 3(3): 175-184.

    Examples
    --------
    >>> from temfpy.optimization import rosenbrock
    >>> import numpy as np
    >>>
    >>> x = [1, 1]
    >>> y = rosenbrock(x)
    >>> np.testing.assert_almost_equal(y, 0)
    """

    if not isinstance(x, (list, tuple, pd.core.series.Series, np.ndarray)):
        sys.exit("The parameter x must be an array like object.")
    else:
        if len(x) < 2:
            sys.exit("The input array x must be at least of length 2.")

    rslt = rosen(x)

    return rslt

def carlberg(x, a, b):
    r"""Carlberg function.

    .. math::
        f(x) = \frac{1}{2}\sum_{i=1}^p a_i (x_i - 1)^2 + b \left[p -
        \sum_{i=1}^p \cos(2 \pi(x_i-1)) \right]

    Parameters
    ----------
    x : array_like
        Input vector with dimension :math:`p`.
    a : array_like
        Input vector with dimension :math:`p`.
    b : float
        Cannot be smaller than zero.
        For more information see Notes.

    Returns
    -------
    float
         Output domain

    Notes
    -----
    If the values in :math:`a` are widely distributed, the function is
    said to be ill-conditioned, making it hard to minimize in some
    directions for Hessian-free numerical methods.
    If :math:`b=0` (see second graph below), the function is
    convex, smooth, and has its minimum at
    :math:`x = \begin{pmatrix}1 & \dots & 1 \end{pmatrix}^T`. For
    :math:`b>0` the function is no longer convex and has many local
    minima (see first graph below). These circumstances make it hard for
    local optimization methods to find the global minimum,
    which is still at
    :math:`x = \begin{pmatrix}1 & \dots & 1 \end{pmatrix}^T`.

    .. figure:: ../../docs/_static/images/fig-carlberg-noise.png
       :align: center
    .. figure:: ../../docs/_static/images/fig-carlberg-no-noise.png
       :align: center

    References
    ----------
    .. [C2019] Carlberg, K. (2019).
       *Optimization in Python*.
       Fundamentals of Data Science Summer Workshops, Stanford.

    Examples
    --------
    >>> from temfpy.optimization import carlberg
    >>> import numpy as np
    >>>
    >>> x = [1, 1]
    >>> a = [1, 1]
    >>> b = 1
    >>> y = carlberg(x,a,b)
    >>> np.testing.assert_almost_equal(y, 0)
    """

    if b < 0:
        sys.exit("Input parameter b must not be smaller than zero.")

    x, a = np.atleast_1d(x), np.atleast_1d(a)

    dimension = len(x)

    fval = 0
    fval += 0.5 * np.sum(a * (x - np.ones(dimension)) ** 2)
    fval += b * dimension
    fval -= b * np.sum(np.cos(2 * np.pi * (x - np.ones(dimension))))

    return fval


# -*- coding: utf-8 -*-

import numpy as np
import matplotlib.pyplot as plt
from liner_regression import *

def show_data(x, y, w=None, b=None):
    plt.scatter(x, y, marker='.')
    if w is not None and b is not None:
        plt.plot(x, w*x+b, c='red')
    plt.show()

# data generation
np.random.seed(272)
data_size = 100
x = np.random.uniform(low=1.0, high=10.0, size=data_size)
y = x * 20 + 10 + np.random.normal(loc=0.0, scale=10.0, size=data_size)

# plt.scatter(x, y, marker='.')
# plt.show()

# train / test split
shuffled_index = np.random.permutation(data_size)
x = x[shuffled_index]
y = y[shuffled_index]
split_index = int(data_size * 0.7)
x_train = x[:split_index]
y_train = y[:split_index]
x_test = x[split_index:]
y_test = y[split_index:]

# visualize data
# plt.scatter(x_train, y_train, marker='.')
# plt.show()
# plt.scatter(x_test, y_test, marker='.')
# plt.show()

# train the liner regression model
regr = LinerRegression(learning_rate=0.01, max_iter=10, seed=314)
regr.fit(x_train, y_train)
print('cost: \t{:.3}'.format(regr.loss()))
print('w: \t{:.3}'.format(regr.w))
print('b: \t{:.3}'.format(regr.b))
show_data(x, y, regr.w, regr.b)

# plot the evolution of cost
plt.scatter(np.arange(len(regr.loss_arr)), regr.loss_arr, marker='o', c='green')
plt.show()


import numpy as np
import math

#   load my  data
[x, y] = np.load('myData.npy')

#   plot my data
import matplotlib.pyplot as plt
plt.figure()
plt.plot(x,y, 'ok')
plt.grid(True)
plt.show()
plt.savefig('myData.png', dpi=300, type='png')

#   define a function to calculate the log likiehood
def calcLogLikelihood(guess, true, n):
    error = true-guess
    sigma = np.std(error)
    f = ((1.0/(2.0*math.pi*sigma*sigma))**(n/2))* \
        np.exp(-1*((np.dot(error.T,error))/(2*sigma*sigma)))
    return np.log(f)

#   define my function which will return the objective function to be minimized
def myFunction(var):
    yGuess = (var[2]*(x**2)) + (var[1]*x) + var[0]
    f = calcLogLikelihood(yGuess, y, float(len(yGuess)))
    return (-1*f)

#    Let's pick some random starting points for the optimizaiton
nvar = 3
var = np.zeros(nvar)
var[0] = -15.5
var[1] = 19.5
var[2] = -1.0

#   let's maximize the liklihood (minimize -1*max(likelihood)
from scipy.optimize import minimize
res = minimize(myFunction, var, method='BFGS',
                options={'disp': True})

#   pefrom least squres fit using scikitlearn
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline
model = Pipeline([('poly', PolynomialFeatures(degree=2)),
    ('linear', LinearRegression(fit_intercept=False))])

model = model.fit(x[:, np.newaxis], y)
coefs = model.named_steps['linear'].coef_

#   plot the data and model fits
plt.figure()
plt.plot(x,y, 'ok')
plt.plot(x,model.predict(x[:,np.newaxis]), '-r', label='Least Squres')
plt.plot(x,(res.x[2]*(x**2)) + (res.x[1]*x) + res.x[0], '--b', label='Max Likelihood')
plt.grid(True)
plt.legend(loc=2)
plt.show()
plt.savefig('maxLikelihoodComp.png', dpi=300, type='png')


from mpl_toolkits.mplot3d.axes3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
from scipy.integrate import odeint

#Define function
def odef(xyz,t,sigma,rho,beta):
    x,y,z = xyz
    return [sigma*(y-x),x*(rho-z)-y,x*y -beta*z]

#Create data
t = np.linspace(0,25,10000)
xyz0 = [1,1,1]
sigma, rho, beta = 8, 28, 8/3

#Integrate under function
xyz = odeint(odef,xyz0,t, args=(sigma,rho,beta))

#Create figure
plt.close()
fig, ax = plt.subplots(1,1,figsize=(10,10),subplot_kw={'projection':'3d'})
ax.set_aspect('equal')
ax.plot(xyz[:,0],xyz[:,1],xyz[:,2],alpha=0.5)
ax.set_title('3D projection of $f(x,y,z)$', size=18)
ax.set_xlabel('$x$', fontsize=16)
ax.set_ylabel('$y$', fontsize=16)
ax.set_zlabel('$z$', fontsize=16)
plt.show()


from math import sqrt

from numba import njit

@njit(fastmath=True)
def is_prime(number: int) -> bool:
    if number <= 1:
        return False
    elif number < 4:
        return True
    elif number % 2 == 0:
        return False
    elif number < 9:
        return True
    elif number % 3 == 0:
        return False
    else:
        for i in range(5, int(sqrt(number)) + 1, 6):
            if number % i == 0:
                return False
            elif number % (i + 2) == 0:
                return False
        return True


import numpy as np

def VRP_and_n(VRPx, VRPy, VRPz, Px, Py, Pz):
    VRP = np.array([VRPx, VRPy, VRPz])
    N = VRP-np.array([Px, Py, Pz])
    n = N/(np.linalg.norm(N))

    return VRP,n

def SRC_matrix(VRP, n, Yx, Yy, Yz):
    """Returns the transformation matrix to change the prism from SRU coordinates
    to SRC.
    Args: VRP coordinates, P coordinates, View-up (Y) coordinates"""

    Y = np.array([Yx, Yy, Yz])
    V = Y-np.dot(np.dot(Y,n),n)
    v = V/(np.linalg.norm(V))
    SRC = np.eye(4)
    u = np.cross(v,n)
    SRC[0,:3] = u
    SRC[1,:3] = v
    SRC[2,:3] = n
    SRC[0,3] = -np.dot(VRP,u)
    SRC[1,3] = -np.dot(VRP,v)
    SRC[2,3] = -np.dot(VRP,n)

    return SRC

def jp_times_proj_matrix(is_perspectiva, dist_projecao,
    Xmin, Xmax, Ymin, Ymax, Umax, Umin, Vmax, Vmin):
    """Returns a matrix used to transform an object from SRC to SRT."""

    u_by_x = (Umax-Umin)/(Xmax-Xmin)
    v_by_y = (Vmax-Vmin)/(Ymax-Ymin)

    if is_perspectiva:
        minus_inverted_dp = -(1/dist_projecao)
        jp_times_proj = np.zeros((4,4))
        jp_times_proj[0,0] = u_by_x
        jp_times_proj[1,1] = -v_by_y
        jp_times_proj[0,2] = (-(Xmin*u_by_x)+Umin)*minus_inverted_dp
        jp_times_proj[1,2] = ((Ymin*v_by_y)+Vmax)*minus_inverted_dp
        jp_times_proj[2,2] = 1
        jp_times_proj[3,2] = minus_inverted_dp

        return jp_times_proj
    else:
        jp = np.eye(4)
        jp[0,0] = u_by_x
        jp[1,1] = -v_by_y
        jp[0,3] = -(Xmin*u_by_x)+Umin
        jp[1,3] = (Ymin*v_by_y)+Vmax

        return jp

def pipeline_steps(M, SRC_matrix, jp_proj_matrix, dist_near, dist_far):
    """
    Returns a Boolean value determining if the object should be drawn
    and a numpy array for the prism after the application of the pipeline.
    Args:
    M is the numpy array for the prism to be drawn.
    SRC_matrix and jp_proj_matrix are the matrices returned from the first_pipeline function.
    dist_near and dist_fear determine the Z limits of the scene.
    """
    M_in_SRC = np.dot(SRC_matrix, M)

    draw = np.all(np.logical_and(
                    np.less(M_in_SRC[2,:],-dist_near),
                    np.greater(M_in_SRC[2,:],-dist_far)
                ))

    if not draw:
        return draw, np.zeros((4,1))
    else:
        pipelinedM = np.dot(jp_proj_matrix, M_in_SRC)

        pipelinedM[0,:] /= pipelinedM[3,:]
        pipelinedM[1,:] /= pipelinedM[3,:]
        pipelinedM[3,:] /= pipelinedM[3,:]

        return draw, pipelinedM

def first_pipeline(VRP, n, Yx, Yy, Yz,
    is_perspectiva, dist_projecao,
    Xmin, Xmax, Ymin, Ymax, Umin, Umax, Vmin, Vmax):
    """Returns matrices that shall be used in the pipeline based on all the parameters necessary
    for the SRU to SRT conversion, except the 3D cut step."""

    SRC = SRC_matrix(VRP, n, Yx, Yy, Yz)
    jp_times_proj = jp_times_proj_matrix(is_perspectiva, dist_projecao,
    Xmin, Xmax, Ymin, Ymax, Umax, Umin, Vmax, Vmin)

    return SRC, jp_times_proj


import numpy as np
import scipy.optimize

def sinfit(tt, yy):
    '''Fit sin to the input time sequence, and return fitting parameters "amp", "omega", "phase", "offset", "freq", "period" and "fitfunc"'''
    tt = np.array(tt)
    yy = np.array(yy)
    ff = np.fft.fftfreq(len(tt), (tt[1]-tt[0]))   # assume uniform spacing
    Fyy = abs(np.fft.fft(yy))
    guess_freq = abs(ff[np.argmax(Fyy[1:])+1])   # excluding the zero frequency "peak"
    guess_amp = np.std(yy) * 2.**0.5
    guess_offset = np.mean(yy)
    guess = np.array([guess_amp, 2.*np.pi*guess_freq, 0., guess_offset])

    def sinfunc(t, A, w, p, c):  return A * np.sin(w*t + p) + c
    popt, pcov = scipy.optimize.curve_fit(sinfunc, tt, yy, p0=guess)
    A, w, p, c = popt
    f = w/(2.*np.pi)
    fitfunc = lambda t: A * np.sin(w*t + p) + c
    return {"amp": A, "omega": w, "phase": p, "offset": c, "freq": f, "period": 1./f, "fitfunc": fitfunc, "maxcov": np.max(pcov), "rawres": (guess,popt,pcov)}

'''
   Parameter-free fitting function. Uses an fft to obtain an initial guess, refines fit via scippy's linear algebra fitting tool
'''