"""
 Copyright 2018 Johns Hopkins University  (Author: Jesus Villalba)
 Apache 2.0  (http://www.apache.org/licenses/LICENSE-2.0)

 Some math functions.
"""
from __future__ import absolute_import
from __future__ import print_function
from __future__ import division
from six.moves import xrange

import numpy as np
import scipy.linalg as la

from ..hyp_defs import float_cpu

def logdet_pdmat(A):
    """Log determinant of positive definite matrix.
    """
    assert(A.shape[0] == A.shape[1])
    R=la.cholesky(A)
    return 2*np.sum(np.log(np.diag(R)))

def invert_pdmat(A, right_inv=False, return_logdet=False, return_inv=False):
    """Inversion of positive definite matrices.
       Returns lambda function f that multiplies the inverse of A times a vector.

    Args:
      A: Positive definite matrix
      right_inv: If False, f(v)=A^{-1}v; if True f(v)=v' A^{-1}
      return_logdet: If True, it also returns the log determinant of A.
      return_inv: If True, it also returns A^{-1}

    Returns:
      Lambda function that multiplies A^{-1} times vector.
      Cholesky transform of A upper triangular
      Log determinant of A
      A^{-1}
    """
    assert(A.shape[0] == A.shape[1])
    R=la.cholesky(A, lower=False)

    if right_inv:
        fh=lambda x: la.cho_solve((R, False), x.T).T
    else:
        fh=lambda x: la.cho_solve((R, False), x)
        #fh=lambda x: la.solve_triangular(R, la.solve_triangular(R.T, x, lower=True), lower=False)

    r = [fh, R]

    logdet = None
    invA = None

    if return_logdet:
        logdet=2*np.sum(np.log(np.diag(R)))
        r.append(logdet)

    if return_inv:
        invA=fh(np.eye(A.shape[0]))
        r.append(invA)

    return r

def invert_trimat(A, lower=False, right_inv=False, return_logdet=False, return_inv=False):
    """Inversion of triangular matrices.
       Returns lambda function f that multiplies the inverse of A times a vector.

    Args:
      A: Triangular matrix.
      lower: if True A is lower triangular, else A is upper triangular.
      right_inv: If False, f(v)=A^{-1}v; if True f(v)=v' A^{-1}
      return_logdet: If True, it also returns the log determinant of A.
      return_inv: If True, it also returns A^{-1}

    Returns:
      Lambda function that multiplies A^{-1} times vector.
      Log determinant of A
      A^{-1}
    """

    if right_inv:
        fh=lambda x: la.solve_triangular(A.T, x.T, lower=not(lower)).T
    else:
        fh=lambda x: la.solve_triangular(A, x, lower=lower)

    if return_logdet or return_inv:
        r = [fh]
    else:
        r = fh

    if return_logdet:
        logdet=np.sum(np.log(np.diag(A)))
        r.append(logdet)

    if return_inv:
        invA=fh(np.eye(A.shape[0]))
        r.append(invA)

    return r

def softmax(r, axis=-1):
    """
    Returns:
      y = \exp(r)/\sum(\exp(r))
    """
    max_r=np.max(r, axis=axis, keepdims=True)
    r=np.exp(r-max_r)
    r/=np.sum(r, axis=axis, keepdims=True)
    return r

def logsumexp(r, axis=-1):
    """
    Returns:
      y = \log \sum(\exp(r))
    """
    max_r=np.max(r, axis=axis, keepdims=True)
    r=np.exp(r-max_r)
    return np.log(np.sum(r, axis=axis)+1e-20) + np.squeeze(max_r, axis=axis)

def logsigmoid(x):
    """
      Returns:
        y = \log(sigmoid(x))
    """
    e = np.exp(-x)
    f = x < -100
    log_p = -np.log(1+np.exp(-x))
    log_p[f] = x[f]
    return log_p

def neglogsigmoid(x):
    """
      Returns:
        y = -\log(sigmoid(x))
    """
    e = np.exp(-x)
    f = x < -100
    log_p = np.log(1+np.exp(-x))
    log_p[f] = - x[f]
    return log_p

def sigmoid(x):
    """
      Returns:
        y = sigmoid(x)
    """
    e = np.exp(-x)
    f = x < -100
    p = 1/(1+np.exp(-x))
    p[f] = 0
    return p

def fisher_ratio(mu1, Sigma1, mu2, Sigma2):
    """Computes the Fisher ratio between two classes
       from the class means and covariances.
    """
    S=Sigma1+Sigma2
    L=invert_pdmat(S)[0]
    delta=mu1-mu2
    return np.inner(delta, L(delta))

def fisher_ratio_with_precs(mu1, Lambda1, mu2, Lambda2):
    """Computes the Fisher ratio between two classes
       from the class means precisions.
    """

    Sigma1 = invert_pdmat(Lambda1, return_inv=True)[-1]
    Sigma2 = invert_pdmat(Lambda2, return_inv=True)[-1]
    return fisher_ratio(mu1, Sigma1, mu2, Sigma2)

def symmat2vec(A, lower=False, diag_factor=None):
    """Puts a symmetric matrix into a vector.

    Args:
      A: Symmetric matrix.
      lower: If True, it uses the lower triangular part of the matrix.
             If False, it uses the upper triangular part of the matrix.
      diag_factor: It multiplies the diagonal of A by diag_factor.

    Returns:
      Vector with the upper or lower triangular part of A.
    """
    if diag_factor is not None:
        A = np.copy(A)
        A[np.diag_indices(A.shape[0])] *= diag_factor
    if lower:
         return A[np.tril_indices(A.shape[0])]
    return A[np.triu_indices(A.shape[0])]

def vec2symmat(v, lower=False, diag_factor=None):
    """Puts a vector back into a symmetric matrix.

    Args:
      v: Vector with the upper or lower triangular part of A.
      lower: If True, v contains the lower triangular part of the matrix.
             If False, v contains the upper triangular part of the matrix.
      diag_factor: It multiplies the diagonal of A by diag_factor.

    Returns:
      Symmetric matrix.
    """

    dim=int((-1+np.sqrt(1+8*v.shape[0]))/2)
    idx_u=np.triu_indices(dim)
    idx_l=np.tril_indices(dim)
    A=np.zeros((dim,dim), dtype=float_cpu())
    if lower:
        A[idx_l]=v
        A[idx_u]=A.T[idx_u]
    else:
        A[idx_u]=v
        A[idx_l]=A.T[idx_l]
    if diag_factor is not None:
        A[np.diag_indices(A.shape[0])] *= diag_factor
    return A

def trimat2vec(A, lower=False):
    """Puts a triangular matrix into a vector.

    Args:
      A: Triangular matrix.
      lower: If True, it uses the lower triangular part of the matrix.
             If False, it uses the upper triangular part of the matrix.

    Returns:
      Vector with the upper or lower triangular part of A.
    """

    return symmat2vec(A, lower)

def vec2trimat(v, lower=False):
    """Puts a vector back into a triangular matrix.

    Args:
      v: Vector with the upper or lower triangular part of A.
      lower: If True, v contains the lower triangular part of the matrix.
             If False, v contains the upper triangular part of the matrix.

    Returns:
      Triangular matrix.
    """
    dim=int((-1+np.sqrt(1+8*v.shape[0]))/2)
    A=np.zeros((dim,dim), dtype=float_cpu())
    if lower:
        A[np.tril_indices(dim)]=v
        return A
    A[np.triu_indices(dim)]=v
    return A

def fullcov_varfloor(S, F, F_is_chol=False, lower=False):
    """Variance flooring for full covariance matrices.

    Args:
      S: Covariance.
      F: Minimum cov or Cholesqy decomposisition of it
      F_is_chol: If True F is Cholesqy decomposition
      lower: True if cholF is lower triangular, False otherwise

    Returns:
      Floored covariance
    """
    if isinstance(F, np.ndarray):
        if not F_is_chol:
            cholF = la.cholesky(F, lower=False, overwrite_a=False)
        else:
            cholF = F
            if lower:
                cholF = cholF.T
        icholF = invert_trimat(cholF, return_inv=True)[-1]
        T = np.dot(np.dot(icholF.T, S) , icholF)
    else:
        T = S/F

    u, d, _ = la.svd(T, full_matrices=False, overwrite_a=True)
    d[d<1.]=1
    T = np.dot(u*d, u.T)

    if isinstance(F, np.ndarray):
        S = np.dot(cholF.T, np.dot(T, cholF))
    else:
        S = F*T
    return S

def fullcov_varfloor_from_cholS(cholS, cholF, lower=False):
    """Variance flooring for full covariance matrices
       using Cholesky decomposition as input/output

    Args:
      cholS: Cholesqy decomposisition of the covariance.
      cholF: Cholesqy decomposisition of the minimum covariance.
      lower: True if matrices are lower triangular, False otherwise

    Returns:
      Cholesky decomposition of the floored covariance
    """

    if isinstance(cholF, np.ndarray):
        if lower:
            cholS = cholS.T
            cholF = cholF.T
        T = np.dot(cholS, invert_trimat(cholF, return_inv=True)[-1])
    else:
        if lower:
            cholS=cholS.T
        T = cholS/cholF
    T = np.dot(T.T,T)
    u, d, _ = la.svd(T, full_matrices=False, overwrite_a=True)
    d[d<1.]=1
    T = np.dot(u*d, u.T)
    if isinstance(cholF, np.ndarray):
        S = np.dot(cholF.T, np.dot(T, cholF))
    else:
        S = (cholF**2)*T
    return la.cholesky(S, lower)

def int2onehot(class_ids, num_classes=None):
    """Integer to 1-hot vector.

    Args:
      class_ids: Numpy array of integers.
      num_classes: Maximum number of classes.

    Returns:
      1-hot Numpy array.
    """

    if num_classes is None:
        num_classes = np.max(class_ids)+1

    p = np.zeros((len(class_ids), num_classes), dtype=float_cpu())
    p[np.arange(len(class_ids)), class_ids]=1
    return p


import numpy as np

class PCA:
    """:class:`PCA` is a class for dimensionality reduction using PCA in Jina.

    :param n_components: Number of components to keep when projecting with PCA
    :param whiten: Flag variable stating if there projecting with whitening
    """

    def __init__(self, n_components: int, whiten: bool = False):
        self.n_components = n_components
        self.whiten = whiten
        self.e_values = None
        self.w = None

    def fit(self, x_mat: np.ndarray):
        """
        Computes the projection matrix of the PCA algorithm and stores it to self.w

        :param x_mat: Matrix of shape (n_observations, n_features)
        """

        x_mat = x_mat - x_mat.mean(axis=0)

        # covariance matrix (n_features, n_features)
        cov = np.cov(x_mat.T) / x_mat.shape[0]

        # Compute eigenvalues eigenvectors of cov
        e_values, e_vectors = np.linalg.eig(cov)

        # Sort eigenvalues by magnitude (higher to lower)
        idx = e_values.argsort()[::-1]
        e_values = e_values[idx]

        # Sort eigen vectors by eigenvalue magnitude (higher to lower)
        e_vectors = e_vectors[:, idx]

        # Projection matrix contains eigenvectors sorted
        self.w = e_vectors
        self.e_values = e_values

    def transform(self, x_mat: np.ndarray) -> np.ndarray:
        """Projects data from n_features to self.n_components features.

        :param x_mat: Matrix of shape (n_observations, n_features)
        :return: Matrix of shape (n_observations, self.n_components)
        """
        if self.w is not None:
            x_mat_projected = x_mat.dot(self.w[:, : self.n_components])
            if self.whiten:
                return x_mat_projected / np.sqrt(self.e_values[0 : self.n_components])
            else:
                return x_mat_projected

    def fit_transform(self, x_mat: np.ndarray) -> np.ndarray:
        """Fits the PCA and returns a transformed data
        :param x_mat:  Matrix of shape (n_observations, n_features)
        :return: Matrix of shape (n_observations, self.n_components)
        """
        self.fit(x_mat)
        return self.transform(x_mat)


# -*- coding: utf-8 -*-
import numpy as np
import scipy as sp

def sine(t, A=1, f=1, D=0):
    """
    t: time
    A: the amplitude, the peak deviation of the function from zero.
    f: the ordinary frequency, the number of oscillations (cycles) that occur each second of time.
    D: non-zero center amplitude
    """
    sine_ = A * np.sin(2 * np.pi * f * t) + D
    return sine_

def cosine(t, A=1, f=1, D=0):
    """
    t: time
    A: the amplitude, the peak deviation of the function from zero.
    f: the ordinary frequency, the number of oscillations (cycles) that occur each second of time.
    D: non-zero center amplitude
    """
    cos_ = A * np.sin(2 * np.pi * f * t) + D
    return cos_

def square(t, A=1, f=1, D=0):
    """
    t: time
    A: the amplitude, the peak deviation of the function from zero.
    f: the ordinary frequency, the number of oscillations (cycles) that occur each second of time.
    D: non-zero center amplitude
    """
    square_ = A * sp.signal.square(2 * np.pi * f * t) + D
    return square_

def sawtooth(t, A=1, f=1, D=0):
    """
    t: time
    A: the amplitude, the peak deviation of the function from zero.
    f: the ordinary frequency, the number of oscillations (cycles) that occur each second of time.
    D: non-zero center amplitude
    """
    sawtooth_ = A * sp.signal.sawtooth(2 * np.pi * f * t, width=1) + D
    return sawtooth_

def triangle(t, A=1, f=1, D=0):
    """
    t: time
    A: the amplitude, the peak deviation of the function from zero.
    f: the ordinary frequency, the number of oscillations (cycles) that occur each second of time.
    D: non-zero center amplitude
    """
    triangle_ = A * sp.signal.sawtooth(2 * np.pi * f * t, width=0.5) + D
    return triangle_

signal_generators = [sine, cosine, square, sawtooth, triangle]


"""
what makes filters high and low-pass; why is a Sobel filter high-pass and a Gaussian filter low-pass?

Well, you can actually visualize the frequencies that these filters block out by taking a look at their
fourier transforms. The frequency components of any image can be displayed after doing a Fourier Transform (FT).
An FT looks at the components of an image (edges that are high-frequency, and areas of smooth color as low-frequency),
and plots the frequencies that occur as points in spectrum. So, let's treat our filters as small images, and display
them in the frequency domain!

Areas of white or light gray, allow that part of the frequency spectrum through!
Areas of black mean that part of the spectrum is blocked out of the image.

Recall that the low frequencies in the frequency spectrum are at the center of the frequency transform image,
and high frequencies are at the edges. You should see that the Gaussian filter allows only low-pass frequencies through,
which is the center of the frequency transformed image.
The sobel filters block out frequencies of a certain orientation
and a laplace (all edge, regardless of orientation) filter, should block out low-frequencies!

"""

import matplotlib.pyplot as plt
import numpy as np

# Define gaussian, sobel, and laplacian (edge) filters

gaussian = (1 / 9) * np.array([[1, 1, 1],
                               [1, 1, 1],
                               [1, 1, 1]])

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

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

# laplacian, edge filter
laplacian = np.array([[0, 1, 0],
                      [1, -4, 1],
                      [0, 1, 0]])

filters = [gaussian, sobel_x, sobel_y, laplacian]
filter_name = ['gaussian', 'sobel_x', 'sobel_y', 'laplacian']

# perform a fast fourier transform on each filter
# and create a scaled, frequency transform image
f_filters = [np.fft.fft2(x) for x in filters]
f_shift = [np.fft.fftshift(y) for y in f_filters]
frequency_tx = [np.log(np.abs(z) + 1) for z in f_shift]

# display 4 filters
for i in range(len(filters)):
    plt.subplot(2, 2, i + 1)
    plt.imshow(frequency_tx[i], cmap='gray')
    plt.title(filter_name[i])
    plt.xticks([])
    plt.yticks([])

plt.show()


import numpy as np
from numpy.linalg import *

def mds(d, dimensions = 2):
    """
    Multidimensional Scaling - Given a matrix of interpoint distances,
    find a set of low dimensional points that have similar interpoint
    distances.
    """
    print('Multidimensional Scaling for scatter plot...')
    (n,n) = d.shape
    E = (-0.5 * d**2)
    # Use mat to get column and row means to act as column and row means.
    Er = np.mat(np.mean(E,1))
    Es = np.mat(np.mean(E,0))
    F = np.array(E - np.transpose(Er) - Es + np.mean(E))
    [U, S, V] = svd(F)
    Y = U * np.sqrt(S)
    return (Y[:,0:dimensions], S)


import math
import numbers
import operator as op

import numpy as np
import scipy.stats as stats

from .random_variables import RV
from .result import (
    Tuple,
    TimeFunction,
    ContinuousTimeFunction,
    DiscreteValued
)
from .results import Results

pi = math.pi
e = math.e
inf = float("inf")

floor = math.floor
ceil = math.ceil

def operation_factory(operation):

    def _op_func(x):
        if isinstance(x, (RV, Tuple, TimeFunction)):
            # recursively call op_fun until x is a scalar
            return x.apply(_op_func)
        elif isinstance(x, Results):
            return x.apply(_op_func)
        else:
            return operation(x)

    return _op_func

sqrt = operation_factory(math.sqrt)
exp = operation_factory(math.exp)
sin = operation_factory(math.sin)
cos = operation_factory(math.cos)
tan = operation_factory(math.tan)
factorial = operation_factory(math.factorial)

def log(value, base=e):
    return operation_factory(lambda x: math.log(x, base))(value)

def mean(x):
    if isinstance(x, numbers.Real):
        raise Exception("Taking the mean with one value is unnecessary.")
    else:
        return sum(x) / len(x)

def cumsum(x):
    return x.cumsum()

def var(x):
    return mean([(i - mean(x)) ** 2 for i in x])

def sd(x):
    return math.sqrt(var(x))

def median(x):
    if isinstance(x, numbers.Real):
        raise Exception("Taking the median of one value is unnecessary.")
    else:
        return np.median(x)

def min_max_diff(x):
    if isinstance(x, numbers.Real):
        raise Exception("Taking the range of one value is unnecessary.")
    else:
        return max(x) - min(x)

def med_abs_dev(x):
    return median(list(abs(i-median(x)) for i in x))

def quantile(q):
    return lambda x: np.percentile(x, q * 100)

def iqr(x):
    if isinstance(x, numbers.Real):
        raise Exception("Taking the iqr of one value is unnecessary.")
    else:
        q75, q25 = np.percentile(x, [75, 25])
        return q75 - q25

def orderstatistics(n):
    if n <= 0:
        raise Exception("Out of bounds. Lowest order is 1.")
    else:
        return lambda x: np.partition(x, n - 1)[n - 1]

def skewness(x):
    if isinstance(x, numbers.Real):
        raise Exception("Finding the skenewss of one value is unnecessary,")
    else:
        return stats.skew(x)

def kurtosis(x):
    if isinstance(x, numbers.Real):
        raise Exception("Finding the kurtosis of one value is unnecessary.")
    else:
        return stats.kurtosis(x)

def moment(k):
    return lambda x: stats.moment(x, k)

def trimmed_mean(alpha):
    return lambda x: stats.trim_mean(x, alpha)

def comparefun(x, compare, value):
    count = 0
    for i in x:
        if compare(i, value):
            count += 1
    return count

def count(func=lambda x: True):
    def _func(x):
        val = 0
        for i in x:
            if func(i):
                val += 1
        return val
    return _func

def count_eq(value):
    def func(x):
        return comparefun(x, op.eq, value)
    return func

def count_neq(value):
    def func(x):
        return comparefun(x, op.ne, value)
    return func

def count_lt(value):
    def func(x):
        return comparefun(x, op.lt, value)
    return func

def count_gt(value):
    def func(x):
        return comparefun(x, op.gt, value)
    return func

def count_geq(value):
    def func(x):
        return comparefun(x, op.ge, value)
    return func

def count_leq(value):
    def func(x):
        return comparefun(x, op.le, value)
    return func

def interarrival_times(continuous_time_function):
    """Given a realization of a continuous-time,
       discrete-state process, returns the interarrival
       times (i.e., the times between each state change).

    Args:
      continuous_time_function: A ContinuousTimeFunction
        object, such as ContinuousTimeMarkovChainResult or
        PoissonProcessResult.
    """
    if not (isinstance(continuous_time_function,
                       ContinuousTimeFunction) and
            isinstance(continuous_time_function,
                       DiscreteValued)):
        raise TypeError(
            "Interarrival times are only defined for "
            "continuous-time, discrete-valued functions."
        )
    return continuous_time_function.get_interarrival_times()

def arrival_times(continuous_time_function):
    """Given a realization of a continuous-time,
       discrete-state process, returns the arrival
       times (i.e., the times when the state changes).

    Args:
      continuous_time_function: A ContinuousTimeFunction
        object, such as ContinuousTimeMarkovChainResult or
        PoissonProcessResult.
    """
    if not (isinstance(continuous_time_function,
                       ContinuousTimeFunction) and
            isinstance(continuous_time_function,
                       DiscreteValued)):
        raise TypeError(
            "Interarrival times are only defined for "
            "continuous-time, discrete-valued functions."
        )
    return continuous_time_function.get_arrival_times()

def states(discrete_valued_function):
    """Given a realization of a discrete-valued function,
       returns an InfiniteVector of the sequence of
       values (or states).

    Args:
      discrete_valued_function: A DiscreteValued object
                                (e.g., MarkovChainResult or
                                       PoissonProcessResult)
    """
    if not isinstance(discrete_valued_function, DiscreteValued):
        raise TypeError(
            "States are only defined for discrete-valued "
            "functions."
        )
    return discrete_valued_function.get_states()


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

__author__ = "Kasra Hosseini"
__license__ = "MIT License"

import numpy as np
from typing import Union

try:
    import pyproj
    pyproj_imported = True
except ImportError:
    pyproj_imported = False

def lonlatdep2xyz_spherical(lons: Union[float, int, np.ndarray, list],
                            lats: Union[float, int, np.ndarray, list],
                            depths: Union[float, int, np.ndarray, list, None]=None,
                            return_one_arr: Union[bool]=True,
                            earth_radius_m: Union[float, int]=6371000):
    """Convert lons/lats/depths to x/y/z

    Parameters
    ----------
    lons : Union[float, int, np.ndarray, list]
    lats : Union[float, int, np.ndarray, list]
    depths : Union[float, int, np.ndarray, list, None], optional
    return_one_arr : Union[bool], optional
    earth_radius_m : Union[float, int], optional
    """
    # co-latitudes:
    colats = 90.0 - lats
    # radii
    r = earth_radius_m - depths

    x = r * np.sin(np.deg2rad(colats)) * np.cos(np.deg2rad(lons))
    y = r * np.sin(np.deg2rad(colats)) * np.sin(np.deg2rad(lons))
    z = r * np.cos(np.deg2rad(colats))

    if return_one_arr:
        return np.vstack([x, y, z]).T
    else:
        return x, y, z

def xyz2lonlatdep_spherical(x: Union[float, int, np.ndarray, list],
                            y: Union[float, int, np.ndarray, list],
                            z: Union[float, int, np.ndarray, list, None]=None,
                            return_one_arr: Union[bool]=True,
                            earth_radius_m: Union[float, int]=6371000):
    """Convert x/y/z/ to lons/lats/depths

    Parameters
    ----------
    x : Union[float, int, np.ndarray, list]
    y : Union[float, int, np.ndarray, list]
    z : Union[float, int, np.ndarray, list, None], optional
    return_one_arr : Union[bool], optional
    earth_radius_m : Union[float, int], optional
    """
    rxy = np.sqrt(x**2 + y**2)
    lons = np.arctan2(y, x)*180./np.pi
    lats = np.arctan2(z, rxy)*180./np.pi
    depths = earth_radius_m - np.sqrt(x**2 + y**2 + z**2)

    if return_one_arr:
        return np.vstack([lons, lats, depths]).T
    else:
        return lons, lats, depths

def lonlatdep2xyz(lons: Union[float, int, np.ndarray, list],
                  lats: Union[float, int, np.ndarray, list],
                  depths: Union[float, int, np.ndarray, list, None]=None,
                  return_one_arr: Union[bool]=True):
    """Convert lons/lats/depths to x/y/z

    Parameters
    ----------
    lons : Union[float, int, np.ndarray, list]
    lats : Union[float, int, np.ndarray, list]
    depths : Union[float, int, np.ndarray, list, None], optional
    return_one_arr : Union[bool], optional
    """

    if not pyproj_imported:
        print(f"[WARNING] pyproj could not be imported.")
        print(f"[WARNING] lonlatdep2xyz_spherical will be used insted.")
        lonlatdep2xyz_spherical(lons, lats, depths, return_one_arr)

    # Define projections
    ecef = pyproj.Proj(proj='geocent', ellps='WGS84', datum='WGS84')
    lla = pyproj.Proj(proj='latlong', ellps='WGS84', datum='WGS84')

    lons = convert2array(lons)
    lats = convert2array(lats)
    if depths is None:
        depths = np.ones(len(lons))
    else:
        depths = convert2array(depths)

    # trainsform to x, y, z (in meters)
    # -1 * depth as pyproj.transform expects altitude
    x, y, z = pyproj.transform(lla, ecef,
                               lons, lats, -1*depths,
                               radians=False)

    if return_one_arr:
        return np.vstack([x, y, z]).T
    else:
        return x, y, z

def convert2array(myinp):
    """Convert myinp ---> np.ndarray"""
    if isinstance(myinp, np.ndarray):
        return myinp
    elif isinstance(myinp, list):
        return np.array(myinp)
    elif isinstance(myinp, int):
        return np.array([myinp])
    elif isinstance(myinp, float):
        return np.array([myinp])
    else:
        print(f"WARNING: input type cannot be converted: {type(myinp)}")
        return None


import numpy as np
from si.data.Dataset import Dataset
from si.supervised.Model import Model
from si.util.Metrics import mse

class LinearRegression(Model):

    def __init__(self, gd = False, epochs = 1000, lr = 0.001):
        super(LinearRegression, self).__init__()
        self.gd = gd
        self.theta = None
        self.num_inter = epochs
        self.lr = lr

    def fit (self, dataset):
        X, Y = dataset.getXy()
        X = np.hstack((np.ones((X.shape[0], 1)), X))
        self.X = X
        self.Y = Y

        # closed from of GD
        if self.gd:
            self.train_gd(X, Y)
        else:
             self.train_closed(X, Y)
        self.is_fitted = True

    def train_closed(self, x, y):
        self.theta = np.linalg.inv(x.T.dot(x)).dot(x.T).dot(y)

    def train_gd(self, x, y):
        m = x.shape[0]
        n = x.shape[1]
        self.history = {} # dicionário com o histórico dos thetas e custo por epoch
        self.theta = np.zeros(n)
        for epoch in range(self.num_inter):
            grad = 1 / m * (x.dot(self.theta) - y).dot(x)
            self.theta -= self.lr * grad
            self.history[epoch] = [self.theta[:], self.cost()]

    def predict(self, x):
        assert self.is_fitted, 'Model must be fitted before predicting'
        _x = np.hstack(([1], x))
        return np.dot(self.theta, _x)

    def cost(self):
        y_pred = np.dot(self.X, self.theta)
        return mse(self.Y, y_pred) / 2

class LinearRegressionReg(LinearRegression):

    def __init__(self, gd = False, epochs = 1000, lr = 0.001, lbd = 1):
        super(LinearRegressionReg, self).__init__()
        self.lbd = lbd
        self.gd = gd
        self.num_inter = epochs

    def train_closed(self, x, y):
        n = x.shape[1]
        identify = np.eye(n)
        identify[0, 0] = 0 # mudar o primeiro elemento para 0 para não dar biased
        self.theta = np.linalg.inv(x.T.dot(x) + self.lbd * identify).dot(x.T).dot(y)
        self.is_fitted = True

    def train(self, x, y):
        m = x.shape[0]
        n = x.shape[1]
        self.history = {} # dicionário com o histórico dos thetas por epoch
        self.theta = np.zeros(n)
        lbds = np.full(m, self. lbd)
        lbds[0] = 0
        for epoch in range(self.num_inter):
            grad = 1 / m * (x.dot(self.theta) - y).dot(x)
            self.theta -= (self.lr / m) * (lbds + grad)
            self.history[epoch] = [self.theta[:], self.cost()]

    def predict(self, x):
        assert self.is_fitted
        _x = np.hstack(([1], x))
        return np.dot(self.theta, _x)

    def cost(self):
        y_pred = np.dot(self.X, self.theta)
        return mse(self.Y, y_pred) / 2


import numpy as np
import math

def main():

    """
    Operations:
    - vector addition
    - scalar multiplication
    - dot product
    - the cross product
    - the angle between two vectors:
    """

    vector_row = np.array([[1, 2, 3, 4]])
    vector_column = np.array([[1], [2], [3], [4]])

    assert vector_row.shape == (1, 4)
    assert vector_column.shape == (4, 1)

    assert (vector_row.T == vector_column).all()
    assert (vector_column.T == vector_row).all()

    assert np.linalg.norm(vector_row, 1) == 4.0
    assert math.isclose(np.linalg.norm(vector_row, 2), 5.5, rel_tol=1e-2)
    assert np.linalg.norm(vector_row, np.inf) == 10.0

    v = np.array([[9, 2, 5]])
    w = np.array([[-3, 8, 2]])
    theta = np.arccos(np.dot(v, w.T) / (np.linalg.norm(v) * np.linalg.norm(w)))

    assert math.isclose(theta[0][0], 1.582, rel_tol=1e-2)
    assert (np.cross(v, w) == [[-36, -33, 78]]).all()

if __name__ == "__main__":
    main()


import numpy as np

s1 = np.array([168, 398, 451, 337, 186, 232, 262, 349, 189, 204, 220, 220, 207, 239, 259, 258,
242, 331, 251, 323, 106, 1055, 170])

s2 = np.array([168, 400, 451, 300, 186, 200, 262, 349, 189, 204, 220, 220, 207, 239, 259, 258,
242, 331, 251, 180, 106, 1055, 200])

#cálculo da distância euclidiana
distancia = np.linalg.norm(s1 - s2)

#média pode ser calculada dessa forma, manipulando as matrizes unidimensionais separadamente
med = (s1 + s2)/2

#Ou criando uma matriz 2X1 e criando a média das linhas para cada elemento
s3 = np.array([s1,s2])

media = np.average(s3, axis=0)

#Compara as matrizes e retorna com o máximo
max = np.maximum(s1,s2)

#Compara as matrizes e retorna com mínimo
min = np.minimum(s1,s2)

print(distancia, '\n', media,'\n', med, '\n', max, '\n', min)


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

# Function
def f(x):
    return exp(x)

# Compute Derivative of f(x) at x0 with step h
def ThreeMidpoint(x0, h):
    df = (f(x0+h)-f(x0-h))/(2*h)
    return df

if __name__ == '__main__':

    x = 0

    print(ThreeMidpoint(x, 0.01))


# Monte Carlo

import math
import numpy as np

S0 = 100.
K = 105.
T = 1.0
r = 0.05
sigma = 0.2

I = 100000

np.random.seed(1000)

z = np.random.standard_normal(I)

ST = S0 * np.exp((r - sigma ** 2 / 2) * T + sigma + math.sqrt(T) * z)
hT = np.maximum( ST - K, 0)
C0 = math.exp(-r * T) * np.mean(hT)

print('Value of the European Call Option: {:5.3f}.'.format(C0))


import numpy as np
import matplotlib.pyplot as plt

def assign_to_clusters(points, centers):
    num_clusters = len(centers)
    clusters = [[] for _ in range(num_clusters)]

    for x in points:

        best_cluster = np.array([np.linalg.norm(x - c) for c in centers]).argmin()

        clusters[best_cluster].append(x)

    return clusters

def compute_centers(clusters):
    return [sum(c) / len(c) for c in clusters]

def objective(clusters, centers):
    return sum(
        sum(np.linalg.norm(x - center) for x in cluster) \
                for center, cluster in zip(centers, clusters)
        )

def kmeans(points, num_clusters):
    centers = np.random.uniform(size=(num_clusters, 2))

    obj_prev, obj = np.inf, np.inf

    time = 0

    while True:
        time += 1

        clusters = assign_to_clusters(points, centers)
        centers = compute_centers(clusters)
        obj = objective(clusters, centers)

        if obj_prev == obj:
            break

        obj_prev = obj

    return assign_to_clusters(points, centers), centers, time

if __name__ == "__main__":
    num_clusters = 9
    num_points = 4000

    np.random.seed(0)

    # generate "true" cluster centers
    #centers = np.random.uniform(size=(num_clusters, 2), low=-5, high=5)

    # generate guassian clusters, points evenly distributed among
    # them
    #clusters = tuple(np.random.normal(loc=c, scale=0.375, size=(num_points // num_clusters, 2)) for c in centers)
    #points = np.concatenate(clusters)
    points = np.random.uniform(low=-1, high=1, size=(num_points, 2))

    clusters, centers, time = kmeans(points, num_clusters)

    print(f"k-means converged in {time} iterations")

    fig, ax = plt.subplots(1)
    for i, c in enumerate(map(np.array, clusters)):
        ax.scatter(c[:, 0], c[:, 1])
        ax.plot([centers[i][0]], [centers[i][1]], color="black", marker="o", markersize=10)

    plt.show()


import math
import numpy as np

""" The formula for sigmoid is S(x) = 1/(1+exp(-x))"""

def basic_sigmoid(x):
    return 1/(1+math.exp(-x))

def sigmoid(x):
    return 1/(1+(np.exp(-x)))

def sigmoid_derivative(x):
    a = sigmoid(x)
    return a*(1-a)


# Program 03b: Nonlinear system, phase portrait with vector plot.
# See Figure 3.12.

import matplotlib.pyplot as plt
import numpy as np
from scipy.integrate import odeint
import pylab as pl

# The 2-dimensional nonlinear system.
def dx_dt(x, t):
    return [x[1], x[0] * (1 - x[0]**2) + x[1]]

# Trajectories in forward time.
ts = np.linspace(0, 10, 500)
ic = np.linspace(-3, 3, 6)
for r in ic:
    for s in ic:
        x0 = [r, s]
        xs = odeint(dx_dt, x0, ts)
        plt.plot(xs[:, 0], xs[:, 1], 'r-')

# Trajectories in backward time.
ts = np.linspace(0, -10, 500)
ic = np.linspace(-3, 3, 6)
for r in ic:
    for s in ic:
        x0 = [r, s]
        xs = odeint(dx_dt, x0, ts)
        plt.plot(xs[:, 0], xs[:, 1], 'r-')

# Label the axes and set fontsizes.
plt.xlabel('x', fontsize=15)
plt.ylabel('y', fontsize=15)
plt.tick_params(labelsize=15)
plt.xlim(-3, 3)
plt.ylim(-3, 3)

# Plot the vectorfield.
X, Y = np.mgrid[-3:3:20j, -3:3:20j]
u = X
v = X * (1 - X**2) + Y
pl.quiver(X, Y, u, v, color='b')

plt.show()


# -*- coding: utf-8 -*-
"Compound interest function"
import numpy as np

def compound_interest(principal: float, annual_rate: float, years: int):
    """Calculates compound interest
    Solves A = P(1+r)^t for
    P = principal
    r = annual rate
    t = number of years to compound

    Args:
        principal (float): The principal (initial) value.
        annual_rate (float): The annual interest rate.
        years (int): Number of years to calculate.

    Returns:
        an array giving the new principal for each year in the sequence.
    """
    return np.array([principal * ((1 + annual_rate) ** yr) for yr in range(years)])


# Code made for Sergio Andrés Díaz Ariza
# 24 Abril 2021
# License MIT
# Transport Phenomena: Python Program-Assessment 2.1

import time
import numpy as np

start_time = time.time()

# Matrix for Pi's Buckingham
A = np.array([[0, 1, 1, 1], [0, -3, -1, 0], [0, -1, 0, 0], [1, 1, -1, -3]])

# We solve for the first Pi
B1 = np.array([-1, 3, 1, 0])
C1 = np.linalg.solve(A, B1)

# We solve for the second Pi
B2 = np.array([0, 2, 1, -2])
C2 = np.linalg.solve(A, B2)

# We solve for the third Pi
B3 = np.array([0, 0, 0, -1])
C3 = np.linalg.solve(A, B3)

# We solve for the fourth Pi
B4 = np.array([0, 1, 0, 0])
C4 = np.linalg.solve(A, B4)

# We solve for the fifth Pi
B5 = np.array([-1, 1, 0, 1])
C5 = np.linalg.solve(A, B5)

print(C1)
print(C2)
print(C3)
print(C4)
print(C5)

print("\n--- %s seconds ---" % (time.time() - start_time))


import numpy as np

class GroundTruthDoublePendulum:
    """
    A class to represent a ground truth representation of a double pendulum
    and all of its system properties
    """

    def __init__(self, m_matrix, l_vec):
        self.m = m_matrix
        self.lengths = l_vec
        self.g = 9.81
        self.state_hist = np.empty((0, 4))  # input
        self.state_dot_hist = np.empty((0, 4))  # label

    def dynamics(self, state):
        """
        :param state: [[theta1],
                       [theta2],
                       [theta1_dot],
                       [theta2_dot]]
               t: time_value (s)
        :return state_dot:
        """
        self.state_hist = np.vstack((self.state_hist, state))

        state_dot = np.zeros(4)

        c = np.cos(state[0] - state[1])  # intermediate variables
        s = np.sin(state[0] - state[1])  # intermediate variables

        state_dot[0] = state[2]  # d(theta 1)
        state_dot[1] = state[3]  # d(theta 2)
        state_dot[2] = (self.m[1] * self.g * np.sin(state[1]) * c - self.m[1] * s * (self.lengths[0] * c * state[2] ** 2 + self.lengths[1] * state[3] ** 2)
                        - (self.m[0] + self.m[1]) * self.g * np.sin(state[0])) / (self.lengths[0] * (self.m[0] + self.m[1] * s ** 2))
        state_dot[3] = ((self.m[0] + self.m[1]) * (self.lengths[0] * state[2] ** 2 * s - self.g * np.sin(state[1]) + self.g * np.sin(state[0]) * c)
                        + self.m[1] * self.lengths[1] * state[3] ** 2 * s * c) / (self.lengths[1] * (self.m[0] + self.m[1] * s ** 2))

        self.state_dot_hist = np.vstack((self.state_dot_hist, state_dot))

        return state_dot

    def polar_to_cartesian(self, theta1, theta2):
        # Mapping from polar to Cartesian
        x1 = self.lengths[0] * np.sin(theta1)  # First Pendulum
        y1 = -self.lengths[0] * np.cos(theta1)

        x2 = x1 + self.lengths[1] * np.sin(theta2)  # Second Pendulum
        y2 = y1 - self.lengths[1] * np.cos(theta2)

        return zip(x1, y1, x2, y2)

    def lagrangian(self, state):
        """
        :param state: [[theta1],
                       [theta2],
                       [theta1_dot],
                       [theta2_dot]]
        :return lagrangian:
        """

        link1_vel = (self.lengths[0]**2)*(state[2]**2)
        T_kinetic_link1 = 1/2*self.m[0]*link1_vel
        link2_vel = link1_vel + (self.lengths[1]**2)*(state[3]**2) \
                    + 2*self.lengths[0]*self.lengths[1]*state[2]*state[3]*np.cos(state[0]-state[1])
        T_kinetic_link2 = 1/2*self.m[1]*link2_vel

        T_kinetic = T_kinetic_link1 + T_kinetic_link2

        V_potential = -(self.m[0] + self.m[1])*self.g*self.lengths[0]*np.cos(state[0]) \
                      - self.m[1]*self.g*self.lengths[1]*np.cos(state[1])

        lagrangian = T_kinetic + V_potential

        return lagrangian


import numpy as np
from matplotlib import pyplot as plt
from matplotlib import ticker

def f(x, y):
    return (1 - x) ** 2 + 100 * (y - x * x) ** 2

def grad(x, y):
    return np.matrix([[2 * x - 2 + 400 * x * (x * x - y)],
                      [200 * (y - x * x)]])

def H(x, y):
    return np.matrix([[1200 * x * x - 400 * y + 2, -400 * x],
                      [-400 * x, 200]])

def gradient_descent_delta(x, y):
    return 1e-3 * grad(x, y)

def newton_delta(x, y):
    return H(x, y).I * grad(x, y)

def optimize(delta_func):
    step_count = 20000
    count = 0
    eps = 1e-8

    x = np.mat([-1, 1]).T
    x_list, y_list = [x[0, 0]], [x[1, 0]]
    while count < step_count:
        delta = delta_func(x[0, 0], x[1, 0])
        if np.linalg.norm(delta) < eps:
            break
        x = x - delta
        x_list.append(x[0, 0])
        y_list.append(x[1, 0])
        count += 1
    print(f'round = {count}')
    return x_list, y_list

if __name__ == '__main__':
    for name, func in zip(['./data/1.jpg', './data/2.jpg'], [gradient_descent_delta, newton_delta]):
        n = 1000
        x = np.linspace(-1, 1.1, n)
        y = np.linspace(-1, 1.1, n)
        X, Y = np.meshgrid(x, y)
        plt.figure()
        plt.contourf(X, Y, f(X, Y), 5, alpha=0, cmap=plt.cm.hot)
        plt.clabel(plt.contour(X, Y, f(X, Y), 8, locator=ticker.LogLocator(),
                               colors='black'), inline=True, fontsize=10)

        x_list, y_list = optimize(func)
        plt.plot(x_list, y_list)
        plt.savefig(name)
        print(f'({x_list[-1]:.3f}, {y_list[-1]:.3f})')


import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import scipy.stats as st
from matplotlib import style
from scipy.stats import multivariate_normal

sns.set_style('whitegrid')

# Metropolis - (Hastings) - Algorithm
def pgauss(x, y, mean, cov):
    """Returns density of bivariate normal with mean=mean and cov=cov."""
    return st.multivariate_normal.pdf([x, y], mean=mean, cov=cov)

def metropolis_hastings(p, nsamples):
    """Returns nsamples of density p using a gaussian proposal and the metropolis algorithm."""
    x, y = -1., -1. # starting values
    samples = np.zeros((nsamples+1, 2))
    samples[0, :] = np.array([x, y])

    for t in range(0, nsamples):
        x_proposal, y_proposal = np.array([x, y]) + np.random.normal(size=2)
        u = np.random.rand()
        A = p(x_proposal, y_proposal) / p(x, y)
        if u < A: # accept with probability min(1, A)
            x, y = x_proposal, y_proposal
        samples[t+1] = np.array([x, y])

    return samples

if __name__ == "__main__":
    # set seed
    np.random.seed(3)

    # location and scale parameters
    mean = np.array([2, 2])
    cov = 0.5 * np.array([[1, 0.5], [0.5, 1]])

    # construct Markov chain
    def ppgauss(x, y):
        return pgauss(x, y, mean, cov)
    mc_samples = metropolis_hastings(ppgauss, nsamples=500)

    # sampling points
    nsamples = 5000
    data = np.random.multivariate_normal(mean, cov, nsamples)

    # construct contour data
    x = np.linspace(-2,5,500)
    y = np.linspace(-2,5,500)
    X,Y = np.meshgrid(x,y)

    pos = np.array([X.flatten(),Y.flatten()]).T
    rv = multivariate_normal(mean, cov)

    # figsize
    plt.figure(figsize=(20, 12))

    # plot samples
    ax = sns.scatterplot(x=data[:,0], y=data[:, 1],
                         alpha=0.4)

    # plotting contours
    ax.contour(x, y, rv.pdf(pos).reshape(500,500),
               levels=[0.03, 0.15, 0.3],
               linewidths=2.5, colors=['black', 'grey', 'darkgray'])

    # ticks label size
    ax.tick_params(axis='both', which='major', labelsize=22)

    # coordinate system settings
    plt.axhline(0, color='black', linewidth=1.)
    plt.axvline(0, color='black', linewidth=1.)
    plt.ylim(-1.2, 4.5)
    plt.xlim(-1.2, 3.8)
    ax.set(frame_on=False)

    # save plot
    plt.savefig("../graphics/toy-mcmc.pdf", bbox_inches='tight', pad_inches=0, transparent=True)

    # add first 10 Markov chain samples
    plt.plot(mc_samples[:10,0],mc_samples[:10, 1], color='brown', marker='o', linewidth=2.25)

    # save plot
    plt.savefig("../graphics/toy-mcmc-with-samples-1.pdf", bbox_inches='tight', pad_inches=0, transparent=True)

    # add first 70 Markov chain samples
    plt.plot(mc_samples[10:70,0],mc_samples[10:70, 1], color='brown', marker='o', linewidth=2.25)

    # save plot
    plt.savefig("../graphics/toy-mcmc-with-samples-2.pdf", bbox_inches='tight', pad_inches=0, transparent=True)

    # add remaining Markov chain samples
    plt.plot(mc_samples[70:,0],mc_samples[70:, 1], color='brown', marker='o', linewidth=2.25)

    # save plot
    plt.savefig("../graphics/toy-mcmc-with-all-samples.pdf", bbox_inches='tight', pad_inches=0, transparent=True)


from numpy import square, sqrt
from scipy.stats import t
from statistics import mean as mn
from statistics import stdev as stdev

class Grubbs:
    def __init__(self, values: list, samples: list = False, remove: bool = False, alpha: float = 0.05):
        """Recieves a list of values used in graph and identify outliers

        Parameters
        ----------
        values : list
            List of values from individual values for graph/statistics
        samples: list
            List with the samples' names to be used as index
        Returns
        -------
        list
            List with new values removing outliers, or indexes to be removed from main table (?)
        """
        self.values = values
        self.alpha = alpha
        crit = self.z_crit(len(values), alpha)
        results, mean, std, crit = self.z_score(values, crit)

        self.mean = "Mean: {}".format(mean)
        self.std = "Standard deviation: {}".format(std)
        self.critical = "Critical Value(λ): {}".format(crit)

        if samples:
            results["samples"] = samples
        else:
            pass

        if remove:
            rmvd = [x if x is not False and y == "No" else "-"
                    for x, y in zip(results["values"], results["outlier"])]
            if "-" in rmvd:
                rmvd.remove("-")
            else:
                pass

            self.keeps = rmvd
        else:
            pass

        self.results = results

        if results["outlier"].count("Yes") == 0:
            self.outliers = "There are no outliers between all samples"
        elif results["outlier"].count("Yes") == 1:
            self.outliers = "There is 1 outlier between all samples"
        else:
            self.outliers = "There are {} outliers between all samples" \
                .format(results["outlier"].count("Yes"))
        pass

    @staticmethod
    def z_crit(n: int, alpha: float) -> float:
        """ Gets both degress of freedom and alpha selected by user
        which is then used to get t critical value using Scipy t ppf
        Also there is a possibility to use one-tailed test to find critical value

        Parameters
        ----------
        n : int
            Degrees of freedom passed automatically to the function
        alpha : float
            Alpha value to be used in the t critical value finding passed by the user
        Returns
        -------
        float
            Absolute value for the z critical value used to compare between z score of each sample
        """
        t_crit = t.ppf(alpha / (2 * n), n - 2)
        num = (n - 1) * t_crit
        den = sqrt(n * (n - 2 + square(t_crit)))
        val_crit = num / den
        return abs(val_crit)

    @staticmethod
    def z_score(vals: list, crit: float) -> tuple:
        """ Recieves values and critical z value to be compared with

        Parameters
        ----------
        vals : list
            List of sample values
        crit : float
            Critical z score to be used to compare with values
        Returns
        -------
        pd.DataFrame
            Dataframe with all data from z score and outlier results
        """
        mean = mn(vals)
        std = stdev(vals)
        zis = [(abs(mean - v) / std) for v in vals]
        otls = ["No" if z < crit else "Yes" for z in zis]
        results = {
            "values": vals,
            "zscore": zis,
            "outlier":otls
        }
        return results, mean, std, crit


'''
Universidad Nacional Autonoma de Mexico.
Licenciatura en ciencias genomicas - Computo Cientifico 2020.
Salvador Gonzalez Juarez.

Proyecto final.
Programa 5: Lagrange.py
Calcula la interpolacion de un valor dado un conjunto de puntos u obtiene el polinomio de la funcion que siguen estos mismos, utilizando el metodo de Interpolacion de Lagrange.
'''

# Importa las librerias que ayudan a realizar el algebra del programa
import sympy as sp
from sympy import sympify, poly

def interpolacion(valores_x, valores_y, flag, valor = 1):
	'''
  	Funcion encargada de aplicar la formula general del metodo de interpolacion de Lagrange.
  	param valores_x: list, lista que contiene todos los valores de 'x'.
  	param valores_y: list, lista que contiene todos los valores de 'y.
  	param flag: bool, bandera que indica la modalidad del programa (si se busca interpolar un valor u obtener el polinomio).
  	param valor: float, valor que se busca interpolar. Por default es '1' y no es requerido si se quiere obtener el polinomio.
  	return el valor interpolado o el polinomio, dependiendo de la modalidad.
  	'''
	if flag:
		x = valor
	else:
		x = sp.Symbol('x')

	# Inicializa las variable que utilizan las iteraciones.
	nominador = 1
	denominador = 1
	polinomio = 0
	indice = 0

	# Realiza las iteraciones correspondientes al metodo de interpolacion de Lagrange.
	for xi in valores_x:
		for xj in valores_x:

			# Continua solo si se cumple la condicion impuesta por el metodo de Lagrange para los valores de 'xj' y 'xi'.
			if xj != xi:
				if flag:
					nominador = nominador * (x - xj)
					denominador = denominador * (xi - xj)
				else:
					nominador = sympify(nominador * (x - xj))
					denominador = sympify(denominador * (xi - xj))

		# Calcula el valor de cada uno de los terminos.
		if flag:
			termino = (nominador / denominador) * valores_y[indice]
			polinomio = polinomio + termino
		else:
			termino = sympify((nominador / denominador) * valores_y[indice])
			polinomio = sympify(polinomio + termino)

		# Inicializa los valores de las variables utilizadas para la siguiente iteracion.
		indice = indice + 1
		nominador = 1
		denominador = 1

	return polinomio

# Inicio del programa.
print("Bienvenido a Lagrange. Este programa calcula la interpolacion de un valor dado un conjunto de puntos u obtiene el polinomio de la funcion que siguen estos mismos, utilizando el metodo de Interpolacion de Lagrange.")

# Pide al usuario un numero correcto de puntos.
cantidad_puntos = 0
while cantidad_puntos <= 1:
	cantidad_puntos = int(input("\nEscribe el numero de puntos: "))
	if cantidad_puntos <= 1:
		print("El valor debe ser mayor a 1. Intentalo de nuevo.")

# Genera un par de listas donde se guardaran los valores en 'x' y en 'y' de los puntos, respectivamente.
valores_x = []
valores_y = []

# Pide al usuario los valores en 'x' y en 'y' de todos los puntos, y los guarda en la lista correspondiente.
for i in range(cantidad_puntos):
	valores_x.append(float(input("\nIngresa el valor en 'x' del punto " + str(i + 1) + ": ")))
	valores_y.append(float(input("Ingresa el valor en 'y' del punto " + str(i + 1) + ": ")))

# Pregunta al usuario que modalidad del programa quiere usar.
flag = None
while (flag != 'i') and (flag != 'p'):
	flag = input("\nSi quieres interpolar un valor, escribe 'i'. Si quieres obtener el polinomio, escribe 'p': ")
	if (flag != 'i') and (flag != 'p'):
		print("Intentalo de nuevo.")

# Si el usuario eligio la modalidad de interpolacion de un valor, continua con el siguiente codigo.
if flag == 'i':

	# Obtiene el valor a interpolar, lo interpola e imprime el resultado.
	lista_valores = (input("\nIngresa los valores que quieres interpolar (separa los valores con un espacio): "))
	lista_valores = lista_valores.split(" ")
	for valor in lista_valores:
		resultado = interpolacion(valores_x, valores_y, True, float(valor))
		print("\nEl valor interpolado de " + valor + " es: " + str(resultado))

# Si el usuario eligio la modalidad de obtencion del polinomio, continua con el siguiente codigo.
else:

	# Obtiene el polinomio de la funcion.
	resultado = interpolacion(valores_x, valores_y, False)

	# Prepara el polinomio para una correcta impresion.
	resultado = str(poly(resultado))
	resultado = resultado.lstrip("Poly(").rstrip(")")
	resultado = resultado.split(",")

	# Imprime solo el polinomio como resultado.
	print("\nEl polinomio es de la funcion es: " + str(resultado[0]))

'''
DICCIONARIO DE VARIABLES:

cantidad_puntos: int, variable que representa la cantidad de puntos que seran usados para hacer la interpolacion.

denominador: float o sympify, multiplicador usado para calcular el denominador de cada uno de los terminos en la funcion interpolacion( ).

flag: bool, bandera que indica la modalidad del programa (si se busca interpolar un valor u obtener el polinomio).

i: int, contador utilizado para recorrer listas.

indice: int, variable de tipo acumulador utilizada para poder accesar de manera correcta a los valores que contiene la lista valores_y, dentro de  la funcion interpolacion( ).

lista_valores: list, lista que contiene todos los valores que el usuario desea interpolar.

nominador: float o sympify, variable de tipo multiplicador usado para calcular el nominador de cada uno de los terminos en la funcion interpolacion( ).

polinomio: float o sympify, variable que contiene el valor del resultado de la interpolacion, ya sea en forma de polinomio o como polinomio resuelto. Se calcula como indica el metodo de Lagrange. Es el valor retornable de la funcion interpolacion( ).

resultado: float o sympify, variable que representa el resultado de la interpolacion de cada valor, o el polinomio que describe la curva formada por los puntos.

termino: float o sympify, variable temporal que almacena el valor de un termino del polinomio, para luego sumarlo con el resto del polinomio. Es usada dentro de la funcion interpolacion( ).

valor: float, variable que representa uno de los valores que el usuario quiere interpolar.

valores_x: list, lista que contiene el valor en 'x' de cada uno de los puntos proporcionados por el usuario.

valores_y: list, lista que contiene el valor en 'y' de cada uno de los puntos proporcionados por el usuario.

x (en modo interpolacion): float, variable que almacena el valor del parametro valor en la funcion interpolacion( ).

x (en modo polinomio): sympify, literal 'x' dentro de la ecuacion.

xi: float, variable que representa uno de los valores de la lista valores_x, y es utilizada para crear el denominador de cada uno de los terminos en la funcion interpolacion( ).

xj: float, variable que representa uno de los valores de la lista valores_x, y es utilizada para crear el nominador y denominador de cada uno de los terminos en la funcion interpolacion( ).
'''


'''
    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).

    PM4Py is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    PM4Py is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
'''
import numpy as np
import sympy

def compute_place_invariants(net):
    """
    We compute the NUllspace of the incidence matrix and obtain the place-invariants.
    :param net: Petri Net of which we want to know the place invariants.
    :return: Set of place invariants of the given Petri Net.
    """

    def compute_incidence_matrix(net):
        """
        Given a Petri Net, the incidence matrix is computed. An incidence matrix has n rows (places) and m columns
        (transitions).
        :param net: Petri Net object
        :return: Incidence matrix
        """
        n = len(net.transitions)
        m = len(net.places)
        C = np.zeros((m, n))
        i = 0
        transition_list = list(net.transitions)
        place_list = list(net.places)
        while i < n:
            t = transition_list[i]
            for in_arc in t.in_arcs:
                # arcs that go to transition
                C[place_list.index(in_arc.source), i] -= 1
            for out_arc in t.out_arcs:
                # arcs that lead away from transition
                C[place_list.index(out_arc.target), i] += 1
            i += 1
        return C

    def extract_basis_vectors(incidence_matrix):
        """
        The name of the method describes what we want t achieve. We calculate the nullspace of the transposed identity matrix.
        :param incidence_matrix: Numpy Array
        :return: a collection of numpy arrays that form a base of transposed A
        """
        # To have the same dimension as described as in https://www7.in.tum.de/~esparza/fcbook-middle.pdf and to get the correct nullspace, we have to transpose
        A = np.transpose(incidence_matrix)
        # exp from book https://www7.in.tum.de/~esparza/fcbook-middle.pdf
        x = sympy.Matrix(A).nullspace()
        # TODO: Question here: Will x be always rational? Depends on sympy implementation. Normaly, yes, we we will have rational results
        x = np.array(x).astype(np.float64)
        return x

    A = compute_incidence_matrix(net)
    return extract_basis_vectors(A)


from scipy.odr import ODR, Model, RealData, unilinear
import numpy as np

def fit_straight_line(x, y, x_err=None, y_err=None, m0=1.0, c0=0.0, nan_error=False, skip_nan=False):
    """
    Use scipy to fit a straight line, with errors or covariances allowed
    on both x and y.

    Parameters
    ----------
    x: array
        x-coordinate
    y: array
        y-coordinate
    m0: float
        optional, default=1, guess at gradient
    c0: float
        optional, default=1, guess at intercept
    x_err: array/float
        optional, default=None, errors either 1D std. dev., 2D covariance, or scalar constant, or None for unspecified
    y_err: array/float
        optional, default=None, errors either 1D std. dev., 2D covariance, or scalar constant, or None for unspecified

    Returns
    -------
    m: float
        gradient

    c: float
        intercept
    """

    kwargs = {}
    nx = np.ndim(x_err)

    if skip_nan:
        w = np.isfinite(x) & np.isfinite(y)
        x = x[w]
        y = y[w]
    else:
        w = slice(None)

    if x_err is None:
        pass
    elif nx == 0:
        kwargs['sx'] = x_err
    elif nx == 1:
        kwargs['sx'] = x_err[w]
    elif nx == 2:
        kwargs['cov_x'] = x_err[w][:,w]
    else:
        raise ValueError("x_sigma_or_cov must be None, scalar, 1D, or 2D")

    ny = np.ndim(y_err)

    if y_err is None:
        pass
    elif ny == 0:
        kwargs['sy'] = y_err
    elif ny == 1:
        kwargs['sy'] = y_err[w]
    elif nx == 2:
        kwargs['cov_y'] = y_err[w][:,w]
    else:
        raise ValueError("x_sigma_or_cov must be None, scalar, 1D, or 2D")

    data = RealData(x, y, **kwargs)
    odr = ODR(data, unilinear, beta0=[m0, c0], maxit=200)
    results = odr.run()

    if results.stopreason != ['Sum of squares convergence']:
        if nan_error:
            return np.nan, np.nan, np.zeros((2,2))*np.nan
        else:
            raise RuntimeError('Failed to straight line' + str(results.stopreason))

    m, c = results.beta
    cov = results.cov_beta
    return m, c, cov


# Jack C. Cook
# Sunday, August 30, 2020

import numpy as np

def process(path):
    """
    This function takes a path to a file and does stuff. [For documentation example.]

    :type path: string
    :param path: a path to the file which contains the features
    :return: None
    """
    print(path)
    return None

def least_squares_max_likelihood(phi_x: np.ndarray, y: np.ndarray) -> np.ndarray:
    """
    Compute the maximum likelihood for least squares
    :param phi_x: the input feature transformed
    :param y: the y values for regression
    :return: the maximum likelihood solution to least squares linear regression
    """
    # TODO: look into sklearn linear regression
    # Take the Moore-Penrose pseudo inverse
    # https://pythonhosted.org/algopy/examples/moore_penrose_pseudoinverse.html
    x_p = np.linalg.pinv(phi_x)
    # finish solving for maximum likelihood
    theta_ml = np.matmul(x_p, y)
    return theta_ml

def phi_polynomial(x: np.ndarray, order=1) -> np.ndarray:
    """
    A hypothesis or basis function which is a polynomial function of x up to order

    :type x: np.ndarray
    :type order: int

    :param x: the original input features used to generate the data
    :param order: the order of the basis function

    :return: the input feature vector after being processed by the basis function
    """
    # TODO: look into np.power()
    phi_x = np.ones_like(x)  # first term is one (bias)
    for i in range(1, order + 1):
        tmp = np.power(x, i)
        phi_x = np.hstack((phi_x, tmp))
    return phi_x

def predicted_values(theta, x):
    # TODO: remove the for loop by using phi_x
    m, n = theta.shape
    y_pred = np.zeros_like(x)
    for i in range(m):
        power = i
        y_pred += theta[i][0] * x ** power
    return y_pred

def predict_values(phi_x: np.ndarray, theta_ml: np.ndarray):
    """
    Predict the values of the maximum likely hood solution
    :param phi_x: the output of the feature vector after processing through the basis function (see phi_polynomial)
    :param theta_ml: the maximum likelihood solution for linear regression using least squares
    (see least_squares_maximum_likelihood)
    :return: the predicted values
    """
    return np.matmul(phi_x, theta_ml)

def average_least_square_error(y1: np.ndarray, y2: np.ndarray):
    """
    Compute the average or mean least squares error
    :param y1: one input vector
    :param y2: one input vector
    :return: the scalar value of the average least squares error
    """
    # https://stackoverflow.com/questions/16774849/mean-squared-error-in-numpy
    mse = (np.square(y2 - y1)).mean()
    return mse

def weight_matrix(x: np.ndarray, training_data: np.ndarray, tao: float):
    """

    :param x: the input value to be locally weighted to
    :param training_data: the training data or examples you have available to train with
    :param tao: the bandwidth parameter
    :return: an identity matrix which is the weight matrix

    >>> w[0] = np.exp(- (training_data[0] - x)**2 / (2 * tao ** 2) )

    """
    # https://www.geeksforgeeks.org/implementation-of-locally-weighted-linear-regression/
    # M is the No of training examples
    M = training_data.shape[0]
    # Initialising W with identity matrix
    W = np.mat(np.eye(M))
    # calculating weights for query points
    # TODO: turn this into vectorized code
    for i in range(M):
        xi = training_data[i]
        denominator = (-2 * tao * tao)
        W[i, i] = np.exp(np.dot((xi - x), (xi - x).T) / denominator)
    return W

def predict(training_data: np.ndarray, Y: np.ndarray, x: np.ndarray, tao: float):
    """

    :param training_data: the feature vector
    :param Y: the output values or target values
    :param x: the query point
    :param tao: bandwidth
    :return: the prediction and theta maximum likelihood
    """
    X_ = training_data
    qx = x
    W = weight_matrix(qx, X_, tao)
    # calculating parameter theta
    theta = np.linalg.pinv(X_.T*(W * X_))*(X_.T*(W * Y))
    # calculating predictions
    pred = np.dot(qx, theta)
    return theta, pred


# $Author$
# $Revision$
# $Date$
# $HeadURL$
# $Id$

#+
# NAME:
#	DISK
#
# PURPOSE:
#	This function returns a byte array containing a disk.  The
#	disk is centered at x, y, and has radius r.  The array is nx,
#	ny in size and has byte type.  Pixel values of 1 indicate that
#	the center of a pixel is within r of x,y.  Pixel values of 0
#	indicate the opposite.  The center of each pixel is the
#	integer position of that pixel.
#
# CATEGORY:
#	Array creation.
#
# CALLING SEQUENCE:
#
#	diskarr = disk(R, X, Y, Nx, Ny)
#		or
#	diskarr = disk(R, Ctr, Sz)
#		which is the same as
#	diskarr = disk(R, [X, Y], [Nx, Ny])
#
# INPUTS:
#	There are two ways to enter the inputs.  If Nx is defined, then:
#	R:	radius of the disk, may be fractional.
#	X:	X position of the center of the disk, may be fractional.
#	Y:	Y position of the center of the disk, may be fractional.
#	Nx:	width of the output array.
#	Ny:	height of the output array.
#	Otherwise:
#	R:	Radius of the disk, may be fractional.
#	Ctr:	Position of the center of the disk, 2-element vector.
#	Sz:	Size of the output array, 2-element vector.
#
# KEYWORDS:
#	STATUS:	(returned) Set to 1 if any part of the disk is
#	outside the image boundaries.
#
# OUTPUTS:
#	This function returns a byte array as described above.
#
# PROCEDURE:
#	Each pixel is evaluated to see whether it is within r of
#	(x,y).  If so, the value is set to 1.  Otherwise it is 0.
#
# SIDE EFFECTS:
#	STATUS is set in caller.
#
# EXAMPLE:
#
#		tvscl, DISK(6.4, 9.9, 10.1, 30, 30)
#		tvscl, DISK(6.4, [9.9,  10.1], [30, 30], STATUS=STATUS)
#		print, STATUS
#		tvscl, DISK(6.4, [ 3,   10.1], [30, 30], STATUS=STATUS)
#		print, STATUS
#		tvscl, DISK(6.4, [27,   10.1], [30, 30], STATUS=STATUS)
#		print, STATUS
#		tvscl, DISK(6.4, [ 9.9,  3],   [30, 30], STATUS=STATUS)
#		print, STATUS
#		tvscl, DISK(6.4, [ 9.9, 27],   [30, 30], STATUS=STATUS)
#		print, STATUS
#
# MODIFICATION HISTORY:
# 	Written by:	Joseph Harrington, Cornell.  2003 April 4
#			jh@oobleck.astro.cornell.edu
#
#	2004 Feb 27	jh added alternate input method
#	2005 Nov 16	jh added STATUS, simplified disk calculation,
#				use double precision
#-

import numpy as np

def disk(r, ctr, size, status=False):

  # return status
  retstatus = status

  # check if disk is off image
  status = 0
  if (ctr[0] - r < 0 or ctr[0] + r > size[0]-1 or
      ctr[1] - r < 0 or ctr[1] + r > size[1]-1 ):
    status = 1

  # calculate pixel distance from center
  # print('disk size:',  size)
  ind = np.indices(size)
  fdisk = (ind[0]-ctr[0])**2.0 + (ind[1]-ctr[1])**2.0

  # return mask disk (and status if requested)
  ret = fdisk <= r**2.0
  if retstatus:
    ret = ret, status
  return ret


# encoding: utf-8
"""
circstat.py -- Circular statistics functions

Exported namespace: mean, std, var

Note: all functions take an array of radian-angle values on [0, 2*pi] as input.

Written by Joe Monaco, 4/17/2007
Center for Theoretical Neuroscience

Copyright (c) 2007 Columbia University. All rights reserved.

This software is provided AS IS under the terms of the Open Source MIT License.
See http://www.opensource.org/licenses/mit-license.php.
"""

# Library imports
from numpy import ones, dot, sin, cos, empty, arange, pi
from scipy.stats import histogram

# Package imports
from .radians import xy_to_rad

def mean(theta, w=None):
	"""First circular moment

    Input: theta - array of radian angle values
          w - optional weighting if angle values are binned
    Returns: scalar circular mean of theta

    See:	http://en.wikipedia.org/wiki/Directional_statistics
	"""
	sz = theta.shape[0]
	if w is None:
	    w = ones(sz, 'd')
	elif w.size != sz:
	    raise ValueError, 'weight array size mismatch'
	s_bar = dot(w, sin(theta))
	c_bar = dot(w, cos(theta))
	return xy_to_rad(c_bar, s_bar)

def std(theta):
	"""Sample circular deviation

    Input: theta - array of radian angle values
    Returns: circular standard deviation
	"""
	return (var(theta))**0.5

def var(theta, Nbins=360):
	"""Sample circular variance, second moment

    Calculated using the minimum variance method with moving cut points.
    See: Weber RO (1997). J. Appl. Meteorol. 36(10), 1403-1415.

    Input: theta - array of radian angle values
          numbins - number of intervals across [0, 2pi] to minimize
    Returns: circular variance
	"""
	N = len(theta)
	delta_t = 2 * pi / Nbins
	lims = (0, 2 * pi)
	x = arange(delta_t, 2*pi + delta_t, delta_t)
	n, xmin, w, extra = histogram(theta, numbins=Nbins, defaultlimits=lims)

	tbar = empty((Nbins,), 'd')
	S = empty((Nbins,), 'd')
	s2 = empty((Nbins,), 'd')

	tbar[0] = (x*n).sum() / N											# A1
	S[0] = ((x**2)*n).sum() / (N - 1)									# A2
	s2[0] = S[0] - N * (tbar[0]**2) / (N - 1)							# A3

	for k in xrange(1, Nbins):
		tbar[k] = tbar[k-1] + (2*pi) * n[k-1] / N						# A4
		S[k] = S[k-1] + (2*pi) * (2*pi + 2*x[k-1]) * n[k-1] / (N - 1)	# A5
		s2[k] = S[k] - N * (tbar[k]**2) / (N - 1)						# A6

	return s2.min()


# https://www.programcreek.com/2014/05/leetcode-minimum-path-sum-java/
# but not really :] Those are the solutions for a general problem of finding the minimum path in a graph.
import numpy as np

def generate_path(map,  dist, sy,  sx,  dy,  dx):
    ''' Generates (and visualizes) path from (sy,sx) to (dy,dx)'''
    # We got the distance map, not build the path by simply picking the nodes with smallest cost.
    path = [['.' for x in range(map.shape[1])] for y in range(map.shape[0])]

    # "Initialize" position.
    x = sx
    y = sy

    while y != dy or x != dx:
        next_x = -1
        next_y = -1
        next_min = np.inf
        move = 'x'
        # Check four directions.
        if (x > 0)  and (dist[y,  x-1] < next_min):
            next_min = dist[y,  x-1]
            next_x = x-1
            next_y = y
            move = '<'
        if (x < map.shape[1]-1)  and (dist[y,  x+1] < next_min):
            next_min = dist[y,  x+1]
            next_x = x+1
            next_y = y
            move = '>'
        if (y > 0)  and (dist[y-1,x] < next_min):
            next_min = dist[y-1, x]
            next_x = x
            next_y = y-1
            move = '^'
        if (y < map.shape[0]-1)  and (dist[y+1,x] < next_min):
            next_min = dist[y+1,x]
            next_x = x
            next_y = y+1
            move = 'd'
        # Make move
        path[y][x] = move
        x = next_x
        y = next_y

    # Indicate start and destination nodes.
    path[sy][sx] = 'o'
    path[dy][dx] = 'x'
    return path

def recursive_cost(map,  dist,  y, x):
    if (x > 0) and (dist[y, x-1] > dist[y, x] + map[y, x]):
        dist[y, x-1] = dist[y, x] + map[y, x-1]
        recursive_cost(map,  dist,  y, x-1)

    if (x < map.shape[1]-1) and (dist[y, x+1] > dist[y, x] + map[y, x+1]):
        dist[y, x+1] = dist[y, x] + map[y, x+1]
        recursive_cost(map,  dist,  y, x+1)

    if (y > 0) and (dist[y-1, x] > dist[y, x] + map[y-1, x]):
        dist[y-1, x] = dist[y, x] + map[y-1, x]
        recursive_cost(map,  dist,  y-1, x)

    if (y < map.shape[0]-1) and (dist[y+1, x] > dist[y, x] + map[y+1, x]):
        dist[y+1, x] = dist[y, x] + map[y+1, x]
        recursive_cost(map,  dist,  y+1, x)
    # return

def min_path_recursive(map, start=None,  dest=None):
    '''Finds minimum path in a given map using a simple recursive algorithm'''
    print(map.shape)
    print("Map:\n",map)

    # Initialize cost map.
    dist = np.empty(map.shape)
    dist.fill(np.inf)

    # Get start and destination coordinates.
    if (start == None):
        sy=0
        sx=0
    else:
        sy=start[0]
        sx=start[1]
    if (dest == None):
        dy = map.shape[0]-1
        dx = map.shape[1]-1
    else:
        dy = dest[0]
        dx = dest[1]

    # Distance from dest to dest is set to 0
    dist[dy, dx] = 0

    recursive_cost(map,  dist,  dy, dx)

    print("Final distances:\n", dist)

    return generate_path(map,  dist,   sy,  sx,  dy,  dx)

def min_path_dijkstra(map, start=None,  dest=None):
    '''Finds minimum path in a given map using the Dijkstra algorithm'''
    print(map.shape)
    print("Map:\n",map)

    # Initialize cost map.
    dist = np.empty(map.shape)
    dist.fill(np.inf)

    # Get start and destination coordinates.
    if (start == None):
        sy=0
        sx=0
    else:
        sy=start[0]
        sx=start[1]
    if (dest == None):
        dy = map.shape[0]-1
        dx = map.shape[1]-1
    else:
        dy = dest[0]
        dx = dest[1]

    # Distance from dest to dest is set to 0
    dist[dy, dx] = 0

    #  Queue of all nodes in the graph.
    Q  = [(y, x) for y in range(map.shape[0]) for x in range(map.shape[1])]

    while any(Q):
        #print("Q=", Q)
        # Find node with minimum distance to target.
        min_dist = np.inf
        min_node =-1
        for i, (y, x) in enumerate(Q):
            if (dist[y, x] < min_dist):
                min_dist = dist[y, x]
                min_node = i
        # Propagate from that node...
        (y, x) = Q.pop(min_node)

        # ... for each new adjacent node - check distances.
        if (x > 0) and (dist[y, x-1] > dist[y, x] + map[y, x]):
            dist[y, x-1] = dist[y, x] + map[y, x-1]

        if (x < map.shape[1]-1) and (dist[y, x+1] > dist[y, x] + map[y, x+1]):
            dist[y, x+1] = dist[y, x] + map[y, x+1]

        if (y > 0) and (dist[y-1, x] > dist[y, x] + map[y-1, x]):
            dist[y-1, x] = dist[y, x] + map[y-1, x]

        if (y < map.shape[0]-1) and (dist[y+1, x] > dist[y, x] + map[y+1, x]):
            dist[y+1, x] = dist[y, x] + map[y+1, x]

        #print("Calculated distances:\n", dist)
    print("Final distances:\n", dist)

    # Path.
    if (start == None):
        sy=0
        sx=0
    else:
        sy=start[0]
        sx=start[1]

    return generate_path(map,  dist,   sy,  sx,  dy,  dx)

if __name__ == "__main__":
    #map = np.array([[1, 2, 11, 10],   [2, 1, 1, 1],  [2, 1, 4, 0]])
    #map = np.array([[1, 30, 1, 3,  1],   [2, 1, 1, 10,  1],  [2, 1, 40, 1,  1]])
    map = np.array([[1, 2, 111, 10],   [21, 1, 101, 11],  [2, 1, 40, 9],  [1, 10, 40, 9],  [1, 1, 10, 1]])

    #p = min_path_dijkstra(map,  start=(0, 0),  dest=(0, 3))
    p = min_path_recursive(map=map,  start=(0, 0),  dest=(2, 3))
    print("Final path: ")
    for row in p:
        print(row)


#!\usr\bin\python
"""
File: yx_ODE.py

Copyright (c) 2016 Michael Seaman

License: MIT

Excercise C.3:
Implements the derivative of a function y(x) to test
how well the Euler method approximates it
"""

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

from Euler import Euler

def dydx(x, y):
    return (2*y - 2)**-1

def plot_Euler_approx(dydx, y_a, delta_x, a, b):
    """
    Plots the Euler approximation of y(x) given function dfdx and
    the initial point y_a from a to b with stepsize delta_x
    """
    xList = np.arange(a, b, delta_x)
    yList = Euler(dydx, y_a, delta_x, a, b)
    plt.plot(xList, yList)
    plt.show()

def y_0(epsilon):
    """
    returns the initial condition given an epsilon value
    """
    return 1 + float(epsilon)**.5

def plot_approx_and_exact(exact, resolution, dfdx, y_a, delta_x, a, b):
    """
    Plots the Euler approximation y(x) in red alongside its
    given exact value in green from a to b
    """
    xList_a = np.arange(a, b, delta_x)
    yList_a = Euler(dfdx, y_a, delta_x, a, b)
    xList_e = np.linspace(a, b, resolution)
    yList_e = np.vectorize(exact)(xList_e)
    plt.plot(xList_a, yList_a, 'r')
    plt.plot(xList_e, yList_e, 'g')
    plt.show()


# --------------------
# cs user: yoav
# full name: Yoav Levy
# id: 314963257
# --------------------
# ----- imports: -----
import math
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from scipy.linalg import qr

# ---------------------

def get_orthogonal_matrix(dim):
    H = np.random.randn(dim, dim)
    Q, R = qr(H)
    return Q

def plot_3d(x_y_z: np.array, title: str):
    """
    plot points in 3D
    :param x_y_z: the points. numpy array with shape: 3 X num_samples (first dimension for x, y, z
    coordinate)
    """
    fig = plt.figure()
    ax = fig.add_subplot(111, projection="3d")
    ax.scatter(x_y_z[0], x_y_z[1], x_y_z[2], s=1, marker=".", depthshade=False)
    ax.set_xlim(-5, 5)
    ax.set_ylim(-5, 5)
    ax.set_zlim(-5, 5)
    ax.set_xlabel("x")
    ax.set_ylabel("y")
    ax.set_zlabel("z")
    ax.set_title(title)
    fig.show()

def plot_2d(x_y, title):
    """
    plot points in 2D
    :param x_y_z: the points. numpy array with shape: 2 X num_samples (first dimension for x, y
    coordinate)
    """
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.scatter(x_y[0], x_y[1], s=1, marker=".")
    ax.set_xlim(-5, 5)
    ax.set_ylim(-5, 5)
    ax.set_xlabel("x")
    ax.set_ylabel("y")
    ax.set_title(title)
    fig.show()

def estimator_function(data):
    """
    This function is the mean estimator function.
    :param data: Coin tosses data.
    :return: The mean of tosses numbers.
    """
    values = []
    for index in range(len(data)):
        values.append(np.mean(data[: index + 1]))
    return values

def plot_q16():
    """
    This function plots the sample mean of a coin toss as a function of the number of tosses (m)
    as requested in question number 16 (a).
    :param data: 5d vector of vectors where the data of each batch is.
    :return: nothing.
    """
    data1 = estimator_function(ndata[0])
    data2 = estimator_function(ndata[1])
    data3 = estimator_function(ndata[2])
    data4 = estimator_function(ndata[3])
    data5 = estimator_function(ndata[4])
    fig, ax = plt.subplots()  # Create a figure containing a single axes.
    ax.plot(range(1, 1001), data1, label="sequence 1")  # plot 1
    ax.plot(range(1, 1001), data2, label="sequence 2")  # plot 2
    ax.plot(range(1, 1001), data3, label="sequence 3")  # plot 3
    ax.plot(range(1, 1001), data4, label="sequence 4")  # plot 4
    ax.plot(range(1, 1001), data5, label="sequence 5")  # plot 5
    ax.set_xlabel("Toss numbers (m)")
    ax.set_ylabel("Sample mean (estimator)")
    ax.legend()
    ax.set_ylim(bottom=0, top=1)
    ax.set_xlim(left=0, right=1000)
    plt.grid(color="#999999", linestyle="--", alpha=0.3)
    ax.set_title("Q16: 5 independent sequences of 1,000 tosses and their mean")
    fig.show()

def print_cov_mat(data):
    cov_mat = np.matmul(
        np.array(
            [
                [1 / (population - 1), 0, 0],
                [0, 1 / (population - 1), 0],
                [0, 0, 1 / (population - 1)],
            ]
        ),
        np.matmul(data, np.transpose(data)),
    )
    print(cov_mat)

def question_11(data):
    """
    Question 11, just plotting the given data.
    :param data: data
    :return: nothing
    """
    plot_3d(
        data, "Q11: Random points gen by the identity matrix as the cov matrix"
    )  # Q11

def question_12(data):
    """
    Question 12 linear transform the data, plot it, and show the newly created cov matrix.
    :param data: data
    :return: data after linear transformation
    """
    s_mat = np.array([[0.1, 0, 0], [0, 0.5, 0], [0, 0, 2]])
    new_data = np.matmul(s_mat, data)
    plot_3d(new_data, "Q12: Linear Transformed the prev data")
    print("------ Covariance Matrix (QUESTION 12) ------")
    print_cov_mat(new_data)
    return new_data

def question_13(data):
    """
    Question 13, create an orthogonal matrix, applay on the given data (already linear transformed)
    plot it, and show the cov matrix. (also prints the transformed data)
    :param data: Linear transformed data.
    :return: nothing.
    """
    print("------ Randomly created orthogonal Matrix ------")
    orthogonal_mat = get_orthogonal_matrix(3)  # Randomly orthogonal matrix
    print(orthogonal_mat)
    new_data = np.matmul(orthogonal_mat, data)
    plot_3d(new_data, "Q13: Applied orthogonal matrix")
    print("------ Covariance Matrix (AFTER ORTHGONAL TRANSFORMATION) ------")
    print_cov_mat(new_data)

def question_14(data):
    """
    Question 14, projection of the data to the x,y plane.
    and prints the result, and plotting the data.
    :param data: data.
    :return: nothing.
    """
    print("------ Projection of the data to the x, y axes (DATA) ------")
    proj_mat = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 0]])
    two_d_data = np.matmul(proj_mat, data)
    print(
        two_d_data[:-1]
    )  # "Elisha Kipnis" said we should plot in 2d if we project to 2d plane
    # (https://moodle2.cs.huji.ac.il/nu19/mod/forumng/discuss.php?d=7931)
    plot_2d(two_d_data[:-1], "Q14: Projection of the data to the x, y axes.")

def question_15(data):
    """
    Question 15, projection of the data which 0.1 > z > -0.4 to the x,y plane.
    and prints the result, and plotting the data.
    :param data:
    :return: nothing.
    """
    print("------ Question 15 0.1 > z > -0.4 (DATA) ------")
    x = []
    y = []
    bool_ind = (-0.4 < data[2]) & (data[2] < 0.1)
    for index, item in enumerate(data[0]):
        if bool_ind[index]:
            x.append(item)
            y.append(data[1][index])
    data_01zm04 = np.array([x, y])
    print(data_01zm04)
    plot_2d(data_01zm04, "Q15: Projection to x, y after filtering dots 0.1 > z > -0.4")

def question_16_a():
    """
    Question 16 a, plotting the experiment.
    :param data: data of 5 batches of 1000 coin tosses.
    :return: nothing.
    """
    plot_q16()

def create_pecentage_c(eps, cummean):
    """
    creates the percentage table
    :param eps: epsilon
    :return: percentage table
    """
    true_false_table = abs(cummean - 0.25) >= eps
    return np.count_nonzero(true_false_table, axis=0) / exp_number

def question_16_bc():
    """
    Question 16 b,
    :param data:
    :return:
    """
    variance = 0.1875
    cummean = np.add.accumulate(ndata, axis=1) / np.arange(1, data_len + 1)
    for eps in epsilon:
        fig, ax = plt.subplots()  # Create a figure containing a single axes.
        chebyshev = [min(variance / (x * (eps ** 2)), 1) for x in range(1, 1001)]
        hoeffding = [min(2 * math.exp(-2 * x * (eps ** 2)), 1) for x in range(1, 1001)]
        percentage = create_pecentage_c(eps, cummean)
        ax.plot(range(1, 1001), chebyshev, label="chebyshev bound")  # plot 1
        ax.plot(range(1, 1001), hoeffding, label="hoeffding bound")  # plot 1
        ax.plot(
            range(1, 1001), percentage, label="percentage satisfying event"
        )  # plot 1
        ax.set_xlabel("Toss numbers (m)")
        ax.set_ylabel("Upper Bound")
        ax.legend()
        ax.set_ylim(bottom=0, top=1.01)
        ax.set_xlim(left=0, right=1000)
        plt.grid(color="#999999", linestyle="--", alpha=0.3)
        ax.set_title("Q16b: epsilon = " + str(eps))
        fig.show()

if __name__ == "__main__":
    # ----------- settings -------------
    population = 50000
    mean = [0, 0, 0]
    cov = np.eye(3)
    data = np.random.multivariate_normal(mean, cov, population).T
    np.set_printoptions(suppress=True)
    # ----------------------------------
    question_11(data)
    transformed_data = question_12(data)
    question_13(transformed_data)
    question_14(data)
    question_15(data)
    # ----- Question no.16 -----
    exp_number =100  # 100000
    data_len = 1000  # 1000
    ndata = np.random.binomial(1, 0.25, (exp_number, data_len))
    epsilon = [0.5, 0.25, 0.1, 0.01, 0.001]
    question_16_a()
    question_16_bc()


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

import numpy as np
import numpy.linalg as ln
import math
import sys

""" This is a simple and deterministic method for matrix sketch.
The original method has been introduced in [Liberty2013]_ .

[Liberty2013] Edo Liberty, "Simple and Deterministic Matrix Sketching", ACM SIGKDD, 2013.
"""

def sketch(mat_a, ell):
    """Compute a sketch matrix of input matrix
    Note that \ell must be smaller than m * 2

    :param mat_a: original matrix to be sketched (n x m)
    :param ell: the number of rows in sketch matrix
    :returns: sketch matrix (\ell x m)
    """

    # number of columns
    m = mat_a.shape[1]

    # Input error handling
    if math.floor(ell / 2) >= m:
        raise ValueError('Error: ell must be smaller than m * 2')
    if ell >= mat_a.shape[0]:
        raise ValueError('Error: ell must not be greater than n')

    # initialize output matrix B
    mat_b = np.zeros([ell, m])

    # compute zero valued row list
    zero_rows = np.nonzero([round(s, 7) == 0.0 for s in np.sum(mat_b, axis = 1)])[0].tolist()

    # repeat inserting each row of matrix A
    for i in range(0, mat_a.shape[0]):

        # insert a row into matrix B
        mat_b[zero_rows[0], :] = mat_a[i, :]

        # remove zero valued row from the list
        zero_rows.remove(zero_rows[0])

        # if there is no more zero valued row
        if len(zero_rows) == 0:

            # compute SVD of matrix B
            mat_u, vec_sigma, mat_v = ln.svd(mat_b, full_matrices=False)

            # obtain squared singular value for threshold
            squared_sv_center = vec_sigma[math.floor(ell / 2)] ** 2

            # update sigma to shrink the row norms
            sigma_tilda = [(0.0 if d < 0.0 else math.sqrt(d)) for d in (vec_sigma ** 2 - squared_sv_center)]

            # update matrix B where at least half rows are all zero
            mat_b = np.dot(np.diagflat(sigma_tilda), mat_v)

            # update the zero valued row list
            zero_rows = np.nonzero([round(s, 7) == 0 for s in np.sum(mat_b, axis = 1)])[0].tolist()

    return mat_b

def calculateError(mat_a, mat_b):
    """Compute the degree of error by sketching

    :param mat_a: original matrix
    :param mat_b: sketch matrix
    :returns: reconstruction error
    """
    dot_mat_a = np.dot(mat_a.T, mat_a)
    dot_mat_b = np.dot(mat_b.T, mat_b)
    return ln.norm(dot_mat_a - dot_mat_b, ord = 2)

def squaredFrobeniusNorm(mat_a):
    """Compute the squared Frobenius norm of a matrix

    :param mat_a: original matrix
    :returns: squared Frobenius norm
    """
    return ln.norm(mat_a, ord = 'fro') ** 2


import sympy as sy

y0, y1, g0, g1, x = sy.symbols("y_0 y_1 g_0 g_1 x")
theta = sy.symbols("theta")
pi = sy.pi
R, w, h, H = sy.symbols("R w h H")

r = 1

theta0 = 2*sy.acos(h/r)
theta1 = 2*sy.acos((h-w)/r)
A0 = r**2 * (theta0 - sy.sin(theta0))/2
A1 = pi*r**2 - r**2 * (theta1 - sy.sin(theta1))/2

eq1 = sy.Eq(sy.simplify(A0/(A0+A1)), sy.simplify(y0/(y0+y1)))
print(eq1)
#print(sy.solve(eq1, h))

phi0 = 2*sy.acos(H/R)
phi1 = 2*sy.acos((H-w)/R)

beta0 = sy.asin(H/R)
beta1 = sy.asin((H-w)/R)

xup0 = -R*sy.sin(beta0)
xdown0 = -R*sy.sin(beta1)
#
#B00 = sy.integrate(sy.sqrt(R**2-x**2)-H,(x, -R*sy.sin(beta0), x0) )
#print(sy.simplify(B00))


import matplotlib.pyplot as plt
from numpy.linalg import solve

plt.style.use('seaborn')

def BlackLittermanNormal(mu_, s2_, tau, v_mu, mu_view, sig2_view):
    # This function computes the Black-Litterman parameters of the updated
    # normal distribution associated with a normal reference model for the
    # market variables
    #  INPUTS
    #   mu_        : [vector] (n_ x 1) base-case expectation
    #   s2_        : [matrix] (n_ x n_) base-case covariance matrix
    #   tau        : [scalar]  certainty level in the reference model
    #   v_mu       : [matrix] (k_ x n_) pick matrix specifying views
    #   mu_view    : [vector] (k_ x 1) vector quantifying views
    #   sig2_view  : [matrix] (k_ x k_) matrix of confidence level of views
    #  OPS
    #   BLmu       : [vector] (n_ x 1) updated expectation
    #   BLs2       : [matrix] (n_ x n_) updated covariance matrix

    # For details on the exercise, see here .

    ## Code
    pos = (1/tau)*v_mu@s2_@v_mu.T + sig2_view

    BLmu = mu_ + (1/tau)*s2_@v_mu.T@solve(pos,mu_view - v_mu@mu_)
    BLs2 = (1 + (1/tau))*s2_ - (1/tau)**2*s2_@v_mu.T@solve(pos,v_mu@s2_)
    return BLmu, BLs2


'''
Monty Hall Simulation in Python
as in: https://en.wikipedia.org/wiki/Monty_Hall_problem.

Particular implementation by Joel Stremmel
and licensed under the MIT license on July 28th, 2020.
'''

import random
import numpy as np

def simulate(switch, n_doors):

	# create doors numbered 1 to the number of doors
	doors = list(range(1, n_doors + 1))

	# randomly select the door that has the car, the players door, and the door revealed by Monty
	# note that Monty only reveals a door which is known to not contain the car
	door_with_car = random.choice(doors)
	door_selected_by_player = random.choice(doors)
	door_opened_by_monty = random.choice([door for door in doors if door not in [door_with_car, door_selected_by_player]])

	# apply switching strategy
	if switch:
		new_door_selected_by_player = random.choice([door for door in doors if door not in [door_selected_by_player, door_opened_by_monty]])

	# apply keeping strategy
	else:
		new_door_selected_by_player = door_selected_by_player

	# return a win as 1 and loss as 0
	return int(new_door_selected_by_player == door_with_car)

if __name__ == '__main__':

	SWITCH = True
	N_DOORS = 3
	N_SIMULATIONS = 10000

	results = [simulate(SWITCH, N_DOORS) for _ in range(N_SIMULATIONS)]
	win_pct = round(np.mean(results), 5) * 100.0

	print(f'Win percentage with {N_DOORS} doors, {N_SIMULATIONS} simulations, and SWITCH={SWITCH}: {win_pct}%.')


import numpy as np

def adam_step(i, g, params, alpha=0.001, beta1=0.9, beta2=0.999, eps=1e-8):
    """
    Implements the ADAM optimization algorithm according to: https://arxiv.org/abs/1412.6980

    :param i: the current iteration index
    :param g: the gradient of the weight
    :param params: a parameter dictionary used to store the momentum
    :param alpha: learning rate
    :param beta1: 1st momentum decay rate
    :param beta2: 2nd momentum decay rate
    :param eps: small epsilon to avoid division by zero
    :return: the correction term to update the parameter the gradient belongs to
    """

    # Calculate the ADAM momenta
    mi = params['mi']
    vi = params['vi']

    mi = beta1 * mi + (1 - beta1) * g
    vi = beta2 * vi + (1 - beta2) * (g ** 2)
    mi_ = mi / (1 - beta1 ** (i+1))
    vi_ = vi / (1 - beta2 ** (i+1))

    # Update
    params['mi'] = mi
    params['vi'] = vi

    return alpha * mi_ / (np.sqrt(vi_) + eps)

def gradient_descent_step(g, learning_rate=0.00001):
    """
    simple gradient descent
    :param g: the gradient
    :param learning_rate: the learning rate
    :return: the corrective term for the parameters the gradient belongs to
    """
    return learning_rate * g


import numpy as np
import random

def data_circle(num_samples, noise):
    """
    Generates the two circles dataset with the given number of samples and noise
    :param num_samples: total number of samples
    :param noise: noise percentage (0 .. 50)
    :return: None
    """
    radius = 5

    def get_circle_label(x, y, xc, yc):
        return 1 if np.sqrt((x - xc)**2 + (y - yc)**2) < (radius * 0.5) else 0

    noise *= 0.01
    points = np.zeros([num_samples, 2])
    labels = np.zeros(num_samples).astype(int)
    # Generate positive points inside the circle.
    for i in range(num_samples // 2):
        r = random.uniform(0, radius * 0.5)
        angle = random.uniform(0, 2 * np.pi)
        x = r * np.sin(angle)
        y = r * np.cos(angle)
        noise_x = random.uniform(-radius, radius) * noise
        noise_y = random.uniform(-radius, radius) * noise
        labels[i] = get_circle_label(x + noise_x, y + noise_y, 0, 0)
        points[i] = (x, y)
    # Generate negative points outside the circle.
    for i in range(num_samples // 2, num_samples):
        r = random.uniform(radius * 0.7, radius)
        angle = random.uniform(0, 2 * np.pi)
        x = r * np.sin(angle)
        y = r * np.cos(angle)
        noise_x = random.uniform(-radius, radius) * noise
        noise_y = random.uniform(-radius, radius) * noise
        labels[i] = get_circle_label(x + noise_x, y + noise_y, 0, 0)
        points[i] = (x, y)
    return points, labels

def data_spiral(num_samples, noise):
    """
    Generates the spiral dataset with the given number of samples and noise
    :param num_samples: total number of samples
    :param noise: noise percentage (0 .. 50)
    :return: None
    """
    noise *= 0.01
    '''
    half = num_samples // 2
    points = np.zeros([num_samples, 2])
    labels = np.zeros(num_samples).astype(int)
    for j in range(num_samples):
        i = j % half
        label = 1
        delta = 0
        if j >= half:  # negative examples
            label = 0
            delta = np.pi
        r = i / half * 5
        t = 1.75 * i / half * 2 * np.pi + delta
        x = r * np.sin(t) + random.uniform(-1, 1) * noise
        y = r * np.cos(t) + random.uniform(-1, 1) * noise
        labels[j] = label
        points[j] = (x, y)
    '''
    n = np.sqrt(np.random.rand(num_samples,1)) * 780 * (2*np.pi)/360
    d1x = -np.cos(n)*n + np.random.rand(num_samples,1) * noise
    d1y = np.sin(n)*n + np.random.rand(num_samples,1) * noise
    points = np.vstack((np.hstack((d1x,d1y)),np.hstack((-d1x,-d1y))))
    labels = np.hstack((np.zeros(num_samples, dtype=np.int64),np.ones(num_samples, dtype=np.int64)))

    return points, labels

def grid_points():
    """
    Generates grid points as the test set
    """
    x_min, x_max = -15, 15  # grid x bounds
    y_min, y_max = -15, 15  # grid y bounds
    xx, yy = np.meshgrid(np.linspace(x_min, x_max, 200),
                         np.linspace(y_min, y_max, 200))
    x = np.c_[xx.ravel(),yy.ravel()]
    y = np.ones(shape=x.shape[0], dtype=np.int64)

    return x, y, xx, yy