import numpy as np

alist = [1,2,3,4,5]
narray = np.array([1,2,3,4])

print(alist)
print(narray)

print(type(alist))
print(type(narray))

print(narray + narray)
print(alist + alist)

print(narray * 3)
print(alist * 4)

npmatrix1 = np.array([narray,narray,narray])
npmatrix2 = np.array([alist,alist,alist])
npmatrix3 = np.array([narray,[1,1,1,1],narray])

print(npmatrix1)
print(npmatrix2)
print(npmatrix3)

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

print('bad matrix :')

badmatrix = np.array([[1,2],[3,4],[5,6,7]])
print(badmatrix)
print(badmatrix * 2)

result = okmatrix * 2 + 1
print('result',result)

result1 = okmatrix + okmatrix
print('result 1 :',result1)

result2 = okmatrix - okmatrix
print('result 2 :',result2)

result = okmatrix * okmatrix
print(result)

matrix3x2 = np.array([[1,2],[3,4],[5,6]])
print('original matrix :')
print(matrix3x2)
print('Transposed matrix :')
print(matrix3x2.T)

nparray = np.array([1,2,3,4])
print('Original array')
print(nparray)
print('Transposed array')
print(nparray.T)

nparray = np.array([[1,2,3,4]])
print('Original array')
print(nparray)
print('Transposed array')
print(nparray.T)

nparray1 = np.array([1,2,3,4])
norm1 = np.linalg.norm(nparray1)

nparray2 = np.array([[1,2],[3,4]])
norm2 = np.linalg.norm(nparray2)

print(norm1)
print(norm2)

nparray2 = np.array([[1, 1], [2, 2], [3, 3]])

normByCols = np.linalg.norm(nparray2, axis=0)
normByRows = np.linalg.norm(nparray2, axis=1)

print(normByCols)
print(normByRows)

nparray1 = np.array([0, 1, 2, 3])
nparray2 = np.array([4, 5, 6, 7])

flavor1 = np.dot(nparray1, nparray2)
print("flavor1 : ",flavor1)

flavor2 = np.sum(nparray1 * nparray2)
print("flavor2 :",flavor2)

flavor3 = nparray1 @ nparray2
print("flavor3 :",flavor3)

flavor4 = 0
for a, b in zip(nparray1, nparray2):
    flavor4 += a * b

print("flavor4 : ",flavor4)

norm1 = np.dot(np.array([1,2]),np.array([3,4]))
norm2 = np.dot([1,2],[3,4])

print(norm1, '=' , norm2)

nparray2 = np.array([[1,-1],[2,-2],[3,-3]])

sumByCols = np.sum(nparray2, axis=0)
sumByRows = np.sum(nparray2, axis=1)

print('Sum By Column : ')
print(sumByCols)
print('Sum by Rows : ')
print(sumByRows)

# nparray2 = np.array([[1,-1],[2,-2],[3,-3]])
mean = np.mean(nparray2)
meanByCols = np.mean(nparray2,axis=0)
meanByRows = np.mean(nparray2,axis=1)

print('mean By Column : ')
print(meanByCols)
print('mean by Rows : ')
print(meanByRows)

nparray2 = np.array([[1,1],[2,2],[3,3]])

nparrayCentered = nparray2 - np.mean(nparray2,axis=0)

print('Original metrix')
print(nparray2)
print('Centered by columns matrix')
print(nparrayCentered)
print('New mean by column')
print(nparrayCentered.mean(axis=0))

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

nparrayCentered = nparray2.T - np.mean(nparray2, axis=1)
nparrayCentered = nparrayCentered.T

print('Original metrix')
print(nparray2)
print('Centered by columns matrix')
print(nparrayCentered)

print('New mean by column')
print(nparrayCentered.mean(axis=1))

#Exercise
NumPyarray1 = np.array([[1,2],[3,4],[5,6]])
NumPyarray2 = np.array([[1,2,3],[3,4,5]])


"""
Sierpinski3d fractal
"""
# Credits: K3D authors at
# https://github.com/K3D-tools/K3D-jupyter/tree/master/examples
#https://simple.wikipedia.org/wiki/Sierpinski_triangle
import numpy as np

N = 30000
x = np.zeros(N)
y = np.zeros(N)
z = np.zeros(N)

x1 = np.empty_like(x)
y1 = np.empty_like(y)
z1 = np.empty_like(z)

# Sierpinski triangle iterative functions
def f1(x, y, z, x1, y1, z1, c):
    x1[c] = 0.5*x[c]
    y1[c] = 0.5*y[c]
    z1[c] = 0.5*z[c]

def f2(x, y, z, x1, y1, z1, c):
    x1[c] = 0.5*x[c] + 1 / 2.0
    y1[c] = 0.5*y[c]
    z1[c] = 0.5*z[c]

def f3(x, y, z, x1, y1, z1, c):
    x1[c] = 0.5*x[c] + 1 / 4.0
    y1[c] = 0.5*y[c] + np.sqrt(3) / 4
    z1[c] = 0.5*z[c]

def f4(x, y, z, x1, y1, z1, c):
    x1[c] = 0.5*x[c] + 1 / 4.0
    y1[c] = 0.5*y[c] + 1.0 / 4
    z1[c] = 0.5*z[c] + np.sqrt(3) / 4

functions = [f1, f2, f3, f4]
probabilities = [1 / 4.0] * 4
assert len(functions) == len(probabilities)

X, Y, Z = x, y, z
for i in range(20):
    # pick indices for each function to be applied
    r = np.random.choice(len(probabilities), size=N, p=probabilities)
    for i, f in enumerate(functions):
        f(x, y, z, x1, y1, z1, r == i)
    x, x1 = x1, x
    y, y1 = y1, y
    z, z1 = z1, z
    if i > 0:
        X, Y, Z = np.hstack([X,x]), np.hstack([Y,y]), np.hstack([Z,z])

# how much memory are we using, how many points there are
print("used mem, Npts=", 3 * X.nbytes // 1024 ** 2, "MB", X.shape[0])

from vtkplotter import Points

Points([X, Y, Z], c="tomato").show(axes=1, bg='white', zoom=2.0)


import numpy as np

import jax.numpy as jnp
from jax import ops

import numpyro
import numpyro.distributions as dist

def linreg_model(X, y):

    ndims = X.shape[1]
    a = numpyro.sample("a", dist.Normal(0, 0.5))

    beta = numpyro.sample("beta", dist.Normal(0, 0.5).expand([ndims]))
    sigma_y = numpyro.sample("sigma_y", dist.Exponential(1))

    # LKJ is the distribution to model correlation matrices.
    rho = numpyro.sample("rho", dist.LKJ(ndims, 2))  # correlation matrix
    sigma_x = numpyro.sample("sigma_x", dist.Exponential(1).expand([ndims]))
    covariance_x = jnp.outer(sigma_x, sigma_x) * rho  # covariance matrix
    mu_x = numpyro.sample("mu_x", dist.Normal(0, 0.5).expand([ndims]))

    numpyro.sample("X", dist.MultivariateNormal(mu_x, covariance_x), obs=X)

    mu_y = a + X @ beta

    numpyro.sample("y", dist.Normal(mu_y, sigma_y), obs=y)

def linreg_imputation_model(X, y):

    ndims = X.shape[1]
    a = numpyro.sample("a", dist.Normal(0, 0.5))

    beta = numpyro.sample("beta", dist.Normal(0, 0.5).expand([ndims]))
    sigma_y = numpyro.sample("sigma_y", dist.Exponential(1))

    # X_impute contains imputed data for each feature as a list
    # X_merged is the observed data filled with imputed values at missing points.
    X_impute = [None] * ndims
    X_merged = [None] * ndims

    for i in range(ndims):  # for every feature
        no_of_missed = int(np.isnan(X[:, i]).sum())

        if no_of_missed != 0:
            # each nan value is associated with a imputed variable of std normal prior.
            X_impute[i] = numpyro.sample(
                "X_impute_{}".format(i), dist.Normal(0, 1).expand([no_of_missed]).mask(False))

            # merging the observed data with the imputed values.
            missed_idx = np.nonzero(np.isnan(X[:, i]))[0]
            X_merged[i] = ops.index_update(X[:, i], missed_idx, X_impute[i])

        # if there are no missing values, its just the observed data.
        else:
            X_merged[i] = X[:, i]

    merged_X = jnp.stack(X_merged).T

    # LKJ is the distribution to model correlation matrices.
    rho = numpyro.sample("rho", dist.LKJ(ndims, 2))  # correlation matrix
    sigma_x = numpyro.sample("sigma_x", dist.Exponential(1).expand([ndims]))
    covariance_x = jnp.outer(sigma_x, sigma_x) * rho  # covariance matrix
    mu_x = numpyro.sample("mu_x", dist.Normal(0, 0.5).expand([ndims]))

    numpyro.sample("X_merged", dist.MultivariateNormal(mu_x, covariance_x), obs=merged_X)

    mu_y = a + merged_X @ beta

    numpyro.sample("y", dist.Normal(mu_y, sigma_y), obs=y)


import logging
import cv2
import numpy as np
import copy

logging.basicConfig(level=logging.DEBUG, format='%(asctime)-15s %(levelname)s \t%(message)s')

def main():
    logging.info("compute_homography.py main()")
    image_filepath = "./images/checkerboard.png"
    features_mm_to_pixels_dict =  {(0, 0): (192, 801, ),
                                  (214.2, 0): (937, 652),
                                  (214.2, 178.5): (798, 67),
                                  (0, 178.5): (101, 197)}
    A = np.zeros((2 * len(features_mm_to_pixels_dict), 6), dtype=float)
    b = np.zeros((2 * len(features_mm_to_pixels_dict), 1), dtype=float)
    index = 0
    for XY, xy in features_mm_to_pixels_dict.items():
        X = XY[0]
        Y = XY[1]
        x = xy[0]
        y = xy[1]
        A[2 * index, 0] = x
        A[2 * index, 1] = y
        A[2 * index, 2] = 1
        A[2 * index + 1, 3] = x
        A[2 * index + 1, 4] = y
        A[2 * index + 1, 5] = 1
        b[2 * index, 0] = X
        b[2 * index + 1, 0] = Y
        index += 1
    # A @ x = b
    x, residuals, rank, singular_values = np.linalg.lstsq(A, b, rcond=None)

    pixels_to_mm_transformation_mtx = np.array([[x[0, 0], x[1, 0], x[2, 0]], [x[3, 0], x[4, 0], x[5, 0]], [0, 0, 1]])
    logging.debug("main(): pixels_to_mm_transformation_mtx = \n{}".format(pixels_to_mm_transformation_mtx))

    test_xy_1 = (399, 500, 1)
    test_XY_1 = pixels_to_mm_transformation_mtx @ test_xy_1
    logging.info("main(): test_xy_1 = {}; test_XY_1 = {}".format(test_xy_1, test_XY_1))

    mm_to_pixels_transformation_mtx = np.linalg.inv(pixels_to_mm_transformation_mtx)
    test_XY_2 = (107.1, 107.1, 1)
    test_xy_2 = mm_to_pixels_transformation_mtx @ test_XY_2
    image = cv2.imread(image_filepath)
    annotated_img = copy.deepcopy(image)
    draw_crosshair(annotated_img, (round(test_xy_2[0]), round(test_xy_2[1])), 20, (0, 255, 0))
    cv2.imwrite("./outputs/annotated.png", annotated_img)

def draw_crosshair(image, center, width, color):
    cv2.line(image, (center[0] - width//2, center[1]), (center[0] + width//2, center[1]), color)
    cv2.line(image, (center[0], center[1] - width//2), (center[0], center[1] + width//2), color)

if __name__ == '__main__':
    main()


from math import sqrt, ceil

from scipy.misc import comb

def count_monomials_leq(n, d):
  '''Number of monomials in n variables of degree less than or equal to d'''
  return int(comb(n+d, d))

def count_monomials_eq(n, d):
  '''Number of monomials in n variables of degree equal to d'''
  return int(comb(n+d-1, d))

def grlex_to_index(grlex):
  '''Returns the grlex ordering number for a given multi-index. Can be expensive for large degrees'''
  total_degree = sum(grlex)
  n = len(grlex)

  index = count_monomials_leq(n, total_degree - 1)
  remaining_degree = total_degree
  for i in range(n):
    for k in range(grlex[i]):
      index += count_monomials_eq(n-1-i, remaining_degree - k)
    remaining_degree -= grlex[i]
  return index

def index_to_grlex(index, n):
  '''Returns the multi-index of length n for a given grlex ordering number'''
  if n < 1:
    raise Exception('number of variables must be positive')
  if index < 0:
    raise Exception('invalid index')

  grlex = [0 for i in range(n)]

  # find sum of grlex
  total_degree = 0
  while count_monomials_leq(n, total_degree) <= index:
    total_degree += 1

  # index among all multiindices with sum total_degree
  rel_idx_search = index - count_monomials_leq(n, total_degree - 1)

  rem_deg = total_degree # keep track of sum of remaining indices
  rel_idx = 0 # keep track of number of multiindices to the left
  for i in range(n-1):
    for k in range(total_degree+1):
      new_rel_idx = rel_idx + count_monomials_eq(n-(i+1), rem_deg-k)
      if new_rel_idx > rel_idx_search:
        grlex[i] = k
        rem_deg -= k
        break
      rel_idx = new_rel_idx

  grlex[-1] = rem_deg

  return tuple(grlex)

def grlex_key(midx):
  return (sum(midx),) + tuple(midx)

def multi_grlex_iter(midx, groups, degrees):
  '''
  Create an iterator that produces ordered exponents, starting
  with the multiindex 'midx', such that total degree of the variables
  in groups[i] does not exceed degrees[i].

  The ordering is based on grlex groupwise, but does not result
  in an overall grlex ordering.

  Example: The iterator grlex_iter( (0,0), [ [0], [1] ], [2], [1] )
       produces the sequence
    (0,0) (1,0) (2,0) (0,1) (1,1) (2,1)
  '''

  # make sure 'groups' is a valid partition
  assert(set([dim for group in groups for dim in group]) == set(range(len(midx))))
  assert(len(groups) == len(degrees))

  # starting multiindices
  start_midx = [ tuple([ midx[dim] for dim in group ]) for group in groups ]

  iterators = [grlex_iter(m0, deg) for m0, deg in zip(start_midx, degrees) ]
  mons = [next(iterator) for iterator in iterators]

  ret = list(midx)
  while True:

    yield tuple(ret)

    for ind in range(len(degrees)):
      # find first that is not at max
      try:
        mons[ind] = next(iterators[ind])
        break
      except StopIteration:

        if ind == len(degrees) - 1:
          raise StopIteration

        # was at max, reset it
        iterators[ind] = grlex_iter(start_midx[ind], degrees[ind])
        mons[ind] = next(iterators[ind])

    # Fill out return tuple
    for group_nr, group in enumerate(groups):
      for pos_nr, pos in enumerate(group):
        ret[pos] = mons[group_nr][pos_nr]

def grlex_iter(midx, deg = None):
  '''
  Create an iterator that produces ordered grlex exponents, starting
  with the multiindex 'midx'. The iterator stops when the total degree
  reaches 'deg'+1. (default: never stop)

  Example: The iterator grlex_iter( (0,2) ) produces the sequence
    (0,2) (1,2) (2,0) (0,3) (1,2) (2,1) (3,0) ...
  '''

  if len(midx) == 0:
    return  # nothing to do here

  midx = list(midx)
  assert(min(midx) >= 0)

  if max(midx) == 0:
    right_ptr = 0
  else:
    # find right-most non-zero element
    for i in range(len(midx)-1, -1, -1):
      if midx[i] != 0:
        right_ptr = i
        break

  while deg is None or sum(midx) < deg + 1:
    yield tuple(midx)
    if right_ptr == 0:
      midx = [0] * (len(midx) - 1) + [sum(midx) + 1]
      right_ptr = len(midx) - 1
    else:
      at_right_ptr = midx[right_ptr]
      midx[right_ptr] = 0
      midx[right_ptr-1] += 1
      midx[-1] = at_right_ptr - 1
      right_ptr = len(midx) - 1 if at_right_ptr != 1 else right_ptr - 1

def vec_to_grlex(L, dim):
  '''
  Given vec(A) for a symmetric matrix A, such that len(vec(A)) = L,
  compute the mapping  vec(A) |--> x^T A x
  to the grlex dim-dimensional monomial basis on multiindex - coefficient form.

  Example: [ a b c ] represents the matrix [ a b ; b c], and the corresponding 1d
  polynomial is a + 2b x + c x^2. Thus the mapping can be represented as
  ( 1., 2., 1. ), [ (0,), (1,), (2,) ]
  '''
  n = (sqrt(1 + 8 * L) - 1)/2  # side of matrix

  if not ceil(n) == n:
    raise TypeError('wrong size for vec(A) for A symmetric')

  j = 0
  i = 0
  i_moniter = grlex_iter( (0,) * dim )
  j_moniter = grlex_iter( (0,) * dim )
  i_mon = next(i_moniter)
  j_mon = next(j_moniter)

  exp = []
  coef = []

  for k in range(L):
    # loop over all elements
    exp += [tuple( [ii+jj for ii,jj in zip(i_mon, j_mon) ] )]
    coef += [1. if i == j else 2.]

    if j == n-1:
      i += 1
      j = i
      i_mon = next(i_moniter)
      j_moniter = grlex_iter( i_mon )
      j_mon = next(j_moniter)

    else:
      j_mon = next(j_moniter)
      j += 1
  return coef, exp


from scipy.optimize import fmin
from functools import partial

# A generic function that we want to find the minimum of
def my_func(x):
    """A quadratic function
    :param x the input x value
    :returns f(x)"""
    return (x-2)**2 - 3

# A fancier quadratic-y type function (but in 2D)
def elliptic_paraboloid(x, y, x0, y0, a, b):
    """ x,y -> f(x) in the shape of a 'bowl'
    :param x - x value in the plane
    :param y - y value in the plane
    :param x0 - amount to shift the bottom of the bowl by in x
    :param y0 - amount to shift the bottom of the bowl by in y
    :param a - scale the bowl's side in x
    :param b - scale the bowl's side in y
    :returns - f(x,y) the bowl's height over the point x,y
    """
    return (x-x0)**2 / a**2 + (y-y0)**2 / b**2

if __name__ == '__main__':
    # Use fmin to find the minimum of my_func
    # The _ says ignore that returned value
    # Note that x_at_min is a list - in this case, a list of dimension 1
    #    The full_output=True prints out the optimization result to the console
    x_at_min, f_at_x_min, _, _, _ = fmin(my_func, 0.2, maxfun=100, full_output=True)
    print("Minimum of f is {0}, happens at x={1}".format(f_at_x_min, x_at_min[0]))
    print("Checking fmin result {0} against func eval {1}".format(f_at_x_min, my_func(x_at_min[0])))

    # This does the same thing
    #   See https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fmin.html
    #   for what the remaining values are
    f_min_ret_vals = fmin(my_func, 0.2, maxfun=100, full_output=True)
    x_at_min_2 = f_min_ret_vals[0][0]
    fx_at_min_2 = f_min_ret_vals[1]
    print("Minimum of f is {0}, happens at x={1}".format(fx_at_min_2, x_at_min_2))

    # Now going to do the 2 dimensional version of a parabaloid
    # Because the function definition has all these extra parameters that
    #  control the shape of the function (x0, etc), we have do what's called
    #  "binding" the variables to values, in this case x0 = 3, y0 = 2, a = 4, b=16
    # partial is a functools method to do this, which has the advantage over a lambda
    #  function of creating a python function that is more "efficient" because it
    #  "freezes" the constant parameters
    # partial takes the function elliptic_paraboloid and "binds" all of the named
    #   parameter arguments
    my_paraboloid = partial(elliptic_paraboloid, x0=3, y0=2, a=4, b=16)
    print("My parabaloid at 3,4: {0}, check {1}".format(my_paraboloid(3, 4), elliptic_paraboloid(3, 4, 3, 2, 4, 16)))

    # Almost done - fmin takes a function that takes in one argument (a list of dimension d)
    #  and outputs one number. Our my_paraboloid function currently takes in x,y, not [x,y].
    # Lambda functions fix that: In this case, it says make a new (temporary, unnamed function)
    #  that takes in one parameter (array) and calls my_paraboloid with array[0] and array[1]
    result = fmin(lambda array: my_paraboloid(array[0], array[1]), [0, 0], maxfun=200, full_output=True)
    print(result)
    print("Function minimum is {0}, found at {1}".format(result[1], result[0]))

    # You shouldn't really do this, because lambda functions are suppose to be unnamed,
    #  but this will show you what the lambda did
    my_func_2D = lambda array: my_paraboloid(array[0], array[1])
    # Notice that my_func_2D takes a list of two numbers, my_parabaloid takes 2 numbers
    print("Call my_parabaloid with 3, 4: {0} {1}".format(my_func_2D([3, 4]), my_paraboloid(3, 4)))


import sympy

def bisection(func, min_guess, max_guess, err_tolerance):
    """
    Find the root of a function using bisection method. (Bracketing method)
    arguments:
    func: f(x)
    min_guess: minimum x as guess
    max_guess: maximum x as guess
    err_tolerance: value where f(root) must be less than err_tolerance
    """

    x_new = 0.5 * (min_guess + max_guess)
    y_mid = func(x_new)
    print("x_new: ", x_new)
    if abs(y_mid) < err_tolerance:
        return x_new
    else:
        min_guess = x_new if y_mid <= 0 else min_guess
        max_guess = x_new if y_mid > 0 else max_guess
        return bisection(func, min_guess, max_guess, err_tolerance)

def regula_falsi(func, min_guess, max_guess, err_tolerance):
    """
    Find the root of a function using false position method. (Bracketing method)
    arguments:
    func: f(x)
    min_guess: minimum x as guess
    max_guess: maximum x as guess
    err_tolerance: value where f(root) must be less than err_tolerance
    """
    y_min = func(min_guess)
    y_max = func(max_guess)
    x_new = (0.5 * y_max * min_guess - y_min * max_guess)/(0.5 * y_max - y_min)
    y_new = func(x_new)
    if abs(y_new) <= err_tolerance:
        return x_new
    else:
        min_guess = x_new if y_new <= 0 else min_guess
        max_guess = x_new if y_new > 0 else max_guess
        return regula_falsi(func, min_guess, max_guess, err_tolerance)

def secant(func, min_guess, max_guess, err_tolerance, max_try=10000):
    """
    Find the root of a function using secant method. (Newton method)
    arguments:
    func: f(x)
    min_guess: minimum x as guess
    max_guess: maximum x as guess
    err_tolerance: value where f(root) must be less than err_tolerance
    """
    y_min = func(min_guess)
    y_max = func(max_guess)
    x_new = max_guess - (y_max * (min_guess - max_guess)/(y_min - y_max))
    y_new = func(x_new)
    print("x_new: ", x_new)
    if abs(y_new) <= err_tolerance:
        return x_new
    elif max_try == 0:
        return ValueError('Unable to find root after try')
    else:
        min_guess = x_new if y_new <= 0 else min_guess
        max_guess = x_new if y_new > 0 else max_guess
        return secant(func, min_guess, max_guess, err_tolerance, max_try - 1)

def newton_rhapson(func, min_guess, max_guess, err_tolerance):
    x = sympy.symbols('x')
    derivatives = sympy.derivatives(func, x)
    pass

def dekker():
    pass

def brent(func, min_guess, max_guess, err_tolerance):
    pass

def j(funct):
    return funct

def f(x):
    return ((x+3) ** 3) - 3

# find root
# root = bisection(f, -5, 5, 0.001)
# print('root for bisection is: ', root)

root = regula_falsi(f, -5, 5, 0.0001)
print('root for regula falsi is: ', root)

# root = secant(f, -2, 0, 0.0001)
# print('root for secant is: ', root)

al = j(f)
print(al(root))


"""
comput analytical response of a simply-supported (SSSS) plate under uniform load
By: Mehdi Paak
"""

import numpy as np

def w(x,y,a,b,h,nu,E,P0):
	"""
	deflextion of the plate at x,y
	units are SI meter

	:param x:
	:param y:
	:param a:
	:param b:
	:param h:
	:param nu:
	:param E:
	:param P0:
	:return:  deflection
	"""
	# 10 is a reasonable value for the analytical solution
	num_mode = 10

	D=E*h**3/(12.0*(1-nu**2))
	alph = 16.0*P0/(D*np.pi**6)
	N=[2*i+1 for i in range(num_mode)]
	M = N
	w=0.0

	for m in M:
		for n in N:
			beta = m*n*((m/a)**2+(n/b)**2)**2
			w = w +  (1.0/beta) * np.sin(m*np.pi*x/a) * np.sin(n*np.pi*y/b)
	w = w * alph
	return w

def wmax_nondim():
	"""
	returns nondimensional maximum deflection which
	is Wmax/(p0*a**4/D)
	D=E*h**3/(12(1-nu**2))

	:return:
	"""

	# 10 is a reasonable value for the analytical solution
	num_mode = 10
	alph = 16.0/np.pi**6

	N=[2*i+1 for i in range(num_mode)]
	M=N
	wmax = 0.0

	for m in M:
		for n in N:
			beta = m*n*(m**2+n**2)**2
			wmax = wmax +  (1.0/beta) * (-1.0)**((m+n-2)/2)
	return wmax*alph


import numpy as np

# function for damped pseudo-inverse
def damped_pseudoinverse(jac, l = 0.01):
    m, n = jac.shape
    if n >= m:
        return jac.T @ np.linalg.inv(jac @ jac.T + l*l*np.eye(m))
    return np.linalg.inv(jac.T @ jac + l*l*np.eye(n)) @ jac.T

# function for skew symmetric
def skew_symmetric(v):
    mat = np.zeros((3, 3))
    mat[0, 1] = -v[2]
    mat[1, 0] = v[2]
    mat[0, 2] = v[1]
    mat[2, 0] = -v[1]
    mat[1, 2] = -v[0]
    mat[2, 1] = v[0]

    return mat

# function for Adjoint
def AdT(tf):
    R = tf.rotation()
    T = tf.translation()
    tr = np.zeros((6, 6))
    tr[0:3, 0:3] = R
    tr[3:6, 0:3] = skew_symmetric(T) @ R
    tr[3:6, 3:6] = R

    return tr

# angle wrap to [-pi,pi)
def angle_wrap(theta):
    while theta < -np.pi:
        theta += 2 * np.pi
    while theta > np.pi:
        theta -= 2 * np.pi
    return theta

# angle wrap multi
def angle_wrap_multi(theta):
    if isinstance(theta, list):
        th = theta
        for i in range(len(th)):
            th[i] = angle_wrap(th[i])
        return th
    elif type(theta) is np.ndarray:
        th = theta
        for i in range(theta.shape[0]):
            th[i] = angle_wrap(th[i])
        return th
    return angle_wrap(theta)


## 3. Bias-variance tradeoff ##

from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
import numpy as np
import matplotlib.pyplot as plt

def train_and_test(cols):
    lr = LinearRegression()
    lr.fit(filtered_cars[cols], filtered_cars['mpg'])
    predictions = lr.predict(filtered_cars[cols])
    # Compute MSE and Variance
    mse = mean_squared_error(filtered_cars['mpg'], predictions)

    variance = np.var(predictions)
    return(mse, variance)

cyl_mse, cyl_var = train_and_test(['cylinders'])
print(cyl_mse, cyl_var)

weight_mse, weight_var = train_and_test(['weight'])
print(weight_mse, weight_var)

## 4. Multivariate models ##

# Our implementation for train_and_test, takes in a list of strings.
def train_and_test(cols):
    # Split into features & target.
    features = filtered_cars[cols]
    target = filtered_cars["mpg"]
    # Fit model.
    lr = LinearRegression()
    lr.fit(features, target)
    # Make predictions on training set.
    predictions = lr.predict(features)
    # Compute MSE and Variance.
    mse = mean_squared_error(filtered_cars["mpg"], predictions)
    variance = np.var(predictions)
    return(mse, variance)

one_mse, one_var = train_and_test(["cylinders"])

two_mse, two_var = train_and_test(['cylinders','displacement'])

three_mse, three_var = train_and_test(['cylinders','displacement','horsepower'])

four_mse, four_var = train_and_test(['cylinders','displacement','horsepower','weight'])

five_mse, five_var = train_and_test(['cylinders','displacement','horsepower','weight','acceleration'])

six_mse, six_var = train_and_test(['cylinders','displacement','horsepower','weight','acceleration','model year'])

seven_mse, seven_var = train_and_test(['cylinders','displacement','horsepower','weight','acceleration','model year','origin'])

print('The MSE and Variance of Two features are: '+ str(two_mse)+' and '+str(two_var) )
print('The MSE and Variance of Three features are: '+ str(three_mse)+' and '+str(three_var) )
print('The MSE and Variance of four features are: '+ str(four_mse)+' and '+str(four_var) )
print('The MSE and Variance of five features are: '+ str(five_mse)+' and '+str(five_var) )
print('The MSE and Variance of six features are: '+ str(six_mse)+' and '+str(six_var) )
print('The MSE and Variance of seven features are: '+ str(seven_mse)+' and '+str(seven_var) )

## 5. Cross validation ##

from sklearn.model_selection import KFold
from sklearn.metrics import mean_squared_error
import numpy as np

def train_and_cross_val(cols):
    features = filtered_cars[cols]
    target = filtered_cars['mpg']

    variance_values = []
    mse_values = []

    #KFold instance
    kf = KFold(n_splits = 10, shuffle = True, random_state = 3)
     # Iterate through over each fold.
    for train_index, test_index in kf.split(features):
        # Training and test sets.
        X_train, X_test = features.iloc[train_index], features.iloc[test_index]
        y_train, y_test = target.iloc[train_index], target.iloc[test_index]

        # Fit the model and make predictions.
        lr = LinearRegression()
        lr.fit(X_train, y_train)
        predictions = lr.predict(X_test)

        # Calculate mse and variance values for this fold.
        mse = mean_squared_error(y_test, predictions)
        var = np.var(predictions)

        # Append to arrays to do calculate overall average mse and variance values.
        variance_values.append(var)
        mse_values.append(mse)

    # Compute average mse and variance values.
    avg_mse = np.mean(mse_values)
    avg_var = np.mean(variance_values)
    return(avg_mse, avg_var)

two_mse, two_var = train_and_cross_val(["cylinders", "displacement"])
three_mse, three_var = train_and_cross_val(["cylinders", "displacement", "horsepower"])
four_mse, four_var = train_and_cross_val(["cylinders", "displacement", "horsepower", "weight"])
five_mse, five_var = train_and_cross_val(["cylinders", "displacement", "horsepower", "weight", "acceleration"])
six_mse, six_var = train_and_cross_val(["cylinders", "displacement", "horsepower", "weight", "acceleration", "model year"])
seven_mse, seven_var = train_and_cross_val(["cylinders", "displacement", "horsepower", "weight", "acceleration","model year", "origin"])

print('The MSE and Variance of Two features are: '+ str(two_mse)+' and '+str(two_var) )
print('The MSE and Variance of Three features are: '+ str(three_mse)+' and '+str(three_var) )
print('The MSE and Variance of four features are: '+ str(four_mse)+' and '+str(four_var) )
print('The MSE and Variance of five features are: '+ str(five_mse)+' and '+str(five_var) )
print('The MSE and Variance of six features are: '+ str(six_mse)+' and '+str(six_var) )
print('The MSE and Variance of seven features are: '+ str(seven_mse)+' and '+str(seven_var) )

## 6. Plotting cross-validation error vs. cross-validation variance ##

# We've hidden the `train_and_cross_val` function to save space but you can still call the function!
import matplotlib.pyplot as plt

two_mse, two_var = train_and_cross_val(["cylinders", "displacement"])
three_mse, three_var = train_and_cross_val(["cylinders", "displacement", "horsepower"])
four_mse, four_var = train_and_cross_val(["cylinders", "displacement", "horsepower", "weight"])
five_mse, five_var = train_and_cross_val(["cylinders", "displacement", "horsepower", "weight", "acceleration"])
six_mse, six_var = train_and_cross_val(["cylinders", "displacement", "horsepower", "weight", "acceleration", "model year"])
seven_mse, seven_var = train_and_cross_val(["cylinders", "displacement", "horsepower", "weight", "acceleration","model year", "origin"])

plt.scatter([2,3,4,5,6,7], [two_mse, three_mse, four_mse, five_mse, six_mse, seven_mse], c='red')
plt.scatter([2,3,4,5,6,7], [two_var, three_var, four_var, five_var, six_var, seven_var], c='blue')
plt.show()

6. Machine Learning Introduction/machine-learning-intermediate/Overfitting-132.py


'''To integrate any user defined function f, or any other function in NumPy, call:
int_simpson(function, lower_limit, upper_limit, num_of_intervals_of_integration)
'''
import numpy as np
def int_simpson(f,a,b,N):
	w=(b-a)/N
	area=(w/3)*(f(a)+f(b))
	i=a
	for i in range(1,N):
		if i%2==0:
			area+=(w/3)*2*f(a+i*w)
		else:
			area+=(w/3)*4*f(a+i*w)
	return area

#print(int_simpson(np.sin,0,(np.pi)/2,100))


import numpy as np

def computeNumericalGradient(J, theta):
  """ Compute numgrad = computeNumericalGradient(J, theta)

  theta: a matrix of parameters
  J: a function that outputs a real-number and the gradient.
  Calling y = J(theta)[0] will return the function value at theta.
  """

  # Initialize numgrad with zeros
  numgrad = np.zeros(theta.shape)

  ## ---------- YOUR CODE HERE --------------------------------------
  # Instructions:
  # Implement numerical gradient checking, and return the result in numgrad.
  # You should write code so that numgrad[i][j] is (the numerical approximation to) the
  # partial derivative of J with respect to theta[i][j], evaluated at theta.
  # I.e., numgrad[i][j] should be the (approximately) partial derivative of J with
  # respect to theta[i][j].
  #
  # Hint: You will probably want to compute the elements of numgrad one at a time.

  # Set Epsilon
  epsilon = 0.0001

  # Outer for loop to check across the x-axis
  for i in range(theta.shape[0]):
    # Inner for loop to check across the y-axis
    for j in range(theta.shape[1]):
      # Copy current theta value to min
      theta_min = theta.copy()
      # Subtract min point by epsilon and store
      theta_min[i,j] = theta_min[i,j] - epsilon
      # Not sure
      cost_min, dW, db = J(theta_min)
      # Copy current theta for max
      theta_max = theta.copy()
      # Add max point by epsilon and store
      theta_max[i,j] = theta_max[i,j] + epsilon
      # ?
      cost_max, dW, db = J(theta_max)

      # Final Result for gradient k
      numgrad[i][j] = (cost_max - cost_min) / (2 * epsilon)

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

  return numgrad


import numpy as np
import math

# Write a function that takes as input a list of numbers, and returns
# the list of values given by the softmax function.
def softmax(L):
    softmax_values = []
    denominator = 0
    for i in L:
        denominator += math.exp(i)
    for i in L:
        softmax_values.append(math.exp(i)/denominator)
    return softmax_values

"""
def softmax(L):
    expL = np.exp(L)
    sumExpL = sum(expL)
    result = []
    for i in expL:
        result.append(i*1.0/sumExpL)
    return result

"""

if __name__ == "__main__":
    L = [-1.0, 2.0, 0.5, 1.2]
    probs = softmax(L)
    print(probs)


import math
import numpy as np

def gauss(A, b, pivoteo=None):
    if A.shape[0] != A.shape[1]:
        print("Error, matriz no es cuadrada")
        return

    Ab = np.zeros((len(A), len(A) + 1))
    Ab[:, :-1] = A
    Ab[:, -1] = b

    n = Ab.shape[0]
    mark = list(range(n))

    procedimiento = []

    for row in range(n):
        err = None
        if pivoteo == "parcial":
            Ab, err = pivoteo_parcial(Ab, row)
        elif pivoteo == "total":
            Ab, mark, err = pivoteo_total(Ab, row, mark)
        if err:
            print(err)

        for col in range(row + 1, n):
            M = Ab[col, row] / Ab[row, row]
            for row_i in range(row, n + 1):
                Ab[col, row_i] -= M * Ab[row, row_i]

            procedimiento.append(Ab.copy().tolist())

    x = sustitucion_regresiva(Ab)
    x = [x[mark[i]] for i in range(n)]

    error = A.dot(x) - b
    return x, Ab.tolist(), error.tolist(), procedimiento

def LU(A, b, pivoteo=None):
    n = A.shape[0]
    P = np.eye(n)
    L = np.eye(n)

    for k in range(n - 1):

        if pivoteo == "parcial":
            A, P = pivoteoLU(A, P, k)

        for i in range(k + 1, n):
            M = A[i, k] / A[k, k]
            for j in range(k, n):
                A[i, j] -= M * A[k, j]

            A[i, k] = M

    U = np.triu(A)
    L = L + np.tril(A, -1)
    B = P.dot(b)

    LB = np.zeros((len(L), len(L) + 1))
    LB[:, :-1] = L
    LB[:, -1] = b

    z = sustpro(LB)

    Uz = np.zeros((len(U), len(U) + 1))
    Uz[:, :-1] = U
    Uz[:, -1] = z

    x = sustreg(Uz)

    return x, L, U

def LUdirecto(A, met):
    n = A.shape[0]
    U = np.eye(n)
    L = np.eye(n)

    for k in range(n):
        sum1 = sum(L[k, p] * U[p, k] for p in range(k))
        if met == 0:
            U[k, k] = (A[k, k] - sum1) / L[k, k]
        elif met == 1:
            L[k, k] = (A[k, k] - sum1) / U[k, k]
        else:
            U[k, k] = np.sqrt(A[k, k] - sum1)
            L[k, k] = U[k, k]

        for i in range(k, n):
            sum2 = sum(L[i, p] * U[p, k] for p in range(k))
            L[i, k] = (A[i, k] - sum2) / U[k, k]

        for j in range(k, n):
            sum3 = sum(L[k, p] * U[p, j] for p in range(k))
            U[k, j] = (A[k, j] - sum3) / L[k, k]

    return L, U

def pivoteoLU(A, P, row):
    n = A.shape[0]

    mayor = abs(A[row, row])
    maxrow = row
    for i in range(row + 1, n):
        if abs(A[i, row]) > mayor:
            mayor = abs(A[i, row])
            maxrow = i

    if mayor == 0:
        return A, P, "El sistema no tiene solución única"

    if maxrow != row:  # Intercambio de filas
        aux = A[row, :]
        auxP = P[row, :]
        A[row, :] = A[maxrow, :]
        P[row, :] = P[maxrow, :]
        A[maxrow, :] = aux
        P[maxrow, :] = auxP

    return A, P, None

def mathJacobiSeidel(x0, A, b, tol, n_iter_max, method):

    D = np.diag(np.diag(A))
    L = -np.tril(A, -1)
    U = -np.triu(A, +1)

    message = ""
    errores = [tol + 1]
    itera = [x0]

    for _ in range(n_iter_max):
        if errores[-1] <= tol:
            message = f"{x0} es una aproximación de la solución del sistema con una tolerancia= {tol}"
            break

        if method == "jacobi":
            T = np.linalg.inv(D).dot(L + U)
            C = np.linalg.inv(D).dot(b)
            x1 = T.dot(x0) + C
        elif method == "seidel":
            T = np.linalg.inv(D - L).dot(U)
            C = np.linalg.inv(D - L).dot(b)
            x1 = T.dot(x0) + C

        # errores.append(np.linalg.norm(x1 - x0))
        itera.append(x0)
        x0 = x1
    else:
        message = f"Fracasó en {n_iter_max} iteraciones"

    return errores, x0, message, itera

def iterativos(x0, A, b, tol, n_iter_max, method):

    message = ""
    errores = [tol + 1]

    for _ in range(n_iter_max):
        if errores[-1] <= tol:
            message = f"{x0} es una aproximación de la solución del sistema con una tolerancia= {tol}"
            break

        x1 = new_jacobi(x0, A, b) if method == "jacobi" else new_seidel(x0, A, b)
        errores.append(np.linalg.norm(x1 - x0))
        x0 = x1.copy()
    else:
        message = f"Fracasó en {n_iter_max} iteraciones"

    return x0.tolist(), message, errores

def new_jacobi(x0, A, b):
    n = A.shape[0]
    x1 = x0.copy()
    for i in range(n):
        suma = sum(A[i, j] * x0[j] for j in range(n) if j != i)
        x1[i] = (b[i] - suma) / A[i, i]

    return x1

def new_seidel(x0, A, b):

    n = A.shape[0]
    x1 = x0.copy()
    for i in range(n):
        suma = sum(A[i, j] * x1[j] for j in range(n) if j != i)
        x1[i] = (b[i] - suma) / A[i, i]

    return x1

def sustitucion_regresiva(Ab):
    n = Ab.shape[0] - 1

    x = np.zeros(n + 1)
    x[n] = Ab[n, n + 1] / Ab[n, n]
    for i in range(n - 1, -1, -1):
        suma = sum(Ab[i, j] * x[j] for j in range(i + 1, n + 1))
        x[i] = (Ab[i, -1] - suma) / Ab[i, i]

    return x

def sustitucion_progresiva(Ab):
    n = Ab.shape[0] - 1
    x = np.zeros(n + 1)
    x[0] = Ab[0, n + 1] / Ab[1, 1]

    for i in range(1, n):
        suma = sum(Ab[i, j] * x[j] for j in range(i - 1))
        x[i] = (Ab[i, n + 1] - suma) / Ab[i, i]

    return x

def pivoteo_parcial(Ab, row):
    n = Ab.shape[0]

    mayor = abs(Ab[row, row])
    max_row = row
    for i in range(row + 1, n):
        if abs(Ab[i, row]) <= mayor:
            continue

        mayor = abs(Ab[i, row])
        max_row = i

    if mayor == 0:
        return Ab, "El sistema no tiene solución única"
    elif max_row != row:
        Ab[[row, max_row]] = Ab[[max_row, row]]

    return Ab, None

def pivoteo_total(Ab, row, mark):
    n = Ab.shape[0]

    mayor = 0
    max_row = row
    max_col = row
    for row_i in range(row, n):
        for col_i in range(row, n):
            if abs(Ab[row_i, col_i]) > mayor:
                mayor = abs(Ab[row_i, col_i])
                max_row = row_i
                max_col = col_i

    if mayor == 0:
        return Ab, mark, "El sistema no tiene solución única"
    if max_row != row:
        aux = Ab[row, :].copy()
        Ab[row, :] = Ab[max_row, :]
        Ab[max_row, :] = aux
    if max_col != row:
        aux = Ab[:, row].copy()
        Ab[:, row] = Ab[:, max_col]
        Ab[:, max_col] = aux
        aux2 = mark[row]
        mark[row] = mark[max_col]
        mark[max_col] = aux2

    return Ab, mark, None


# shows how linear regression analysis can be applied to moore's law
#
# notes for this course can be found at:
# https://deeplearningcourses.com/c/data-science-linear-regression-in-python
# https://www.udemy.com/data-science-linear-regression-in-python
# transistor count from: https://en.wikipedia.org/wiki/Transistor_count

from __future__ import print_function, division
from builtins import range
# Note: you may need to update your version of future
# sudo pip install -U future

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

X = []
Y = []

# some numbers show up as 1,170,000,000 (commas)
# some numbers have references in square brackets after them
non_decimal = re.compile(r'[^\d]+')

for line in open('moore.csv'):
    r = line.split('\t')

    x = int(non_decimal.sub('', r[2].split('[')[0]))
    y = int(non_decimal.sub('', r[1].split('[')[0]))
    X.append(x)
    Y.append(y)

X = np.array(X)
Y = np.array(Y)

plt.scatter(X, Y)
plt.show()

Y = np.log(Y)
plt.scatter(X, Y)
plt.show()

# copied from lr_1d.py
denominator = X.dot(X) - X.mean() * X.sum()
a = ( X.dot(Y) - Y.mean()*X.sum() ) / denominator
b = ( Y.mean() * X.dot(X) - X.mean() * X.dot(Y) ) / denominator

# let's calculate the predicted Y
Yhat = a*X + b

plt.scatter(X, Y)
plt.plot(X, Yhat)
plt.show()

# determine how good the model is by computing the r-squared
d1 = Y - Yhat
d2 = Y - Y.mean()
r2 = 1 - d1.dot(d1) / d2.dot(d2)
print("a:", a, "b:", b)
print("the r-squared is:", r2)

# how long does it take to double?
# log(transistorcount) = a*year + b
# transistorcount = exp(b) * exp(a*year)
# 2*transistorcount = 2 * exp(b) * exp(a*year) = exp(ln(2)) * exp(b) * exp(a * year) = exp(b) * exp(a * year + ln(2))
# a*year2 = a*year1 + ln2
# year2 = year1 + ln2/a
print("time to double:", np.log(2)/a, "years")


import scipy.integrate as spi
import numpy as np

print('Triple integral computed by SciPy nquad')
print('Example 3-01 nquad')
print('Integral of x + yz^2 from z=1 to z=2, y=z+1 to y=z+2 and from x=y+x to x=2(y+z)')

integrand = lambda x, y, z : x + y * z ** 2

bounds_z = lambda : [1., 2.]
bounds_y = lambda z : [z+1, z+2]
bounds_x = lambda z, y : [y+z, 2 * (y+z)]
ya=lambda z: z + 1
yb=lambda z: z + 2
xa=lambda z, y : y + z
xb=lambda z, y : 2 * (y + z)

result, error = spi.nquad(integrand, [bounds_x, bounds_y, bounds_z])
print('Result is ', result, 'with error ', error)


import numpy as np
from numpy import linalg
from numpy import random
from scipy import integrate
from scipy.optimize import curve_fit

##########################################################
#gauusian_weight
#compute a gaussian weight matrix W, given a vector x
#the reference observation point i (index) and a bandwith
#tau
##########################################################

def gaussian_weight(x, i, tau):
    #compute the vector of weights
    w = np.exp(-((x-x[i])**2.)/(tau))
    #transform into a diagonal array
    W = np.diag(w)
    return W

#########################################################
#Weighted linaer regression
#perform linear regression given a matrix of inputs
#X, whose rows correspond to observations and whose columns
#correspond to features and a vector of outputs
#y
#inputs:
#X - matrix of inputs (mXn) with m features and n observations
#y - vector of ouput observations (size n)
#W - n X n weight matrix
#ouputs:
#theta - vector of best-fit parameters
#########################################################

def weighted_linear_reg(X,y,W):
    m1 = linalg.inv(np.dot(np.dot(X.T,W),X))
    m2 = np.dot(np.dot(X.T,W),y)

    theta = np.dot(m1,m2)
    return theta

#####################################################################
#Loess
#Compute a locally weighted linear regression given a vector
#of inputs x and a vector of outputs y
#Inputs
#x - vector of inputs
#y - vector of ouputs
#degree - degree of polynomial for fit (1 or 2)
#alpha - smoothing parameter. Defined as the ratio of tau**2. divided
#by the square in the difference between the two extrema in x

def loess(x,y,degree,alpha):
    n = len(x)
    #######################################################
    #First degree polynomial regression
    #######################################################
    if degree == 1:

        #First feature in the X array is a constant
        #Second feature is the value of x
        const = np.ones(n)
        X = np.array([const,x]).T
        W = np.identity(n)
        tau = alpha * np.sqrt((x[0]-x[-1])**2.)

        #Construct the theta matrix
        Theta = np.zeros([2,n])
        #Construct the prediction vector
        yp = np.zeros(n)
        #Loop over observations
        for i in range(n):
            #compute the weight matrix
            W = gaussian_weight(x,i,tau)
           #perform linear regression
            theta = weighted_linear_reg(X,y,W)
            Theta[:,i] = theta
            yp[i] = np.dot(X[i,:],theta)
        return [Theta,yp]

    #######################################################
    #Second degree polynomial regression
    #######################################################
    if degree == 2:

        #First feature in the X array is a constant
        #Second feature is the value of x
        #Third feature is the value of x**2.
        const = np.ones(n)
        X = np.array([const,x,x**2.]).T
        W = np.identity(n)
        tau = alpha * np.sqrt((x[0]-x[-1])**2.)

        #Construct the theta matrix
        Theta = np.zeros([3,n])

        #Construct the prediction vector
        yp = np.zeros(n)
        #Loop over observations
        for i in range(n):
            #compute the weight matrix
            W = gaussian_weight(x,i,tau)
            #perform linear regression
            theta = weighted_linear_reg(X,y,W)
            Theta[:,i] = theta
            yp[i] = np.dot(X[i,:],theta)
        return [Theta,yp]

##################################
#Numerical Laplace transform
##################################
#Inputs
#t - time vector
#f - vector of discrete values of function to be transformed
#S - vector of Laplace frequencies to evaluate the transform at

def laplace(t, f, S):
    L = np.zeros(len(S))
    for i, s in enumerate(S):
        y = f*np.exp(-s*t)
        L[i] = integrate.trapz(y, t)
    return L

def get_cross_validation_score(t, y, func, p0=None):
    """ Obtain the leave-one-out cross-validation score for
    a functional model of a data set over a given fitting window.

    Parameters
    ----------
    t : 1-d array
        Length N vector  of values for the independent variable of a dataset.
    y : 1-d array
        Length N vector of values for the dependent variable of a dataset.
    func : callable function
           Model to fit the data against. Must be of the form
           `f(t, p1, p2, ..., pM)` where `t` is the independent variable and
           `p1, p2, ..., pM` is a set of M parameters to fit.

    p0 : 1-d array or list, `optional`
         Initial guesses for the M parameters to fit, [p1, p2, ..., pM]

    Returns
    -------
    cv : float
         Leave-one-out cross-validation score for the model
    """
    cv = 0.
    yskip = y
    tskip = t
    n = len(tskip)
    # Loop over each (t, y) point and perform a
    # fit to func with that point removed
    # calculate MSE for that point
    for i in range(n):
        ytest = np.delete(yskip, i)
        ttest = np.delete(tskip, i)
        try:
            paramsi = curve_fit(func, ttest, ytest, p0=p0, maxfev=10000)[0]
            yfiti = func(tskip[i], *paramsi)
            erri = (yskip[i]-yfiti)**2.
        except RuntimeError:
            erri = 1.e3
        cv = cv + erri
    # Average cv scores
    cv = cv/np.float(n)

    # If fit is not found for this window, penalize strongly
    try:
        paramsi = curve_fit(func, t, y, p0=p0, maxfev=10000)[0]
    except RuntimeError:
        cv = 1.e6
    return cv

def minimize_cv_error(t, y, twindows, func, p0=None):
    """ Find the fitting interval that minimizes the cross-validation
    error for a model fitted to a sub-interval of a dataset,
    given a set of possible intervals in the independent variable
    over which to perform the fit.

    Parameters
    ----------
    t : 1-d array
        Length N vector  of values for the independent variable of a dataset.
    y : 1-d array
        Length N vector of values for the dependent variable of a dataset.
    twindows : List of len 2 lists
               List of the form [[t0, tend1], [t0, tend2], ...]
               where [t0, tendi] represents the `ith` closed interval over which to
               fit the data set and find the cross-validation score.
    func : callable function
           Model to fit the data against. Must be of the form
           `f(t, p1, p2, ..., pM)` where `t` is the independent variable and
           `p1, p2, ..., pM` is a set of M parameters to fit.

    p0 : 1-d array or list, `optional`
         Initial guesses for the M parameters to fit, [p1, p2, ..., pM]

    Returns
    -------
    twindow_min : List
                  List of the form `[t0, tend]' where t0 and tend are the
                  beginning and end of the closed interval in ``t`` that
                  provides the lowest cross-validation score for the fitted
                  model
    pmin : list
           Optimal parameters for fitting the model ``func`` to the data
           over the sub-interval ``twindow_min``
    CV_min : float
             Leave-one-out cross-validation error for the model ``func``
             over the interval ``twindow_min``
    """
    CVs = []
    params = []
    for twindow in twindows:
        tinds = [np.argmin(np.abs(t-twindow[0])),
                 np.argmin(np.abs(t-twindow[1]))]
        tfit = t[tinds[0]: tinds[1]+1]
        yfit = y[tinds[0]: tinds[1]+1]
        CVs.append(get_cross_validation_score(tfit, yfit, func, p0))
        try:
            paramsi = curve_fit(func, tfit, yfit, p0=p0, maxfev=100000)[0]
            params.append(paramsi)
        except RuntimeError:
            params.append(None)
    CV_argmin = np.argmin(CVs)
    CV_min = CVs[CV_argmin]
    twindow_min = twindows[CV_argmin]
    pmin = params[CV_argmin]
    return [twindow_min, pmin, CV_min]

def laplace_merge(omega, G1, G2, G1_Fdirect, G2_Fdirect):
    """ Find the interval where the Laplace transform is valid over
    the frequency space and replace the Fourier transform data with
    the Laplace transform modulus.

    Parameters
    ----------
    omega : 1-d array
            Vector of angular frequencies in units of rad/s
    G1 : 1-d array
         Storage modulus at angular frequencies ``omega`` in units of Pa
         calculated using Fourier transform
    G2 : 1-d array
         Loss modulus at angular frequencies ``omega`` in units of Pa
         calculated using Fourier transform
    G1_Fdirect: 1-d array
                Storage modulus at angular frequencies ``omega`` in units
                of Pa calculated using direct Laplace transform
    G2_Fdirect: 1-d array
                Loss modulus at angular frequencies ``omega`` in units
                of Pa calculated using direct Laplace transform

    Returns
    -------
    G1_plot : 1-d array
              Loss modulus at angular frequencies ``omega`` in units of Pa
              after merging Laplace and Fourier transform moduli
    G2_plot : 1-d array
              Storage  modulus at angular frequencies ``omega`` in units of
              Pa after merging Laplace and Fourier transform moduli
    """
    G1_lower = 0
    G2_lower = 0
    G1_upper = len(omega) - 1
    G2_upper = len(omega) - 1
    for n in range(len(omega)-1):
        if (G1[n]-G1_Fdirect[n])/np.abs(G1[n]-G1_Fdirect[n]) != (G1[n+1]-G1_Fdirect[n+1])/np.abs(G1[n+1]-G1_Fdirect[n+1]):
            G1_lower = n
            break
    for n in range(len(omega)-1):
        if (G2[n]-G2_Fdirect[n])/np.abs(G2[n]-G2_Fdirect[n]) != (G2[n+1]-G2_Fdirect[n+1])/np.abs(G2[n+1]-G2_Fdirect[n+1]):
            G2_lower = n
            break
    for n in range(len(omega)-1):
        if (G1[len(omega)-1-n]-G1_Fdirect[len(omega)-1-n])/np.abs(G1[len(omega)-1-n]-G1_Fdirect[len(omega)-1-n]) != (G1[len(omega)-2-n]-G1_Fdirect[len(omega)-2-n])/np.abs(G1[len(omega)-2-n]-G1_Fdirect[len(omega)-2-n]):
            G1_upper = len(omega)-1-n
            break
    for n in range(len(omega)-1):
        if (G2[len(omega)-1-n]-G2_Fdirect[len(omega)-1-n])/np.abs(G2[len(omega)-1-n]-G2_Fdirect[len(omega)-1-n]) != (G2[len(omega)-2-n]-G2_Fdirect[len(omega)-2-n])/np.abs(G2[len(omega)-2-n]-G2_Fdirect[len(omega)-2-n]):
            G2_upper = len(omega)-1-n
            break

    G1_plot = np.zeros_like(G1)
    G2_plot = np.zeros_like(G2)
    G1_plot[0:G1_lower] = G1[0:G1_lower]
    G1_plot[G1_lower:G1_upper] = G1_Fdirect[G1_lower:G1_upper]
    G1_plot[G1_upper:] = G1[G1_upper:]
    G2_plot[0:G2_lower] = G2[0:G2_lower]
    G2_plot[G2_lower:G2_upper] = G2_Fdirect[G2_lower:G2_upper]
    G2_plot[G2_upper:] = G2[G2_upper:]
    return G1_plot, G2_plot


import numpy as np
class loss_Function():
    def __init__(self,loss_type):
        self.loss_type=loss_type
        self.Lambda=0
        #self.pred
        #self.labels

    def log(self, X): #replace log(0)=-9999999 instead of -inf
        with np.errstate(divide='ignore'):
            res = np.log(X)
        res[np.isneginf(res)]=-9999999
        return res

    def getLambda(self):
        return self.Lambda
    def setLambda(self,Lambda):
        self.Lambda= Lambda

    def forward(self,pred,labels,weights_sum):
        self.m=labels.shape[1]
        self.pred=pred
        self.labels=labels
        self.weights_sum=weights_sum
        self.regularization_cost=((self.Lambda)/(2*self.m))*(self.weights_sum)

        if self.loss_type=="LOG":
            return (((1/self.m)*np.sum(-self.log(np.absolute((labels/2)-0.5+pred))))+self.regularization_cost)

        elif self.loss_type=="MEAN":
            return ((np.sum(np.power(pred-labels,2))/(2*self.m))+self.regularization_cost)

        elif self.loss_type=="CROSSENTROPY":
            return (((-1/self.m)*np.sum(np.multiply(labels, self.log(pred))+np.multiply(1-labels, self.log(1-pred))))+self.regularization_cost)

        elif self.loss_type=="SoftmaxCrossEntropy":
            exp = np.exp(pred) # exp of all output nodes
            sums = np.sum(exp, axis=0) # sum of exponentials of each example
            softmax = exp/sums # softmax output for all nodes
            self.softmax = softmax # cache softmax
            L = -1*self.log(softmax[labels==1]) # individual losses for each example
            return (((1/self.m)*np.sum(L))+self.regularization_cost)

    def backward(self):
        if self.loss_type=="LOG":
            return (((-1/self.pred)*((1+self.labels)/2)) + ((1/(1-self.pred))*((1-self.labels)/2)))

        elif self.loss_type=="MEAN":
            return (self.pred-self.labels)

        elif self.loss_type=="CROSSENTROPY":
            return ((-self.labels/self.pred)+(1-self.labels/1-self.pred))

        elif self.loss_type=="SoftmaxCrossEntropy":
            return (self.softmax - self.labels)

#lossfn=Loss_Functions('MEAN')
#pred=np.array([[1,1,1]])
#labels=np.array([[1,1,-1]])

#loss=lossfn.forward(pred, labels)
#grad=lossfn.backward()
#print(loss)
#print(grad)


import numpy as np

def describe_error_disparity(diff_vector, absolute=None, badn_error=[.5, 1, 2, 3, 4, 5]):

    metrics = describe_error_depth(diff_vector, absolute=absolute)

    abs_diff_vector = np.abs(diff_vector)
    metrics['EPE'] = np.mean(abs_diff_vector)
    for n in badn_error:
        metrics['Bad'+str(n)] = (np.sum(abs_diff_vector>n)/len(abs_diff_vector))*100
    return metrics

def describe_error_depth(diff_vector, absolute=None):
    metrics={}
    metrics['RMS'] = np.sqrt(np.sum((diff_vector)**2)/len(diff_vector))
    if (absolute is None) or (absolute == True):
        abs_diff_vector = np.abs(diff_vector)
        metrics['Mean (Absolute)'] = np.mean(abs_diff_vector)
        metrics['Median (Absolute)'] = np.median(abs_diff_vector)
        metrics['Std (Absolute)'] = np.std(abs_diff_vector)
        metrics['Max (Absolute)'] = np.amax(abs_diff_vector)
        metrics['Min (Absolute)'] = np.amin(abs_diff_vector)
        metrics['Q1 (Absolute)'] = np.percentile(abs_diff_vector,25)
        metrics['Q3 (Absolute)'] = np.percentile(abs_diff_vector,75)

    if (absolute is None) or (absolute == False):
        metrics['Mean'] = np.mean(diff_vector)
        metrics['Median'] = np.median(diff_vector)
        metrics['Std'] = np.std(diff_vector)
        metrics['Max'] = np.amax(diff_vector)
        metrics['Min'] = np.amin(diff_vector)
        metrics['Q1'] = np.percentile(diff_vector,25)
        metrics['Q3'] = np.percentile(diff_vector,75)
    return metrics


# -*- coding: utf-8 -*-
"""
Created on 21/05/2021
@author: Paschalis Giakoumoglou 10054
@author: Christos Kyratsous 10105
"""

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

def f(x):
    return math.exp(3*x)*math.sin(2*x)

def rectLeft(f, a, b, N):
    h = (b-a)/N
    tot = 0
    for i in range(N):
        tot += f(a+i*h)
    return tot*h

def rectRight(f, a, b, N):
    h = (b-a)/N
    tot = 0
    for i in range(N):
        tot += f(a+i*h+h)
    return tot*h

def trapezoid(f, a, b, N):
    h = (b - a) / N
    tot = f(a)+f(b)
    for i in range(1, N):
        tot += 2 * f(a + i * h)
    return (h / 2) * tot

def simp(f, a,b,N):
    h = (b-a)/N
    tot = f(a) + f(b)
    for i in range(1,N,1):
        tot += f(a+i*h)*(2*(i%2 == 0) + 4*(i%2 == 1))
    return tot*h/3

def calcError(f, a, b, maxErrors, realResult):
    val = np.arange(10,1010,10)
    val = val.astype(np.int64)
    exponents = np.array([1,1,2,4])
    x = np.zeros((100,1), np.double)
    y = np.zeros((100,1), np.double)
    z = np.zeros((100,1), np.double)
    name = ["Left Rectangle", "Right Rectangle", "Trapezoid", "Simpson"]
    for counter, fun in enumerate([rectLeft, rectRight, trapezoid, simp]):
        for i, N in enumerate(val):
            y[i] = math.log10(((abs(fun(f ,a, b, N)-realResult))))
            x[i] = math.log10((b-a)/N)
            z[i] = math.log10(maxErrors[counter]/val[i]**exponents[counter])

        plt.xlabel("Values of h")
        plt.ylabel("Absolute Error")
        plt.plot(x, y, marker = 'o')
        plt.plot(x, z, marker = 'o')
        plt.title(name[counter])
        plt.show()

realResult = 3/13*math.exp(3*math.pi/4)+2/13
a = 0
b = math.pi/4
maxErrors = np.array([(((b-a)**2)/2)*3*math.exp(3*math.pi/4),
                      (((b-a)**2)/2)*3*math.exp(3*math.pi/4),
                      ((b-a)**3)/12 * 5 * math.exp(3*math.pi/4),
                      ((b-a)**5/180) * 119 * math.exp(3*math.pi/4)])
calcError(f, a, b, maxErrors, realResult)


import numpy as np
import scipy as sp

def E_sq(z, om0):
    """
    A function giving Hubble's law for flat cosmology

    :param z: redshift value
    :param om0: matter density
    :return: A value for the Hubble parameter
    """
    return om0 * (1 + z) ** 3 + 1 - om0

def f_integrand(z, om0):
    """
    A function for the redshift integrand in the intrinsic alignment calculation

    :param z: redshift value
    :param om0: matter density
    :return: redshift integrand
    """
    return (z + 1) / (E_sq(z, om0)) ** 1.5

def D_single(z, om0):
    """
    Provides the normalised linear growth factor

    :param z: single redshift value
    :param om0: matter density
    :return: normalised linear growth factor
    """
    first_integral = sp.integrate.quad(f_integrand, z, np.inf, args=(om0))[0]
    second_integral = sp.integrate.quad(f_integrand, 0, np.inf, args=(om0))[0]

    return (E_sq(z, om0) ** 0.5) * first_integral / second_integral

def D_1(z, om0):
    """
    Normalised linear growth factor (D_plus)

    :param z: single redshift value or array values
    :param om0: matter density
    :return: normalised linear growth factor
    """

    if (isinstance(z, float)) or (isinstance(z, int)):
        D_values = D_single(z, om0)
    else:
        z = list(z)
        D_values = [D_single(z[i], om0) for i in range(len(z))]
        D_values = np.array(D_values)

    return D_values

def F_nla(z, om0, A_ia, rho_c1, eta=0., z0=0., lbar=0., l0=1e-9, beta=0.):
    """
    NLA intrinsic alignment amplitude

    :param z: redshift value
    :param om0: matter density
    :param A_ia: amplitude parameter
    :param rho_c1: rho_crit x C1 (C1 approx 1.508e+27 cm3 / g)
    :param eta: redshift dependence
    :param z0: arbitrary redshift pivot parameter
    :param lbar: average luminosity of source galaxy population
    :param l0: arbitrary luminosity pivot parameter
    :param beta: luminosity dependence
    :return: NLA F(z) amplitude
    """

    prefactor = - A_ia * rho_c1 * om0
    inverse_linear_growth = 1. / D_1(z, om0)
    redshift_dependence = ((1+z)/(1+z0))**eta
    luminosity_dependence = (lbar/l0)**beta

    return prefactor * inverse_linear_growth * redshift_dependence * luminosity_dependence


# numpy <- it is useful to calculate array and matrix
import numpy as np

x = np.array([1.0, 2.0, 3.0])
print(x / 2.0)
# numpy.ndarray
print(type(x))

# calculate array <- it is necessary to be element-wise
y = np.array([2.0, 4.0, 6.0])
print(x + y)
print(x - y)
print(x * y)
print(x / y)

A = np.array([[1, 2], [3, 4]])
print(A)
# 2 × 2
print(A.shape)
# int64
print(A.dtype)

# calculate matrix
B = np.array([[3, 0], [0, 6]])
print(A + B)
print(A * 10)

# numpy can create N array
# in math, we call
# 1 array to vector
# 2 array to array
# more then 3 array to tensor

# in calculate A * 10, 10 becomes 2 × 2
# it is called by broadcast
# so we can calculate different size array

C = np.array([[1, 2], [3, 4]])
D = np.array([10, 20])
print(C * D)

# to access ndarray
X = np.array([[51, 55], [14, 19], [0, 4]])
print(X)
print(X[0])
print(X[0][1])

for row in X:
    print(row)

# n array to vector
X = X.flatten()
print(X)
# to get index number 0, 2, 4
print(X[np.array([0, 2, 4])])
# so we can get element by condition
# X > 15 will be the result array [True, True, False, True, False, False, dtype=bool]
print(X > 15)
print(X[X > 15])


# Code made for : Sergio Andrés Díaz Ariza
# Specialization--> Statistics in Python Coursera
# 23 April, 2021
# License MIT

import numpy as np
import pandas as pd
from scipy.stats import t

pd.set_option('display.max_columns', 30)  # set so can see all columns of the DataFrame

# Import the data
df = pd.read_csv("nap_no_nap.csv")

# First, look at the DataFrame to get a sense of the data
print(df.head())

############################################################################
# Average bedtime confidence interval for napping and non napping toddlers #
# Create two 95% confidence intervals for the average bedtime, one for     #
# toddler who nap and one for toddlers who don't.                          #
# ##########################################################################

# First, isolate the column 'night bedtime' for those who nap into a new variable, and those who didn't nap
# into another new variable.

df["bedtime_nap"] = df["night bedtime"].where(df["napping"] == 1)
df["bedtime_no_nap"] = df["night bedtime"].where(df["napping"] == 0)
dx = df[["bedtime_nap", "bedtime_no_nap"]]  # replace(np.nan, 0)  # fillna(0)

# Now find the sample mean bedtime for nap and no_nap.

# Group by the Data
nap_mean_bedtime = dx.bedtime_nap.mean()
no_nap_mean_bedtime = dx.bedtime_no_nap.mean()
print("Mean of the toddlers nap:\t{}".format(nap_mean_bedtime))
print("Mean of the toddlers no_nap:\t{}".format(no_nap_mean_bedtime))

# Find the t_stars for the 95% confidence intervals

# Careful the t-distribution has two tails, then you dont do (1 - 0.05)= 0.95 instead (1 - 0.05 / 2)
nap_t_star = t.ppf(1 - 0.05 / 2, dx.bedtime_nap.count()-1)
no_nap_t_star = t.ppf(1 - 0.05 / 2, dx.bedtime_no_nap.count()-1)

print("t* of the interval toddlers nap:\t{}".format(nap_t_star))
print("t* of the interval toddlers no_nap:\t{}".format(no_nap_t_star))

# Now to create our confidence intervals. For the average bedtime for nap and no nap,
# find the upper and lower bounds for the respective 95% confidence intervals.

# Standard Error for each Population of the mean
sd_nap = dx["bedtime_nap"].std()  # standard Deviation
sd_no_nap = dx["bedtime_no_nap"].std()
se_nap = sd_nap / np.sqrt(dx.bedtime_nap.count())    # Is of the sample not of the populaton
se_no_nap = sd_no_nap / np.sqrt(dx.bedtime_no_nap.count())

print("Standard Error of the Napping Group:\t{}".format(se_nap))
print("Standard Error of the No-Napping Group:\t{}".format(se_no_nap))

# Confidence Intervals
lcb_nap = nap_mean_bedtime - nap_t_star * se_nap
ucb_nap = nap_mean_bedtime + nap_t_star * se_nap
lcb_no_nap = no_nap_mean_bedtime - no_nap_t_star * se_no_nap
ucb_no_nap = no_nap_mean_bedtime + no_nap_t_star * se_no_nap
print("\nConfidence Interval Napping:\t({}, {})".format(lcb_nap, ucb_nap))
print("\nConfidence Interval No-Napping:\t({}, {})".format(lcb_no_nap, ucb_no_nap))

Inferential Satatistics Analysis/Introduction Confidence Intervals/Assesment/Assesment_confidence_intervals.py


"""
A module for generating parametrized utility and production functions.

"""

from numba import njit

def utility_function_factory(ν):
    """
    Create a CRRA utility function for consumtpion with degree of relative
    risk aversion `ν`.

    Parameters
    ----------
    ν : scalar(float)
        Degree of relative risk aversion

    Returns
    ----------
    utility_function : callable
        A JIT-compiled utility function that takes consumption `c` as an input
        and returns the corresponding CRRA utility.

    References
    ----------

    .. [1] https://en.wikipedia.org/wiki/Isoelastic_utility

    """

    if not ν > 1:
        raise ValueError('ν must be greater than 1.')

    @njit
    def utility_function(c):
        return c ** (1 - ν) / (1 - ν)

    return utility_function

def production_function_factory(A, σ_1, σ_2):
    """
    Create a Cobb-Douglas production function with capital, labor and
    intermediary good inputs and its corresponding derivatives.

    Parameters
    ----------
    A : scalar(float)
        Scale of production parameter.

    σ_1 : scalar(float)
        Capital share of production.

    σ_2 : scalar(float)
        Labor share of production.

    Returns
    ----------
    F : callable
        A JIT-compiled utility function that takes capital `K`, labor `L` and
        intermediate goods `M` as inputs and returns the corresponding output.

    F_K : callable
        The JIT-compiled derivative function of `F` with respect to `K`.

    F_L : callable
        The JIT-compiled derivative function of `F` with respect to `L`.

    F_M : callable
        The JIT-compiled derivative function of `F` with respect to `M`.

    References
    ----------

    .. [1] https://en.wikipedia.org/wiki/Cobb%E2%80%93Douglas_production_function

    """

    if not A > 0:
        raise ValueError("A must be strictly greater than 0.")

    if not (σ_1 > 0 and σ_1 < 1):
        raise ValueError('σ_1 must be in (0, 1).')

    if not (σ_2 > 0 and σ_2 < 1):
        raise ValueError('σ_2 must be in (0, 1).')

    @njit
    def F(K, L, M):
        return A * K ** σ_1 * L ** σ_2 * M ** (1 - σ_1 - σ_2)

    @njit
    def F_K(K, L, M):
        return A * σ_1 * K ** (σ_1 - 1) * L ** σ_2 * M ** (1 - σ_1 - σ_2)

    @njit
    def F_L(K, L, M):
        return A * σ_2 * K ** σ_1 * L ** (σ_2 - 1) * M ** (1 - σ_1 - σ_2)

    @njit
    def F_M(K, L, M):
        return A * (1 - σ_1 - σ_2) * K ** σ_1 * L ** σ_2 * M ** (-σ_1 - σ_2)

    return F, F_K, F_L, F_M


import math
from math import pi

import numpy as np

def calc_full_radial_coord(nx):
    """Calculates the radial coordinates for a single full spoke.

    Args:
        nx (int): Size of image along one side.
    """
    ind = math.floor(nx / 2)  # End point
    r = np.linspace(-ind, ind, num=nx, dtype=np.float32)
    return r

def calc_half_radial_coord(nx, ramp=False, device=-1):
    """Calculates the radial coordinates for a single half spoke.

    Args:
        nx (int): Size of image along one side.
        ramp (bool): Whether ramp sampling is included.
    """
    ind = math.floor(nx / 2)  # End point
    num_points = nx / 2
    r = np.linspace(0, ind, num=num_points, dtype=np.float32)
    # TODO: Implement ramping and ramp sampling
    return r

def unif_radial_3d(nx, num_theta):
    """Uniform 3D radial trajectory.

    `theta` is the polar angle (rotates away from z axis).
    `phi` is azimuthal angle (rotates in xy-plane)

    Args:
        nx (int): Size of image along one side.
        num_theta (int): Number of polar positions theta.

    Returns:
        traj (array): Trajectory. Shape (na, ns, 3).
    """
    r = calc_full_radial_coord(nx)

    # Calculate polar coordinates
    thetas, dtheta = np.linspace(0, pi, num=num_theta,
                                 endpoint=False, retstep=True)

    area = dtheta * dtheta
    # This is roughly the area that one sample should occupy
    # (divided by radius)

    traj = []

    for theta in thetas:
        if theta == 0:
            dphi = 2 * pi
        else:
            dphi = area / dtheta / np.sin(theta)

        num_phi = math.ceil(2 * pi / dphi / 2)

        phis = np.linspace(0, pi, num=num_phi, endpoint=False)
        # Only 0 to pi because full spoke (other half is sampled)

        for phi in phis:
            z = r * np.cos(theta)
            y = r * np.sin(theta) * np.sin(phi)
            x = r * np.sin(theta) * np.cos(phi)

            traj.append(np.stack((x, y, z), 1))

    traj = np.stack(traj, axis=0)
    return traj

def ga_radial_2d(nx, na):
    """Golden angle 2D radial trajectory.

    Args:
        nx (int): Size of image along one side.
        na (int): Number of spokes.

    Returns:
        traj (array): Trajectory. Shape (na, ns, 2).
    """
    tau = (np.sqrt(5) + 1) / 2
    golden_angle = 180 / tau
    theta_init = 90
    angles = np.linspace(theta_init, theta_init + golden_angle*(na - 1), na)

    r = calc_full_radial_coord(nx)

    traj = np.zeros((na, nx, 2), dtype=np.float32)
    thetas = np.expand_dims(np.radians(angles), axis=1)
    traj[:, :, 0] = np.cos(thetas) * r
    traj[:, :, 1] = np.sin(thetas) * r
    return traj


import numpy as np

def rsiFunc(prices, n=14):
    deltas = np.diff(prices)
    seed = deltas[:n+1]
    up = seed[seed>=0].sum()/n
    down = -seed[seed<0].sum()/n
    rs = up/down
    rsi = np.zeros_like(prices)
    rsi[:n] = 100. - 100./(1.+rs)

    for i in range(n, len(prices)):
        delta = deltas[i-1] # cause the diff is 1 shorter

        if delta>0:
            upval = delta
            downval = 0.
        else:
            upval = 0.
            downval = -delta

        up = (up*(n-1) + upval)/n
        down = (down*(n-1) + downval)/n

        rs = up/down
        rsi[i] = 100. - 100./(1.+rs)

    return rsi


import numba as nb

@nb.njit
def extgcd_recurse(a: int, b: int) -> tuple[int, int, int]:
    if not b:
        return a, 1, 0
    g, s, t = extgcd_recurse(b, a % b)
    return g, t, s - a // b * t

@nb.njit
def extgcd(a: int, b: int) -> tuple[int, int, int]:
    x0, x1, x2, x3 = 1, 0, 0, 1

    while b:
        q, r = divmod(a, b)
        x0, x1 = x1, x0 - x1 * q
        x2, x3 = x3, x2 - x3 * q
        a, b = b, r
    return a, x0, x2

@nb.njit
def gcd(a: int, b: int) -> int:
    while b:
        a, b = b, a % b
    return a

@nb.njit
def lcm(a: int, b: int) -> int:
    return a // gcd(a, b) * b


from sympy import symbols, simplify, pprint

x = symbols('x')
y = symbols('y')

rosenbrock = (1 - x)**2 + 10*(y - x**2)**2
print('rosenbrock:')
pprint(rosenbrock)
print('d_rosenbrock_dx:')
pprint(simplify(rosenbrock.diff(x)))
print('d_rosenbrock_dy:')
pprint(simplify(rosenbrock.diff(y)))
print('d2_rosenbrock_dx2:')
pprint(simplify(rosenbrock.diff(x).diff(x)))
print('d2_rosenbrock_dxy:')
pprint(simplify(rosenbrock.diff(x).diff(y)))
print('d2_rosenbrock_dy2:')
pprint(simplify(rosenbrock.diff(y).diff(y)))


import numpy as np

class LinearRegressionNumpy:
    """Linear Regresion using gradient descent algorithm.
    Implemented in numpy.
    Related equations:
    # hypothesis function: y_pred = xw + b
    # Loss function: J = (1/2m) sum_i^m (h(xi)-yi)^2
    # Take derivative of the loss function w.r.t. the parameters: dw/dJ = (1/m) sum_i^m (h(xi)-yi)x
    """

    def __init__(self):
        self.w_ = None
        self.intercept_ = None
        self.coef_ = None

    def fit(self, x, y, learning_rate=0.5, n_iterations=1000):
        """Fit the training data using Gradient Descent
        Args:
            x (np.array): Training samples, shape = [n_samples, n_features]
            y (np.array): Target samples, shape = [n_samples, n_target_values]
            learning_rate (float, optional): Learning rate. Defaults to 0.5.
            n_iterations (int, optional): Number of iterations i.e., num. of epochs. Defaults to 1000.
        """

        self.cost_ = []

        # Create weight matrix including the bias term
        np.random.seed(0)
        self.w_ = np.random.rand(x.shape[1]+1, y.shape[1])*0.01

        # Add 1 to the beginning of input vectors for the bias.
        m = x.shape[0]
        x = np.hstack((np.ones((m,1)), x))

        # Shuffle the training data.
        indices = np.arange(0, m)
        np.random.shuffle(indices)
        x = x[indices]
        y = y[indices]

        # Start of training.
        for _ in range(n_iterations):

            # hypothesis function
            y_pred = np.dot(x, self.w_)

            # residuals, i.e. errors
            residuals = y_pred - y

            # Derivatives of the loss function w.r.t weights, dw/dJ:
            gradient_vector = np.dot(x.T, residuals)

            # Update weights
            self.w_ -= (learning_rate / m) * gradient_vector

            # Save loss function. Might be useful.
            cost = np.sum((residuals ** 2)) / (2 * m)
            self.cost_.append(cost)

        # Intercept and slope (coefficients).
        self.intercept_ = self.w_[0]
        self.coef_ = self.w_[1:]

    def predict(self, x):
        """Predicts for test samples.
        Args:
            x (np.array): Test samples, shape = [n_samples, n_features]

        Returns:
            np.array: Predicted value, shape = [n_samples, n_target_values]
        """

        # Add 1 vector to the inputs for bias.
        m = x.shape[0]
        x = np.hstack((np.ones((m,1)), x))

        # Return hypothesis
        return np.dot(x, self.w_)


import numpy as np

g1 = np.array([1.5, 0.5])
g2 = np.array([1, 2])

G = np.array([[1.5, 1],
              [0.5, 2]])
print("斜角坐标系到笛卡尔坐标系的变换矩阵(列是斜角坐标系的两个基向量笛卡尔读数)\n", G)
metricsTensor = np.array([[g1 @ g1, g1 @ g2],
                          [g2 @ g1, g2 @ g2]])

print("度量张量的第一种求法：基向量点乘\n", metricsTensor)
metricsTensor = G.T @ G
print("度量张量的第二种求法：G的转置矩阵乘以G\n", metricsTensor)
print("G的转置\n", G.T)

Va = np.array([1, 1])
print("向量V的斜角坐标读数Va\n", Va)
Ve = G @ Va
print("已知斜角读数和转换矩阵G，求迪卡尔读数Ve,需要用G@Va, 或者Va@G.T, "
      "用Va@G是不可以的\n",
      G @ Va, Va @ G.T, Va @ G)
Vb = metricsTensor @ Va
print("向量V的对偶坐标读数Vb=metricsTensor@Va\n", Vb)
print("------------------------------\n")
print("度规张量的最原始解释: 将向量的斜角坐标转换成笛卡尔坐标再运算，"
      "(Va @ G.T) @ (G @ Va)\n",
      (Va @ G.T) @ (G @ Va))

# print("度规张量的最原始解释: 将向量的斜角坐标转换成笛卡尔坐标再运算，"
#       "(Va @ G.T) @ (G @ Va)\n",
#       np.matmul( np.matmul(Va, G.T), np.matmul(G, Va)))

print("------------------------------\n")
print("笛卡尔点乘Ve@Ve\n", Ve @ Ve)

print("对偶点乘Vb@Va Va@Vb\n", Vb @ Va, Va @ Vb)
print("用度量张量求长度\n", Va @ metricsTensor @ Va)


import numpy as np

def R(angle):
    rad_angle = (angle)*np.pi/180
    return np.array([[np.cos(rad_angle), -np.sin(rad_angle)],
                     [np.sin(rad_angle), np.cos(rad_angle)]])

if __name__ == "__main__":
    a = np.array([0, 1])
    print(np.dot(R(180), a))


from scipy.optimize import minimize

def residual(x):
    return (peak_infections(x) - 0.1) ** 2

x0 = 0.5
res = minimize(residual, x0, method="Nelder-Mead", options={'fatol':1e-04})
print(res)

## Edit

import numpy as np
from scipy.integrate import odeint
from  scipy.optimize import minimize
import pandas as pd

d = {'Week': [1, 2,3,4,5,6,7,8,9,10,11], 'incidence': [206.1705794,2813.420201,11827.9453,30497.58655,10757.66954,7071.878779,3046.752723,1314.222882,765.9763902,201.3800578,109.8982006]}
df = pd.DataFrame(data=d)

def peak_infections(beta, df):

    # Weeks for which the ODE system will be solved
    weeks = df.Week.to_numpy()

    # Total population, N.
    N = 1000
    # Initial number of infected and recovered individuals, I0 and R0.
    I0, R0 = 10, 0
    # Everyone else, S0, is susceptible to infection initially.
    S0 = N - I0 - R0
    J0 = I0
    # Contact rate, beta, and mean recovery rate, gamma, (in 1/days).
    gamma = 1/7 * 7 #rate should be in weeks now
    # A grid of time points (in days)
    t = np.linspace(0, weeks[-1], weeks[-1] + 1)

    # The SIR model differential equations.
    def deriv(y, t, N, beta, gamma):
        S, I, R, J = y
        dS = ((-beta * S * I) / N)
        dI = ((beta * S * I) / N) - (gamma * I)
        dR = (gamma * I)
        dJ = ((beta * S * I) / N)
        return dS, dI, dR, dJ

    # Initial conditions are S0, I0, R0
    # Integrate the SIR equations over the time grid, t.
    solve = odeint(deriv, (S0, I0, R0, J0), t, args=(N, beta, gamma))
    S, I, R, J = solve.T

    return I/N

def residual(x, df):

    # Total population, N.
    N = 1000
    incidence = df.incidence.to_numpy()/N
    return np.sum((peak_infections(x, df)[1:] - incidence) ** 2)

x0 = 0.5
res = minimize(residual, x0, args=(df), method="Nelder-Mead", options={'fatol':1e-04})
print(res)

8 - Minimising root function through scipy.optimize/code.py


import numpy as np
from matplotlib import pyplot as plt

def plotData(x, y, h):
    plt.subplots()
    plt.plot(x, h)
    plt.scatter(x, y, c='red')
    plt.grid(color='gray', linestyle='--', linewidth=.6, axis='both', which='both', alpha=.4)
    plt.show()

def compute_error(b, m, data):
    total_error = 0
    for i in range(0,len(data)):
        x = data[i,0]
        y = data[i,1]
        total_error += (y - (m * x + b)) ** 2
    return total_error/float(len(data))

def step_gradient(b_current, m_current, data, learning_rate):
    b_gradient = 0
    m_gradient = 0
    N = float(len(data))
    for i in range(0, len(data)):
        x = data[i,0]
        y = data[i,1]
        b_gradient += -(2/N) * (y - ((m_current * x) + b_current))
        m_gradient += -(2/N) * x * (y - ((m_current * x) + b_current))
    new_b = b_current - (learning_rate * b_gradient)
    new_m = m_current - (learning_rate * m_gradient)
    return [new_b, new_m]

def gradient_descent_runner(data, initial_b, initial_m, learning_rate,  num_iterations):
    b = initial_b
    m = initial_m
    for i in range(num_iterations):
        b, m = step_gradient(b,m, np.array(data), learning_rate)
    return [b,m]

def run():
    # hyperparaters/learning rate/alpha
    learning_rate = 0.0001
    # y = mx + b (slope formula)
    initial_b = 0 # initial y-intercept guess
    initial_m = 0 # initial slope guess
    num_iterations = 1000
    data = np.genfromtxt('../../data/test_scores.csv', delimiter=',')
    cost = compute_error(initial_b, initial_m, data)
    print("Slope : {}, Y Intercept : {}, Cost : {}".format(initial_b,initial_m, cost))
    [b, m] = gradient_descent_runner(data, initial_b, initial_m, learning_rate,  num_iterations)
    print("Slope : {}, Y Intercept : {}, Cost : {}".format(m,b, compute_error(b, m, data)))
    predictions = data[:,0] * m + b
    plotData(data[:,0], data[:,1],predictions)

run()


import numpy as np

def road_length(road_points):
    dxs = road_points[1:, 0] - road_points[0:-1, 0]
    dys = road_points[1:, 1] - road_points[0:-1, 1]
    delta_ss = np.sqrt(dxs * dxs + dys * dys)
    return np.sum(delta_ss)

def normalized_distance(distance_function, road_points1, road_points2):
    length1 = road_length(road_points1)
    length2 = road_length(road_points2)
    return distance_function(road_points1, road_points2) / np.array([length1, length2]).max()

def normalized_distance_value(distance_value, road_points1, road_points2):
    length1 = road_length(road_points1)
    length2 = road_length(road_points2)
    return distance_value / np.array([length1, length2]).max()

# print(normalized_distance(simple_frechet, road_points1, road_points2))
# print(normalized_distance(relative_angle_distance, road_points1, road_points2))


import numpy as np

def fdr(pvalues, alpha=0.05):
    """
    Calculate the p-value cut-off to control for
    the false discovery rate (FDR) for multiple testing.

    If by controlling for FDR, all of n null hypotheses
    are rejected, the conservative Bonferroni bound (alpha/n)
    is returned instead.

    Arguments
    ---------
    pvalues     : array
                  (n, ), p values for n multiple tests.
    alpha       : float, optional
                  Significance level. Default is 0.05.

    Returns
    -------
                : float
                  Adjusted criterion for rejecting the null hypothesis.
                  If by controlling for FDR, all of n null hypotheses
                  are rejected, the conservative Bonferroni bound (alpha/n)
                  is returned.

    Notes
    -----

    For technical details see :cite:`Benjamini:2001` and
    :cite:`Castro:2006tz`.

    Examples
    --------
    >>> import pysal.lib
    >>> import numpy as np
    >>> np.random.seed(10)
    >>> w = pysal.lib.io.open(pysal.lib.examples.get_path("stl.gal")).read()
    >>> f = pysal.lib.io.open(pysal.lib.examples.get_path("stl_hom.txt"))
    >>> y = np.array(f.by_col['HR8893'])
    >>> from pysal.explore.esda.moran import Moran_Local
    >>> from pysal.explore.esda import fdr
    >>> lm = Moran_Local(y, w, transformation = "r", permutations = 999)
    >>> fdr(lm.p_sim, 0.1)
    0.002564102564102564
    >>> fdr(lm.p_sim, 0.05) #return the conservative Bonferroni bound
    0.000641025641025641

    """

    n = len(pvalues)
    p_sort = np.sort(pvalues)[::-1]
    index = np.arange(n, 0, -1)
    p_fdr = index * alpha / n
    search = p_sort < p_fdr
    sig_all = np.where(search)[0]
    if len(sig_all) == 0:
        return alpha/n
    else:
        return p_fdr[sig_all[0]]


import numpy as np
import matplotlib.pyplot as plt
import cvxpy as cp

P = np.array([  [1, -1/2],
                [-1/2, 2]])

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

x = cp.Variable(2)

obj = cp.quad_form(x,P) + q.T @ x
u = cp.Parameter(2)
A = np.array([  [1,2],
                [1,-4],
                [1,1]])

constraints = [
    A[0] @ x <= u[0],
    A[1] @ x <= u[1],
    A[2] @ x >= -5
]

prob = cp.Problem(cp.Minimize(obj), constraints=constraints)

# (a)
u.value = np.array([-2,-3])
prob.solve()
print('status:', prob.status)
print('prob value:', prob.value)
lS = np.array([c.dual_value for c in constraints])
xS = np.array(x.value)
print('primal optimal', xS)
print('dual optimal:', lS)

# Check KKT
print('Checking KKT')
# primal feasible
assert np.array([c.value() for c in constraints]).all()
# dual feasible
assert (lS >= 0).all()
# comp. slackness
f = [c.expr.value for c in constraints]
assert np.isclose(lS @ f , 0)
# grad_x L(xS,lS) = 0
L_grad = 2*(P @ xS) + q + A.T @ lS
print('L grad', L_grad)
assert np.isclose(L_grad, 0).all()
print('KKT ok')

print('x2:', round(xS[1], 2))
print('lambda3:', round(lS[2],2))

# (b)
print('Pertubation table')

delta = [0, -0.1 ,0.1]
p_org = prob.value
dV = []
for d1 in delta:
    for d2 in delta:
        u.value = np.array([-2 + d1, -3 + d2])
        p_exact = prob.solve()
        assert prob.status == cp.OPTIMAL
        p_pred = p_org - lS.T @ [d1,d2,0]
        assert p_pred <= p_exact
        print(f'd1 {d1} d2 {d2} p_pred {p_pred} p_exact {p_exact} '
                f'exact - pred:{p_exact - p_pred}'
            )
        dV.append((p_exact - p_pred))

print('diff argsort', np.argsort(dV))


#NumberTh functions with SymPy package
#-------------------------------------

from sympy import ntheory
from sympy import sieve
from sympy import generate

sieve._reset() # An infinite list of prime numbers, implemented as a dynamically growing sieve of Eratosthenes.#When a lookup is
# requested involving an odd number that has not been sieved,the sieve is automatically extended up to that number.

print(13 in sieve)  # prints 'True' if the input is prime,

sieve.extend(40) #for input n grows the sieve to contain all primes <= n

print(sieve[10])  # returns n th prime

sieve.extend_to_no(15)  # extends upto n th prime

# mobiusrange(a,b) genarates Mobius numbers for the range [a,b), means output will be a list.
Mob_func = sieve.mobiusrange(7,12)
print('\n Mobius function outputs in the given range:',[i for i in Mob_func])

# primerange(a,b) generates all prime numbers in the range [a,b)
Prime_list = sieve.primerange(1,5)
print('\n Primes in the given range are :',[i for i in Prime_list])

#search(n) return the indices i, j of the primes that bound n. If n is prime then i == j. Although n can be an expression,
# if ceiling cannot convert it to an integer then an error will be raised.
x,y = sieve.search(25)
print('\n The given input is in between ','(',x,',',y,')','th Primes \n')

#totientrange(a, b) generates all totient numbers for the range [a, b). Simply it is a list of outputs
Totient_range = sieve.totientrange(1,10)
print(' Values of Totient(n):' ,[i for i in Totient_range])

#sympy.ntheory.generate.prime(nth)  return the n th prime number. Logarithmic integral of x is a pretty nice approximation
# for number of primes <= x, i.e. li(x) ~ pi(x)
print('\n','See this! The 1000000th prime: ', generate.prime(1000000))

#sympy.ntheory.generate.primepi(n) returns the value of the prime counting function pi(n) = the number of prime numbers
# less than or equal to n.
print('\n','There are ',generate.primepi(40),' primes < or = the given input')

#sympy.ntheory.generate.nextprime(n, ith=1) return the ith prime greater than n
print('\n','Next 5th prime after 2 is ',generate.nextprime(2, ith=5))

#sympy.ntheory.generate.prevprime(n) return the largest prime smaller than n
print('\n','Largest previous prime of given input: ', generate.prevprime(8))


from math import sqrt
from numpy import median

def distance(p0, p1):
    return sqrt((p0[0] - p1[0])**2 + (p0[1] - p1[1])**2)

def avg_distance(point, points):
    return median([distance(point, other_point) for other_point in points])


#!/usr/bin/python

import numpy as np
from scipy import stats

'''Leave one out k nearest neighbours regression
This finds the k nearest neighbours and estimates a function
from the average of the values from those of its neighbours

We then get a residual for each sample.
This is the difference between the true function value and its estimate.

The final step is to calculate the mean square error (MSE) which is just the
mean squared length of the residuals.
MSE is always non-negative and close to 0 means a good estimation.

This can work with real valued or vector valued functions'''

def find_neighbours(number_neighbours, sample_index, dists):
    #output the indices of the nearest number_neighbours
    s = dists[sample_index]
    sort_index = np.argsort(s)
    return([x for x in sort_index[1:number_neighbours+1] if x != sample_index])

def Regression(number_neighbours, sample_index, dists, Function):
    s = dists[sample_index]
    sort_index = np.argsort(s)
    nearest_neighbours = [x for x in sort_index[1:number_neighbours+1] if x!=sample_index]
    neighbour_values = [Function[x] for x in nearest_neighbours]
    return(np.mean(neighbour_values, axis = 0))

def Mean_Square_estimator(number_neighbours, dists, Function):
    n = dists.shape[0]
#    Estimate = [Regression]
    Residuals = [(Function[i]-Regression(number_neighbours, i, dists, Function)) for i in range(n)]
    Square_errors = [np.dot(Residuals[i],Residuals[i]) for i in range(n)]
    return(np.mean(Square_errors, axis = 0))

if __name__ == '__main__':
    '''Example'''
    number_neighbours=5
    Y= np.array([(0,10,2,3),(10,0,2,2),(2,2,0,6),(3,2,6,0)])
    C1=[2,3,-1,4]
    C2=[[10,30],[0,0],[3,10],[10,20]]
    Regression(3,0,Y,C1)
    Regression(3,1,Y,C1)
    Regression(2,3,Y,C1)
    mse=Mean_Square_estimator(number_neighbours, Y,C1)
    print(mse)
    mse=Mean_Square_estimator(number_neighbours, Y,C2)
    print(mse)
    print(find_neighbours(2,0,Y))
    print(find_neighbours(2,1,Y))
    print(find_neighbours(2,2,Y))


'''
=== Power spectral density estimation ====
>The power spectrum of a time series x(t) describes the distribution of power
into frequency components composing that signal. The frequency spectrum X(f) of the
time series x(t) is calculated by Fourier Transform.

> A 'Fast Fourier Transform' (FFT) is a
relatively fast algorithm for calculating
the discrete Fourier transform (DFT)
of a continuous, discrete-sampled, time series signal.
Periodogram and Welch's methods are also spectral analysis tools commonly used.

> Welch's method improves upon the periodogram method by reducing noise in
exchange for reducing the frequency resolution.

>Here are various examples of estimating the power spectral density of a time-
domain signal.

----------
KTR 2016

'''
#===========================================================
import matplotlib.pyplot as plt
import scipy.signal as signal
import numpy as np
import math

plt.close("all")

def nextpow2(x):
    '''
    calculate the next power of 2 of x
    '''
    return (x-1).bit_length()

def time_signal(f0, amp,t):
    '''
    calculate time domain signal w\ and w\o noise
    '''
    # non noisey sigal y
    y = amp*np.sin(2 * np.pi * f0 * t) # fundamental component
    y = amp/2*np.sin(2 * np.pi * f0/2 * t) + y # subharmonic component
    # add noise to signal ynoise
    noise = np.random.normal(0,.01,len(t))
    ynoise = y + noise
    # plot signal
    fig = plt.figure(1)
    plt.subplot(2,2,1)
    plt.plot(t, y,'b-',linewidth=1.2)
    plt.plot(t, ynoise,'r--',linewidth=1.2)
    plt.xlabel('time')
    plt.ylabel('amp')
    plt.title('Time-domain signal')
    plt.grid()
    plt.show()
    return y, ynoise

def freq_spec(y,ynoise,t,Fs):
    '''
    Calculate fft/freqeuncy spectrum
    '''
    freq  = np.linspace(0,Fs,len(t)) # freq vec for non-zero pad signal
    freqn = np.linspace(0,Fs, 2**nextpow2(len(ynoise)))
    # fft of non-noisey signal
    Y = np.fft.fft(y) # fft of y (non-noisey signal)
    Y = np.abs(Y)**2 #
    Y = (Y/max(Y))# normalize and log scale
    # fft of noisey signal
    Y2 = np.fft.fft(ynoise,2**nextpow2(len(ynoise))) # fft with zero padding to length Nzero
    Y2 = np.abs(Y2)
    Y2 = Y2/max(Y2) # normalize and log scale
    # plot --------------------
    fig = plt.figure(1)
    plt.subplot(2,2,2)
    plt.plot(freq, Y, 'b-')
    plt.plot(freqn, Y2, 'r--',linewidth=1.2)
    plt.ylabel('Amplitude (norm)')
    plt.xlabel('frequency (Hz)')
    plt.title('Frequency Spectrum')
    plt.grid()
    plt.show()
    plt.xlim([0,Fs/2])

def periodogram_spec(y, ynoise, Fs):
    '''
    calculate PSD using method of periodograms
    '''
    # signal w\o noise
    f, Pper_spec = signal.periodogram(y, Fs, 'flattop', scaling='spectrum')
    Pper_spec = Pper_spec/max(Pper_spec) # normalize re max
    # signal w\ noise
    f, Pper_spec2 = signal.periodogram(ynoise, Fs, 'flattop', scaling='spectrum')
    Pper_spec2 = Pper_spec2/max(Pper_spec2) # normalize re max
    # plot ------------
    fig = plt.figure(1)
    plt.subplot(2,2,3)
    plt.semilogy(f, Pper_spec,'b-')
    plt.semilogy(f, Pper_spec2,'r-',linewidth=1.2)
    plt.xlabel('frequency [Hz]')
    plt.ylabel('PSD')
    plt.grid()
    plt.show()
    plt.title('Method of periodograms')
def Welch_spec(y, ynoise, Fs):
    '''
    calculate PSD using Welchs method
    '''
    # signal w\ noise
    f, Pwelch_spec2 = signal.welch(ynoise, Fs, scaling='spectrum')
    Pwelch_spec2 = Pwelch_spec2/max(Pwelch_spec2)
    # signal w\o noise
    f, Pwelch_spec = signal.welch(y, Fs, scaling='spectrum')
    Pwelch_spec = Pwelch_spec/max(Pwelch_spec)
    # plot
    fig = plt.figure(1)
    plt.subplot(2,2,4)
    plt.semilogy(f, Pwelch_spec2/max(Pwelch_spec2),'r-',linewidth=1.2)
    plt.semilogy(f, Pwelch_spec/max(Pwelch_spec)  ,'b-')
    plt.xlabel('frequency [Hz]')
    plt.ylabel('PSD')
    plt.title('Welchs method ')
    plt.grid()
    plt.show()

def PSDexample():
    '''
    main
    '''
    f0 = float(input('Enter center frequency (f0): '))
    amp = float(input('Enter amplitude of f0: '))
    # paramters
    Fs = 10*f0 # make sure we dont alias
    t = np.arange(0,float(10*f0/2),float(1/Fs))

    # make signal
    y, ynoise = time_signal(f0, amp,t)
    # take FFT
    freq_spec(y,ynoise,t,Fs)
    # method of periodogram
    periodogram_spec(y, ynoise, Fs)
    # welchs method
    Welch_spec(y, ynoise, Fs)

PSDexample()


import numpy as np
import seaborn as sns
import scipy.stats as stats
import matplotlib.pyplot as plt
import matplotlib.animation as animation

sns.set_theme()

class Estimate:
    """

    This class estimates using Monte Carlo Simulation in Question 1 and 2.
    It will also animate and save a GiF along with a confidence interval graph.

    Inputs:

    low, high -- Xlimit and Ylimit of the graph.
    l -- Number of points to take e.g. 1, 2, 10, 100, ....
    area_func -- Function to generate the points
    mask_func -- Function to mask the generate points to select the area in
                 which "useful" points will lie
    estimate_func -- Function to estimate the value from the masked points.

    E.g.

    Q1

    area_func - generated points in a 2x2 Square.
    mask_func - mask points where x^2 + y^2 <= 1 (radius of circle)
    estimate_func - pi = masked points / total points

    Q2

    area_func - generated points in 1x1 square.
    mask_func - mask points where for a point (x, y) f(x) is greater than equals y
    estimate_func - integral = masked points / total points

    """

    def __init__(self, low, high, l, area, mask, estimate, true_val):
        self.fig, self.ax = plt.subplots(1, 2, figsize=(6.4, 3.2))
        self.low = low
        self.high = high
        self.l = l  # points to take e.g. 100, 400 etc....
        self.time_steps = len(self.l)
        self.estimates = []  # estimates we make
        self.area_func = area  # generate the points in area
        self.mask_func = mask  # mask the area appropriately
        self.estimate_func = estimate  # func for estimating from mask
        self.true_val = true_val

    def init_func(self):
        for ax_ in self.ax.flatten():
            ax_.clear()

    def plotScatter(self, arr, ax, c="r"):
        ax.scatter(arr[:, 0], arr[:, 1], c=c)

    def __call__(self, i):
        self.init_func()

        points = self.l[i]
        estimates = []
        for _ in range(100):
            # generate 100 times to make sure we calculate the Confidence Interval
            area = self.area_func(self.low, self.high, points)
            mask = self.mask_func(area)
            masked = area[mask]
            estimate = self.estimate_func(self.high, self.low, masked, points)
            estimates.append(estimate)
        self.estimates.append(estimates)

        estimate = np.mean(estimates)

        self.ax[1].plot(self.l[: i + 1], np.mean(self.estimates, axis=1))
        self.ax[1].axhline(self.true_val, c="r")

        self.fig.suptitle(f"Points : {points} | Estimation = {estimate:.5}")
        self.plotScatter(area, self.ax[0])
        self.plotScatter(masked, self.ax[0], c="b")
        self.fig.tight_layout()

    def animate(self, name):
        ani = animation.FuncAnimation(
            fig=self.fig,
            func=self,
            init_func=self.init_func,
            interval=150,
            frames=self.time_steps,
        )
        ani.save(name, writer="ffmpeg")
        plt.close()

    def ci_graph(self, name, title=""):
        x = np.array(self.estimates)
        points = np.sqrt(self.l)
        mu = x.mean(axis=1)
        sd = np.sqrt(x.var(axis=1))
        ci = (1.645 * sd) / points  # calculate the confidence intervals
        plt.plot(self.l, mu)
        plt.title(title)
        plt.xlabel("Points")
        plt.ylabel("Estimate")
        plt.fill_between(self.l, (mu - ci), (mu + ci), alpha=0.5)
        plt.savefig(name)
        plt.close()

# Q1
# 3.14
low = -1.0
high = 1.0
l = list(range(1, 3001, 10))

def area(low, high, points):
    return np.random.default_rng().uniform(low, high, (points, 2))

def mask(area):
    return np.sum(area ** 2, axis=1) <= 1

def estimate(high, low, masked, points):
    return ((high - low) ** 2) * (masked.shape[0] / points)

sim = Estimate(low, high, l, area, mask, estimate, 3.14159)

sim.animate("1.mp4")
sim.ci_graph("1_ci.png", "Monte Carlo Estimates")

# --------------------------------------------------------------
# Q2
# 0.4815990593768372
low = 0.0
high = 1.0
l = list(range(1, 2001, 10))

def area(low, high, points):
    return np.random.default_rng().uniform(low, high, (points, 2))

def mask(area):
    f = (
        lambda x: (
            (-65536 * (x ** 8))
            + (262144 * (x ** 7))
            - (409600 * (x ** 6))
            + (311296 * (x ** 5))
            - (114688 * (x ** 4))
            + (16384 * (x ** 3))
        )
        / 27
    )
    val = f(area[:, 0])
    out = np.less_equal(val, area[:, 1])
    return (val >= 0) & ~out  # mask True where --- f(x) >= y and x >= 0

def estimate(high, low, masked, points):
    return ((high - low) ** 2) * (masked.shape[0] / points)

sim = Estimate(low, high, l, area, mask, estimate, 0.48159)
sim.animate("2.mp4")
sim.ci_graph("2_ci.png", "Monte Carlo Estimates")

# References

# https://stackoverflow.com/questions/63453435/animate-scatter-plot-with-colorbar-using-matplotlib