# Databricks notebook source
# MAGIC %md
# MAGIC
# MAGIC # [SDS-2.2, Scalable Data Science](https://lamastex.github.io/scalable-data-science/sds/2/2/)
# MAGIC
# MAGIC This is used in a non-profit educational setting with kind permission of [Adam Breindel](https://www.linkedin.com/in/adbreind).
# MAGIC This is not licensed by Adam for use in a for-profit setting. Please contact Adam directly at `adbreind@gmail.com` to request or report such use cases or abuses.
# MAGIC A few minor modifications and additional mathematical statistical pointers have been added by Raazesh Sainudiin when teaching PhD students in Uppsala University.

# COMMAND ----------

# MAGIC %md
# MAGIC Archived YouTube video of this live unedited lab-lecture:
# MAGIC
# MAGIC [![Archived YouTube video of this live unedited lab-lecture](http://img.youtube.com/vi/Vwou20grUD4/0.jpg)](https://www.youtube.com/embed/Vwou20grUD4?start=378&end=2146&autoplay=1) [![Archived YouTube video of this live unedited lab-lecture](http://img.youtube.com/vi/-LLL3MUl9ps/0.jpg)](https://www.youtube.com/embed/-LLL3MUl9ps?start=0&end=2467&autoplay=1)

# COMMAND ----------

# MAGIC %md
# MAGIC #### We can also implement the model with mini-batches -- this will let us see matrix ops in action:
# MAGIC
# MAGIC (N.b., feed_dict is intended for small data / experimentation. For more info on ingesting data at scale, see https://www.tensorflow.org/api_guides/python/reading_data)

# COMMAND ----------

# we know these params, but we're making TF learn them

REAL_SLOPE_X1 = 2 # slope along axis 1 (x-axis)
REAL_SLOPE_X2 = 3 # slope along axis 2 (y-axis)
REAL_INTERCEPT = 5 # intercept along axis 3 (z-axis), think of (x,y,z) axes in the usual way

# COMMAND ----------

import numpy as np
# GENERATE a batch of true data, with a little Gaussian noise added

def make_mini_batch(size=10):
  X = np.random.rand(size, 2) #
  Y = np.matmul(X, [REAL_SLOPE_X1, REAL_SLOPE_X2]) + REAL_INTERCEPT + 0.2 * np.random.randn(size)
  return X.reshape(size,2), Y.reshape(size,1)

# COMMAND ----------

# MAGIC %md
# MAGIC To digest what's going on inside the function above, let's take it step by step.

# COMMAND ----------

 Xex = np.random.rand(10, 2) # Xex is simulating PRNGs from independent Uniform [0,1] RVs
 Xex # visualize these as 10 orddered pairs of points in the x-y plane that makes up our x-axis and y-axis (or x1 and x2 axes)

# COMMAND ----------

Yex = np.matmul(Xex, [REAL_SLOPE_X1, REAL_SLOPE_X2]) # + REAL_INTERCEPT + 0.2 * np.random.randn(size)
Yex

# COMMAND ----------

# MAGIC %md
# MAGIC The first entry in Yex is obtained as follows (change the numbers in the produc below if you reevaluated the cells above) and geometrically it is the location in z-axis of the plane with slopes given by REAL_SLOPE_X1 in the x-axis and REAL_SLOPE_X2 in the y-aixs with intercept 0 at the point in the x-y or x1-x2 plane given by (0.68729439,  0.58462379).

# COMMAND ----------

0.68729439*REAL_SLOPE_X1 +  0.58462379*REAL_SLOPE_X2

# COMMAND ----------

# MAGIC %md
# MAGIC The next steps are adding an intercept term to translate the plane in the z-axis and then a scaled (the multiplication by 0.2 here) gaussian noise from independetly drawn pseudo-random samples from the standard normal or Normal(0,1) random variable via `np.random.randn(size)`.

# COMMAND ----------

Yex = np.matmul(Xex, [REAL_SLOPE_X1, REAL_SLOPE_X2]) + REAL_INTERCEPT # + 0.2 * np.random.randn(10)
Yex

# COMMAND ----------

Yex = np.matmul(Xex, [REAL_SLOPE_X1, REAL_SLOPE_X2])  + REAL_INTERCEPT + 0.2 * np.random.randn(10)
Yex # note how each entry in Yex is jiggled independently a bit by 0.2 * np.random.randn()

# COMMAND ----------

# MAGIC %md
# MAGIC Thus we can now fully appreciate what is going on in `make_mini_batch`. This is meant to substitute for pulling random sub-samples of batches of the real data during stochastic gradient descent.

# COMMAND ----------

make_mini_batch() # our mini-batch of Xx and Ys

# COMMAND ----------

import tensorflow as tf

batch = 5 # size of batch

tf.reset_default_graph() # this is important to do before you do something new in TF

# we will work with single floating point precision and this is specified in the tf.float32 type argument to each tf object/method
x = tf.placeholder(tf.float32, shape=(batch, 2)) # placeholder node for the pairs of x variables (predictors) in batches of size batch
x_aug = tf.concat( (x, tf.ones((batch, 1))), 1 ) # x_aug is a concatenation of a vector of 1`s along the first dimension

y = tf.placeholder(tf.float32, shape=(batch, 1)) # placeholder node for the univariate response y with batch many rows and 1 column
model_params = tf.get_variable("model_params", [3,1]) # these are the x1 slope, x2 slope and the intercept (3 rows and 1 column)
y_model = tf.matmul(x_aug, model_params) # our two-factor regression model is defined by this matrix multiplication
# note that the noise is formally part of the model and what we are actually modeling is the mean response...

error = tf.reduce_sum(tf.square(y - y_model))/batch # this is mean square error where the sum is computed by a reduce call on addition

train_op = tf.train.GradientDescentOptimizer(0.02).minimize(error) # learning rate is set to 0.02

init = tf.global_variables_initializer() # our way into running the TF session

errors = [] # list to track errors over iterations

with tf.Session() as session:
    session.run(init)
    for i in range(500):
      x_data, y_data = make_mini_batch(batch) # simulate the mini-batch of data x1,x2 and response y with noise
      _, error_val = session.run([train_op, error], feed_dict={x: x_data, y: y_data})
      errors.append(error_val)

    out = session.run(model_params)
    print(out)

# COMMAND ----------

REAL_SLOPE_X1, REAL_SLOPE_X2, REAL_INTERCEPT # compare with rue parameter values - it's not too far from the estimates

# COMMAND ----------

import matplotlib.pyplot as plt

fig, ax = plt.subplots()
fig.set_size_inches((4,3))
plt.plot(errors)
display(fig)


# Distributions in Pandas

import pandas as pd
import numpy as np

np.random.binomial(1, 0.5)

np.random.binomial(1000, 0.5)/1000

chance_of_tornado = 0.01/100
np.random.binomial(100000, chance_of_tornado)

chance_of_tornado = 0.01

tornado_events = np.random.binomial(1, chance_of_tornado, 1000000)

two_days_in_a_row = 0
for j in range(1,len(tornado_events)-1):
    if tornado_events[j]==1 and tornado_events[j-1]==1:
        two_days_in_a_row+=1

print('{} tornadoes back to back in {} years'.format(two_days_in_a_row, 1000000/365))

np.random.uniform(0, 1)

np.random.normal(0.75)

Formula for standard deviation
$$\sqrt{\frac{1}{N} \sum_{i=1}^N (x_i - \overline{x})^2}$$

distribution = np.random.normal(0.75,size=1000)

np.sqrt(np.sum((np.mean(distribution)-distribution)**2)/len(distribution))

np.std(distribution)

import scipy.stats as stats
stats.kurtosis(distribution)

stats.skew(distribution)

chi_squared_df2 = np.random.chisquare(2, size=10000)
stats.skew(chi_squared_df2)

chi_squared_df5 = np.random.chisquare(5, size=10000)
stats.skew(chi_squared_df5)

%matplotlib inline
import matplotlib
import matplotlib.pyplot as plt

output = plt.hist([chi_squared_df2,chi_squared_df5], bins=50, histtype='step',
                  label=['2 degrees of freedom','5 degrees of freedom'])
plt.legend(loc='upper right')

# Hypothesis Testing

df = pd.read_csv('grades.csv')

df.head()

len(df)

early = df[df['assignment1_submission'] <= '2015-12-31']
late = df[df['assignment1_submission'] > '2015-12-31']

early.mean()

late.mean()

from scipy import stats
stats.ttest_ind?

stats.ttest_ind(early['assignment1_grade'], late['assignment1_grade'])

stats.ttest_ind(early['assignment2_grade'], late['assignment2_grade'])

stats.ttest_ind(early['assignment3_grade'], late['assignment3_grade'])

book/_build/jupyter_execute/pandas/Week 4-Introduction to Data Science[Coursera].py


from math import pi

import numpy as np

def cylinderArea(r: float, h: float):
    """
    Computes the total area of a cylinder

    :param r: cylinder base radius
    :param h: cylinder height

    :return: cylinder area
    :rtype: float
    """

    if r > 0 and h > 0:
        return pi * pow(r, 2) * 2 + 2 * pi * r * h
    else:
        return np.nan

def fibonacci(n: int):
    """
    Computes the first n Fibonacci's sequence elements

    Parameters:
    :param n: the amount of elements

    :return: n first Fibonacci's sequence elements
    :rtype: np.ndarray
    """

    if n <= 0 or isinstance(n, float):
        return None
    else:
        arr = np.ndarray(shape=(1, n), dtype=int)

        for i in range(0, n):
            if i == 0 or i == 1:
                arr[0][i] = 1
            else:
                arr[0][i] = arr[0][i - 1] + arr[0][i - 2]

        return arr


import matplotlib.pyplot as plt
import seaborn as sns; sns.set()
import numpy as np
import pandas as pd
from scipy.optimize import minimize
import scipy.stats
import time
from mls import plot_rosenbrock, plot_posterior
#- Optimization

#- Derivatives
#- 1. by hand

def f(x):
    return np.cos(np.exp(x)) / x ** 2

def fp(x):
    return -2 * np.cos(np.exp(x)) / x ** 3 - np.exp(x) * np.sin(np.exp(x)) / x ** 2

x = np.linspace(1, 3, 50)
plt.plot(x, f(x), label='$f(x)$')
plt.plot(x, fp(x), '.-', lw=1, label='$f\'(x)$')
plt.legend();
plt.show()

#- 2. numerically : finite difference
fp_numeric=np.gradient(f(x),x)
plt.plot(x, (fp_numeric - fp(x)), '.-', lw=1, label='absolute error')
plt.plot(x, (fp_numeric - fp(x)) / fp(x), '.', label='relative error')
plt.legend();
plt.show()

#- 3. Automatic Differentiation
from autograd import grad, elementwise_grad
import autograd.numpy as anp

def f_auto(x):
    return anp.cos(anp.exp(x)) / x ** 2

fp_auto = elementwise_grad(f_auto)
plt.plot(x, fp_auto(x) - fp(x), '.-', lw=1);
plt.show()

def sinc(x):
    return anp.sin(x) / x if x != 0 else 1.

#- methods
def rosenbrock(x):
    x0, x1 = x
    return (1 - x0) ** 2 + 100.0 * (x1 - x0 ** 2) ** 2
plot_rosenbrock()
plt.show()

#- rosenbrock is not a convex function
x0 = np.linspace(-1.5, 1.5, 100)
plt.plot(x0, rosenbrock([x0, 1.0]))
plt.show()

#- scipy optimize
opt = minimize(rosenbrock, [-1, 0], method='Nelder-Mead', tol=1e-4)
print(opt.message, opt.x)

#- using jacobian
rosenbrock_grad = grad(rosenbrock)
opt = minimize(rosenbrock, [-1, 0], method='CG', jac=rosenbrock_grad, tol=1e-4)
print(opt.message, opt.x)

#- see the optimization develop
def optimize_rosenbrock(method, use_grad=False, x0=-1, y0=0, tol=1e-4):

    all_calls = []
    def rosenbrock_wrapped(x):
        all_calls.append(x)
        return rosenbrock(x)

    path = [(x0,y0)]
    def track(x):
        path.append(x)

    jac = rosenbrock_grad if use_grad else False

    start = time.time()
    opt = minimize(rosenbrock_wrapped, [x0, y0], method=method, jac=jac, tol=tol, callback=track)
    stop = time.time()

    assert opt.nfev == len(all_calls)
    njev = opt.get('njev', 0)
    print('Error is ({:+.2g},{:+.2g}) after {} iterations making {}+{} calls in {:.2f} ms.'
          .format(*(opt.x - np.ones(2)), opt.nit, opt.nfev, njev, 1e3 * (stop - start)))

    xrange, yrange = plot_rosenbrock(path=path, all_calls=all_calls)

optimize_rosenbrock(method='Nelder-Mead', use_grad=False)
plt.show()

#- Can use different methods: Nelder-Mead, CG, Newton-CG, Powell, BFGS
#- initial point can have big effect on optimization cost

def cost_map(method, tol=1e-4, ngrid=50):
    xrange, yrange = plot_rosenbrock(shaded=False)
    x0_vec = np.linspace(*xrange, ngrid)
    y0_vec = np.linspace(*yrange, ngrid)
    cost = np.empty((ngrid, ngrid))
    for i, x0 in enumerate(x0_vec):
        for j, y0 in enumerate(y0_vec):
            opt = minimize(rosenbrock, [x0, y0], method=method, tol=tol)
            cost[j, i] = opt.nfev
    plt.imshow(cost, origin='lower', extent=[*xrange, *yrange],
               interpolation='none', cmap='magma', aspect='auto', vmin=0, vmax=250)
    plt.colorbar().set_label('Number of calls')

cost_map('Nelder-Mead')
plt.show()

cost_map('BFGS')
plt.show()

#- stochastic optimization: loops on data
D = scipy.stats.norm.rvs(loc=0, scale=1, size=200, random_state=123)
x = np.linspace(-4, +4, 100)
plt.hist(D, range=(x[0], x[-1]), bins=20, normed=True)
plt.plot(x, scipy.stats.norm.pdf(x,loc=0,scale=1))
plt.xlim(x[0], x[-1]);
plt.show()

#- likelihood
def NLL(theta, D):
    mu, sigma = theta
    return anp.sum(0.5 * (D - mu) ** 2 / sigma ** 2 + 0.5 * anp.log(2 * anp.pi) + anp.log(sigma))

#-priors
def NLP(theta):
    mu, sigma = theta
    return -anp.log(sigma) if sigma > 0 else -anp.inf

#- posterior
def NLpost(theta, D):
    return NLL(theta, D) + NLP(theta)

plot_posterior(D);
plt.show()

#- Optimization with gradient decent
NLpost_grad = grad(NLpost)

#-step
def step(theta, D, eta):
    return theta - eta * NLpost_grad(theta, D) / len(D)

def GradientDescent(mu0, sigma0, eta, n_steps):
    path = [np.array([mu0, sigma0])]
    for i in range(n_steps):
        path.append(step(path[-1], D, eta))
    return path

plot_posterior(D, path=GradientDescent(mu0=-0.2, sigma0=1.3, eta=0.2, n_steps=15));
plt.show()

#- Stochastic Gradient Descent
def StochasticGradientDescent(mu0, sigma0, eta, n_minibatch, eta_factor=0.95, seed=123, n_steps=15):
    gen = np.random.RandomState(seed=seed)
    path = [np.array([mu0, sigma0])]
    for i in range(n_steps):
        minibatch = gen.choice(D, n_minibatch, replace=False)
        path.append(step(path[-1], minibatch, eta))
        eta *= eta_factor
    return path

plot_posterior(D, path=StochasticGradientDescent(
    mu0=-0.2, sigma0=1.3, eta=0.2, n_minibatch=100, n_steps=100));
plt.show()

#- with no decay of learning rate
plot_posterior(D, path=StochasticGradientDescent(
    mu0=-0.2, sigma0=1.3, eta=0.2, eta_factor=1, n_minibatch=100, n_steps=100))

#- smaller minibatch
plot_posterior(D, path=StochasticGradientDescent(
    mu0=-0.2, sigma0=1.3, eta=0.15, eta_factor=0.97, n_minibatch=20, n_steps=75))


# -*- coding:UTF8 -*-
import numpy as np
import sys,os
import math

from tools.loadData import load_array
from tools import ulti

path = os.getcwd()

def Schmidt_procedure(mat, m, n):
    # mat_B = mat.copy()
    Q = np.zeros((m,n))
    R = np.zeros((n,n))
    for col in range(n):
        curr_col = mat[:,col]
        # print(math.sqrt(np.sum(np.square(curr_col))))
        if col == 0:
            R[0,col] = math.sqrt(np.sum(np.square(curr_col)))
            q = curr_col / R[0,col]
            Q[:,col] = q.copy()
        else:
            q = curr_col.copy()
            for j in range(col):
                R[j,col] = np.matmul(Q[:, j], mat[:, col])

            for k in range(col):
                q -= R[k,col] * Q[:, k]
            R[col,col] = math.sqrt(np.sum(np.square(q)))
            q = q/ R[col,col]
            Q[:,col] = q.copy()
    return Q,R

if __name__ == "__main__":
    input_file=path+'/data/example2.txt'
    # output_file=''
    matrix, m, n = load_array(input_file,"QR")
    ulti.print_array(matrix, m, n )
    if matrix.size ==0:
        print("input Error!")
        sys.exit()
    # 首先判断矩阵是否是列线性无关的
    mat_rank = ulti.rank_of_matrix(matrix,m,n)
    if mat_rank<n:
        print("Error, the matrix with linearly dependent columns Can Not be uniquely factored as A=QR!\n\n")
        print("123")
        sys.exit()
    Q,R = Schmidt_procedure(matrix,m,n)
    print("Q=")
    ulti.print_array(Q,m,n)
    print("R=")
    ulti.print_array(R,n,n)


import numpy as np

def quad_trapezoid(a, b, f, n):
    '''
    Composite quadrature using trapezoid method.

    Input
      a : integration interval start
      b : integration interval end
      f : function reference
      n : number of segments
    Output
      I : integral estimation
    '''
    if (b <= a):
        raise Exception('Error: interval start must be less than interval stop')
    h = (b - a)/n
    x = a
    s = f(a)
    for _ in range(0, n-1):
        x += h
        s += 2*f(x)
    s += f(b)
    I = h*s/2
    return I

def quad_simpson(a, b, f, n):
    '''
    Composite quadrature using 1/3 Simpson's method.

    Input
      a : integration interval start
      b : integration interval end
      f : function reference
      n : number of segments
    Output
      I : integral estimation
    '''
    if (b <= a):
        raise Exception('Error: interval start must be less than interval stop')
    h = (b - a)/n
    x = a
    s = f(x) + 4*f(x+h/2)
    for _ in range(0, n-1):
        x += h
        s += 2*f(x) + 4*f(x+h/2)
    s += f(b)
    I = h*s/6
    return I

def quad_trapezoid_adapt(a, b, f, tol):
    '''
    Composite quadrature using adaptive trapezoid method.

    Input
      a : integration interval start
      b : integration interval end
      f : function reference
      tol: tolerance
    Output
      I : integral estimation
      x : nodes x-axis list
    '''
    I1 = quad_trapezoid(a, b, f, 1)
    I2 = quad_trapezoid(a, b, f, 2)
    if (abs(I1-I2)/3 <= tol):
        I = I2;
        x = [ a, (a+b)/2, b ]
    else:
        I1, x1 = quad_trapezoid_adapt(a, (a+b)/2, f, tol/2)
        I2, x2 = quad_trapezoid_adapt((a+b)/2, b, f, tol/2)
        I = I1 + I2
        x = list(set(x1).union(set(x2)))
    return I, x

def quad_simpson_adapt(a, b, f, tol):
    '''
    Composite quadrature using adaptive Simpson method.

    Input
      a : integration interval start
      b : integration interval end
      f : function reference
      tol: tolerance
    Output
      I : integral estimation
      x : nodes x-axis list
    '''
    I1 = quad_simpson(a, b, f, 1)
    I2 = quad_simpson(a, b, f, 2)
    if (abs(I1-I2)/15 <= tol):
        I = I2;
        x = [ a, (a+b)/2, b ]
    else:
        I1, x1 = quad_simpson_adapt(a, (a+b)/2, f, tol/2)
        I2, x2 = quad_simpson_adapt((a+b)/2, b, f, tol/2)
        I = I1 + I2
        x = list(set(x1).union(set(x2)))
    return I, x

def quad_romberg(a, b, f, n):
    '''
    Romberg quadrature

    Input:
      a : integration interval start
      b : integration interval end
      f : function reference
      n : number of rows in Romberg tableau
    Output:
      I : integral estimation
    '''
    r = quad_romberg_tab(a, b, f, n)
    I = r[n-1, n-1]
    return I

def quad_romberg_tab(a, b, f, n):
    '''
    Romberg quadrature table

    Input:
      a : integration interval start
      b : integration interval end
      f : function reference
      n : number of rows in Romberg tableau
    Output:
      r : Romberg table containing the integral values
    '''
    if (b <= a):
        raise Exception('Error: interval start must be less than interval stop')
    h = [ (b - a)/2**i for i in range(0, n) ]
    r = np.zeros((n,n))
    r[0,0] = (b - a) * (f(a) + f(b)) / 2
    for i in range(1, n):
        subtotal = 0
        for k in range(1, 2**(i-1)+1):
            subtotal += f(a + (2*k - 1)*h[i])
        r[i,0] = r[i-1,0]/2 + h[i]*subtotal
        for k in range(1, i+1):
            r[i,k] = (4**k * r[i,k-1] - r[i-1,k-1]) / (4**k - 1)
    return r


import numpy as np
import findiff
import pandas as pd

def calculate_findiff_coefs(offsets, deriv):
    """
    Calculate Finite Difference Coefficients

    Parameters
    ----------
    offsets : array_like
    deriv : int

    Returns
    -------
    coefs : ndarray

    Examples
    --------
    >>> calculate_findiff_coefs([-2, -1, 0, 1, 2], 4)
    array([ 1., -4.,  6., -4.,  1.])

    >>> calculate_findiff_coefs([-2, -1, 0, 1, 2, 1.99], 3)
    array([  -0.1867,   -0.6722,    3.7688,   -6.0505, -124.5   ,  127.6406])

    References
    ----------
    https://web.media.mit.edu/~crtaylor/calculator.html
    https://github.com/maroba/findiff/blob/e8ca33707e3e25d76bf0f93b2391e466209287b1/findiff/coefs.py
    """

    if len(offsets) < deriv + 1:
        raise ValueError("Length of offsets should be larger than derivative order plus 1.")
    if len(offsets) != len(set(offsets)):
        # Note that this program could not handle pathological cases, only make simple check instead.
        raise ValueError("Possibly exactly same offset value is given. Please check `offsets'.")

    offsets = np.asarray(offsets)
    matrix = np.array([offsets**n for n in range(len(offsets))])
    rhs = np.zeros(len(offsets))
    rhs[deriv] = np.math.factorial(deriv)

    return np.linalg.solve(matrix, rhs)

def calculate_GRR_trig(offsets_half, deriv, fx_pos, fx_neg, f0=None):
    """
    Calculate (Generalized) Rutishauser–Romberg Triangle

    Parameters
    ----------
    offsets_half : array_like
        Positive half part of offsets.  For example, if all offsets are [-2, -1, 0, 1, 2],
            then `offsets_half' should be [1, 2].
        Must be a geometric sequence. This function does not make double check on this.
        Do not contain zero in this array.
    deriv : int
    fx_pos : array_like
        Value list of f(offsets_half). Dimension should be the same to `offsets_half'.
    fx_neg : array_like
        Value list of f(-offsets_half). Dimension should be the same to `offsets_half'.
    f0 : float or None
        Value of f(0). May leave as None if derivative order is odd number.

    Returns
    -------
    grr_trig : ndarray
        (Generalized) Rutishauser–Romberg triangle.
    """

    if deriv % 2 == 0 and f0 is None:
        raise ValueError("`f0' should be provided if derivative order is even number.")
    else:
        f0 = 0 if f0 is None else f0
    if len(offsets_half) < 2:
        raise ValueError("length of `offsets_half' must >= 2 in order to calculate ratio.")

    comp_len = (deriv + 1) // 2
    mat_size = len(offsets_half) - comp_len
    grr_trig = np.zeros((mat_size, mat_size))
    ratio = offsets_half[-1] / offsets_half[-2]

    for r in range(mat_size):
        i_end = r + comp_len
        offsets = np.concatenate([offsets_half[r:i_end], -offsets_half[r:i_end], [0]])
        coef_list = calculate_findiff_coefs(offsets, deriv)
        val_list = np.concatenate([fx_pos[r:i_end], fx_neg[r:i_end], [f0]])
        grr_trig[r, 0] = (coef_list * val_list).sum()
    for c in range(1, mat_size):
        for r in range(mat_size-c):
            grr_trig[r, c] = (ratio**(2*c) * grr_trig[r, c-1] - grr_trig[r+1, c-1]) / (ratio**(2*c) - 1)

    for r in range(mat_size):
        for c in range(mat_size-r, mat_size):
            grr_trig[r, c] = np.nan
    return grr_trig

def calculate_GRR_trig_with_f(offsets_half, deriv, f):
    """
    Calculate (Generalized) Rutishauser–Romberg Triangle with Given Function

    Parameters
    ----------
    offsets_half : array_like
    deriv : int
    f : function
        Should be able to handle both array_like and float input.

    Returns
    -------
    grr_trig : ndarray
        (Generalized) Rutishauser–Romberg triangle.
    """

    fx_pos = [f(o) for o in offsets_half]
    fx_neg = [f(-o) for o in offsets_half]
    f0 = f(0)
    return calculate_GRR_trig(offsets_half, deriv, fx_pos, fx_neg, f0)

def check_grr_trig_converge(grr_trig):
    """
    Convergence Check Matrix of (Generalized) Rutishauser–Romberg Triangle

    Parameters
    ----------
    grr_trig : ndarray
        (Generalized) Rutishauser–Romberg triangle.

    Return
    ------
    mat_chk : ndarray
    """

    n = len(grr_trig)
    mat_chk = np.zeros((n, n))
    for r in range(0, n-1):
        for c in range(1, n-r-1):
            mat_chk[r, c] = np.abs(grr_trig[r, c] - grr_trig[r+1, c]) + np.abs(grr_trig[r, c] - grr_trig[r, c-1])
    for r in range(n):
        mat_chk[r, 0] = np.nan
    for r in range(0, n):
        for c in range(n-r-1, n):
            mat_chk[r, c] = np.nan
    return mat_chk

def output_pd_grr_trig(offsets_half, grr_trig, tolerance=3):
    """
    Pandas Presentation of (Generalized) Rutishauser–Romberg Triangle

    Parameters
    ----------
    offsets_half : array_like
    grr_trig : ndarray
        (Generalized) Rutishauser–Romberg triangle.
    tolerance : int
        Number of minimum difference cells in convergence check matrix.

    Return
    ------
    df : pandas.io.formats.style.Styler
        Pandas show of GRR triangle.
    df_check : pandas.io.formats.style.Styler
        Pandas show of convergence check matrix of GRR triangle.
    """
    n = len(grr_trig)
    df = pd.DataFrame(grr_trig, columns=range(n), index=offsets_half[:n])
    df_check = pd.DataFrame(check_grr_trig_converge(grr_trig), columns=range(n), index=offsets_half[:n])
    df.replace(np.nan, "", regex=True, inplace=True)
    df_check.replace(np.nan, "", regex=True, inplace=True)

    t = check_grr_trig_converge(grr_trig).flatten()
    t = t.argsort()[:3]
    t = np.array([t // n, t % n]).T

    def highlight_cells(x):
        df = x.copy()
        df.loc[:,:] = ''
        for r, c in t:
            df.iloc[r, c] = "background-color: lightgreen"
            df.iloc[r, c-1] = "background-color: lightgreen"
            df.iloc[r+1, c] = "background-color: lightgreen"
        return df

    df = df.style.apply(highlight_cells, axis=None)
    df_check = df_check.style.apply(highlight_cells, axis=None)
    return df, df_check


# Bite Size Bayes

Copyright 2020 Allen B. Downey

License: [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](https://creativecommons.org/licenses/by-nc-sa/4.0/)

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

## Estimating the difference between proportions

From [the Reddit statistics forum](https://www.reddit.com/r/statistics/comments/fsieqf/q_distribution_of_difference_of_correlated_coin/)

> You have two coins, and a sequence of flips from each. Your goal is to assess the distribution of the difference in coin flips. You could do Bayesian updating with a Beta prior to get a posterior for each coin, and assuming independence take the convolution. If you had the additional information that the average of the coin probabilities was p, how would you incorporate it? Is there any way to avoid the convolution and build a prior on the difference in coin flip probabilities directly? Thanks!

I suggested:

> Suppose the probability of heads for the two coins is p+x and p-x. If we know p, all we have to estimate is x.
>
>The likelihood function is the product of two binomials.
>
>I would use a grid method to estimate the posterior.

Here's how:

xs = np.arange(101) / 100
uniform = pd.Series(1, index=xs)
uniform /= uniform.sum()

We can use `binom.pmf` to compute the likelihood of the data for each possible value of $x$.

from scipy.stats import binom

def compute_likelihood(xs, p_avg, k, n):
    p_low = p_avg - xs
    p_high = p_avg + xs

    likelihood = (binom.pmf(k, n, p=p_low) +
                  binom.pmf(k, n, p=p_high)) /2
    likelihood = np.nan_to_num(likelihood)

    return likelihood

xs = uniform.index
p_avg = 0.5
k = 75
n = 100
likelihood1 = compute_likelihood(xs, p_avg, k, n)

plt.plot(likelihood1)
plt.xlabel('x')
plt.title('Likelihood 1');

xs = uniform.index
p_avg = 0.5
k = 25
n = 100
likelihood2 = compute_likelihood(xs, p_avg, k, n)

plt.plot(likelihood2)
plt.xlabel('x')
plt.title('Likelihood 2');

Now we can do the Bayesian update in the usual way, multiplying the priors and likelihoods:

posterior = uniform * likelihood1 * likelihood2

Computing the total probability of the data:

total = posterior.sum()
total

And normalizing the posterior.

posterior /= total

Here's what it looks like.

posterior.plot()

plt.xlabel('Probability of heads (x)')
plt.ylabel('Probability')
plt.title('Posterior distribution, uniform prior');

The posterior mean is pretty close to the value I used to construct the data, 0.25.

def pmf_mean(pmf):
    """Compute the mean of a PMF.

    pmf: Series representing a PMF

    return: float
    """
    return np.sum(pmf.index * pmf)

pmf_mean(posterior)


#Runge Kutta Fourth (4) Order Method
import numpy as np

def dy(x,y):
    dyvalue = x*y + y**2
    return dyvalue
 #Note: change the derivative function based on question!!!!!!  Example: y-x

y0 = 1 # input y for xo
x = 0
h = 0.1
y = y0
finalx = 0.3
interval = finalx - x
n = interval/h
i = 0

while i<=n:
    print("_________________" + str(i)+ "_________________"+"\n")
    k1 = dy(x,y) *h
    print("k1= "+str(k1))
    k2 = dy(x+h*0.5,y+k1*0.5) *h
    print("k2= "+str(k2))
    k3 = dy(x+h*0.5,y+k2*0.5) *h
    print("k3= "+str(k3))
    k4 = dy(x+h,y+k3) *h
    print("k4= "+str(k4))
    y = y + (k1+2*k2+2*k3+k4)/6
    x = x+h
    i+=1
    print("\n")
    print("y =       " + str(y))
    print("\n")
    print("x =   " + str(x))
    print("\n")


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

import numpy as np
import scipy.optimize as opt
import matplotlib.pyplot as plt

#poly function.
def poly_func(w, x):
    f = np.poly1d(w)
    return f(x)

#sin(x)
def sin_func(x):
    return np.sin(x)

#calculate the error between estimation and the groudtruth.
def err_func(w, y, x):
    return y - poly_func(w, x)

#regulization
def reg_err_func(w, y, x):
    reg_err = y - poly_func(w, x)
    reg_err = np.append(reg_err, np.sqrt(np.e**(1/9)) * w)
    return reg_err

#plot
def show(n,m,Regularization):
    x = np.linspace(0, 2*np.pi, n) #equal difference pick up point according to the sample amount.
    y0 = sin_func(x)
    y1 = [np.random.normal(0, 0.1) + y for y in y0] #add guassian noise
    ''' polyfit: x, y, degree; the w is the coefficient.'''
    w = np.polyfit(x,y1,m)
    if not Regularization :
        plsq = opt.leastsq(err_func, w, args=(y1, x))
    else:
        plsq = opt.leastsq(reg_err_func, w, args=(y1, x))
    # plt.figure(figsize = (6,4))
    plt.figure()
    plt.plot(np.linspace(0, 2*np.pi, 100), sin_func(np.linspace(0, 2*np.pi, 100)), 'g-', label='sin(x)')
    plt.plot(np.linspace(0, 2*np.pi, 100), poly_func(plsq[0], np.linspace(0, 2*np.pi, 100)), 'r', label='fitted curve')
    plt.plot(x, y1, 'bo', label='noise point')
    plt.xlabel('x')
    plt.ylabel('t')
    plt.xlim(-0.2,2*np.pi + 0.2)
    plt.ylim(-1.4,1.4)
    plt.legend()
    if not Regularization:
        title = "degree=" + str(m) + " sample="  + str(n)
        plt.title(title)
        plt.savefig("degree=" + str(m) + " sample="  + str(n) + ".png")
    else:
        title = "degree=" + str(m) +   " sample = "  + str(n) + " with regularization term"
        plt.title(title)
        plt.savefig("degree=" + str(m) +   " sample="  + str(n) + " with regularization term.png")
    print title + " done! regularization:" + str(Regularization)
    # plt.show()

if __name__ == '__main__':
    print 'start homework 1...'
    show(10,3,False)# fit degree 3 curve in 10 samples
    show(10,9,False)# fit degree 9 curve in 10 samples
    show(15,9,False)# fit degree 9 curve in 15 samples
    show(100,9,False)# fit degree 9 curve in 100 samples
    show(10,9,True)# fit degree 9 curve in 10 samples but with regularization term


'''

Vector Norm - Finding length of vector

Many ways to find length of the vector:

1. L1 Norm - Lasso regression (a.k.a Taxicab Norm or Manhattan Norm)
2. L2 Norm - Ridge regression (a.k.a Eucledian Norm)
3. p Norm
4. Vector Max Norm

Norm Equation: https://image.slidesharecdn.com/02linearalgebra-171229202748/95/02-linear-algebra-15-638.jpg?cb=1514579308

'''

'''

how to find length of a vector ?
- draw a triangle to the vector and apply pythagoras theorem i,e sqrt of a2 + b2 = c

what if vector is in 3D ?
- sqrt of a2 + b2 + c2 = d

this process of knowing the length of vector is known as L2 Norm.

'''

# L2 norm implementation
import numpy as np
x = np.array([2,3,4])
y = np.array([3,4])

# length of vector y can be found using
# norm function using linear algebra module
from numpy.linalg import norm
length_of_y = norm(y) # by default L2 form
# print length_of_y --> 5.0

# length of vector y can be found using
# norm function using linear algebra module
length_of_x = norm(x) # by default L2 form
# print length_of_x --> 5.385164807134504

# lets verify length of vector x by manually calculating:
np.sqrt((4+9+16))
# 5.385164807134504

# L2 Norm of the vector i,e Ridge regression is used for regularization technique in Machine learning

'''
L1 Norm

vector --> [-1,2,3,-4]

take absolute value of each component and summazation i,e

1 + 2 + 3 + 4 = 10, is known as L1 Norm

'''

# L1 norm implementation

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

l1_norm = norm(x,ord = 1) # ord = 1 --> find L1 norm of the vector
# print l1_norm --> 10.0

'''
p norm

- generalized form
- p = 2 , L2 norm
- p = 1, L1 norm

'''

# p norm implementation

z = np.array([-1,2,3,-4])
#p=1
p1 = norm(z,1)
# print p1 --> 10.0

#p=2
p2 = norm(z,2)
# print p2 --> 5.477225575051661

#p=3
p3 = norm(z,3) # take cube on each component, summation & take cube root of sum
# print p3 --> 4.641588833612778

# in summary, based on p value, squaring and root varies

'''
Vector Max Norm

- return max value in vector
'''

# implementation of vector max norm

x = np.random.randint(0,1000,10)
# x --> array([357, 460, 303, 200, 845, 617,  69, 870, 818, 840])

# we can use np.max but crux is how can we use/prove with the norm equation
# we get vector max norm when p = infinity
from math import inf
max_norm = norm(x,ord=inf)
# max_norm --> 870.0

'''
SUMMARY:

- L1 norm can be calculated by sum of absolute values of the vector
- L2 norm can be calculated by square root of the sum of the squared vector values
- Max norm can be calculated by finding maximum vector value

'''


import numpy as np

def get_largest_eigen_vectors(matrix, n=None):
    """
    Computes the eigen vectors of 'matrix' and returns the n largest ones.
    If n is set to None, then return them all in decreasing order.
    """
    _, n_features = matrix.shape
    if n is None:
        n = n_features

    assert n > 0, 'Need to select at least one eigen vector.'

    eigen_values, eigen_vectors = np.linalg.eig(matrix)
    idx = eigen_values.argsort()[::-1] # ordering the eigen values in decreasing order
    ordered_eigen_vectors = eigen_vectors[:,idx] # ordering the eigen vectors
    eigen_vectors = ordered_eigen_vectors[:,range(n)]

    return eigen_vectors

def get_lowest_eigen_vectors(matrix, n=None):
    """
    Computes the eigen vectors of 'matrix' and returns the n lowest ones.
    If n is set to None, then return them all in increasing order.
    """
    _, n_features = matrix.shape
    if n is None:
        n = n_features

    assert n > 0, 'Need to select at least one eigen vector.'

    eigen_values, eigen_vectors = np.linalg.eig(matrix)
    idx = eigen_values.argsort() # ordering the eigen values in decreasing order
    ordered_eigen_vectors = eigen_vectors[:,idx] # ordering the eigen vectors
    eigen_vectors = ordered_eigen_vectors[:,range(n)]

    return eigen_vectors

def as_column_vect(vector):
    """
    Takes a row or a columns vector and makes sure it returns a column vector.
    """
    try:
        nrow, ncol = vector.shape
        assert nrow == 1 or ncol == 1, 'This is not a vector'
    except ValueError: # case where we have an array instead of a matrix.
        vector = np.matrix(vector)
        nrow, ncol = vector.shape

    if ncol == 1:
        return vector
    return vector.T

def whiten_data(data):
    """
    Returns a transformed version of 'data' such that its covariance matrix
    is equal to the identity.
    The data is assumed to be shaped such that:
    rows: the features/variables
    columns: the individuals
    """
    covariance_matrix = data * data.T
    eig_values, eig_vectors = np.linalg.eig(covariance_matrix)
    eig_values = np.abs(eig_values) # some values are so close to 0 that they are
                                    # estimated as negative
    D_m12 = np.diag(1. / np.sqrt(eig_values)) # D_m12 == D^(-1/2)
    whitened_data = (eig_vectors * D_m12 * eig_vectors.T) * data
    return whitened_data

def center_data(data):
    """
    Returns a transformed version of 'data' such that the column-vector of its
    mean is null.
    The data is assumed to be shaped such that:
    rows: the features/variables
    columns: the individuals
    """
    return data - data.mean(axis=1)

def normalize_data(data):
    """
    Center the datapoints and whiten them.
    """
    centered_data = center_data(data)
    whitened_centered_data = whiten_data(centered_data)
    return whitened_centered_data

def filter_low_values(values, threshold=1.e-11):
    """
    Filter low and negative values of the array 'values'.
    If a value is found to be below the threshold: normalize it by 0
    """
    return np.array([v if v > threshold else 0 for v in values])


import numpy as np
import matplotlib.pylab as plt

def softmax(a):
    exp_a = np.exp(a)
    return exp_a / np.sum(exp_a)

a = np.array([0.3, 2.9, 4.0])
print(softmax(a))

a = np.array([1010, 1000, 990])
print(softmax(a))

#実際には値がでかいとエラーとなる
#したがって計算結果が変わらない特質から最大の要素で分子分母全体をマイナスする
def softmax(a):
    exp_a = np.exp(a - np.max(a))
    return exp_a / np.sum(exp_a)

a = np.array([1010, 1000, 990])
y = softmax(a)
print(y)
print(np.sum(y))

#特徴は合計が1になるので確率として扱える点
#出力層に回帰問題では恒等関数、分類問題ではソフトマックスを用いるのが通例


import numpy as np
import helpers

def mse(y, tx, w):
    """
    Mean squared error loss function.

    Parameters
    ----------
    y : Vector
        Output.
    tx : Matrix
        Input.
    w : Vector
        weights.

    Returns
    -------
    loss function.

    """
    e = y - tx @ w
    return (1/(2*tx.shape[0])) * np.sum(e**2)

def mae(y, tx, w):
    """
    Mean absolute error.

    Parameters
    ----------
    y : Vector
        Output.
    tx : Array
        Input.
    w : Vector
        Weights.

    Returns
    -------
    loss function.

    """

    return np.mean(np.abs(y - tx @ w))

def logistic_error(y, tx, w):
    """
    Logistic error

    Parameters
    ----------
    y : Vector
        Output.
    tx : Matrix
        Input.
    w : TYPE
        Weights.

    Returns
    -------
    loss : Scalar
        Loss function.

    """
    a = helpers.sigmoid(tx @ w)
    loss = - (1 / tx.shape[0]) * np.sum((y * np.log(a)) + ((1 - y) * np.log(1 - a)))
    return loss

def reg_logistic_error(y, tx, w, lambda_, reg):
    """
    Logistic error with regularization

    Parameters
    ----------
    y : Vector
        Output.
    tx : Matrix
        Input.
    w : Vector
        Weights.
    lambda_ : Scalar
        Constant
    reg: Scalar
        L1 or L2 regularization

    Returns
    -------
    loss : Scalar
        Loss function.

    """
    assert (reg==1 or reg==2), "reg needs to be 1 or 2"
    loss = logistic_error(y, tx, w) + lambda_ * (np.linalg.norm(w, reg) ** reg)
    return loss


# LIBRARIES
from sympy import *
import numpy as np
import matplotlib.pyplot as plt

# COLLOCATION METHOD
## SYMBOLS
x, y, C1, C2 = symbols('x y C1 C2')

## THIRD ORDER POLYNOMIAL
y_ = C1*x*(2 - x) + C2*x*(1 - x)**2 + 1*x

## POLYNOMIAL DERIVATIVER
dy_   = y_.diff(x)
ddy_  = y_.diff(x,x)

## RESIDUALS
R = -ddy_ - y_ + x**2

## EQUATION SYSTEMS
R1 = R.subs(x, 1/5)
R2 = R.subs(x, 4/5)
system = [R1, R2]
var = [C1, C2]
sol = solve(system, var)
C1_ = sol[C1]
C2_ = sol[C2]

## POLYNOMIAL ACHIEVED BY THE COLLOCATION METHOD
y2 = y_.subs([(C1,C1_), (C2,C2_)]).simplify()
dy2 = dy_.subs([(C1,C1_), (C2,C2_)]).simplify()
ddy2 = ddy_.subs([(C1,C1_), (C2,C2_)]).simplify()
R_c = -ddy2 - y2 + x**2
display(y2)

## VERIFY BOUNDARY CONDITIONS
print("y(0) = 0: ", y_.subs(x,0).simplify() == 0)
print("dy(1) = 1: ", dy_.subs(x,1).simplify() == 1)

# EXACT SOLUTION
x = Symbol('x')
f = Function('f')(x)
ics = {f.subs(x,0): 0, f.diff(x).subs(x,1): 1}
display(ics)
ode = Eq(-f.diff(x,x) - f + x**2,0)
display(ode)
fsol = dsolve(ode, f, ics = ics)
fsol.simplify()

# POLYNOMIAL - EXACT SOLUTION
f = x**2 - sin(x)/cos(1) + 2*sin(x)*tan(1) + 2*cos(x) - 2

# PLOT
fig, ax = plt.subplots(figsize = (12, 7))
x_range = np.arange(0.0, 1.1, 0.05)

x_ = list()
y_collocation = list()
y_exact = list()

for i in x_range:
    x_.append(i)
    value_collocation = y2.subs([(x, i)])
    y_collocation.append(value_collocation)

    value_exact = f.subs([(x, i)])
    y_exact.append(value_exact)

plt.plot(x_, y_exact, '-s')
plt.plot(x_, y_collocation, '-*')
plt.xlim([0, 1])
plt.legend(["$Exact \, Solution$","$Collocation \, method$"])
plt.grid()
plt.show()
fig.savefig('Lista01_Q8a.png',dpi=300)  # save figure as png (dpi = number of pixels)


# MSDS 400 Module 9 Practice 2

from numpy import linspace, math
import matplotlib.pyplot as plt

'''
This particular module takes a few seconds to load.  Be patient. Using the
NumPy math library does speed things up somewhat.  When the prompt asks for a
value, enter it and a plot will appear.

The program will ask for a user supplied value to be entered at the prompt.
The integration will use the standard normal density function to find the
probability that a standard normal random variable will be <= the value
specified by the user  A plot will show the area integrated, the user
supplied value and the area under the curve.
'''

# This is the density function for a standard normal distribution.

def f(inp):
    return (math.exp(-inp * inp / 2)) / math.sqrt(2.0 * math.pi)

'''
The following values determine the total interval considered and also
the increment used for numerical integration.  We are dividing 12 standard
deviations into 1200 subintervals thus defining delta.
'''
xa = -6.0
xb = 6.0
num = 1200
delta_calc = float((xb - xa) / num)

def integrate(a, b, delta):  # Simpson's rule is used to integrate over [a,b].
    total = 0.0
    i = 0
    n = int(float((b - a) / delta))
    if b == a:
        return
    else:
        while i < n:
            x0 = a + delta * i
            x1 = x0 + delta / 2
            x2 = x0 + delta
            total = total + delta * (f(x0) + 4.0 * f(x1) + f(x2)) / 6
            i = i + 1
        return total

x = linspace(xa, xb, num)
y = []
cdf = []

for z in x:
    y = y + [f(z)]
    cdf = cdf + [integrate(xa, z, delta_calc)]

print('Value of the variable x for integration=?')
value = float(input())

fvalue = integrate(-6, value, delta_calc)
fvalue = round(fvalue, 4)
print('Area with x= {} equals {}'.format(value, fvalue))

plt.xlim(xa - 1, xb + 1)
plt.ylim(0, 1)

string = ' with x = ' + str(value) + ' and area = ' + str(fvalue)
plt.title('Normal Density and CDF' + string)
plt.xlabel('x-axis')

plt.plot(x, y, 'b', label='Density')
plt.plot(x, cdf, 'r', label='CDF')
plt.legend()

plt.fill_between(x, y, where=(x <= value), color='grey', alpha='0.3')
plt.scatter(value, fvalue, s=30, c='r', marker='o')
plt.show()

# Exercise #1:  Refer to Lial Chapter 18 Section 18.3.  Using this code,
# reproduce the results shown in Example 3(c).

# Exercise #2: Refer to Lial Chapter 18 Section 18.3.  Using this code,
# reproduce the results in Example 4 part(a).

src/Classes/MSDS400/Module 9/practice/Module 9 Practice 2.py


#import skfuzzy
import numpy as np
from math import pi
from scipy.special import erf
import matplotlib.pylab as plt

#________________________V-shaped transfer functions______________________
def v1(x):
   v1=abs(erf((np.sqrt(pi)/2)*x))
   return v1

def v2(x):
   v2=abs(np.tanh(x))
   return v2

def v3(x):
   v3= abs(x/np.sqrt(1+np.square(x)))
   return v3

def v4(x):
   v4= abs((2/pi)*np.arctan((pi/2)*x))
   return v4
##______________________S-shaped transfer functions_______________________

def s1(x):

    s1=1 / (1 + np.exp(-2*x))

    return s1

def s2(x):
    s2 = 1 / (1 + np.exp(-x))
    return s2
# s2 is called logistic function and can be imported using scipy.special.expit(x) library

def s3(x):
    s3=1 / (1 + np.exp(-x/3))
    return s3

def s4(x):
    s4=1 / (1 + np.exp(-x/2))
    return s4

##________________________the sigmoid functions_________________________

# A customized function for SIGMOID

def sigmf1(x,b,c):
    b=10
    c=.5
    y = 1 / (1. + np.exp(- c * (x - b)))

    return y

## Built-in function for SIGMOID using skfuzzy.membership library

def sigmf2(x,b,c):
    b=10
    c=.5
    y=skfuzzy.membership.sigmf(x,b,c)

    return y

x = np.arange(-8, 8, 0.1)
# x is used inside this script and will be replaced by a binary individual (1-d binary vector)
#when the transfer function is called or imported in the optimizers scripts


# -*- coding: utf-8 -*-
"""
Created on Wed Mar  6 22:33:31 2019

@author: Ham

HackerRanch Challenge: Eye and Identity

identity

The identity tool returns an identity array.
An identity array is a square matrix
with all the main diagonal elements as 1 and the rest as 0.
The default type of elements is float.

import numpy
print numpy.identity(3) #3 is for  dimension 3 X 3

#Output
[[ 1.  0.  0.]
 [ 0.  1.  0.]
 [ 0.  0.  1.]]

eye

The eye tool returns a 2-D array with 1's as the diagonal and 0's elsewhere.
The diagonal can be main, upper or lower depending on the optional parameter k.
A positive k is for the upper diagonal, a negative k is for the lower,
and a k=0 (default) is for the main diagonal.

import numpy
print numpy.eye(8, 7, k = 1)    # 8 X 7 Dimensional array with first upper diagonal 1.

#Output
[[ 0.  1.  0.  0.  0.  0.  0.]
 [ 0.  0.  1.  0.  0.  0.  0.]
 [ 0.  0.  0.  1.  0.  0.  0.]
 [ 0.  0.  0.  0.  1.  0.  0.]
 [ 0.  0.  0.  0.  0.  1.  0.]
 [ 0.  0.  0.  0.  0.  0.  1.]
 [ 0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.]]

print numpy.eye(8, 7, k = -2)   # 8 X 7 Dimensional array with second lower diagonal 1.

Task

Your task is to print an array of size NxM
with its main diagonal elements as 1's and 0's everywhere else.

Input Format

A single line containing the space separated values of N and M.
N denotes the rows.
M denotes the columns.

Output Format

Print the desired NxM array.

Sample Input

3 3

Sample Output

[[ 1.  0.  0.]
 [ 0.  1.  0.]
 [ 0.  0.  1.]]

"""

import numpy

if __name__ == '__main__':
    numpy.set_printoptions(legacy='1.13')
    # The legacy= stuff above came from the Discussion forum
    #n, m = map(int, input().strip().split())
    print(numpy.eye(*map(int, input().strip().split())))


# 1. A famous researcher observed that chimpanzees hunt and eat meat as part of their regular diet.
# Sometimes chimpanzees hunt alone, while other times they form hunting parties. The following table
# summarizes research on chimpanzee hunting parties, giving the size of the hunting party and the percentage
# of successful hunts.  Use Python to graph the data and find the least squares line. Then use the equation
# to predict the percentage of successful hunts, and the rate that percentage is changing, if there are 20
# chimpanzees in a hunting party.

import numpy as np
import matplotlib.pyplot as plt

x = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10,  11, 12, 13, 14, 15, 16])
y = np.array([15, 25, 23, 39, 35, 53, 40, 57, 60, 58, 70, 70, 73, 70, 70, 77])
A = np.vstack([x, np.ones(len(x))]).T
print(A)

m, c = np.linalg.lstsq(A, y)[0]
print(m, c)

x2 = np.append([x], [20])
y2 = np.append([y], [m * 20 + c])
plt.plot(x, y, 'o', label = 'Data', markersize = 6)
plt.plot(x2, m * x2 + c, 'b', label = 'Fitted')
plt.plot(20, m * 20 + c, 'rs')
plt.legend()
plt.show()

print('Expected Percentage with 20 Chimps:', m * 20 + c)
print('Rate of change:', m)

# 2. One gram of soybean meal provides at least 2.5 units of vitamins and 5 calories.
# One gram of meat byproducts provides at least 4.5 units of vitamins and 3 calories.
# One gram of grain provides at least 5 units of vitamins and 10 calories.  If a gram of
# soybean costs 6 cents, a gram of meat byproducts costs 7 cents, and a gram of grain costs 8 cents,
# use Python to determine what mixture of the three ingredients will provide at least 48 units of vitamins
# and 54 calories per serving at a minimum cost?  What will be the minimum cost?

from scipy.optimize import linprog as lp
import numpy as np

# minimize: 6x + 7y + 8z
# subject to:
#   2.5x + 4.5y + 5z >= 48
#   5x + 3y + 10z >= 54
#   x, y, z  >= 0

A = np.array([[-2.5, -4.5, -5], [-5, -3, -10]])
b = np.array([-48, -54])
buy = lp(np.array([6, 7, 8]), A, b)

print('Buy', buy.x[0], 'grams soybean,',
      buy.x[1], 'grams meat byproducts, and',
      buy.x[2], 'grams grain.')
print('Minimum per serving cost = $',
      sum(buy.x * np.array([6, 7, 8])) / 100)

# 3. A new test has been developed to detect a particular type of cancer.  The test must be evaluated before it is put
# into use.  A medical researcher selects a random sample of 1,000 adults and finds (by other means) that 4% have this
# type of cancer. Each of the 1,000 adults is given the new test, and it is found that the test indicates cancer in 99%
# of those who have it and in 1% of those who do not.
# a) Based on these results, what is the probability of a randomly chosen person having cancer given that the test indicates cancer?
# b) What is the probability of a person having cancer given that the test does not indicate cancer?

has_cancer = 0.04
true_pos = 0.99
false_pos = 0.01

a = has_cancer * true_pos
b = (1 - has_cancer) * false_pos
prob = a / (a + b)
print("a) Probability that person has cancer given that the test indicates cancer:",
      round(prob * 100, 3), "%.")

a = has_cancer * (1 - true_pos) # False neg
b = (1 - has_cancer) * (1 - false_pos) # True neg
prob = a / (a + b)
print("b) Probability that person has cancer given that the test doesn't indicates cancer:",
      round(prob * 100, 3), "%.")

# 4. The following is a graph of a third-degree polynomial with leading coefficient 1.  Determine the function depicted
# in the graph.  Using Python, recreate the graph of the original function, 𝑓(𝑥), as well as the graph of its first and
# second derivatives.

import numpy as np
import matplotlib.pyplot as plt

def f(x):
    y = (x + 3) * (x + 1) * (x - 2)
    return (y)

def g(x):
    #f(x) = x^3 + 2x^2 - 5x - 6
    y = 3 * x ** 2 + 4 * x - 5
    return(y)

def h(x):
    y = 6 * x + 4
    return(y)

x = np.arange(-4, 4.5, 0.5)

plt.plot(x, f(x), 'b', label = 'y = f(x)')
plt.plot(x, g(x), 'b-.', label = "y = f'(x)")
plt.plot(x, h(x), 'b--', label = "y = f''(x)")
plt.plot( 1, f( 1), 'ys')
plt.plot(-2, f(-2), 'ys')
plt.plot( 2, f( 2), 'rs')
plt.plot(-1, f(-1), 'rs')
plt.plot(-3, f(-3), 'rs')
plt.plot(x, x * 0, 'g')
plt.legend()
plt.show()

# 5. For a certain drug, the rate of reaction in appropriate units is given by 𝑅'𝑡=4/(𝑡+1) + 3/sqrt(𝑡+1) where 𝑡 is time (in hours)
# after the drug is administered. Calculate the total reaction to the drug from 𝑡=1 to 𝑡=12.

import numpy as np
import matplotlib.pyplot as plt
from scipy import integrate

def f(t):
    y = 4 * np.log(1 + t) + 6 * np.sqrt(1 + t)
    return (y)

def g(t):
    y = 4 / (t + 1) + 3 / (np.sqrt(1 + t))
    return (y)

t = np.arange(1, 12.5, 0.5)
plt.plot(t, f(t), 'b', label = 'R(t)')
plt.plot(t, g(t), 'r', label = "R'(t)")
plt.legend()
plt.show()

total = f(12) - f(1)
print(total)

# Confirm
func = lambda t: 4 / (t + 1) + 3 / (np.sqrt(1 + t))
total = integrate.quad(func, 1, 12)
print(total[0])
print("Total reaction from t=1 to t=12 is",
      round(total[0], 4))

# 6. The nationwide attendance per day for a certain summer blockbuster can be approximated using the equation
# 𝐴(𝑡)=13𝑡^2𝑒^-t, where A is the attendance per day in thousands of people and t is the number of months since the
# release of the film. Find and interpret the rate of change of the daily attendance after 4 months and interpret
# the result.

import numpy as np
from scipy.misc import derivative
import matplotlib.pyplot as plt

def A(t):
    y = 13 * t**2 * np.exp(-t)
    return (y)

h = 1e-5
a = (A(4+h) - A(4)) / h
print(a)
print(A(np.arange(0, 6, 1)))

t = np.arange(0, 10, 1)
plt.plot(t, A(t), 'b', label = 'A(t)')
plt.plot(t, derivative(A, t, dx = 1e-5), 'r--', label = "A'(t)")
plt.legend()
plt.show()

# Confirm
der = derivative(A, 4, dx = 1e-5)
print(der)
print("Rate of change at t=4", der)

# 7. The population of mathematicians in the eastern part of Evanston is given by the formula 𝑃(𝑡)=(𝑡^2+100) * ln(𝑡+2),
# where t represents the time in years since 2000. Using Python, find the rate of change of this population in 2006.

import numpy as np
from scipy.misc import derivative
import matplotlib.pyplot as plt

def P(t):
    y = (t**2 + 100) * np.log(t + 2)
    return (y)

h = 1e-5
a = (P(6 + h) - P(6)) / h
print(a)

t = np.arange(0, 10, 1)
plt.plot(t, P(t), 'b', label = 'P(t)')
plt.plot(t, derivative(P, t, dx = 1e-5), 'r--', label = "P'(t)")
plt.legend()
plt.show()

# Confirm
der = derivative(P, 6, dx = 1e-5)
print(der)
print("Rate of change at t=2006", der)

# 8. The rate of change in a person's body temperature, with respect to the dosage of x milligrams of a certain diuretic,
# is given by 𝐷′(x) = 2 / (x+6). One milligram raises the temperature 2.2°C. Find the function giving the total
# temperature change. If someone takes 5 milligrams of this diuretic what will their body temperature be, assuming they
# start off at a normal temperature of 37oC?

import numpy as np
import matplotlib.pyplot as plt

def f(x):
    y = 2 * np.log(x + 6)
    return (y)

def g(x):
    y = 2 / (x + 6)
    return (y)

temp = 37
C = 2.2 - f(1)

t = np.arange(0, 5.5, 0.5)
plt.plot(t, f(t) + C, 'b', label = 'D(x)')
plt.plot(t, g(t), 'r--', label = "D'(x)")
plt.legend()
plt.show()

new_temp = f(5) + C + temp
print(new_temp)
print("Temp after taking 5 milligrams=", new_temp)

# 9. The following function represents the life of a lightbulb in years based on average consumer usage.
# Show 𝑓(𝑥) is a probability density function on [0,∞).
# 𝑓(𝑥) = x^3/12, if 0 <= x <= 2
# 𝑓(𝑥) = 16/x^4, if x > 2
# Determine the probability a lightbulb will last between 1 and 5 years.

import numpy as np
import matplotlib.pyplot as plt

x1 = np.arange(0, 2.1, 0.1)
x2 = np.arange(2.1, 20, 0.1)

def f(x):
    y = x**3 / 12
    return (y)

def f2(x):
    y = 16 / x**4
    return(y)

plt.plot(x1, f(x1), 'b')
plt.plot(x2, f2(x2), 'r')
plt.legend()
plt.show()

tot = 0
for x in range(5000000):
    val = x / 100
    if (val <= 2): tot += f(val)
    if (val > 2): tot += f2(val)
print("f(x) for [0,inf) is ", round(tot / 100, 2),
      "and is a probability density")

tot = 0
for x in range(100, 500):
    val = x / 100
    if (val <= 2): tot += f(val)
    if (val > 2): tot += f2(val)
print(tot)
print("Probability lightbulb lasts 1-5 years=",
      tot, "%")


import numpy as np
import matplotlib.pyplot as plt

x = np.arange(-10, 10, 0.001)
y = 1 / (1 + np.exp(-x))
plt.plot(x, y)
plt.suptitle(r'$y=\frac{1}{1+e^{-x}}$', fontsize=20)
plt.grid(color='gray')
plt.grid(linewidth='1')
plt.grid(linestyle='--')
plt.show()

x = np.linspace(-10, 10, 100)
y = 1 / (1 + np.exp(-x))
plt.xlabel("x")
plt.ylabel("y")
plt.title("sigmoid function and its derivative image")
plt.plot(x, y, color='r', label="sigmoid")
y = np.exp(-x) / pow((1 + np.exp(-x)), 2)
plt.plot(x, y, color='b', label="sigmoid derivative")
plt.legend()  # 将plot标签里面的图注印上去
plt.show()

r = np.linspace(-20, 20, 500)
plt.xlabel("r")
plt.ylabel("y")
plt.title("Gaussian RBF")
for delta in [1, 5, 0.5]:
    y = np.exp(-(r ** 2 / (2 * delta ** 2)))
    plt.plot(r, y, label="δ = " + str(delta))

plt.legend()  # 将plot标签里面的图注印上去
plt.show()

r = np.linspace(-20, 20, 500)
plt.xlabel("r")
plt.ylabel("y")
plt.title("Inverse multiquadrics RBF")
for delta in [1, 5, 0.5]:
    y = 1 / (np.sqrt(r ** 2 + delta ** 2))
    plt.plot(r, y, label="δ = " + str(delta))

plt.legend()
plt.show()

r = np.linspace(-10, 10, 500)
plt.xlabel("r")
plt.ylabel("y")
plt.title("Reflected Sigmoidal RBF")
for delta in [1, 5, 0.5]:
    y = 1 / (1 + np.exp(r ** 2 / delta ** 2))
    plt.plot(r, y, label="δ = " + str(delta))

plt.legend()
plt.show()

# mpmath 绘制图像
# import mpmath as mp
# mp.plot(lambda x: x*x, [-10, 10])

# mpmath 绘制三维图像
# import mpmath as mp
# import math
# mp.splot(lambda x, y: x*math.exp(-x*x-y*y))

# sympy 绘制图像
# from sympy.plotting import plot
# from sympy import symbols
#
# x = symbols('x')
# p2 = plot(x*x, (x, -10, 10))
from sympy.plotting import plot
from sympy import symbols
from sympy.functions import exp

r = symbols('r')
p = []
for delta in [1, 5, 0.5]:
    y = 1 / (1 + exp(r ** 2 / delta ** 2))
    p.append((y, (r, -10, 10)))
p2 = plot(*p)

# import mpmath as mp
# import math
# mp.plot(lambda r: 1 / (1 + math.exp(r ** 2 / delta ** 2)), [-10, 10])


import numpy as np

"""
A2-Part-2: Generate a complex sinusoid

Write a function to generate the complex sinusoid that is used in DFT computation of length N (samples),
corresponding to the frequency index k. Note that the complex sinusoid used in DFT computation has a
negative sign in the exponential function.

The amplitude of such a complex sinusoid is 1, the length is N, and the frequency in radians is 2*pi*k/N.

The input arguments to the function are two positive integers, k and N, such that k < N-1.
The function should return cSine, a numpy array of the complex sinusoid.

EXAMPLE: If you run your function using N=5 and k=1, the function should return the following numpy array cSine:
array([ 1.0 + 0.j,  0.30901699 - 0.95105652j, -0.80901699 - 0.58778525j, -0.80901699 + 0.58778525j,
0.30901699 + 0.95105652j])
"""
def genComplexSine(k, N):
    """
    Inputs:
        k (integer) = frequency index of the complex sinusoid of the DFT
        N (integer) = length of complex sinusoid in samples
    Output:
        The function should return a numpy array
        cSine (numpy array) = The generated complex sinusoid (length N)
    """
    ## Your code here
    time_domain = np.arange(0, N)
    x = np.exp(-1j * 2 * np.pi * k * time_domain / N)
    return(x)


#This problem was asked by Amazon.[EASY}

#Given n numbers, find the greatest common denominator between them.

#For example, given the numbers [42, 56, 14], return 14.
#Solution using numpy
import numpy as np

A = [14,56,42]

print(np.gcd.reduce(A))

#Solution: Scaling for any number of input values
n = int(input('HOW MANY NUMBERS YOU WANT TO CALCULATE GCD?: '))
a = list(map(int,input('ENTER THE NUMBER TO COMPUTE GCD: ').strip().split()))[:n]

def compute_gcd(num1,num2):
    x = 1
    while x:
        if max(num1,num2) % min(num1,num2) == 0:
            return min(num1,num2)
            x = 0
        else :
            r = max(num1,num2)%min(num1,num2)
            return compute_gcd(max(num1,num2),r)

while True:
        a[0] = compute_gcd(a[0],a[1])
        a.pop(1)
        if len(set(a))>2:
            a.pop(2)
        if len(set(a)) == 1:
            break
a = set(a)
print(f"GCD OF {n} NUMBERS IS {a}")

#Solution by Euclidean algo with loops for more than 2 numbers
def compute_gcd(x, y):

    while(y):
        x, y = y, x % y

    return x

# Driver Code
l = [ 42, 56, 14]

num1 = l[0]
num2 = l[1]
gcd = compute_gcd(num1, num2)

for i in range(2, len(l)):
    gcd = compute_gcd(gcd, l[i])

print(f"the gcd among the numbers {l} is {gcd}")

#Solution using functools:

import functools as f
A = [56, 42, 14]
g = lambda a,b:a if b==0 else g(b,a%b)   #Gcd for two numbers
print(f.reduce(lambda x,y:g(x,y),A))     #Calling gcd function throughout the list.

# Solution using maths in fixed set of numbers:
import math
A=[56,42,14]
b=A[0]
for j in range(1,len(A)):
    s=math.gcd(b,A[j])
    b=s
print(f'GCD of array elements is {b}.')

# Solution using maths.gcd for n numbers
import math
def compute_gcd(lst):

    if len(lst) == 0:   # trivial case
        return -1
    while len(lst) > 1:
        a = lst.pop()
        b = lst.pop()
        c = math.gcd(a,b) if a >= b else math.gcd(b, a)
        lst.append(c)
    return lst.pop()
def test_gcd():
    assert compute_gcd([42, 56, 14]) == 14
    assert compute_gcd([3, 6]) == 3
    assert compute_gcd([1]) == 1
    assert compute_gcd([]) == -1

if __name__ == "__main__":
    test_gcd()


import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.spatial import ConvexHull, convex_hull_plot_2d

## Ejemplo 2D
rng = np.random.default_rng()
points = rng.random((100, 2))   # 30 random points in 2-D
hull = ConvexHull(points)
plt.plot(points[:,0], points[:,1], 'o')
for simplex in hull.simplices:
    plt.plot(points[simplex, 0], points[simplex, 1], 'k--')

## Ejemplo 3D
rng = np.random.default_rng()
points3d = rng.random((300, 3))   # 30 random points in 2-D
hull3d = ConvexHull(points3d)
vertices3d = points3d[hull3d.vertices,:]

fig = plt.figure()
ax = fig.add_subplot(projection="3d")
ax.scatter(points3d[:,0], points3d[:,1], points3d[:,2], c='black')
ax.scatter(vertices3d[:,0], vertices3d[:,1], vertices3d[:,2], c='red')

for simplex in hull3d.simplices:
    ax.plot(points3d[simplex, 0], points3d[simplex, 1], points3d[simplex, 2], 'g--')

plt.show()

simplices = hull3d.simplices

out = []

for i in simplices:
    p0 = points3d[i[0],:]
    p1 = points3d[i[1],:]
    p2 = points3d[i[2],:]

    temp = [p0, p1, p2]
    out.append(temp)

points_simplices = np.array(out)


import numpy as np #import scientific computing library
from numba import cuda
import matplotlib.pyplot as plt # import plotting library
# define values for global variables
TPB = 8 #number of threads per block

#function definitions for problem 2
def gen_array_1(n):
    """Generate an array of n values equally spaced over the
    interval [0,2*Pi] using for loop.
    """
    if n < 0:
        return None
    if n == 0:
        return np.array([])
    if n == 1:
        return np.array([0])
    interval = 2 * np.pi / (n - 1)
    array = []
    val = 0
    for i in range(n):
        array.append(val)
        val += interval
    return np.array(array)

def gen_array_2(n):
    """Generate an array of n values equally spaced over the
    interval [0,2*Pi] using numpy's linspace.
    """
    return np.linspace(0, 2*np.pi, n)

def plot_res(n):
    """Plot the values as a function of index number.
    """
    x = np.array([i for i in range(n)])
    y = gen_array_2(n)
    plt.plot(x, y, 'o')
    plt.show()

#function definitions for problem 3
# 3a
def scalar_mult(u, c):
    """Compute scaler multiplication of an array and a number.
    Args: an np.array u, a number c
    Returns: an array
    """
    n = u.shape[0]
    out = np.zeros(n)
    for i in range(n):
        out[i] = u[i] * c
    return out

# 3b
def component_add(a, b):
    """Compute conponent wise addition of two arrays.
    Args: two arrays with same length
    Returns: an numpy array with the same length with input arrays
    """
    n = a.shape[0]
    out = np.zeros(n)
    for i in range(n):
        out[i] = a[i] + b[i]
    return out

# 3c
def linear_function(c, x, d):
    """Evaluate the linear function y = c * x + d
    Args: c,x,d: arrays with the same length
    Returns: the result of the evaluation, an numpy array.
    """
    return component_add(scalar_mult(x, c), d)

# 3d
def component_mult(a, b):
    """Compute the component wise multiplication of two arrays
    Args: two arrays with same length
    Returns: an numpy array
    """
    n = a.shape[0]
    out = np.zeros(n)
    for i in range(n):
        out[i] = a[i] * b[i]
    return out

# 3e
def inner(a, b):
    """Compute the inner product of two arrays
    Args: two arrays with same length
    Returns: a float number
    """
    n = a.shape[0]
    out = 0
    for i in range(n):
        out += a[i] * b[i]
    return out

# 3f
def norm(a):
    """Compute the L2 norm of input array
    Args: an array a
    Return: a float number
    """
    n = a.shape[0]
    out = 0
    for i in range(n):
        out += a[i] * a[i]
    return np.sqrt(out)

#function definitions for problem 4
# 4a
@cuda.jit
def scalar_mult_kernel(d_out, d_u, d_c):
    """Kernel fucntion to do scalar multiplication"""
    i = cuda.grid(1)
    n = d_u.shape[0]
    if i >= n:
        return
    d_out[i] = d_u[i] * d_c

def nu_scalar_mult(u, c):
    """Wrapper to do scalar multiplication
    Args: a np array and a scalar number
    Returns: result of the scalar multiplication
    """
    n = u.shape[0]
    d_u = cuda.to_device(u)
    # d_c = cuda.to_device(c)
    d_c = c
    d_out = cuda.device_array(n)
    blocks = (n + TPB - 1) // TPB
    threads = TPB
    scalar_mult_kernel[blocks, threads](d_out, d_u, d_c)
    return d_out.copy_to_host()

# 4b
@cuda.jit
def component_add_kernel(d_out, d_u, d_v):
    """Kernel function to do component wise add"""
    i = cuda.grid(1)
    n = d_u.shape[0]
    if i >= n:
        return
    d_out[i] = d_u[i] + d_v[i]

def nu_component_add(u, v):
    """Wrapper function to do component wise add.
    Args: two numpy arrays
    Returns: a numpy array, the sum of inputs
    """
    n = u.shape[0]
    d_u = cuda.to_device(u)
    d_v = cuda.to_device(v)
    d_out = cuda.device_array(n)
    blocks = (n + TPB - 1) // TPB
    threads = TPB
    component_add_kernel[blocks, threads](d_out, d_u, d_v)
    return d_out.copy_to_host()

# 4c

@cuda.jit
def linear_function_kernel(d_out, d_c, d_x, d_d):
    """Kernel function the calculate linear function"""
    i = cuda.grid(1)
    n = d_x.shape[0]
    if i >= n:
        return
    d_out[i] = d_c * d_x[i] + d_d[i]

def nu_linear_function(c, x, d):
    """Wrapper function to calculate linear function.
    Args: a scaler, a numpy array, another numpy array as bias
    Return: a numpy array.
    """
    n = x.shape[0]
    d_c = c
    d_x = cuda.to_device(x)
    d_d = cuda.to_device(d)
    d_out = cuda.device_array(n)
    blocks = (n + TPB - 1) // TPB
    threads = TPB
    linear_function_kernel[blocks, threads](d_out, d_c, d_x, d_d)
    return d_out.copy_to_host()

# 4d
@cuda.jit
def component_mult_kernel(d_out, d_u, d_v):
    """Kernel function to do component wise multiplication."""
    i = cuda.grid(1)
    n = d_u.shape[0]
    if i >= n:
        return
    d_out[i] = d_u[i] * d_v[i]

def nu_component_mult(u ,v):
    """Wrapper function to do component wise multiplication.
    Args: two numpy arrays
    Return: the result, a numpy array
    """
    n = u.shape[0]
    d_u = cuda.to_device(u)
    d_v = cuda.to_device(v)
    d_out = cuda.device_array(n)
    blocks = (n + TPB - 1) // TPB
    threads = TPB
    component_mult_kernel[blocks, threads](d_out, d_u, d_v)
    return d_out.copy_to_host()

# 4e
@cuda.jit
def inner_kernel(d_accum, d_u, d_v):
    """Kernel function to do inner product."""
    n = d_u.shape[0]
    i = cuda.grid(1)
    if i >= n:
        return
    # accumulate the component wise product
    cuda.atomic.add(d_accum, 0, d_u[i] * d_v[i])

def nu_inner(u, v):
    """Wrapper function to do inner product.
    Args: two numpy arrays
    Returns: a float number.
    """
    n = u.shape[0]
    accum = np.zeros(1)
    d_accum = cuda.to_device(accum)
    d_u = cuda.to_device(u)
    d_v = cuda.to_device(v)
    blocks = (n + TPB - 1) // TPB
    threads = TPB
    inner_kernel[blocks, threads](d_accum, d_u, d_v)
    accum = d_accum.copy_to_host()
    return accum[0]

# 4f
@cuda.jit
def norm_kernel(d_accum, d_u):
    """Kernel function to calculate norm."""
    n = d_u.shape[0]
    i = cuda.grid(1)
    if i >= n:
        return
    # accumulate the component wise product
    cuda.atomic.add(d_accum, 0, d_u[i] * d_u[i])

def nu_norm(u):
    """Wrapper function to calculate norm.
    Args: a numpy array
    Return: a float number"""
    n = u.shape[0]
    accum = np.zeros(1)
    d_accum = cuda.to_device(accum)
    d_u = cuda.to_device(u)
    blocks = (n + TPB - 1) // TPB
    threads = TPB
    norm_kernel[blocks, threads](d_accum, d_u)
    accum = d_accum.copy_to_host()
    return np.sqrt(accum[0])

# function definitions for problem 5
# 5.a.v reverse dot
# alternative version of inner kernel: calculate sum serially
@cuda.jit
def inner_kernel_serial(d_uv, d_u, d_v):
    """Kernel function to do inner product.
    Returns a numpy array: the component wise multiplication of
    two arrays
    """
    n = d_u.shape[0]
    i = cuda.grid(1)
    if i >= n:
        return
    d_uv[i] = d_u[i] * d_v[i]

# sum the contributions in serial order
def inner_diff(u, v):
    """Calculate the difference between two ways of accumlation
    when doing inner product.
    Args: two numpy arrays.
    Returns: a float number, the inner product.
    """
    n = u.shape[0]
    d_u = cuda.to_device(u)
    d_v = cuda.to_device(v)
    uv = np.zeros(n)
    d_uv = cuda.to_device(uv)
    blocks = (n + TPB - 1) // TPB
    threads = TPB
    inner_kernel_serial[blocks, threads](d_uv, d_u, d_v)
    uv = d_uv.copy_to_host()
    sum_s = 0
    sum_r = 0
    for i in range(n):
        # accumulate components from head to tail
        sum_s += uv[i]
        # accumulate components from tail to head
        sum_r += uv[n-i-1]
    print("serial dot: ", sum_s)
    print("reverse dot: ", sum_r)
    return np.abs(sum_s - sum_r)

def main():
    N_2 = 11
    # Problem 2
    print("Problem 2")
    print("2.1")
    print("N = ", N_2, ": ", gen_array_1(N_2))
    print()
    print("2.2")
    print("N = ", N_2, ": ", gen_array_2(N_2))
    print()
    print("2.3")
    print("See Figure 1.")
    print()

    # Problem 3
    u = np.ones(5)
    v = np.ones(5)
    c = 2
    d = 3
    print("Problem 3")
    print("u = ", u)
    print("v = ", v)
    print("c = ", c)
    print("d = ", d)
    print("scalar mult: ", scalar_mult(u, c))
    print("component add: ", component_add(u, v))
    print("component mult: ", component_mult(u,v))
    print("linear function: ", linear_function(c, u, v))
    print("inner: ", inner(u, v))
    print("norm: ", norm(u))
    print()

    # Problem 4
    print("Problem 4")
    print("u = ", u)
    print("v = ", v)
    print("c = ", c)
    print("d = ", d)
    print("scalar mult: ", nu_scalar_mult(u, c))
    print("component add: ", nu_component_add(u, v))
    print("component mult: ", nu_component_mult(u,v))
    print("linear function: ", nu_linear_function(c, u, v))
    print("inner: ", nu_inner(u, v))
    print("norm: ", nu_norm(u))
    print()

    # Problem 5
    N_5 = 5
    print("Problem 5")
    print("N = ", N_5)
    v = np.ones(N_5)
    u = np.ones(N_5)
    for i in range(1, N_5):
        u[i] = 1 / (N_5 - 1)
    print("5.1")
    print("v = ", v)
    print()
    print("5.2")
    print("u = ", u)
    print()
    print("5.3")
    z = nu_scalar_mult(u, -1)
    # print("z = -u = ", z)
    print("norm(u + z) = ", nu_norm(nu_component_add(u, z)))
    print()
    print("5.4")
    print("u dot v = ", nu_inner(u, v))
    print()
    print("5.5")
    # print("u reverse dot v = ")
    print("Dot result difference: ", inner_diff(u, v))
    print()
    print("I repeat 5.5 for larger Ns, here are the result:")
    print("N = 1e4\nserial dot:  2.0\nreverse dot:  2.0\nDot result difference:  9.01723140601e-13")
    print()
    print("N = 1e5\nserial dot:  2.0\nreverse dot:  2.0\nDot result difference:  2.2226664953e-12")
    print()
    print("N = 1e6\nserial dot:  2.00000000001\nreverse dot:  2.00000000001\nDot result difference:  4.08117983852e-13")
    print()
    print("N = 1e7\nserial dot:  2.0000000005\nreverse dot:  2.00000000023\nDot result difference:  2.74033240544e-10")
    print()
    print("N = 1e8\nserial dot:  2.00000000613\nreverse dot:  1.99999999776\nDot result difference:  8.36787017455e-09")
    print()
    print("When N increase to 1e6, the order of summation starts to influnce results.\nThis is because of the float number round off error. The data type is 'np.float64',\nso the difference is smaller than the 'np.float32' case.")

    # plot for problem 2.3
    plot_res(N_2)
    plt.close()

if __name__ == '__main__':
	main()


import numpy as np

a = np.array([
    (1, 3, 5, 7),
    (3, 5, 7, 1),
    (5, 7, 1, 3),
    (7, 1, 1, 5)
], dtype=np.float64)

b = np.array([12, 0, 4, 16], dtype=np.float64)

def solve_gauss_m(a, b):
    a = a.copy()
    b = b.copy()

    for i in range(0, a.shape[0]):
        main_row_index = i + np.argmax(a[range(i, a.shape[1]), i])
        main_row_el = a[main_row_index, i]

        # Swap main row with ith row
        if main_row_index != i:
            a[[main_row_index, i], :] = a[[i, main_row_index], :]
            b[[main_row_index, i]] = b[[i, main_row_index]]
        # print(a)
        # print(b)

        a[i, :] /= main_row_el
        b[i] /= main_row_el

        for row_index in range(i + 1, a.shape[1]):
            multiplier = a[row_index, i]
            a[row_index, :] -= a[i, :] * multiplier
            b[row_index] -= b[i] * multiplier

        # print(a)
        # print(b)

    x = np.empty_like(b)

    for i in range(0, a.shape[0]):
        row_index = x.size - 1 - i
        x[row_index] = b[row_index]
        for j in range(0, i):
            x[row_index] -= a[row_index, a.shape[1]-1 - j] * x[x.size-1 - j]

    return x

MAX_STEPS = 100

def solve_seidel(a, b, max_steps=MAX_STEPS, epsilon=1e-04):
    x = np.zeros_like(b)

    for step in range(max_steps):
        x_new = np.zeros_like(x)

        for i in range(x.size):
            for j in range(0, i):
                x_new[i] -= a[i, j] / a[i, i] * x_new[j]
            for j in range(i + 1, x.size):
                x_new[i] -= a[i, j] / a[i, i] * x[j]
            x_new[i] += b[i] / a[i, i]

        print("Step", step, ":", x_new)

        if np.allclose(x, x_new, atol=epsilon, rtol=0):
            return x_new

        x = x_new

    return x_new

print("Gauss (with selection of main element):", solve_gauss_m(a, b))
print("numpy.linalg.solve:", np.linalg.solve(a, b))

a = np.array([
    [3, -1, 1],
    [-1, 2, 0.5],
    [1, 0.5, 3]
], dtype=np.float64)
b = np.array([1, 1.75, 2.5], dtype=np.float64)

print("Seidel:", solve_seidel(a, b, epsilon=0.0001))
print("numpy.linalg.solve:", np.linalg.solve(a, b))


"""

Tanh Function: f(x) = 2g(2x)-1
                    = (e^x - e^-x)/(e^x + e^-x)

Created by: MJ

"""

"""
The tanh function, a.k.a. hyperbolic tangent function, is a rescaling of the logistic sigmoid, such that its outputs range from -1 to 1. (There’s horizontal stretching as well.)

Yes it matters for technical reasons. Basically for optimization. It is worth reading Efficient Backprop by LeCun et al.

There are two reasons for that choice (assuming you have normalized your data, and this is very important):

Having stronger gradients: since data is centered around 0, the derivatives are higher. To see this, calculate the derivative of the tanh function and notice that its range (output values) is [0,1].
The range of the tanh function is [-1,1] and that of the sigmoid function is [0,1]

Avoiding bias in the gradients. This is explained very well in the paper, and it is worth reading it to understand these issues.
"""
import numpy as np
import math

def tanh(x,library ="math"):
	if library == "math":
		return math.tanh(x)
	else:
		return np.tanh(x)

def tanh_derivative(x,library="math"):
	if library == "math":
		return 1- math.tanh(x)**2
	else:
		return 1-np.tanh(x)**2

if __name__ == "__main__":
	print("tanh value is: ",tanh(0.75))
	print("tanh derivative is: ",tanh_derivative(0.75))


from sympy import *
import matplotlib.pyplot as plt
import numpy as np
# For a certain automobile, Upper M(x), represents the miles per gallon obtained at a speed of x miles per hour.

x = symbols( 'x' )
M = -.015 * x**2 + 1.36*x - 7.2 # 30 <= x <= 60
#M = -0.015 * x**2 + 1.44*x - 7.1 # 30 <= x <= 60
dM = diff( M, x )

critical_values = solve( dM, x )

g_xlim = [ 30, 60 ]
lam_m = lambdify( x, M, np )

x_vals = np.linspace( g_xlim[0], g_xlim[1], 10000, endpoint=True )
y_vals = lam_m( x_vals )

min_val = g_xlim[ 0 ]
min = M.subs( { x: min_val } )
# (Type an integer or decimal rounded to the nearest thousandth as needed.)
print( 'Absolute Minimum MPG is {0} at {1} mph'.format( round( min, 3 ), round( min_val ) ) )

max_val = critical_values[ 0 ]
max = M.subs( { x: max_val } )
# (Type an integer or decimal rounded to the nearest thousandth as needed.)
print( 'Absolute Maximum MPG is {0} at {1} mph'.format( round( max, 3 ), round( max_val ) ) )

plt.plot( x_vals, y_vals, label = '' )
plt.show()


import math
from math import sqrt
import numbers
import matrix as m
import numpy as np

def zeroes(height, width):
        """
        Creates a matrix of zeroes.
        """
        g = [[0.0 for _ in range(width)] for __ in range(height)]
        return Matrix(g)

def identity(n):
        """
        Creates a n x n identity matrix.
        """
        I = zeroes(n, n)
        for i in range(n):
            I.g[i][i] = 1.0
        return I

class Matrix(object):

    # Constructor
    def __init__(self, grid):
        self.g = grid
        self.h = len(grid)
        self.w = len(grid[0])

    #
    # Primary matrix math methods
    #############################

    def determinant(self):
        """
        Calculates the determinant of a 1x1 or 2x2 matrix.
        """
        if not self.is_square():
            raise(ValueError, "Cannot calculate determinant of non-square matrix.")
        if self.h > 2:
            raise(NotImplementedError, "Calculating determinant not implemented for matrices largerer than 2x2.")

        # TODO - your code here
        if self.h == 1:

            return self.g[0][0]
        if self.h == 2:

            return self.g[0][0]*self.g[1][1]-self.g[0][1]*self.g[1][0]

    def trace(self):
        """
        Calculates the trace of a matrix (sum of diagonal entries).
        """
        if not self.is_square():
            raise(ValueError, "Cannot calculate the trace of a non-square matrix.")

        # TODO - your code here
        summ=0
        for i in range(len(self.g)):
            for j in range(len(self.g[0])):
                if i == j:
                    summ+=self.g[i][j]

        return summ

    def inverse(self):
        """
        Calculates the inverse of a 1x1 or 2x2 Matrix.
        """
        if not self.is_square():
            raise(ValueError, "Non-square Matrix does not have an inverse.")
        if self.h > 2:
            raise(NotImplementedError, "inversion not implemented for matrices larger than 2x2.")

        # TODO - your code here
        inverse = []

        det = Matrix.determinant(self)
        if len(self.g)==1 or len(self.g[0])==1:
            inverse.append([1/self.g[0][0]])

        if len(self.g)==2 or len(self.g[0])==2:
            inverse = ([self.g[1][1]/det,-1*self.g[0][1]/det],[-1*self.g[1][0]/det,self.g[0][0]/det])
        invers = m.Matrix(inverse)
        return invers

    def T(self):

            matrix_transpose = [[0 for y in range(len(self.g))]for x in range(len(self.g[0]))]
            #matrix_transpose = np.zeros((len(matrix[0]),len(matrix)))
         #matrix_transpose = []
        #     print(len(matrix))
        #     print(len(matrix[0]))
            for i in range(len(self.g)):
                for j in range(len(self.g[0])):
                    matrix_transpose[j][i] = self.g[i][j]
            matrixTrans = m.Matrix(matrix_transpose)
            return matrixTrans

    def is_square(self):
        return self.h == self.w

    #
    # Begin Operator Overloading
    ############################
    def __getitem__(self,idx):
        """
        Defines the behavior of using square brackets [] on instances
        of this class.

        Example:

        > my_matrix = Matrix([ [1, 2], [3, 4] ])
        > my_matrix[0]
          [1, 2]

        > my_matrix[0][0]
          1
        """
        return self.g[idx]

    def __repr__(self):
        """
        Defines the behavior of calling print on an instance of this class.
        """
        s = ""
        for row in self.g:
            s += " ".join(["{} ".format(x) for x in row])
            s += "\n"
        return s

    def __add__(self,other):
        """
        Defines the behavior of the + operator
        """
        if self.h != other.h or self.w != other.w:
            raise(ValueError, "Matrices can only be added if the dimensions are the same")
        #
        # TODO - your code here
        #

        # initialize matrix to hold the results
        matrixSum = []

        # matrix to hold a row for appending sums of each element
        row = []

        # TODO: write a for loop within a for loop to iterate over
        # the matrices
        for i in range(len(self.g)):
            row=[]
            for j in range(len(self.g[0])):
        # TODO: As you iterate through the matrices, add matching
        # elements and append the sum to the row variable
                row.append(self.g[i][j]+other.g[i][j])
        # TODO: When a row is filled, append the row to matrixSum.
        # Then reinitialize row as an empty list
            matrixSum.append(row)
            matrixSummation = m.Matrix(matrixSum)
        return matrixSummation

    def __neg__(self):
        """
        Defines the behavior of - operator (NOT subtraction)

        Example:

        > my_matrix = Matrix([ [1, 2], [3, 4] ])
        > negative  = -my_matrix
        > print(negative)
          -1.0  -2.0
          -3.0  -4.0
        """
        #
        # TODO - your code here
        #
        matrixNeg = []

        # matrix to hold a row for appending sums of each element
        row = []

        # TODO: write a for loop within a for loop to iterate over
        # the matrices
        for i in range(len(self.g)):
            row=[]
            for j in range(len(self.g[0])):
        # TODO: As you iterate through the matrices, add matching
        # elements and append the sum to the row variable
                row.append(-1*self.g[i][j])
        # TODO: When a row is filled, append the row to matrixSum.
        # Then reinitialize row as an empty list
            matrixNeg.append(row)
            matrixNegative = m.Matrix(matrixNeg)
        return matrixNegative

    def __sub__(self, other):
        """
        Defines the behavior of - operator (as subtraction)
        """
        #
        # TODO - your code here
        #
        # initialize matrix to hold the results
        matrixSub = []

        # matrix to hold a row for appending sums of each element
        row = []

        # TODO: write a for loop within a for loop to iterate over
        # the matrices
        for i in range(len(self.g)):
            row=[]
            for j in range(len(self.g[0])):
        # TODO: As you iterate through the matrices, add matching
        # elements and append the sum to the row variable
                row.append(self.g[i][j]-other.g[i][j])
        # TODO: When a row is filled, append the row to matrixSum.
        # Then reinitialize row as an empty list
            matrixSub.append(row)
            matrixSubtract = m.Matrix(matrixSub)
        return matrixSubtract

    def __mul__(self, other):
        """
        Defines the behavior of * operator (matrix multiplication)
        """
        #
        # TODO - your code here
        #

        product = []
        current_rowA = []
        current_rowB = []
        Other = m.Matrix(other.g)
        transB = Other.T()
        row_results=[]
        for i in range(len(self.g)):
            for j in range(len(other.g[0])):
                result=0
                current_rowA=self.g[i]
                current_rowB=transB[j]
                for k in range(len(current_rowB)):
                    result += current_rowA[k]*current_rowB[k]
                row_results.append(result)
            product.append(row_results)
            row_results=[]
            matrixMul = m.Matrix(product)
        return matrixMul

    def __rmul__(self, other):
        """
        Called when the thing on the left of the * is not a matrix.

        Example:

        > identity = Matrix([ [1,0], [0,1] ])
        > doubled  = 2 * identity
        > print(doubled)
          2.0  0.0
          0.0  2.0
        """
        matrixRmul = []

        # matrix to hold a row for appending sums of each element
        row = []

        # TODO: write a for loop within a for loop to iterate over
        # the matrices
        for i in range(len(self.g)):
            row=[]
            for j in range(len(self.g[0])):
        # TODO: As you iterate through the matrices, add matching
        # elements and append the sum to the row variable
                row.append(other*self.g[i][j])
        # TODO: When a row is filled, append the row to matrixSum.
        # Then reinitialize row as an empty list
            matrixRmul.append(row)
            matrixRmult = m.Matrix(matrixRmul)
        return matrixRmult

        if isinstance(other, numbers.Number):
            pass
            #
            # TODO - your code here
            #


#autor: Rodolfo Quispe
import numpy as np
import matplotlib.pyplot as plt

#define the original function
def f(x, w):
  return w[0]*(x**3) + w[1]*(x**2) + w[2]*(x) + w[3]

#define  squared sum as cost function
def cost(calculated_y, expected_y):
  return ((expected_y - calculated_y)**2).sum()

#given the weights w (a, b, c, d), x
#and y (training data), compute gradient
def gradient(x, y, w):
  fact = np.vstack( (x*x*x, x*x, x , np.ones(len(x))) )
  return 2 * (f(x, w) - y) * fact

# define the update function delta w
def delta_w(w_k, x, t, learning_rate):
  return learning_rate * gradient(x, t, w_k).sum(axis = 1)

def plot_results(x, y, w):
  fig = plt.figure(figsize=(12, 6))
  fig.add_subplot(111)

  # Plot the target t versus the input x
  plt.plot(x, y, 'o', label='data')
  # Plot the initial line
  x = list(range(-10, 10))
  plt.plot(x, [f(i,w) for i in x],'g-',label='f(x)')

  #set extra plot parameters
  plt.title('Question 2: Gradient Descent with learning rate 1e-4')
  plt.xlim(-6,6)
  plt.ylim(-60,60)
  plt.xlabel('x')
  plt.ylabel('f(x)', fontsize=15)
  plt.legend(loc=2)
  plt.grid()

  plt.show()
  fig.savefig("t01_2.png")

if __name__ == "__main__":

  x_train = np.array([-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5])
  y_train = np.array([-53.9, -28.5, -20.7, -3.6, -9.8, 5.0,
                        4.2, 5.1, 11.4, 27.4, 44.0])

  # Set the initial weight parameter
  w = np.array([0, 0, 0, 0])
  # Set the learning rate
  learning_rate = 1.0e-4

  #number of gradient descent iterations
  nb_of_iterations = 50

  # Start performing the gradient descent updates,
  # and print the weights and cost:

  # Lists to store the weight,costs values
  w_cost = [ cost(f(x_train, w), y_train)]
  w_status = [w]
  for i in range(nb_of_iterations):
      #Get the delta w update
      dw = delta_w(w, x_train, y_train, learning_rate)
      #Update the current weight parameter
      w = w - dw
      #Add weight,cost to lists
      w_cost.append(cost(f(x_train, w), y_train))
      w_status.append(w)

  print ('iterations', nb_of_iterations)
  print ('cost', w_cost[-1])
  print ('best parameters', w_status[-1])
  plot_results(x_train, y_train, w_status[-1])


import numpy as np
import matplotlib.pyplot as plt
import scipy.integrate as integrate
from lib.printer import printer

def func(x):
    return np.exp(x)

def trapezoidal(a, b, y=None, N=10, f=func):
    integral = 0
    x = np.linspace(a, b, N + 1)
    if y is None:
        y = f(x)
    y = np.array(y)
    l = len(y)
    if len(x) != len(y):
        raise ValueError("length of x must be same as y")
    for i in range(len(x)):
        if i == 0 or i == len(x) - 1:
            integral += y[i]
        else:
            integral += 2 * y[i]
    integral *= (b - a) / (2 * N)

    # Alternatively,
    """
    y_1=y[:l-1:2]
    y_2=y[1:l:2]

    integral = (b-a)/(N*2) * (np.sum(y_1 + y_2))
    """
    return integral

def simpson(a, b, y=None, N=10, f=func):
    integral = 0
    x = np.linspace(a, b, N + 1)
    if y is None:
        y = f(x)
    y = np.array(y)
    l = len(y)
    if len(x) != l:
        raise ValueError("length of x must be same as y")

    for i in range(len(x)):
        if i == 0 or i == len(x) - 1:
            integral += y[i]
        elif i % 2 == 0:
            integral += 2 * y[i]
        else:
            integral += 4 * y[i]

    integral *= (b - a) / (N * 3)
    # Alternatively,
    """
    y_1=y[:l-2:2]
    y_2=y[1:l-1:2]
    y_3=y[2:l:2]

    integral = (b-a)/(N*3) * (np.sum(y_1 + 4*y_2 + y_3))
    """
    return integral

def integration(a, b, ni, function=None):
    trueval = integrate.quad(function, a, b)
    trapval = trapezoidal(a, b, N=ni, f=function)
    simpval = simpson(a, b, N=ni, f=function)

    # PRINTING
    printer(
        np.array(
            [
                ["Method used", "Integral", "Error constant "],
                ["Quad method scipy", trueval[0], trueval[1]],
                ["Trapezoidal rule", trapval, trueval[1] - (trapval - trueval[0])],
                ["Simpson's rule", simpval, trueval[1] - (simpval - trueval[0])],
            ]
        )
    )

    # PLOTTING CONVERGENCE GRAPH

    y_data_2, y_data, y_data_3 = [], [], []
    n_array = np.arange(40, 370, 2)
    h_array = (b - a) / n_array

    for n in n_array:
        x_data = np.linspace(a, b, n + 1)
        y_data.append(trapezoidal(a, b, N=n, f=function))
        y_data_2.append(simpson(a, b, N=n, f=function))
        y_data_3.append(integrate.simpson(func(x_data), x_data))

    plt.xscale("log")
    plt.plot(h_array, y_data, label="Trapezoidal rule")
    plt.scatter(h_array, y_data, label="Trapezoidal rule")
    plt.plot(h_array, y_data_2, label="Simpson's rule")
    plt.scatter(h_array, y_data_2, label="Trapezoidal rule")
    plt.plot(h_array, y_data_3, linestyle="--", label="scipy's simpson implementation")
    plt.legend()

if __name__ == "__main__":

    # n=int(input("Enter the number of sub-intervals : "))
    # a=int(input("Enter the lower-limit(a) : "))
    # b=int(input("Enter the upper-limit(b) : "))
    """
    PART 3(a)
    """
    V = [0, 0.5, 2.0, 4.05, 8, 12.5, 18, 24.5, 32, 40.5, 50]
    trapval = trapezoidal(0, 1, y=V, N=10)
    simpval = simpson(0, 1, y=V, N=10)

    printer(
        np.array([["Trapezoidal rule", trapval], ["Simpson's rule", simpval]]),
        column=False,
    )

    """
    PART 3(b)
    https://github.com/scipy/scipy/blob/v1.7.1/scipy/integrate/_quadrature.py#L433-L555
    """
    integration(0, 13, 100, function=func)
    plt.show()


#sympy

from pylab import *
import sympy as sp

print(pi)
print(sp.pi)

#there is a symbolic pi, which we can evaluate using the evalf method.

#get 32 digits of pi
print(sp.pi.evalf(32))

#There are other kinds of numeric objects

p = sp.Rational(3,7)
q = sp.Rational(2,3)
print(p+q)
print(p*q)

#when dealing with symbolic results, things can get ugly
print(sp.integrate('x*sin(3/x)'))

#that is ugly. Sympy has a "pretty print" function.
sp.pprint(sp.integrate('x*sin(3/x)'))

#In general, we want to use symbolic variables.
sp.var('x,y')
sp.pprint(x/2)

#now we can define expressions in terms of symbolic variables.
s = x*sp.sin(3/x)

#Now we can evaluate this expression anywhere.
print(s.subs(x,pi))

#kcooper does song and dance between 'f' and 'f(x)'
#f(x) is NOT a function, it's the value of the function. It's the value of function
# f is just the function and is a rule for mapping one input to another.
#function you give it an input and get an output.
#s is just an expression and is a collection of symbols you cannot do s(x)
print(s.subs(x,pi).evalf(50))

#We have noted that s is an expression. Let's make a function.

t = lambda x: x*sp.sin(3/x)

#y is a symbolic variable.
#Note now the difference between the expression s and the function t
sp.pprint(s(y))
sp.pprint(t(y))

#Now let's define a new expression.
#don't care about 64.0 or anything because they are just symbolic numbers and we could use evalf() to evalute to arbitrary precision
u = 64*x**4-100*x**3 + 22*x**2 - 65*x+4

#didn't have to tell him how many points to choose or anything.
sp.plot(u,(x,-.5,1.5))
#sympy plot doesn't need the show() command

3h = arange(-0.5,1.5,0.001)

h = (1.5 -(-.5))/1000.0
x_vec = arange(-.5,1.5+h,h)

u_vec = zeros(len(x_vec))

#have to evaluate u at 1001 points.
for i in arange(len(x_vec)):
	u_vec[i] = u.subs(x, x_vec[i])

plot(x_vec, u_vec)
show()

#***********************************NOVEMBER 30TH LECTURE NOTES *******************************

#trying to find the root of the polynomial. K Cooper designed it to have the root at x = 1/16
print(u.subs(x,sp.Rational(1,16)))
first_root = sp.Rational(1,16)

v = sp.simplify(u / (x-first_root))
sp.pprint(v)

#simplify always checks to divide out polynomial factors, and execute trig identities.
#x^2 + 2x + 1 is a perfect square.
sp.pprint((x**2 + 2*x + 1) / (x + 1) )
sp.pprint(sp.simplify((x**2 + 2*x + 1) / (x + 1)))
sp.pprint((sp.sin(x)**2 + sp.cos(x)**2 / sp.cos(x)))u

#We can factor polynomials
sp.pprint(u.factor())

#we can also expand polynomials
w = u.factor()
sp.pprint(w)
sp.pprint(sp.expand(w))

sp.var('a')
p = (x -1) * (x-a) * (x +a)

sp.pprint(p.expand().collect(x))
sp.pprint(p.expand().collect(a))
sp.pprint(sp.horner(p,x))

#sympy can do calculus

#this limit would be 1
sp.pprint(sp.limit(sp.sin(x) / x , x, 0))

#limit would be zero(0) because it's 0 * bounded function
sp.pprint(sp.limit( x * sp.sin(3/x), x, 0))

#you can take a limit of a limit
sp.pprint(sp.limit(sp.limit((x**2+1) / (a*x**2 + 2*x + 1), x , sp.oo), a, 97)

#sympy can take derivatives
#differentiate u with respect to x
sp.pprint(sp.diff(u,x))

#we can take a second derivative.
sp.pprint(sp.diff(u,x,2))

u_prime = sp.diff(u,x)
sp.pprint(u_prime)

u_double_prime = sp.diff(u,x,2)
sp.pprint(u_double_prime.expand() - sp.diff(u_prime,x))

#the things inside would have to be REAL
arc_length_integrand = sp.sqrt(1 + sp.diff(u,x)**2)

arc_length = sp.integrate(arc_length_integrand, (x,0,1))

#it doesn't know how to evaluate the integral so it'll just print the last known correct answer.
#but we need to force it to do the integral
sp.pprint(arc_length)

#can get 10 digits if you want.
arc_length = sp.integrate(arc_length_integrand, (x,0,1)).evalf(10)
sp.pprint(arc_length)

#more integrals
#u is a quartic polynomial
sp.pprint(sp.integrate(u))

sp.pprint(sp.integrate(arc_length) )

#showing to use a tuple going from negative infinity to positive infinity.
#use .evalf() to make him do it with 20 digits of accuracy.
sp.pprint(sp.integrate(sp.exp(-x**2), [x,-sp.oo,sp.oo]).evalf(20))
#solution is sqrt(pi)

# ******************* SERIES **********************88
#why do we learn series? because polynomials are what we can evluate on a computer.
#anything we can write as a series we can integrate.

s = sp.series(sp.sin(x), x)

sp.pprint(s)

#big O denotes what the dominant term is. Exampel is  O<x^6> means that x^6 is the dominant term

#can expand something other than 0.
s = sp.series(sp.sin(x), x, 3)

#now what if we want 16 terms from the series.
s = sp.series(sp.sin(x), x, 3, 16)

#this would be expanding around 0
s = sp.series(sp.sin(x), x, 0, 16)

#you get a list of ordered terms. We could reconstruct the function by taking the sum of these terms in the list.
t = s.as_ordered_terms()

#if you give it a negative index it will start at the end.
t = sum(s.as_ordered_terms()[0:-1])

sp.pprint(t)

h = 0.01
x_vec = arange(-1.0, 1.0 + h, h)

#y_vec must be numbers!
y_vec = zeros(len(x_vec))

for i in arange(len(x_vec)):
	y_vec[i] = t.subs(x, x_vec[i]).evalf()

plot(x_vec, y_vec, x_vec, sin(x_vec))
show()

#********************* DECEMBER 4th NOTES ***************

#we could use less terms to make it look worse.


# 6.1 David C. Lay Inner Product

# An inner product is a generalization of the dot product. In a vector space, it is a
# way to multiply vectors together, with the result of this multiplication being a scalar.

'''
Example: Compute u.v and v.u for u = col(2, -5, -1), v = col(3, 2, -3)

u.v = transpose(u).v = (3 * 2) + (- 5 * 2) + (-1 * -3) = -1
v.u = 10 Because u.v = v.u
'''

u, v = [2, -5, -1], [4, 2, -3]

inner_prod = 0

for i in range(len(u)):
    inner_prod += u[i] * v[i]

print(inner_prod)

# numpy library
import numpy as np

u = np.array([2, -5, -1]);
v = np.array([4, 2, -3]);

inner_prod = np.inner(v, u)

print(inner_prod)

# for 1-d array np.inner(u, v) is same as sum(u[:] * v[:])

inner_prod = sum(u[:] * v[:])
print(inner_prod)

# or  simply

inner_prod = u * v
inner_prod = sum(inner_prod)
print(inner_prod)

x = np.array([[1], [2]])
x_trans = np.transpose(x)
out = np.dot(x_trans, x)

print(sum(out)[0])

# for multi-dimentional array
a = np.array([[1,2], [3,4]])
b = np.array([[11, 12], [13, 14]])

print(np.inner(a, b))
'''
    In the above case, the inner product is calculated as −

    1*11+2*12, 1*13+2*14
    3*11+4*12, 3*13+4*14
'''


'''

hypothesis
    O^T * x = O0X0 + O1X1 + ... + OmXm
    h(O) = O^T * x

cost function
    cost(h(x), y) = (h(x) - y)^2
    J(O) = (1/2*m) * sum(cost(h(x), y))

fit parameters with gradient descent
    Oj = Oj - learning_rate * partial_derivative(J(O))
    partial_derivative = 1/m * sum((h(xi) - yi) * xij)

'''

class LinearRegression:
    def __init__(self, learning_rate=0.001, iters=10000, fit_intercept=True, theta=None):
        self.learning_rate = learning_rate
        self.iters = iters
        self.fit_intercept = fit_intercept
        self.theta = theta

    def __add_intercept(self, X):
        intercept = np.ones((X.shape[0], 1))
        return np.concatenate((intercept, X), axis=1)

    def __cost(self, h, y):
        return np.sum((h - y) * (h - y)) / (2*y.size)

    def fit(self, X, y):
        if self.fit_intercept:
            X = self.__add_intercept(X)

        if self.theta is None:
            self.theta = np.zeros(X.shape[1])

        for i in range(self.iters):
            h = np.dot(X, self.theta)
            cost = self.__cost(h, y)
            for j in range(len(self.theta)):
                gradient = np.dot(h-y, X[:, j])
                self.theta[j] = self.theta[j] - (self.learning_rate / y.size * gradient)
        print(cost)

if __name__ == '__main__':
    import numpy as np
    hours_studied = np.array([[2], [4], [6], [8], [10], [13], [13.5], [14], [16], [19], [25]])
    grades = np.array([1, 1, 1, 2, 2, 3, 3, 3, 4, 4, 5])

    print("GRADIENT DESCENT")
    model = LinearRegression()
    model.fit(hours_studied, grades)

    '''
    gradients, cost = linear_regression(hours_studied, grades)
    print(gradients)
    print(cost)

    predict = lambda x: gradients[1]*x + gradients[0]
    print(predict(21))

    '''

    '''
    NORMAL EQUATION METHOD
    theta = (X^T * X)^-1 * X^T * y
    '''

    print("NORMAL EQ")
    hours_studied_ones = np.concatenate((np.ones((len(hours_studied), 1)), hours_studied), axis=1)
    theta = np.dot(np.dot(np.linalg.inv(np.dot(np.transpose(hours_studied_ones), hours_studied_ones)), np.transpose(hours_studied_ones)), grades)
    model = LinearRegression(theta=theta, iters=1)
    model.fit(hours_studied, grades)

'''
SHORT SELLING
ABC share 54$

borrow 5 abc shares and sell them for 270$

ABC share 45$

buy 5 abc shares for 225$ and give them back to creditor

profit = 270$ - 45$ = 25$
'''