import math
import numpy as np

def Rastrigin(n):
    #f(x) = A*n + [sum (x_i² - A*cos(2*pi*x_i)) i = 1 .. n]
    A = 10
    def f(x):
        return A*n + sum(map(lambda x: x**2-A*math.cos(2*math.pi*x), x))
    def grad_f(x):
        grad = []
        for i in range(len(x)):
            grad.append(2*x[i] + 2*math.pi*A*math.sin(2*math.pi*x[i]))
        return np.array(grad)
    def hess_f(x):
        hess = [[2+(A*2*math.pi)**2*math.cos(2*math.pi*x[i]) if i==j else 0 for j in range(n)] for i in range(n)]
        return np.array(hess)
    return {"function": f, "gradient":grad_f, "hessian":hess_f, "search_region": None, "global_minimum":[0 for i in range(n)], "name":"Rastrigin"}

def Rosenbrock(n):
    # f(x, y) = 100(y-x²)² + (1-x)²
    if n != 2:
        return None
    def f(entry):
        x, y = entry[0], entry[1]
        return 100*(y-x**2)**2 + (1-x)**2
    def grad_f(entry):
        #return aprox_grad(f, entry)
        x, y = entry[0], entry[1]
        return np.array([2*(200*x**3-200*x*y+x-1), 200*(y-x**2)])
    def hess_f(entry):
        x, y = entry[0], entry[1]
        return np.array([[-400*(y-x**2)+800*x**2+2, -400*x],[-400*x, 200]])
    return {"function": f, "gradient": grad_f, "hessian": hess_f, "search_region": None, "global_minimum":[1 for i in range(n)], "name":"Rosenbrock"}

def Sphere(n):
    # f(x) = sum x_i² i = 1 .. n
    def f(x):
        return sum(map(lambda it: it**2, x))
    def grad_f(x):
        grad = [2*x[i] for i in range(n)]
        return np.array(grad)
    def hess_f(x):
        hess = [[2 if i==j else 0 for j in range(n)] for i in range(n)]
        return np.array(hess)
    return {"function": f, "gradient":grad_f, "hessian":hess_f, "search_region":None, "global_minimum":[0,0], "name":"Sphere"}

def Goldstein_price(n):
    if n != 2:
        return None
    def f(entry):
        x, y = entry[0], entry[1]
        return (1+(x+y+1)**2*(19-14*x+3*x**2-14*y+6*x*y+3*y**2))*(30+(2*x-3*y)**2*(18-32*x+12*x**2+48*y-36*x*y+27*y**2))
    def grad_f(entry):
        return aprox_grad(f, entry)
    return {"function": f, "gradient":grad_f, "hessian":None, "search_region": [-2,2], "global_minimum":[0,-1], "name":"Goldstein price"}

def aprox_deriv(f, x, h=10**(-4)):  # symetric difference quotient
    dy = f(x+h) - f(x-h)
    return dy/(2*h)

def aprox_grad(f, x):
    grad = []
    for i in range(len(x)):
        fs = lambda inp: f([*x[0:i], inp, *x[(i+1):len(x)]])
        grad.append(aprox_deriv(fs, x[i]))
    #fs = [lambda inp: f([*x[0:i], inp, *x[(i+1):len(x)]]) for i in range(len(x))]
    return np.array(grad)

def sph(x):
    return x[0]**2+x[1]**2

fns = [Rastrigin, Rosenbrock, Sphere, Goldstein_price]


from statistics import mean
import numpy as np
import random
import matplotlib.pyplot as plt
from matplotlib import style
style.use('ggplot')

def create_dataset(hm,variance,step=2,correlation=False):
    val = 1
    ys = []
    for i in range(hm):
        y = val + random.randrange(-variance,variance)
        ys.append(y)
        if correlation and correlation == 'pos':
            val+=step
        elif correlation and correlation == 'neg':
            val-=step

    xs = [i for i in range(len(ys))]

    return np.array(xs, dtype=np.float64),np.array(ys,dtype=np.float64)

def best_fit_slope_and_intercept(xs,ys):
    m = (((mean(xs)*mean(ys)) - mean(xs*ys)) /
         ((mean(xs)*mean(xs)) - mean(xs*xs)))

    b = mean(ys) - m*mean(xs)

    return m, b

def coefficient_of_determination(ys_orig,ys_line):
    y_mean_line = [mean(ys_orig) for y in ys_orig]

    squared_error_regr = sum((ys_line - ys_orig) * (ys_line - ys_orig))
    squared_error_y_mean = sum((y_mean_line - ys_orig) * (y_mean_line - ys_orig))

    print(squared_error_regr)
    print(squared_error_y_mean)

    r_squared = 1 - (squared_error_regr/squared_error_y_mean)

    return r_squared

xs, ys = create_dataset(40,40,2,correlation='pos')
m, b = best_fit_slope_and_intercept(xs,ys)
regression_line = [(m*x)+b for x in xs]
r_squared = coefficient_of_determination(ys,regression_line)
print(r_squared)

plt.scatter(xs,ys,color='#003F72', label = 'data')
plt.plot(xs, regression_line, label = 'regression line')
plt.legend(loc=4)
plt.show()


import numpy as np

def compute_sides(coord):

    ax = coord[0, 0]
    ay = coord[1, 0]  # Coordinates of the point A
    bx = coord[0, 1]
    by = coord[1, 1]  # Coordinates of the point B
    cx = coord[0, 2]
    cy = coord[1, 2]  # Coordinates of the point C

    ab = compute_length(ax, ay, bx, by)
    bc = compute_length(bx, by, cx, cy)
    ca = compute_length(cx, cy, ax, ay)
    return ab, bc, ca

def compute_length(p1x, p1y, p2x, p2y):
    return np.sqrt(np.power(p1x-p2x, 2)+np.power(p1y-p2y, 2))

def compute_area(coord):
    """ make sure the triangle exists"""
    [a, b, c] = sorted(compute_sides(coord))
    if a + b - c < np.finfo(float).eps:
        return 0
    s = (a + b + c) / 2
    return (s*(s-a)*(s-b)*(s-c)) ** 0.5

def circum_inside(coord):
    ax = coord[0, 0]
    ay = coord[1, 0]  # Coordinates of the point A
    bx = coord[0, 1]
    by = coord[1, 1]  # Coordinates of the point B
    cx = coord[0, 2]
    cy = coord[1, 2]  # Coordinates of the point C

    d = 2 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by))

    # Coordinates of the circumcenter
    ux = ((ax * ax + ay * ay) * (by - cy) + (bx * bx + by * by)
          * (cy - ay) + (cx * cx + cy * cy) * (ay - by)) / d
    uy = ((ax * ax + ay * ay) * (cx - bx) + (bx * bx + by * by)
          * (ax - cx) + (cx * cx + cy * cy) * (bx - ax)) / d

    # Radius of the circle
    r = np.sqrt((ax - ux)**2 + (ay - uy)**2)

    # Determine whether or not the circumcircle is covered by the unit circle
    area = compute_area(coord)
    if np.sqrt(ux**2+uy**2) + r <= 1 and area > np.finfo(float).eps:
        return True

def is_inside(a):
    return a[0]**2 + a[1]**2 <= 1

N = 10**6  # Number of Monte Carlo samples in both methods

# Generate samples by rejecting/accepting

count = 0  # Initialize the number of desired groups
k = 0
data_inside = np.zeros((2, 3))

while k <= 3*N:
    pt = np.random.uniform(-1, 1, 2)  # draw one point
    if is_inside(pt):  # Determine if the point lies inside the unit circle
        s = k % 3
        data_inside[0, s] = pt[0]
        data_inside[1, s] = pt[1]
        if k % 3 == 2 and circum_inside(data_inside):
            count += 1
        k += 1

per_rej = count / N

# Generate samples by polar coordinates
ct = 0  # Initialize the number of desired groups
for i in range(N):
    data_polar = np.array([np.sqrt(np.random.random(3)),
                           np.random.uniform(-np.pi, np.pi, 3)])
    # Tranform polar coornates to cartesian coordinates
    sp = data_polar.copy()
    sc = np.array([sp[0, :]*np.cos(sp[1, ]), sp[0, :]*np.sin(sp[1, ])])
    if circum_inside(sc):
        ct += 1

per_polar = ct / N

print("The Monte Carlo method (N={}) tell the answer is:".format(N))
print("polar     coordinates: {:.6f}".format(per_polar))
print("Cartesian coordinates: {:.6}".format(per_rej))
print("But formula 2*pi/15  : {:.6}".format(2*np.pi/15))


import math
from datetime import date, timedelta
from scipy import special
from sys import maxsize
import itertools

def sphere_volume(r):
    """ a function that computes the volume of a sphere, given its radius r. """
    return 4/3*math.pi*r**3

def quadratic_equation(a, b, c):
    """ a function that computes the real roots of a given quadratic equation a*X^2+b*X+c=0. """
    d = math.sqrt(b**2-4*a*c)

    r1 = (-b+d)/(2*a)
    r2 = (-b-d)/(2*a)

    return (r1, r2)

def number_of_zeros(lst):
    """  a function that returns the number of zeros in a given simple list of numbers lst. """
    return len(list(filter(lambda x: x == 0, lst)))

def draw_pascal(n):
    """ a function that takes an integer n as a parameter and prints the first n rows of the Pascal's triangle. """
    # Iterate through every line
    # and print entries in it
    for line in range(0, n):

        # Every line has number of
        # integers equal to line
        # number
        for i in range(0, line + 1):
            print(bc(line, i), " ", "")
        # '/n'
        print()

def euler():
    """ returns the unique positive integer whose square has the form 1_2_3_4_5_6_7_8_9_0, where each "_" is a single digit."""

def days(date1, date2):
    """a function that takes two dates, date1 and date2, in some format, and returns the number of days from date1 to date2, inclusive."""

    # this will give you a list containing all of the dates
    numDays = [str(date1 + timedelta(days=x))
               for x in range((date2-date1).days + 1)]

    return "\n".join(numDays)

def remove_consecutive_dups(lst):
    """ return a copy of lst with consecutive duplicates of elements eliminated. For example, for lst = [a, a, a, a, b, c, c, a, a, d, e, e, e, e], the returned list is [a, b, c, a, d, e]. """
    return [v for i, v in enumerate(lst) if i == 0 or v != lst[i-1]]

def remove_dups(lst):
    """ return a copy of lst with duplicates of elements eliminated. For example, for lst = [a, a, a, a, b, c, c, a, a, d, e, e, e, e], the returned list is [a, b, c, d, e]. """
    s = sorted(lst)
    return [v for i, v in enumerate(s) if i == 0 or v != s[i-1]]

def replicate(lst, n):
    """ Replicate each of the elements of lst a given number of times. For example, for lst = [a, b, c] and n = 3, the returned list is [a, a, a, b, b, b, c, c, c]. """
    def rep_helper(val, n):
        i = 0
        v = ""
        while i < n:
            v = v + val
            i += 1
        return v
    list_of_lists = [list(rep_helper(a, n)) for a in lst]
    return [val for sublist in list_of_lists for val in sublist]

def split_list(lst, n):
    """ split lst into two parts with the first part having n elements, and return a list that contains these two parts. """
    return [ lst[:n+1], lst[n+2:] ]

def min_max_median(lst):
    """ a function that takes a simple list of numbers lst as a parameter and returns a list with the min, max, and the median of lst. """
    s = sorted(lst)
    n = len(s)
    return [ s[0], s[-1], s[n//2] if n % 2 == 1  else (s[n//2 - 1] + s[n//2]) / 2]

def bc(n, k):
    """ return the binomial coefficient "n choose k". Can you figure out a method that is less likely to cause an overflow than using the formula(n*(n-1)*...*(n-k+1))/(k*(k-1)*...*2)? """

    return special.binom(n, k)

def subsets(s, n):
    """ return the set of n-element subsets of s. """
    return list(itertools.combinations(s, n))

def max_subarray(arr):
    """ return a contiguous subarray within arr which has the largest sum. """
    max_so_far = -maxsize - 1
    max_ending_here = 0
    max_itr = 0
    min_itr = 0

    for i in range(0, len(arr)):
        max_ending_here = max_ending_here + arr[i]
        if (max_so_far < max_ending_here):
            max_so_far = max_ending_here
            temp = i
        if (temp > max_itr):
            min_itr = max_itr
            max_itr = temp

        if max_ending_here < 0:
            max_ending_here = 0
    return arr[min_itr:max_itr + 1]


#-------------------------------------------------------------------------
# Note: please don't use any additional package except the following packages
import numpy as np
import math
#-------------------------------------------------------------------------
'''
    Problem 1: PCA
    In this problem, you will implement a version of the principal component analysis method to reduce the dimensionality of data.
    You could test the correctness of your code by typing `nosetests -v test1.py` in the terminal.

    Notations:
            ---------- input data ------------------------
            n: the number of data instances (for example, # of images), an integer scalar.
            p: the number of dimensions (for example, # of pixels in each image), an integer scalar.
            X: the feature matrix, a float numpy matrix of shape n by p.
            ---------- computed data ----------------------
            mu: the average vector of matrix X, a numpy float matrix of shape 1 by p.
                Each element mu[0,i] represents the average value in the i-th column of matrix X.
            Xc: the centered matrix X, a numpy float matrix of shape n by p.
                Each column has a zero mean value.
            C:  the covariance matrix of matrix X, a numpy float matrix of shape p by p.
            k:  the number of dimensions to reduce to (k should be smaller than p), an integer scalar
            E:  the eigen vectors of matrix X, a numpy float matrix of shape p by p.
                Each column of E corresponds to an eigen vector of matrix X.
            v:  the eigen values of matrix X, a numpy float array of length p.
                Each element corresponds to an eigen value of matrix X. E and v are paired.
            Xp: the projected feature matrix with reduced dimensions, a numpy float matrix of shape n by k.
             P: the projection matrix, a numpy float matrix of shape p by k.
            -----------------------------------------------
'''

#--------------------------
def centering_X(X):
    '''
        Centering matrix X, so that each column has zero mean.
        Input:
            X:  the feature matrix, a float numpy matrix of shape n by p. Here n is the number of data records, p is the number of dimensions.
        Output:
            Xc:  the centered matrix X, a numpy float matrix of shape n by p.
            mu:  the average row vector of matrix X, a numpy float matrix of shape 1 by p.
        Note: please don't use the np.cov() function. There seems to be a bug in their code which will result in an error in later test cases.
              Please implement this function only using basic numpy functions, such as np.mean().
    '''

    #########################################
    ## INSERT YOUR CODE HERE

    n, p = X.shape

    _1 = np.ones(n)

    mu = 1/n * np.dot(_1, X)
    Xc = X - mu

    #########################################
    return Xc, mu

#--------------------------
def compute_C(Xc):
    '''
        Compute the covariance matrix C.
        Input:
            Xc:  the centered feature matrix, a float numpy matrix of shape n by p. Here n is the number of data records, p is the number of dimensions.
        Output:
            C:  the covariance matrix, a numpy float matrix of shape p by p.
        Note: please don't use the np.cov() function here. Implement the function using matrix multiplication.
    '''

    #########################################
    ## INSERT YOUR CODE HERE

    n, p = Xc.shape

    C = 1 / (n-1) * np.dot(Xc.T, Xc)

    #########################################
    return C

#--------------------------
def compute_eigen_pairs(C):
    '''
        Compute the eigen vectors and eigen values of C.
        Input:
            C:  the covariance matrix, a numpy float matrix of shape p by p.
        Output:
            E:  the eigen vectors of matrix C, a numpy float matrix of shape p by p. Each column of E corresponds to an eigen vector of matrix C.
            v:  the eigen values of matrix C, a numpy float array of length p. Each element corresponds to an eigen value of matrix C. E and v are paired.
        Hint: you could use np.linalg.eig() to compute the eigen vectors of a matrix.
    '''

    #########################################
    ## INSERT YOUR CODE HERE

    v, E = np.linalg.eig(C)

    #########################################
    return E,v

#--------------------------
def compute_P(E,v,k):
    '''
        Compute the projection matrix P by combining the eigen vectors with the top k largest eigen values.
        Input:
            E:  the eigen vectors of matrix X, a numpy float matrix of shape p by p. Each column of E corresponds to an eigen vector of matrix X.
            v:  the eigen values of matrix X, a numpy float array of length p. Each element corresponds to an eigen value of matrix X. E and v are paired.
            k:  the number of dimensions to reduce to (k should be smaller than p), an integer scalar
        Output:
            P: the projection matrix, a numpy float matrix of shape p by k.
    '''

    #########################################
    ## INSERT YOUR CODE HERE

    select_indices = np.argsort(v)[::-1][:k]

    top_k = v[select_indices]
    P = E[:, select_indices]

    #########################################
    return P

#--------------------------
def compute_Xp(Xc,P):
    '''
        Compute the projected feature matrix Xp by projecting data Xc using matrix P.
        Input:
            Xc:  the feature matrix after centering, a float numpy matrix of shape n by p. Here n is the number of data records, p is the number of dimensions.
             P: the projection matrix, a numpy float matrix of shape p by k.
        Output:
            Xp: the feature matrix after projection (dimension reduced), a numpy float matrix of shape p by k.
    '''

    #########################################
    ## INSERT YOUR CODE HERE
    Xp = np.matmul(Xc, P)
    #########################################
    return Xp

#--------------------------
def PCA(X, k=1):
    '''
        Compute PCA of matrix X.
        Input:
            X:  the feature matrix, a float numpy matrix of shape n by p. Here n is the number of data records, p is the number of dimensions.
            k:  the number of dimensions to output (k should be smaller than p)
        Output:
            Xp: the feature matrix with reduced dimensions, a numpy float matrix of shape n by k.
             P: the projection matrix, a numpy float matrix of shape p by k.
        Note: in this problem, you cannot use existing package for PCA, such as scikit-learn
    '''

    #########################################
    ## INSERT YOUR CODE HERE

    # centering matrix X

    Xc, _ = centering_X(X)

    # compute covariance matrix C

    C = compute_C(Xc)

    # compute eigen pairs of L

    E, v = compute_eigen_pairs(C)
    # compute the projection matrix

    # project the data into lower dimension using projection matrix P and centered data matrix X

    P = compute_P(E, v, k)

    Xp = compute_Xp(Xc, P)

    #########################################
    return Xp, P


from mpmath import mp
import numpy

def bisection_search(f, low:float, high:float):
    """
    A root finding method that does not rely on derivatives

    :param f: a function f: X -> R
    :param low: the lower bracket
    :param high: the upper limit bracket
    :return: the location of the root, e.g. f(mid) ~ 0
    """
    # flip high and low if out of order
    if f(high) < f(low):
        low, high = high, low

    # find mid point
    mid = .5 * (low + high)

    while True:

        # bracket up
        if f(mid) < 0:
            low = mid
        # braket down
        else:
            high = mid

        # update mid point
        mid = .5 * (high + low)

        # break if condition met
        if abs(high - low) < 10 ** (-(mp.dps / 2)):
            break

    return mid

def concave_max(f, low:float, high:float):
    """
    Forms a lambda for the approximate derivative and finds the root

    :param f: a function f: X -> R
    :param low: the lower bracket
    :param high: the upper limit bracket
    :return: the location of the root f'(mid) ~ 0
    """
    # create an approximate derivative expression
    scale = high - low

    h = mp.mpf('0.' + ''.join(['0' for i in range(int(mp.dps / 1.5))]) + '1') * scale
    df = lambda x: (f(x + h) - f(x - h)) / (2.0 * h)

    return bisection_search(df, low, high)

def chev_points(n:int, lower:float = -1, upper:float = 1):
    """
    Generates a set of chebychev points spaced in the range [lower, upper]
    :param n: number of points
    :param lower: lower limit
    :param upper: upper limit
    :return: a list of multipressison chebychev points that are in the range [lower, upper]
    """
    #generate chebeshev points on a range [-1, 1]
    index = numpy.arange(1, n+1)
    range_ = abs(upper - lower)
    return [(.5*(mp.cos((2*i-1)/(2*n)*mp.pi)+1))*range_ + lower for i in index]

def remez(func, n_degree:int, lower:float=-1, upper:float=1, max_iter:int = 10):
    """
    :param func: a function (or lambda) f: X -> R
    :param n_degree: the degree of the polynomial to approximate the function f
    :param lower: lower range of the approximation
    :param upper: upper range of the approximation
    :return: the polynomial coefficients, and an approximate maximum error associated with this approximation
    """
    # initialize the node points

    x_points = chev_points(n_degree + 2, lower, upper)

    A = mp.matrix(n_degree + 2)
    coeffs = numpy.zeros(n_degree + 2)

    # place in the E column
    mean_error = float('inf')

    for i in range(n_degree + 2):
        A[i, n_degree + 1] = (-1) ** (i + 1)

    for i in range(max_iter):

        # build the system
        vander = numpy.polynomial.chebyshev.chebvander(x_points, n_degree)

        for i in range(n_degree + 2):
            for j in range(n_degree + 1):
                A[i, j] = vander[i, j]

        b = mp.matrix([func(x) for x in x_points])
        l = mp.lu_solve(A, b)

        coeffs = l[:-1]

        # build the residual expression
        r_i = lambda x: (func(x) - numpy.polynomial.chebyshev.chebval(x, coeffs))

        interval_list = list(zip(x_points, x_points[1:]))
        #         interval_list = [[x_points[i], x_points[i+1]] for i in range(len(x_points)-1)]

        intervals = [upper]
        intervals.extend([bisection_search(r_i, *i) for i in interval_list])
        intervals.append(lower)

        extermum_interval = [[intervals[i], intervals[i + 1]] for i in range(len(intervals) - 1)]

        extremums = [concave_max(r_i, *i) for i in extermum_interval]

        extremums[0] = mp.mpf(upper)
        extremums[-1] = mp.mpf(lower)

        errors = [abs(r_i(i)) for i in extremums]
        mean_error = numpy.mean(errors)

        if numpy.max([abs(error - mean_error) for error in errors]) < 0.000001 * mean_error:
            break

        x_points = extremums

    return [float(i) for i in numpy.polynomial.chebyshev.cheb2poly(coeffs)], float(mean_error)

def c_code_gen(data_type, name, poly_coeffs, comments = None):
    method_string = f'{data_type} {name} ({data_type} x)' + '{\n'

    if comments is not None:
        method_string += '\t// ' + str(comments) + ' \n\n'

    data_type_converter = '' if data_type == 'double' else 'f'

    method_string += '\n'.join([f'\tconst {data_type} a_{i} = {str(val) + data_type_converter};' for i, val in enumerate(poly_coeffs)])

    horner = 'return a_0+'
    for i in range(len(poly_coeffs)-2):
        horner += f'x*(a_{i+1} +'
    horner += f'x*a_{len(poly_coeffs)-1}' + ')'*(len(poly_coeffs)-2) + ';\n}'

    return method_string + '\n \t' + horner


import numpy as np
import math

def true(x, t, pro=1):

    if pro == 1:
        return np.cos(math.pi*t) * np.sin(math.pi*x)

    elif pro == 2:
        return np.sin(t) * np.sin(4*math.pi*x)

def ex3(h, tao, pro=1):
    xmin = 0
    xmax = 1
    tmin = 0
    tmax = 1
    xs = np.arange(xmin, xmax+h, h)
    ts = np.arange(tmin, tmax+tao, tao)
    if pro == 1:
        a = 1
        def u0(_i):
            _x =xs[_i]
            return np.sin(math.pi*_x)
        def ut0(_i):
            return 0
    elif pro == 2:
        a = 1 / (16*math.pi**2)
        def u0(_i):
            return 0
        def ut0(_i):
            _x = xs[_i]
            return np.sin(4*math.pi*_x)

    r = a*tao / h
    print("r={}" .format(r), end=',')

    U = np.zeros((len(ts), len(xs)))
    print("网格节点数（时间层，空间层）:",U.shape)

    for x in xs:
        i = list(xs).index(x)
        U[0][i] = u0(i)
    for x in xs:
        i = list(xs).index(x)
        U[1][i] = 0.5 * r**2 * (u0(i-1) + u0(i+1)) + (1 - r**2) * u0(i) + tao * ut0(i)
    for k in ts[1:-1]:
        ki = list(ts).index(k)
        for j in xs[1:-1]:
            ji = list(xs).index(j)
            U[ki+1][ji] = r**2 * (U[ki][ji-1] + U[ki][ji+1]) + 2*(1-r**2)*U[ki][ji] - U[ki-1][ji]

    #print(U)

    UTrue = np.zeros((len(ts), len(xs)))
    for x in xs:
        for t in ts:
            UTrue[list(ts).index(t)][list(xs).index(x)] = true(x, t, pro=pro)
    #print(UTrue)

    print("误差：", np.linalg.norm(U - UTrue))

'''
print("r > 1, 不稳定：")
ex3(0.1, 0.2, pro=1)
ex3(0.05, 0.1, pro=1)
ex3(0.005, 0.01, pro=1)

print("r = 1")
ex3(0.2, 0.2, pro=1)
ex3(0.1, 0.1, pro=1)
ex3(0.05, 0.05, pro=1)
ex3(0.01, 0.01, pro=1)

print("r < 1, 稳定：")
ex3(0.2, 0.1, pro=1)
ex3(0.1, 0.05, pro=1)
ex3(0.01, 0.005, pro=1)
'''
a2 = 16*math.pi**2
print("r > 1, 不稳定：")
ex3(0.0012, 0.2, pro=2)
ex3(0.00063, 0.1, pro=2)
#ex3(0.005, 0.01, pro=2)

print("r < 1, 稳定：")
ex3(0.5, 0.5, pro=2)
ex3(0.25, 0.25, pro=2)
#ex3(2/a2, 1, pro=2)


#1.解一元二次方程
import math
a = float(input('a='))
b = float(input('b='))
c = float(input('c='))
root = pow(b , 2) - 4 * a * c
if root > 0:
    r1 = (-b + math.sqrt(root)) / (2 * a)
    r2 = (-b - math.sqrt(root)) / (2 *a)
    print(r1 ,r2)
elif root == 0:
    r1 = -b / 2 * a
    print(r1)
else:
    print('The equation has no real roots')

#2.学习加法
import random

num1 = random.randint(0 , 100)
num2 = random.randint(0 , 100)
print(num1 , num2)
sum_ = int(input('亲，请输入这两个整数的和：'))
if num1 + num2 == sum_:
    print('答案正确，你真棒！')
else:
    print('答案错误，再试一次吧！')

#3.找未来数据
def week(day,fut):
    days = ['Sunday','Monday','Tuesday','Wendnesday','Thursday','Friday','Saturday']      #创建一个列表
    a = days[day]
    b = days[(day + fut) % 7]
    print("Today is %s and the future day is %s"%(a,b) )

def Start():
    day = int(input("Enter today's day:"))
    fut = int(input("Enter the number of days elapsed since today:"))
    week(day,fut)
Start()

#4.对三个整数排序
a = int(input("请输入第一个整数："))
b = int(input("请输入第二个整数："))
c = int(input("请输入第三个整数："))
d = [a,b,c]
d.sort()     #升序sort()
print(d)

#5.比较价钱
money1 = float(input("请输入第一种大米的价钱："))
weight1 = float(input("请输入第一种大米的重量："))
money2 = float(input("请输入第二种大米的价钱："))
weight2 = float(input("请输入第二种大米的重量："))
per1 = money1 / weight1
per2 = money2 / weight2
if per1 > per2:
    print('第二种包装价格更好')
elif per1 < per2:
    print('第一种包装价格更好')
else:
    print('两种大米价格相同')

#6.找出一个月中的天数
year = int(input('请输入年份：'))
month = int(input('请输入月份：'))
list1 = [1,3,5,7,8,10,12]
list2 = [4,6,9,11]
if month in list1:
    print('%d年%d月份有31天'%(year,month))
elif month in list2:
    print('%d年%d月份有30天'%(year,month))
else:
    if year % 400 == 0 and year % 4 ==0:
        print('%d年%d月份有29天'%(year,month))
    else:
        print('%d年%d月份有28天'%(year,month))

#7.头或尾
import numpy as np
res = np.random.choice(['正面','反面'])
print(res)
guess = input("请输入'正面','反面':")
if res == guess:
    print('恭喜你，答对啦！')
else:
    print('很遗憾，答错了!')

#8.剪刀石头布
import random
computer = random.randint(1,3)
print(computer)
user = int(input("请输入[1.石头 2.剪刀 3.布]"))
if computer != user:
    if  computer == 1  and user == 2:
        print('你输了，再来一局')
    elif computer == 2 and user == 3:
        print('你输了，再来一局')
    elif computer == 3 and user == 1:
        print('你输了，再来一局')
    else:
        print('你赢了，太棒了')
else :
    print('平局，再来一局')

#9.一周的星期几
year = int(input('请输入年份(eg.2008):'))
month = int(input('请输入月份(1-12):'))
days = int(input('请输入月份的第几天(1-31):'))
week = ['Saturday','Sunday','Monday','Tuesday','Wendnesday','Thursday','Friday']
if month == 1:
    month = 13
    year = year - 1
if month ==2:
    month = 14
    year = year - 1
h = int(days+((26*(month+1))//10)+(year%100)+((year%100)/4)+((year//100)/4)+5*year//100)%7
day = week[h]
print('Day of the week is %s'%day)

#10.选出一张牌
import numpy as np
card = np.random.choice(['Ace','2','3','4','5','6','7','8','9','10','Jack','Queen','King'])
color = np.random.choice(['梅花','红桃','方块','黑桃'])
print('The card you picked is the %s of %s'%(card,color))

#11.回文数
num = int(input('亲，请输入一个三位数：'))
a = num % 10 *100 + num // 10 % 10 *10 + num // 100
if num == a:
    print('%d是回文数'%num)
else:
    print('%d不是回文数'%num)

#或者
num = input('输入一个三位数：')
a = num[2]
b = num[0]
if a == b :
    print('%r是回文数'%num)
else:
    print('%r不是回文数'%num)

#12.计算三角形的周长
a = int(input("请输入三角形的边长:"))
b = int(input("请输入三角形的边长:"))
c = int(input("请输入三角形的边长:"))
if a < b + c and b < a + c and c < b + a:
    sum = a + b + c
    print("三角形的周长是：%d"%sum)
else:
    print('亲，您输入的边长是不合法的哦！')

#13.统计正数和负数的个数然后计算这些数的平均值
positive_number = 0
negative_number = 0
sum_number = 0
for i in range(100):
    n = int(input('请输入整数:'))
    if n != 0:
        if n > 0:
            positive_number += 1
        else:
            negative_number += 1
    else:
        break
    sum_number += n
average_number = sum_number / (positive_number + negative_number)
print('您输入的正数有%d个，负数有%d个，平均值是%.2f'%(positive_number,negative_number,average_number))

#14.计算未来学费
money = 10000
ten_money = 0
for i in range(13):
    money = money * (1 + 0.05)
    if i == 9:
        print("十年后的学费是:%d"%money)
    if i >= 9:
        ten_money += money
print("十年后大学四年的总学费:%d"%ten_money)

#15.找出可以被5和6同时整除的数
count = 0
for i in range(100,700):
    if i % 5 == 0 and i % 6 == 0:
        print(i,end = " ")
        count += 1
        if count % 10 == 0:
            print()

#16.找出最小的n满足n²>12000；找出最大的n满足n³<12000；
n = 1
while n ** 2 < 12000:
    n += 1
print(n)

n_ = 1
while n_ ** 3 < 12000:
    n_ += 1
print(n_-1)

#17.演示消除错误（1+1/2+1/3+.....+1/n）
a = 0
for i in range(1,5001):
    a += 1 / i
print('从左到右的和为：%r'%a)

b = 0
for i in range(5000,0,-1):
    b += 1 / i
print('从右到左的和为：%r'%b)

#18.数列求和(1/3+3/5+5/7+9/11+11/13+....95/97+97/99)

sum = 0
for i in range(1,99,2):
    j = i + 2
    sum += i / j
print("数列的和是:%f"%sum)

#19.计算∏=4（1-1/3+1/5-1/7+1/9-1/11+.....+(-1)**(i+1)/(2i-1)）

pi = 0
for i in range(1,100000):
    pi += 4 * ((-1) ** (i + 1) / (2 * i - 1))
print('pi = %r '%pi)

#20.完全数
for i in range(1,10000):     #i是在1~10000里的数
    a = 0    #除了本身的和
    for j in range(1,i):
        if i % j == 0:    #j是i的本身
            a += j
    if a == i:
        print(a,end = (" "))

#21.组合(7*6/2=21,)
count = 0
for i in range(1,8,2):
    for j in range(2,8):
        if i != j :
            count += 1
            print(i,j,end = "\n")
print("组合共有%d个数"%count)


from ._integrand import Integrand
from ..discrete_distribution import DigitalNetB2
from ..true_measure import Uniform
from numpy import *

class BoxIntegral(Integrand):
    """
    $B_s(x) = \\left(\\sum_{j=1}^d x_j^2 \\right)^{s/2}$

    >>> l1 = BoxIntegral(DigitalNetB2(2,seed=7), s=[7])
    >>> x1 = l1.discrete_distrib.gen_samples(2**10)
    >>> y1 = l1.f(x1)
    >>> y1.shape
    (1024, 1)
    >>> y1.mean(0)
    array([0.75156724])
    >>> l2 = BoxIntegral(DigitalNetB2(5,seed=7), s=[-7,7])
    >>> x2 = l2.discrete_distrib.gen_samples(2**10)
    >>> y2 = l2.f(x2,compute_flags=[1,1])
    >>> y2.shape
    (1024, 2)
    >>> y2.mean(0)
    array([ 6.67548708, 10.52267786])

    References:

    [1] D.H. Bailey, J.M. Borwein, R.E. Crandall,Box integrals,
    Journal of Computational and Applied Mathematics, Volume 206, Issue 1, 2007, Pages 196-208, ISSN 0377-0427,
    https://doi.org/10.1016/j.cam.2006.06.010. (https://www.sciencedirect.com/science/article/pii/S0377042706004250)

    [2] https://www.davidhbailey.com/dhbpapers/boxintegrals.pdf
    """

    def __init__(self, sampler, s=array([1,2])):
        """
        Args:
            sampler (DiscreteDistribution/TrueMeasure): A
                discrete distribution from which to transform samples or a
                true measure by which to compose a transform
            s (list or ndarray): vectorized s parameter, len(s) is the number of vectorized integrals to evalute.
        """
        self.parameters = ['s']
        self.s = array([s]) if isscalar(s) else array(s)
        self.dprime = len(self.s)
        self.sampler = sampler
        self.true_measure = Uniform(self.sampler, lower_bound=0., upper_bound=1.)
        super(BoxIntegral,self).__init__() # output dimensions per sample

    def g(self, t, **kwargs):
        compute_flags = kwargs['compute_flags'] if 'compute_flags' in kwargs else ones(self.dprime,dtype=int)
        n,d = t.shape
        Y = zeros((n,self.dprime),dtype=float)
        for j in range(self.dprime):
            if compute_flags[j] == 1:
                Y[:,j] = (t**2).sum(1)**(self.s[j]/2)
        return Y

    def _spawn(self, level, sampler):
        return BoxIntegral(sampler=sampler,s=self.s)


"""
Inferring a binomial proportion via grid approximation.
"""
import matplotlib.pyplot as plt
plt.style.use('seaborn-darkgrid')
import numpy as np
from hpd import hpd

def bern_grid(theta, p_theta, data, credib=.95):
    """
    Bayesian updating for Bernoulli likelihood and prior specified on a grid.
    Input arguments:
     theta is a vector of theta values, all between 0 and 1.
     p_theta is a vector of corresponding probability _masses_.
     data is a vector of 1's and 0's, where 1 corresponds to a and 0 to b.
     credib is the probability mass of the credible interval, default is 0.95.
    Output:
     p_theta_given_data is a vector of posterior probability masses over theta.
     Also creates a three-panel graph of prior, likelihood, and posterior
     probability masses with credible interval.
    Example of use:
     Create vector of theta values.
     bin_width = 1/1000
     theta_grid = np.arange(0, 1+bin_width, bin_width)
     Specify probability mass at each theta value.
     > rel_prob = np.minimum(theta_grid, 1-theta_grid) relative prob at each theta
     > prior = rel_prob / sum(rel_prob) probability mass at each theta
     Specify the data vector.
     data_vec = np.repeat([1, 0], [11, 3])  # 3 heads, 1 tail
     Call the function.
     > posterior = bern_grid( theta=theta_grid , p_theta=prior , data=data_vec )
    """

# Create summary values of data
    z = sum(data[data == 1])  # number of 1's in data
    N = len(data)  # number of flips in data
# Compute the likelihood of the data for each value of theta.
    p_data_given_theta = theta**z * (1 - theta)**(N - z)
# Compute the evidence and the posterior.
    p_data = sum(p_data_given_theta * p_theta)
    p_theta_given_data = p_data_given_theta * p_theta / p_data
    # Determine the limits of the highest density interval
    x = np.random.choice(theta, size=5000, replace=True, p=p_theta_given_data)
    intervals = hpd(x, alpha=1-credib)

# Plot the results.
    plt.figure(figsize=(12, 12))
    plt.subplots_adjust(hspace=0.7)

#    # Plot the prior.
    locx = 0.05
    mean_theta = sum(theta * p_theta)  # mean of prior, for plotting
    plt.subplot(3, 1, 1)
    plt.plot(theta, p_theta)
    plt.xlim(0, 1)
    plt.ylim(0, np.max(p_theta)*1.2)
    plt.xlabel(r'$\theta$')
    plt.ylabel(r'$P(\theta)$')
    plt.title('Prior')
    plt.text(locx, np.max(p_theta)/2, r'mean($\theta$;%5.2f)' % mean_theta)
    # Plot the likelihood:
    plt.subplot(3, 1, 2)
    plt.plot(theta, p_data_given_theta)
    plt.xlim(0, 1)
    plt.ylim(0, np.max(p_data_given_theta)*1.2)
    plt.xlabel(r'$\theta$')
    plt.ylabel(r'$P(D|\theta)$')
    plt.title('Likelihood')
    plt.text(locx, np.max(p_data_given_theta)/2, 'data: z=%s, N=%s' % (z, N))
    # Plot the posterior:
    mean_theta_given_data = sum(theta * p_theta_given_data)
    plt.subplot(3, 1, 3)
    plt.plot(theta, p_theta_given_data)
    plt.xlim(0, 1)
    plt.ylim(0, np.max(p_theta_given_data)*1.2)
    plt.xlabel(r'$\theta$')
    plt.ylabel(r'$P(\theta|D)$')
    plt.title('Posterior')
    loc = np.linspace(0, np.max(p_theta_given_data), 5)
    plt.text(locx, loc[1], r'mean($\theta$;%5.2f)' % mean_theta_given_data)
    plt.text(locx, loc[2], 'P(D) = %g' % p_data)
    # Plot the HDI
    plt.text(locx, loc[3],
             'Intervals =%s' % ', '.join('%.3f' % x for x in intervals))
    for i in range(0, len(intervals), 2):
        plt.fill_between(theta, 0, p_theta_given_data,
                         where=np.logical_and(theta > intervals[i],
                                              theta < intervals[i+1]),
                         color='blue', alpha=0.3)
    plt.savefig('Figure_6.1.png')
    plt.show()
    return p_theta_given_data

###Create vector of theta values.
bin_width = 1/1000.
theta_grid = np.arange(0, 1+bin_width, bin_width)
##Specify probability mass at each theta value.
rel_prob = np.array([0.1] * len(theta_grid))  # uniform prior
rel_prob = np.array([0.1] * len(theta_grid))  # uniform prior
prior = rel_prob / sum(rel_prob)  # probability mass at each theta

#### figure 6.2 ###
#np.random.seed(123)
#a = [0.1] * 50
#b = np.linspace(0.1, 1, 50)
#c = np.linspace(1, 0.1, 50)
#d = [0.1] * 50
#p_theta = np.concatenate((a, b, c, d))
#prior = np.where(p_theta != 0 , p_theta / sum(p_theta), 0.)
#width = 1. / len(p_theta)
#theta_grid = np.arange(width/2 , (1-width/2)+width, width)

### figure 6.3 ###
#np.random.seed(123)
#a = np.repeat([0], [50])
#b = np.linspace(0, 1, 50)
#c = (np.linspace(1, 0, 20))**2
#d = np.random.uniform(size=3)
#e = np.repeat([1], [20])
#p_theta = np.concatenate((a, b, c, d, e))
#prior = np.where(p_theta != 0 , p_theta / sum(p_theta), 0.)
#width = 1. / len(p_theta)
#theta_grid = np.arange(width/2 , (1-width/2)+width, width)

###Specify the data vector.
data_vec = np.repeat([1, 0], [11, 3])  # 3 heads, 1 tail
###Call the function.
posterior = bern_grid(theta=theta_grid, p_theta=prior, data=data_vec)


# test_10.py
''' FUNCIONES
Ejemplo para el curso de métodos numéricos
por Ing. Giancarlo Ortiz '''
# Instalar módulos
''' Las funciones en Python se definen con la palabra clave def, seguida del nombre de la función.
Sus parámetros se escriben entre parentesis y pueden incluir valores por defecto.
Otra forma de escribir funciones, aunque menos utilizada, es con la palabra clave lambda.
El valor devuelto en las funciones con def será el dado con la instrucción return. '''

# DECLARACIÓN:
# Importar módulos que requieren instalación
import numpy as np
from numpy import ndarray
from numpy.core.records import array

# Definiciones de nuevas funciones con la palabra clave Def
def _menor(A: ndarray, i: int, j: int) -> ndarray:
    ''' Define el menor de la matriz A como Ă(i, j).

        ## Parámetros:
            A (array): una matriz.
            i (int): el primer indice.
            j (int): el segundo indice.

        ## Devoluciones:
            B (array): retorna el menor.
    '''
    B = np.delete(A, i-1, axis=0)
    C = np.delete(B, j-1, axis=1)
    return C

def _cofactor(A: ndarray, i: int, j: int) -> float:
    ''' Define el cofactor A(i, j) de una matriz A dada.

        ## Parámetros:
            A (array): una matriz.
            i (int): el primer indice.
            j (int): el segundo indice.

        ## Devoluciones:
            B (float): retorna el cofactor.
    '''
    B = _menor(A, i, j)
    C = pow(-1, i+j) * round(np.linalg.det(B), 3)
    return C

def _matriz_de_cofactores(A: ndarray):
    ''' Define la matriz de cofactores, que se obtiene de sustituir cada termino de A(i,j) por el C(i,j).

        ## Parámetros:
            A (array): una matriz.

        ## Devoluciones:
            B (array): la matriz de cofactores.
    '''
    filas = A.shape[0]
    columnas = A.shape[1]
    B = np.zeros_like(A)
    for i in range(filas):
        for j in range(columnas):
            B[i, j] = _cofactor(A, i+1, j+1)
    return B

def _matriz_adjunta(A: ndarray):
    ''' Define la matriz de adjunta, que se obtiene de la transpuesta de la matriz de cofactores.

        ## Parámetros:
            A (array): una matriz.

        ## Devoluciones:
            B (array): la matriz adjunta.
    '''
    B = np.transpose(_matriz_de_cofactores(A))
    return B

def _determinante(A: list) -> float:
    ''' Define el determinante de una matriz A dada.

        ## Parámetros:
            A (array): una matriz.

        ## Devoluciones:
            B (float): el determinante de la matriz.
    '''
    filas = A.shape[0]
    columnas = A.shape[1]
    if (filas != columnas):
        return f"ERROR: Determinante no esta definido para matrices {filas}x{columnas}"
    if (filas == 1):
        return A[0][0]
    sum = 0
    for i in range(filas):
        C = pow(-1, i+2) * A[i][0]
        M = _menor(A, i+1, 1)
        D = C * _determinante(M)
        sum = D + sum
    return sum

# Definiciones de nuevas funciones con la palabra clave Lambda
cof = lambda A: _matriz_de_cofactores(A)
adj = lambda A: _matriz_adjunta(A)
det = lambda A: _determinante(A)
inv = lambda A: (1/det(A))*adj(A)

# Asignación y llamado a funciones declaradas en el script
def _calcular_matrices(A):
    ''' Imprimir en pantalla una demostración de la funcionalidad.
    '''
    # funciones definidas en el script
    Matriz_Cofactores = cof(A)
    Matriz_Adjunta = adj(A)
    Matriz_Inversa = inv(A)
    return Matriz_Cofactores, Matriz_Adjunta, Matriz_Inversa

# Función que no retorna ningún valor
def demo(Array):
    ''' Imprimir en pantalla una demostración de la funcionalidad.
    '''
    M13 = _menor(Array, 1, 3)
    C13 = _cofactor(Array, 1, 3)

    # Asignación multiple usando una función que retorna múltiples valores
    Cof, Adj, Inv = _calcular_matrices(Array)

    # función incorporada en NumPy para comparar
    Inv_np = np.linalg.inv(Array)

    # Salida Estándar
    print(f"--------------------------------------------------------")
    print(f"Dada una matriz de entrada A en R³:")
    print(f">>>")
    print(f"\n{Array}\n")
    print(f"--------------------------------------------------------")
    print(f"Se tiene que cada matriz  Ă(i,j) se define como el menor")
    print(f"que resulta  de eliminar  la i-ésima fila  y  la j-ésima")
    print(f"columna, por ejemplo A₁₃ es:")
    print(f">>>")
    print(f"\n{M13}\n")
    print(f"--------------------------------------------------------")
    print(f"El cofactor se define como C(i,j) =(-1)²det(Ă(i,j)), por")
    print(f"ejemplo C₁₃ es igual a {C13};  finalmente se tiene que la")
    print(f"matriz de  cofactores de A en R³ definida como Cof(A) es")
    print(f"aquella  que resulta de  reemplazar cada elemento por su")
    print(f"cofactor:")
    print(f">>>")
    print(f"\n{Cof}\n")
    print(f"--------------------------------------------------------")
    print(f"Y la matriz adjunta  definida  como la transpuesta de la")
    print(f"matriz de cofactores es Adj(A) =trs(Cof(A)) que resulta:")
    print(f">>>")
    print(f"\n{Adj}\n")
    print(f"--------------------------------------------------------")
    print(f"Finalmente dado que inv(A) = (1/det(A)).adj(A) se  tiene")
    print(f"que la inversa definida en el script y la de NumPy son:")
    print(f">>>")
    print(f"\n{Inv}\n")
    print(f">>>")
    print(f"\n{Inv_np}\n")

# EJECUCIÓN:
# Definición de un vector de prueba en R³ usando un tipo de dato de NumPy
Array_de_prueba = np.array([[1, 1, 1], [-1, 2, -3], [3, 0, 2]])
demo(Array_de_prueba)


import numpy as np

def spherical_to_cartesian(inclination_radians: np.ndarray, azimuth_radians: np.ndarray, radius: np.ndarray = None):
    """convert spherical to 3D cartesian coordinates

    spherical coordinates have to be specified as separate ndarrays of inclination angle (0 radians in zenith, pi
    radians in nadir) and optionally a radial coordinate (which is otherwise implicitly assumed to be 1 for unit
    vectors);

    input array dimensions are arbitrary but must match between the different arrays;
    returns tuple of cartesian coordinates in separate x, y and z arrays"""

    if radius is None:
        radius = 1

    sin_theta = np.sin(inclination_radians)
    x = radius * sin_theta * np.cos(azimuth_radians)
    y = radius * sin_theta * np.sin(azimuth_radians)
    z = radius * np.cos(inclination_radians)
    return x, y, z

def cartesian_to_spherical(x: np.ndarray, y: np.ndarray, z: np.ndarray, normalized: bool = False, with_radius: bool = False):
    """convert (optionally normalized) 3D cartesian to spherical coordinates

    spherical coordinates follow the inclination-azimuth convention: inclination angle theta goes from 0 radians in the
    zenith (north pole) to pi radians at nadir (south pole), azimuth is circular from 0 to 2 pi radians, with 0 in x
    direction

    inputs are numpy ndarrays with arbitrary but matching dimensions; if normalized == True, inputs are assumed to be of
    unit length; with_radius determines whether only inclination and azimuth, or if also the radial coordinate should be
    returned in case the inputs are not normalized
    returns spherical coordinates in separate arrays: inclination, azimuth [and radius, when with_radius == True]
    """

    if not normalized:
        radius = np.sqrt(x**2 + y**2 + z**2)
        inv_radius = 1 / radius
        x *= inv_radius
        y *= inv_radius
        z *= inv_radius
    else:
        radius = np.ones_like(x)

    inclination = np.arccos(z)
    azimuth = np.arctan2(y, x)

    if with_radius:
        return inclination, azimuth, radius
    else:
        return inclination, azimuth

def cartesian_to_plane_stereographic(x: np.ndarray, y: np.ndarray, z: np.ndarray):
    """convert 3D cartesian vectors on the unit sphere to 2D coordinates by transforming as:

    x_2d = x / (1 + z)
    y_2dr = y / (1 + z)

    this corresponds to a stereographic projection from the lower pole (nadir), which maps the upper hemisphere (z > 0)
    inside the unit circle in the x-y-plane

    inputs numpy ndarrays with arbitrary but matching dimensions;
    returns tuple x_par, y_par with same dimensions as inputs"""

    inv_denom = 1. / (1 + z)
    return x * inv_denom, y * inv_denom

def plane_to_cartesian_stereographic(x_2d: np.ndarray, y_2d: np.ndarray):
    """transform 2D coordinates from the x-y-plane to 3D cartesian coordinates on the unit sphere by means of a
    stereographic projection from the lower pole, which maps 2D points from within the x-y unit circle to the upper
    hemisphere

    inputs: x_2d, y_2d: np.ndarrays with arbitrary but matching dimensions
    returns 3-tuple of np.ndarrays x, y, z, where each array has the same shape as the inputs"""

    len_sqr = x_2d ** 2 + y_2d ** 2
    x = 2 * x_2d / (len_sqr + 1)
    y = 2 * y_2d / (len_sqr + 1)
    z = (1 - len_sqr) / (1 + len_sqr)

    return x, y, z


import numpy as np

######################################################################################################
# Scalars 																							 #
######################################################################################################
#
# Scalars in NumPy are a bit more involved than in Python.
# Instead of Python’s basic types like int, float, etc., NumPy lets you specify signed and unsigned types, as well as
# different sizes. So instead of Python’s int, you have access to types like uint8, int8, uint16, int16, and so on.

scalar = np.array(5)
print('Scalar = ', scalar, 'shape: ', scalar.shape)

scalarSum = scalar + 11
print('Scalar sum: ', scalar, ' + 11 = ', scalarSum)

######################################################################################################
# Vectors 																							 #
######################################################################################################
#
# To create a vector, you'd pass a Python list to the array function, like this:

vector = np.array([1, 2, 3])
print('Vector: ', vector, 'shape: ', vector.shape)

# Access an element within the vector using indices, like this:
print('vector[1] = ', vector[1])

# NumPy also supports advanced indexing techniques.
# For example, to access the items from the second element onward, you would say:
print(vector[1:])

######################################################################################################
# Matrices 																							 #
######################################################################################################
#
# You create matrices using NumPy's array function, just you did for vectors.
# However, instead of just passing in a list, you need to supply a list of lists, where each list represents a row.
# So to create a 3x3 matrix containing the numbers one through nine, you could do this:

matrix = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print('Matrix: \n', matrix)
print('Matrix shape: ', matrix.shape)
print('matrix[1][2] = ', matrix[1][2])

######################################################################################################
# Tensors 																							 #
######################################################################################################
#
# Tensors are just like vectors and matrices, but they can have more dimensions.
# For example, to create a 3x3x2x1 tensor, you could do the following:

tensor = np.array([[[[1],[2]],[[3],[4]],[[5],[6]]],[[[7],[8]],\
    [[9],[10]],[[11],[12]]],[[[13],[14]],[[15],[16]],[[17],[18]]]])

print('Tensor: \n', tensor)
print('Tensor shape: ', tensor.shape)

# Access items just like with matrices, but with more indices. So t[2][1][1][0] will return 16.
print('tensor[2][1][1][0]', tensor[2][1][1][0])

######################################################################################################
# Element-wise operations																			 #
######################################################################################################
# The Python way
# Suppose you had a list of numbers, and you wanted to add 5 to every item in the list.
# Without NumPy, you might do something like this:

p_values = [1, 2, 3, 4, 5]
for i in range(len(p_values)):
    p_values[i] += 5

# now values holds [6, 7, 8, 9, 10]

# The NumPy way
# In NumPy, we could do the following:

np_values = [1, 2, 3, 4, 5]
np_values = np.array(np_values) + 5

print('[1, 2, 3, 4, 5] + 5 == ', np_values)

# now values is an ndarray that holds [6, 7, 8, 9, 10]

# We should point out, NumPy actually has functions for things like adding, multiplying, etc.
# But it also supports using the standard math operators. So the following two lines are equivalent:

some_array = np.array([1, 2, 3, 4, 5])
np_mult_1 = np.multiply(some_array, 5)
np_mult_2 = some_array * 5

print('[1, 2, 3, 4, 5] * 5 == ', np_mult_2)

# Init with zeros:
zero_matrix = np_mult_2 * 0

# now every element in m is zero, no matter how many dimensions it has
print(np_mult_2, '* 0 == ', zero_matrix)

######################################################################################################
# Element-wise Matrix Operations 																	 #
######################################################################################################
#
#The same functions and operators that work with scalars and matrices also work with other dimensions.
# You just need to make sure that the items you perform the operation on have compatible shapes.
#
# Let's say you want to get the squared values of a matrix.
# That's simply x = m * m (or if you want to assign the value back to m, it's just m *= m
#
# This works because it's an element-wise multiplication between two identically-shaped matrices.
# (In this case, they are shaped the same because they are actually the same object.)
#
# Here's the example:

a = np.array([[1, 3], [5, 7]])
print('A:\n', a)
# displays the following result:
# array([[1, 3],
#        [5, 7]])

b = np.array([[2, 4], [6, 8]])
print('B:\n', b)
# displays the following result:
# array([[2, 4],
#        [6, 8]])

print('A + B:\n', a + b)
# displays the following result
#      array([[ 3,  7],
#             [11, 15]])


# -*- coding: utf-8 -*-
from __future__ import print_function, division
# Import Library of Numpy
import numpy as np

# Version of Numpy
np.__version__

#NumPy is an N-dimensional array type called ndarray
#NumPy rank 1 array
x1 = np.array([1, 2, 3, 4,5])
print ('x1 =', x1)
print ('------------------------------')
print ('type (x1) =', type (x1))
print ('------------------------------')
print ('x1.shape =', x1.shape)
print ('------------------------------')
print ('x1.size =', x1.size) # number of elements
print ('------------------------------')
print (x1[0],',',x1[1],',',x1[2],',',x1[3]) #Prints "1 2 3 4"
print ('------------------------------')
x1[3] = 0
print (x1) # Change an element
#A data type object (an instance of numpy.dtype class) describes how the bytes in the fixed-size block of memory corresponding to an array item should be interpreted.
'''#int8 = Byte (-128 to 127)
#int16 = Integer (-32768 to 32767)
#int32 = Integer (-2147483648 to +2147483647)
#int64 = Integer (-9223372036854775808 to +9223372036854775807)
#Boolean = (True or False) stored as a byte
#float = Shorthand for float64 : Double precision float: sign bit, 11 bits exponent, 52 bits mantissa
#complex = Shorthand for complex128 : Complex number
#int8, int16, int32, int64 can be replaced by equivalent string 'i1', 'i2','i4', etc. '''

x1a = np.array([1, 2, 3, 4] , dtype='float') #Desired data type of array, optional
#x1a = np.array([1, 2, 3, 4] , dtype='i1')
#x1a = np.array([1, 2, 3, 4] , dtype= complex)
print ('x1a =', x1a)
print ('------------------------------')
print ('type (x1a) =', type (x1a))
print ('------------------------------')
print ('x1a.dtype =', x1a.dtype)
print ('------------------------------')
print ('x1a.itemsize =', x1a.itemsize) # 8 byte
print ('------------------------------')
print ('x1a.size =', x1a.size)
print ('------------------------------')
print ('x1a.shape =', x1a.shape)
print ('------------------------------')
x1b = np.array([[.25, 2, 3, 4],[5, 6, 7, 8],[9, 10, 11, 12],[13, 14, 15, 16]])
print (x1b.dtype) #dtype is float
print ('------------------------------')
print ('x1b.shape =', x1b.shape) #(4,4)
print ('------------------------------')
x1ba = x1b [0:2,1:3]
print ('x1ba [0:2,1:3] : ', x1ba)
print ('------------------------------')
print ('x1b [0,2] : ', x1b [0,2])
print ('------------------------------')
x1b [0,2] = 17
print ('x1b = ', x1b)
print ('------------------------------')
print ('x1ba : ', x1ba) # has changed   3=>17
print ('------------------------------')
x1ba = +x1b [0:2,1:3] #x1ba dosen't depend on x1b
x1b [0,2] = 125
print ('------------------------------')
print ('x1b = ', x1b)
print ('------------------------------')
print ('x1ba : ', x1ba) # dosen't change
print ('------------------------------')
x1c = np.array([[.25, 2, 3, 4],[8],[9, 10, 11, 12],[13, 14, 15, 16]])
print ('x1c.shape =', x1c.shape) #(4,)
print ('------------------------------')
x1d = x1a.copy()
print('x1d =',x1d)
print ('------------------------------')
print ('x1d is x1a =', x1d is x1a) # False
print ('------------------------------')
print('x1d.ndim =',x1d.ndim) # dimension

# More than one dimension
#NumPy rank 2 array
x2 = np.array([[1,2,3,4],[4,3,2,1]])
print (x2)
print (type (x2))
print (x2.shape)
print (x2[0,0],',',x2[1,3])

#Return a array setting values to zero
x2a = np.zeros((4,2))
print ('x2a =',x2a)
x2b = np.zeros_like(x2)
print ('------------------------------')
print('x2b =',x2b)
print ('------------------------------')
x2b1 = np.zeros((2,4,2))
print('x2b1 =',x2b1)
print('x2b1.ndim = ',x2b1.ndim)
print ('------------------------------')
#Return a array setting values to one
x2c = np.ones((4,3))
print('x2c =',x2c)
print ('------------------------------')
x2c = np.ones((4,3), dtype='i1')
print('x2c =',x2c)
print ('------------------------------')
x2d = (4,3)
x2e = np.ones(x2d, dtype='int')
print('x2e =',x2e)
print ('------------------------------')

#numpy.full => Return a new array of given shape and type, filled with fill_value.
x2f = np.full((3,3),0)
print('x2f =',x2f)
print ('------------------------------')
x2g = np.full((2,2),1)
print('x2g =',x2g)
print ('------------------------------')
x2h = np.full((7,),6)
print('x2h =',x2h)
print ('------------------------------')
x2i = np.full((5,2),'test')
print('x2i =',x2i)
print ('------------------------------')

#numpy.eye => Return a 2-D array with ones on the diagonal and zeros elsewhere
x2j = np.eye(4)
print('x2j =',x2j)
print ('------------------------------')
x2k = np.eye(2, dtype=int)
print('x2k =',x2k)
print ('------------------------------')
x2l = np.eye(5, k=2, dtype=int)   # k => Index of the diagonal : 0 (the default) refers to the main diagonal
print('x2l =',x2l)
print ('------------------------------')

# array filled with random values
x2m = np.random.random((3,2))
print('x2m =',x2m)
print ('------------------------------')
#Create an array of the given shape and populate it with random samples from a uniform distribution over
x2n = np.random.rand(2,5) # 2,5 are the dimensions of the returned array
print('x2n =',x2n)
print ('------------------------------')
#uniform([low, high, size]) 	Draw samples from a uniform distribution
x2o = np.random.uniform(2,7,(2,2))
print('x2o =',x2o)
print ('------------------------------')
#standard_normal([size]) 	Draw samples from a standard Normal distribution (mean=0, stdev=1)
x2p = np.random.standard_normal((2,2))
print('x2p =',x2p)
print ('------------------------------')

#numpy.arange(start,stop,step, dtype=None)
#Return evenly spaced values within a given interval.
x3 = np.arange(7)
print ('x3 =',x3)
print ('------------------------------')
x3a = np.arange(7.0)
print ('x3a =',x3a)
print ('------------------------------')
x3b = np.arange(4.5)
print ('x3b =',x3b)
print ('------------------------------')
x3c = np.arange(4,19)
print ('x3c =',x3c)
print ('------------------------------')
x3d = np.arange(4,19,3)   # 3 is step
print ('x3d =',x3d)
print ('------------------------------')
x3e = np.arange(4,19,3, dtype=float)
print ('x3e =',x3e)
print ('------------------------------')
x3f = np.arange(4,19,2.4)
print ('x3f =',x3f)
print ('------------------------------')
#numpy.linspace (start, stop, num) => Return evenly spaced numbers over a specified interval
#from numpy import pi
x3g = np.linspace(3.0, 9.0, num=9)
print ('x3g =',x3g)
print ('------------------------------')

# A matrix is a specialized 2-D array that retains its 2-D nature through operations
x3 = np.matrix([[1,2],[3,4]]) # Or x2 = np.matrix('1 2; 3 4')
print(x3)
print (type (x3))
print (x3.shape)
print (x3[1,1], x3[0,1], x3[0,0], x3[1,0])

#Computation on NumPy Arrays
#Dot product of two arrays. Specifically,
#   If both a and b are 1-D arrays, it is inner product of vectors (without complex conjugation).
#   If both a and b are 2-D arrays, it is matrix multiplication, but using matmul or a @ b is preferred.
#  If either a or b is 0-D (scalar), it is equivalent to multiply and using numpy.multiply(a, b) or a * b is preferred.
x4 = np.dot(7,2)
print("x4 = ",x4)
print ('------------------------------')
x5 = np.ones((2,3))
print("x5 = ",x5)
print ('------------------------------')
x6 = np.array([[2,5],[3,7],[9,6]])
x7 = np.dot(x5,x6)
print("x7 = ",x7)
print ('------------------------------')
x8 = x5 @ x6
print("x8 = ",x8)
print ('------------------------------')
x9 = np.array([[2,2],[4,4]])
x10 = np.multiply(x9,x9)
print('x10 = ',x9,'*',x9,' = ',x10)
print ('------------------------------')
print('x9 * x9 = ',x9*x9)
print ('------------------------------')
print('x9 @ x9 = ',x9@x9)
print ('------------------------------')
#numpy.prod => Return the product of array elements over a given axis
x11 = np.prod (x9)
print("x11 = ",x11)
print ('------------------------------')
#The product of an empty array is the neutral element 1
x12 = np.prod([] , dtype='i1')
print("x12 = ",x12)
print ('------------------------------')
# we can also specify the axis over which to multiply
x13 = np.prod([[1, 2, 3],[4, 5, 6],[11, 10, 2]], axis=0)
print("x13 = ",x13)
print ('------------------------------')

#Broadcasting => The term broadcasting describes how numpy treats arrays with different shapes during arithmetic operations
x14 = np.array([1.0, 2.0, 3.0, 4.0])
x15 = x14 + 7
print("x15 = ",x15)
print ('------------------------------')
x16 = np.ones((4,4))
x17 = x14 + x16
print("x17 = ",x17)
print ('------------------------------')
x18 = np.ones((4,1))
x19 = x14 + x18
print("x19 = ",x19)
print ('------------------------------')

#numpy.sum => Sum of array elements over a given axis
x20 = np.sum([1.5, 2.5])
print("x20 = ",x20)
print ('------------------------------')
x21 = np.sum([[2,4],[6,8]])
print("x21 = ",x21)
print ('------------------------------')
x22 = np.sum([[2,4],[6,8]], axis=0)
print("x22 = ",x22)
print ('------------------------------')
x23 = np.sum([[2,4],[6,8]], axis=1)
print("x23 = ",x23)
print ('------------------------------')
#numpy.cumsum => Return the cumulative sum of the elements along a given axis
a = np.array([[1,2,3], [7,8,9]])
x24 = np.cumsum(a)
print("x24 = ",x24)
print ('------------------------------')
x25 = np.cumsum(a, axis=0)  # sum over rows for each of the 3 columns
print("x25 = ",x25)
print ('------------------------------')
x26 = np.cumsum(a, axis=1) # sum over columns for each of the 2 rows
print("x26 = ",x26)
print ('------------------------------')
#numpy.subtract
x27 = np.subtract(a,a)
print("x27 = ",x27)
print ('------------------------------')
#numpy.divide => Returns a true division of the inputs
x28 = np.divide(a,2)
print("x28 = ",x28)
print ('------------------------------')
x29 = np.floor_divide(a,2)
print("x29 = ",x29)
print ('------------------------------')
x30 = np.true_divide(a,2)
print("x30 = ",x30)
print ('------------------------------')

#numpy.math
'''
1- sin(x, /[, out, where, casting, order, ...]) 	Trigonometric sine, element-wise.
2- cos(x, /[, out, where, casting, order, ...]) 	Cosine element-wise.
3- tan(x, /[, out, where, casting, order, ...]) 	Compute tangent element-wise.
4- arcsin(x, /[, out, where, casting, order, ...]) 	Inverse sine, element-wise.
5- arccos(x, /[, out, where, casting, order, ...]) 	Trigonometric inverse cosine, element-wise.
6- arctan(x, /[, out, where, casting, order, ...]) 	Trigonometric inverse tangent, element-wise.
7- hypot(x1, x2, /[, out, where, casting, ...]) 	Given the “legs” of a right triangle, return its hypotenuse.
8- arctan2(x1, x2, /[, out, where, casting, ...]) 	Element-wise arc tangent of x1/x2 choosing the quadrant correctly.
9- degrees(x, /[, out, where, casting, order, ...]) 	Convert angles from radians to degrees.
10- radians(x, /[, out, where, casting, order, ...]) 	Convert angles from degrees to radians.
11- unwrap(p[, discont, axis]) 	Unwrap by changing deltas between values to 2*pi complement.
12- deg2rad(x, /[, out, where, casting, order, ...]) 	Convert angles from degrees to radians.
13- rad2deg(x, /[, out, where, casting, order, ...]) 	Convert angles from radians to degrees.
14- Hyperbolic functions
15- sinh(x, /[, out, where, casting, order, ...]) 	Hyperbolic sine, element-wise.
16- cosh(x, /[, out, where, casting, order, ...]) 	Hyperbolic cosine, element-wise.
17- tanh(x, /[, out, where, casting, order, ...]) 	Compute hyperbolic tangent element-wise.
18- arcsinh(x, /[, out, where, casting, order, ...]) 	Inverse hyperbolic sine element-wise.
19- arccosh(x, /[, out, where, casting, order, ...]) 	Inverse hyperbolic cosine, element-wise.
20- arctanh(x, /[, out, where, casting, order, ...]) 	Inverse hyperbolic tangent element-wise.
21- Rounding
22- around(a[, decimals, out]) 	Evenly round to the given number of decimals.
23- round_(a[, decimals, out]) 	Round an array to the given number of decimals.
24- rint(x, /[, out, where, casting, order, ...]) 	Round elements of the array to the nearest integer.
25- fix(x[, out]) 	Round to nearest integer towards zero.
26- floor(x, /[, out, where, casting, order, ...]) 	Return the floor of the input, element-wise.
27- ceil(x, /[, out, where, casting, order, ...]) 	Return the ceiling of the input, element-wise.
28- trunc(x, /[, out, where, casting, order, ...]) 	Return the truncated value of the input, element-wise.
29- Sums, products, differences
30- prod(a[, axis, dtype, out, keepdims]) 	Return the product of array elements over a given axis.
31- sum(a[, axis, dtype, out, keepdims]) 	Sum of array elements over a given axis.
32- nanprod(a[, axis, dtype, out, keepdims]) 	Return the product of array elements over a given axis treating Not a Numbers (NaNs) as ones.
33- nansum(a[, axis, dtype, out, keepdims]) 	Return the sum of array elements over a given axis treating Not a Numbers (NaNs) as zero.
34- cumprod(a[, axis, dtype, out]) 	Return the cumulative product of elements along a given axis.
35- cumsum(a[, axis, dtype, out]) 	Return the cumulative sum of the elements along a given axis.
36- nancumprod(a[, axis, dtype, out]) 	Return the cumulative product of array elements over a given axis treating Not a Numbers (NaNs) as one.
37- nancumsum(a[, axis, dtype, out]) 	Return the cumulative sum of array elements over a given axis treating Not a Numbers (NaNs) as zero.
38- diff(a[, n, axis]) 	Calculate the n-th discrete difference along given axis.
39- ediff1d(ary[, to_end, to_begin]) 	The differences between consecutive elements of an array.
40- gradient(f, *varargs, **kwargs) 	Return the gradient of an N-dimensional array.
41- cross(a, b[, axisa, axisb, axisc, axis]) 	Return the cross product of two (arrays of) vectors.
42- trapz(y[, x, dx, axis]) 	Integrate along the given axis using the composite trapezoidal rule.
43- Exponents and logarithms
44- exp(x, /[, out, where, casting, order, ...]) 	Calculate the exponential of all elements in the input array.
45- expm1(x, /[, out, where, casting, order, ...]) 	Calculate exp(x) - 1 for all elements in the array.
46- exp2(x, /[, out, where, casting, order, ...]) 	Calculate 2**p for all p in the input array.
47- log(x, /[, out, where, casting, order, ...]) 	Natural logarithm, element-wise.
48- log10(x, /[, out, where, casting, order, ...]) 	Return the base 10 logarithm of the input array, element-wise.
49- log2(x, /[, out, where, casting, order, ...]) 	Base-2 logarithm of x.
50- log1p(x, /[, out, where, casting, order, ...]) 	Return the natural logarithm of one plus the input array, element-wise.
51- logaddexp(x1, x2, /[, out, where, casting, ...]) 	Logarithm of the sum of exponentiations of the inputs.
52- logaddexp2(x1, x2, /[, out, where, casting, ...]) 	Logarithm of the sum of exponentiations of the inputs in base-2.
53- Other special functions
54- i0(x) 	Modified Bessel function of the first kind, order 0.
55- sinc(x) 	Return the sinc function.
56- Floating point routines
57- signbit(x, /[, out, where, casting, order, ...]) 	Returns element-wise True where signbit is set (less than zero).
58- copysign(x1, x2, /[, out, where, casting, ...]) 	Change the sign of x1 to that of x2, element-wise.
59- frexp(x[, out1, out2], / [[, out, where, ...]) 	Decompose the elements of x into mantissa and twos exponent.
60- ldexp(x1, x2, /[, out, where, casting, ...]) 	Returns x1 * 2**x2, element-wise.
61- nextafter(x1, x2, /[, out, where, casting, ...]) 	Return the next floating-point value after x1 towards x2, element-wise.
62- spacing(x, /[, out, where, casting, order, ...]) 	Return the distance between x and the nearest adjacent number.
63- Arithmetic operations
64- add(x1, x2, /[, out, where, casting, order, ...]) 	Add arguments element-wise.
65- reciprocal(x, /[, out, where, casting, ...]) 	Return the reciprocal of the argument, element-wise.
66- negative(x, /[, out, where, casting, order, ...]) 	Numerical negative, element-wise.
67- multiply(x1, x2, /[, out, where, casting, ...]) 	Multiply arguments element-wise.
68- divide(x1, x2, /[, out, where, casting, ...]) 	Divide arguments element-wise.
69- power(x1, x2, /[, out, where, casting, ...]) 	First array elements raised to powers from second array, element-wise.
70- subtract(x1, x2, /[, out, where, casting, ...]) 	Subtract arguments, element-wise.
71- true_divide(x1, x2, /[, out, where, ...]) 	Returns a true division of the inputs, element-wise.
72- floor_divide(x1, x2, /[, out, where, ...]) 	Return the largest integer smaller or equal to the division of the inputs.
73- float_power(x1, x2, /[, out, where, ...]) 	First array elements raised to powers from second array, element-wise.
74- fmod(x1, x2, /[, out, where, casting, ...]) 	Return the element-wise remainder of division.
75- mod(x1, x2, /[, out, where, casting, order, ...]) 	Return element-wise remainder of division.
76- modf(x[, out1, out2], / [[, out, where, ...]) 	Return the fractional and integral parts of an array, element-wise.
77- remainder(x1, x2, /[, out, where, casting, ...]) 	Return element-wise remainder of division.
78- divmod(x1, x2[, out1, out2], / [[, out, ...]) 	Return element-wise quotient and remainder simultaneously.
79- Handling complex numbers
80- angle(z[, deg]) 	Return the angle of the complex argument.
81- real(val) 	Return the real part of the complex argument.
82- imag(val) 	Return the imaginary part of the complex argument.
83- conj(x, /[, out, where, casting, order, ...]) 	Return the complex conjugate, element-wise.
84- Miscellaneous
85- convolve(a, v[, mode]) 	Returns the discrete, linear convolution of two one-dimensional sequences.
86- clip(a, a_min, a_max[, out]) 	Clip (limit) the values in an array.
87- sqrt(x, /[, out, where, casting, order, ...]) 	Return the positive square-root of an array, element-wise.
88- cbrt(x, /[, out, where, casting, order, ...]) 	Return the cube-root of an array, element-wise.
89- square(x, /[, out, where, casting, order, ...]) 	Return the element-wise square of the input.
90- absolute(x, /[, out, where, casting, order, ...]) 	Calculate the absolute value element-wise.
91- fabs(x, /[, out, where, casting, order, ...]) 	Compute the absolute values element-wise.
92- sign(x, /[, out, where, casting, order, ...]) 	Returns an element-wise indication of the sign of a number.
93- heaviside(x1, x2, /[, out, where, casting, ...]) 	Compute the Heaviside step function.
94- maximum(x1, x2, /[, out, where, casting, ...]) 	Element-wise maximum of array elements.
95- minimum(x1, x2, /[, out, where, casting, ...]) 	Element-wise minimum of array elements.
96- fmax(x1, x2, /[, out, where, casting, ...]) 	Element-wise maximum of array elements.
97- fmin(x1, x2, /[, out, where, casting, ...]) 	Element-wise minimum of array elements.
98- nan_to_num(x[, copy]) 	Replace nan with zero and inf with finite numbers.
99- real_if_close(a[, tol]) 	If complex input returns a real array if complex parts are close to zero.
100- interp(x, xp, fp[, left, right, period]) 	One-dimensional linear interpolation.'''
#numpy.reshape => Gives a new shape to an array without changing its data
x31 = np.arange(8).reshape(4,2) # 4*2 = 8 (number of elements)
print("x31 = ",x31)
print ('------------------------------')
x31a = np.arange(4).reshape(4) # reshape(4) => number of columns
print("x31a = ",x31a)
print ('------------------------------')
x32 = np.math.inf #IEEE 754 floating point representation of (positive) infinity.
print("x32 = ",x32)
print ('------------------------------')
#x33 = np.array([1]) / 0
#print("x33 = ",x33)
#print ('------------------------------')
x34 = np.math.nan #not a number
print("x34 = ",x34)
print ('------------------------------')

#Mask => Logic functions
'''
Logical operations:
logical_and(x1, x2, /[, out, where, ...]) 	Compute the truth value of x1 AND x2 element-wise.
logical_or(x1, x2, /[, out, where, casting, ...]) 	Compute the truth value of x1 OR x2 element-wise.
logical_not(x, /[, out, where, casting, ...]) 	Compute the truth value of NOT x element-wise.
logical_xor(x1, x2, /[, out, where, ...]) 	Compute the truth value of x1 XOR x2, element-wise.'''

x35 = np.arange(18).reshape(3,2,3) # 3*2*3 = 9 (number of elements)
print("x35 = ",x35)
print("x35.ndim = ",x35.ndim) # ndim = 3
print ('------------------------------')
x35a = np.arange(24).reshape(4,2,3)
print("x35a = ",x35a)
print("x35a.ndim = ",x35a.ndim) # ndim = 3
print ('------------------------------')
x35b = np.arange(40).reshape(2,5,4)
print("x35b = ",x35b)
print("x35b.ndim = ",x35b.ndim) # ndim = 3
print ('------------------------------')
x35c = np.arange(20).reshape(1,5,4)
print("x35c = ",x35c)
print("x35c.ndim = ",x35c.ndim) # ndim = 3
print ('------------------------------')
x36 = x35 < 4
print("x36 = ",x36)
print ('------------------------------')
x37 = x35 [x36]
print("x37 = ",x37)
print ('------------------------------')
x38 = np.logical_and(x35>=2 , x35<5)
print("x38 = ",x38)
print ('------------------------------')
x39 = x35 [x38]
print("x39 = ",x39)
print ('------------------------------')

#other
#NumPy.unique => Find the unique elements of an array
x40 = np.unique([1, 1, 2, 2, 3, 3,4,4,4,5])
print("x40 = ",x40)
print ('------------------------------')
x41 = np.unique(np.array(([[1, 1], [2, 2], [3,2], [4,5]])))
print("x41 = ",x41)
print ('------------------------------')
x42 = np.unique(np.array([[1, 2, 3], [1, 2, 3], [2, 3, 4]]), axis=0) #Return the unique rows of a 2D array
print("x42 = ",x42)
print ('------------------------------')
#numpy.union1d =>  Find the union of two arrays
a = np.arange(12,27,2).reshape(2,4)
print("a = ",a)
print ('------------------------------')
b = np.ones((2,3), dtype='1i')
print("b = ",b)
print ('------------------------------')
c = np.arange(1,7).reshape(3,2)
print("c = ",c)
print ('------------------------------')
x43 = np.union1d(b,c)
print("x43 = ",x43)
print ('------------------------------')
# find the union of more than two arrays, use functools.reduce
from functools import reduce
x44 = reduce(np.union1d, (a,b,c))
print("x44 = ",x44)
print ('------------------------------')
#numpy.intersect1d => Find the intersection of two arrays
x45 = np.intersect1d(b,c)
print("x45 = ",x45)
print ('------------------------------')
x46 = reduce(np.intersect1d, ([1, 3, 5, 3], [6, 1, 3, 1], [7, 3, 1, 2]))
print("x46 = ",x46)
print ('------------------------------')

# numpy.sort => Return a sorted copy of an array
x47 = np.array([[1,4,2,2],[3,1,4,5],[8,8,7,20]])
x48 = np.sort(x47)
print("x48 = ",x48)
print ('------------------------------')
x49 = np.sort(x47, axis=0)
print("x49 = ",x49)
print ('------------------------------')
x50 = np.sort(x47, axis=1)
print("x50 = ",x50)
print ('------------------------------')
x51 = np.sort(x47, axis=None)
print("x51 = ",x51)
print ('------------------------------')
#Using tuple for numpy.ndarray
x52 = np.array ((2,3,4,5))
print("x52 = ",x52)
print ('------------------------------')
x53 = np.array (((2,3,4,5),(5,6,8,7)))
print("x53 = ",x53)
print ('------------------------------')
print("type (x53) : ",type (x53))

#numpy.vstack => Stack arrays in sequence vertically (row wise)
x54 = np.array([2, 4, 6])
x55 = np.array([1, 3, 7])
x56 = np.vstack((x54,x55)) #The numbe of column (x54,x55) is same
#x56 = np.vstack([x54,x55])
print("x56 = ",x56)
print ('------------------------------')
x57 = np.vstack((x55,x54))
print("x57 = ",x57)
print ('------------------------------')
#numpy.hstack => Stack arrays in sequence horizontally (column wise)
x58 = np.hstack((x54,x55))
print("x58 = ",x58)

# numpy.ndarray and (for    in  )
x59 = np.array([[2,85,79,34],[5,78,36,44],[12,0,1,99]])
print('x59 = ')
for x in x59:   #show the rows separately
    print(x)
print ('------------------------------')
x60 = x59.reshape(12)
print('x60 = ')
for x in x60:
    print(x)
print ('------------------------------')
#numpy.ravel => Return a contiguous flattened array.
print('x59 = ')
for x in np.ravel(x59):  #for x in x60.ravel()
    print(x)
print ('------------------------------')
#Iterating Over Arrays
print('x59 = ')
for x in np.nditer(x59):
    print (x)
print ('------------------------------')
x61 = iter(x59)
for x in x59:
    print ('next(x61) = ',next(x61))
    print ('.............')
print ('------------------------------')
x62 = iter(x59)
for x in x59:
    y = next(x62)
    for z in y:
        print (z)

x63 = np.mean (x59)
print("mean = ",x63)
print ('------------------------------')
x64 = np.var (x59)
print("var = ",x64)
print ('------------------------------')
x65 = np.std (x59)
print("std = ",x65)
print ('------------------------------')
x66 = np.median (x59)
print("median = ",x66)
print ('------------------------------')

#numpy.polyval(p, x) => Evaluate a polynomial at specific values
# ax^2+bx+c = 0
# 2x^2+x+3 = 0
x67 = np.array([2,1,3])
print ('x=2 => 2x^2+x+3 = 0, ', np.polyval(x67,2))
print ('------------------------------')
# numpy.polyder => Return the derivative of the specified order of a polynomial
print ('np.polyder(x67) : ', np.polyder(x67))
print ('------------------------------')
#numpy.polyint => Return an antiderivative (indefinite integral) of a polynomial
print ('np.polyint(x67) : ', np.polyint(x67))
print ('------------------------------')

# Comparing Lists and Arrays

# Make a list
L_1 = [1, 2, 3, 4]
# Make Equivalent Array
A_1 = np.array ([1, 2, 3, 4])

# print reverse of list
L_1.reverse()

#L_1 = L_1[::-1]

for e in L_1:
	print (e)

print ('-----------------')

# reverse the array
A_1 = A_1[::-1]

for i in A_1:
	print (i)

print ('-----------------')

# insert an element into list
L_1.append(5)
print (L_1)

print ('-----------------')

#A_1.append(5)   # Error
#print A_1

#A_1 = A_1 + [6,7] # eRROR
#print A_1

#print ('-----------------')

L_1 = L_1 + [6,7]
print (L_1)

print ('-----------------')

L_1 =  L_1 + L_1
print (L_1)

L_2 = []
for i in L_1:
	L_2.append(i+i)
print (L_2)

print ('-----------------')

A_1 = A_1+A_1
print (A_1)

print ('-----------------')

A_1 = A_1 * 2
print (A_1)

L_1 = L_1 * 2
print (L_1)

print ('-----------------')

L_1 = [1, 2, 3, 4]
#L_1 = L_1 **2 # Error
#print (L_1)

L_3 = []
for i in L_1:
	L_3.append(i * i)
print (L_3)

print ('-----------------')

A_1 = np.array ([1, 2, 3, 4])
A_1 = A_1 ** 2
print(A_1)

print ('-----------------')

print (np.sqrt(A_1))
print (np.log(A_1))
print (np.exp(A_1))

# for the list u need use a for loop
print ('-----------------')

x = np.array([1,2])
y = np.array([2,1])

dot = 0

for i, j in zip(x, y):
	dot += (i*j)
print(dot)

print (x*y)  # * ----> element by element multiplication

print (np.sum(x*y))
print ((x*y).sum())
print (np.dot(x,y))  #   dot ------> matrix multiplication
# angle between x, y
angle = np.arccos(x.dot(y)/(np.linalg.norm(x) * np.linalg.norm(y)))
print (angle)

x = np.array([[1,2,3],[4,5,6]])
y = np.array([[7,8],[9,10],[11,12]])

print(x.dot(y))
print (np.dot(x,y))
print(y.dot(x))
# print (x*y)
# print (np.sum(x*y))
# print ((x*y).sum())
print ('-----------------')

# Magnitude of the vector:  np.sqrt((x*x).sum())
print (np.linalg.norm(x))

print ('-----------------')

# list of lists
L = [[1, 2], [3, 4]]

A = np.array ([[4, 5], [6, 7]])

print(L[0])
print(L[0][0])
print(A[0][0])
print(A[0,0])

A_2 = np.matrix(L)
print(A_2)
A_3 = np.array(A_2)
print (A_3)

print ('-----------------')

# transpose

print (A_3.T)

# matrix is -----> two-dimensional numpy array
# vector is -----> one-dimensional array

print ('-----------------')

Z_a = np.zeros(14)
print (Z_a)

Z_a = np.zeros((14, 14))
print (Z_a)

Z_a = np.ones((14, 14))
print (Z_a)

Z_r = np.random.random ((14, 14))
print (Z_r)

#Z_r = np.random.randn ((14, 14))
#print (Z_r)

Z_r = np.random.randn (14, 14)
print (Z_r)

print (Z_r.mean())

print (Z_r.var())

print ('-----------------')

#inverse
A_4 = np.array([[2, 4], [1, 3]])
print (A_4)
A_4_inv = np.linalg.inv(A_4)
print(A_4_inv)
print (np.linalg.det(A_4))
print (np.diag(A_4))

# 1, 2 go in the diagonal and everything else is 0.
print (np.diag ([8, 9]))

# Compute the outer/inner product of two vectors
x = np.array([4, 8])
y = np.array([16, 32])
print (np.outer(x, y))
print (np.inner(x, y))
print (x.dot(y))
print (np.diag(A_4).sum())
print (np.trace(A_4))

print ('-----------------')

X_r = np.random.randn(100, 4)  # 100 samples and 4 features
cov = np.cov(X_r)
print (cov.shape)

# covariance <--------> transpose
cov = np.cov(X_r.T)   # 4 * 4
print (cov)

print ('-----------------')

#Covariance is a Symmetricmatrix
#eigenvalues, eigenvectors = np.eig(x) or np.eigh(x)
#eigh -------> symmetric and Hermitian matrix
#Symmetric means A = A**T
#Hermitian means A = A**H
#A**H = conjugate transpose of A

print (np.linalg.eigh(cov))
print (np.linalg.eig(cov))

print ('-----------------')


import numpy as np
import matplotlib.pyplot as plt

N = 50

x_start, x_end = -2.0, 2.0
y_start, y_end = -1.0, 1.0

x = np.linspace(x_start, x_end, N)
y = np.linspace(y_start, y_end, N)

X, Y = np.meshgrid(x, y)

gamma = 5.0
x_vortex, y_vortex = 0.0, 0.0

def get_velocity_vortex(strength, xv, yv, X, Y):

    u = + strength / (2 * np.pi) * (Y - yv) / ((X - xv)**2 + (Y - yv)**2)
    v = - strength / (2 * np.pi) * (X - xv) / ((X - xv)**2 + (Y - yv)**2)

    return u, v

def get_stream_function_vortex(strength, xv, yv, X, Y):

    psi = strength / (4 * np.pi) * np.log((X - xv)**2 + (Y - yv)**2)

    return psi

u_vortex, v_vortex = get_velocity_vortex(gamma, x_vortex, y_vortex, X, Y)

psi_vortex = get_stream_function_vortex(gamma, x_vortex, y_vortex, X, Y)

size = 10
plt.figure(figsize=(size, (y_end - y_start) / (x_end - x_start) * size))
plt.xlabel('x', fontsize=16)
plt.ylabel('y', fontsize=16)
plt.xlim(x_start, x_end)
plt.ylim(y_start, y_end)
plt.streamplot(X, Y, u_vortex, v_vortex, density=2,
               linewidth=1, arrowsize=1, arrowstyle='->')
plt.scatter(x_vortex, y_vortex, color='#CD2305', s=80, marker='o')

plt.show()

strength_sink = -1.0
x_sink, y_sink = 0.0, 0.0

def get_velocity_sink(strength, xs, ys, X, Y):

    u = strength / (2 * np.pi) * (X - xs) / ((X - xs)**2 + (Y - ys)**2)
    v = strength / (2 * np.pi) * (Y - ys) / ((X - xs)**2 + (Y - ys)**2)

    return u, v

def get_stream_function_sink(strength, xs, ys, X, Y):

    psi = strength / (2 * np.pi) * np.arctan2((Y - ys), (X - xs))

    return psi

u_sink, v_sink = get_velocity_sink(strength_sink, x_sink, y_sink, X, Y)

psi_sink = get_stream_function_sink(strength_sink, x_sink, y_sink, X, Y)

u = u_vortex + u_sink
v = v_vortex + v_sink
psi = psi_vortex + psi_sink

size = 10
plt.figure(figsize=(size, (y_end - y_start) / (x_end - x_start) * size))
plt.xlabel('x', fontsize=16)
plt.ylabel('y', fontsize=16)
plt.xlim(x_start, x_end)
plt.ylim(y_start, y_end)
plt.streamplot(X, Y, u, v, density=2, linewidth=1,
               arrowsize=1, arrowstyle='->')
plt.scatter(x_vortex, y_vortex, color='#CD2305', s=80, marker='o')

plt.show()


import copy
import generators
import math
import neighbor_states as ns
import numpy as np
import plotter
import random as rand

def get_matrix(cities):
    n = len(cities)
    result = np.zeros((n, n))

    for x in range(n):
        for y in range(n):
            x1 = cities[x][0]
            y1 = cities[x][1]
            x2 = cities[y][0]
            y2 = cities[y][1]
            distance = np.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
            result[x][y] = distance
            result[y][x] = distance
    return result

def path_distance(path, distances):
    distance = 0
    for i in range(len(path) - 1):
        distance += distances[path[i][2]][path[i + 1][2]]
    return distance

def travelling_salesman_problem(n, iterations, temperature, decay_rate, swap_type, low, high, distribution):
    if distribution == "uniform":
        path = generators.get_uniform_distribution_points(low, high, n)
    elif distribution == "normal" or distribution == "gaussian":
        path = generators.get_normal_distribution_points(low, high, n)
    elif distribution == "groups" or distribution == "9 groups":
        path = generators.get_9_groups_of_points(low, high, n)
    else:
        raise ValueError("Error: distribution argument was not uniform, normal or groups!")

    first_path = copy.copy(path)

    if swap_type != "consecutive" and swap_type != "arbitrary":
        raise ValueError("Error: swap type argument was not consecutive or arbitrary!")

    distances = get_matrix(path)

    rand.shuffle(path)

    best_path = path
    min_distance = path_distance(best_path, distances)

    iters = []
    dists = []
    temperatures = []

    for i in range(iterations):
        iters.append(i)

        new_path = copy.copy(path)
        if swap_type == "consecutive":
            new_path = ns.consecutive_swap(new_path)
        else:
            new_path = ns.consecutive_swap(new_path)

        old_path_distance = path_distance(path, distances)
        new_path_distance = path_distance(new_path, distances)

        dists.append(new_path_distance)

        if new_path_distance < old_path_distance:
            path = new_path
            if new_path_distance < min_distance:
                min_distance = new_path_distance
        elif math.exp(-(new_path_distance - old_path_distance)/temperature) > rand.uniform(0, 1):
            path = new_path

        temperatures.append(temperature)
        temperature *= decay_rate

    distances_plot_data = (copy.copy(iters), dists)
    temperatures_plot_data = (iters, temperatures)
    return first_path, best_path, distances_plot_data, temperatures_plot_data

n = 200
iterations = 500000
temperature = 1000
decay_rate = 0.99995
swap_type = "consecutive"
low = 0
high = 1
distribution = "uniform"

first_path, best_path, distances_plot_data, temperatures_plot_data = travelling_salesman_problem(n, iterations, temperature, decay_rate, swap_type, low, high, distribution)

plotter.plot_data(first_path, best_path, distances_plot_data, temperatures_plot_data)


import math
import numpy as np

def norm(vec: np.ndarray):
    return math.sqrt(math.pow(vec[0], 2) + math.pow(vec[1], 2) + math.pow(vec[2], 2))

def dot(vec: np.ndarray, vec2: np.ndarray):
    return vec[0] * vec2[0] + vec[1] * vec2[1] + vec[2] * vec2[2]

def normalize(vec: np.ndarray):
    """Returns a normalized vector of norm 1."""
    return vec / max(norm(vec), 1e-8)

def normalize_batch(vec: np.ndarray):
    return vec / np.maximum(np.linalg.norm(vec, axis=-1), 1e-8)


import json
import math
import numpy as np

def compute_z(x, y):
    theta = np.array([x, y, 0])
    p = np.exp(theta) / np.sum(np.exp(theta))
    return p[1]

n = 111
m = 111

xs = np.linspace(-3, 3, n)
ys = np.linspace(-3, 3, m)

X, Y = np.meshgrid(xs, ys)
Z = np.empty_like(X)

for i in range(n):
    for j in range(m):
        x = X[i, j]
        y = Y[i, j]
        Z[i, j] = compute_z(x, y)

points = np.column_stack([X.ravel(), Y.ravel(), Z.ravel()])

# by default, this point cloud would be displayed with Z facing up.
# let's rotate it to make it prettier

def rotate3d_x(points, theta):
    c = math.cos(theta)
    s = np.sin(theta)

    R = np.array([[1, 0, 0],
                  [0, c, -s],
                  [0, s, c]])
    return np.dot(points, R)

def rotate3d_z(points, theta):
    c = math.cos(theta)
    s = np.sin(theta)

    R = np.array([[c, -s, 0],
                  [s, c, 0],
                  [0, 0, 1]])
    return np.dot(points, R)

points = rotate3d_z(points, np.pi / 4)
points = rotate3d_x(points, np.pi / 2.5)

data = {
    'x': points[:, 0].tolist(),
    'y': points[:, 1].tolist(),
    'z': points[:, 2].tolist()
}

template = f"""\
var n = {n};
var m = {m};
var data = {json.dumps(data)};
"""

with open('data.js', 'w')  as f:
    print(template, file=f)