import numpy as np

def exp_rate_cumprod(cumprod: list, rate_init: float, d_rate: float):
    """Exponential Model.
    This function calculates rate from cumulative production.

    Parameters
    ---
    cumprod : list, ndarray
        cumulative production
    rate_init : float
        initial rate
    d_rate : float
        decline rate
    Return
    ---
    rate : ndarray
        production rate
    """
    cumprod = np.asarray(cumprod)
    return rate_init - d_rate * cumprod

def exp_rate_time(time: list, rate_init: float, d_rate: float):
    """Exponential Model.
    This function calculates rate from time.

    Parameters
    ---
    time : list, ndarray
        time series
    rate_init : float
        initial rate
    d_rate : float
        decline rate
    Return
    ---
    rate : ndarray
        production rate
    """
    time = np.asarray(time)
    return rate_init * np.exp(-d_rate * time)

def exp_cumprod_rate(rate: list, rate_init: float, d_rate: float):
    """Exponential Model.
    This function calculates cumulative production from rate.

    Parameters
    ---
    rate : list, ndarray
        production rate
    rate_init : float
        initial rate
    d_rate : float
        decline rate
    Return
    ---
    cumprod : ndarray
        cumulative production
    """
    rate = np.asarray(rate)
    return (rate_init - rate)/d_rate

def exp_cumprod_time(time: list, rate_init: float, d_rate: float):
    """Exponential Model.
    This function calculates cumulative production from time.

    Parameters
    ---
    time : list, ndarray
        time series
    rate_init : float
        initial rate
    d_rate : float
        decline rate
    Return
    ---
    cumprod : ndarray
        cumulative production
    """
    raise NotImplementedError()

def exp_time_cumprod(cumprod: list, rate_init: float, d_rate: float):
    """Exponential Model.
    This function calculates time from cumulative production.

    Parameters
    ---
    cumprod : list, ndarray
        cumulative production
    rate_init : float
        initial rate
    d_rate : float
        decline rate
    Return
    ---
    time : ndarray
        time series
    """
    raise NotImplementedError()

def exp_time_rate(rate: list, rate_init: float, d_rate: float):
    """Exponential Model.
    This function calculates time from rate.

    Parameters
    ---
    rate : list, ndarray
        production rate
    rate_init : float
        initial rate
    d_rate : float
        decline rate
    Return
    ---
    time : ndarray
        time series
    """
    rate = np.asarray(rate)
    return np.log(rate_init/rate) / d_rate

def har_rate_cumprod(cumprod: list, rate_init: float, d_rate: float):
    """Harmonic Model.
    This function calculates rate from cumulative production.

    Parameters
    ---
    cumprod : list, ndarray
        cumulative production
    rate_init : float
        initial rate
    d_rate : float
        decline rate
    Return
    ---
    rate : ndarray
        production rate
    """
    cumprod = np.asarray(cumprod)
    return np.exp(np.log(rate_init) - d_rate * cumprod / rate_init)

def har_rate_time(time: list, rate_init: float, d_rate: float):
    """Harmonic Model.
    This function calculates rate from time.

    Parameters
    ---
    time : list, ndarray
        time series
    rate_init : float
        initial rate
    d_rate : float
        decline rate
    Return
    ---
    rate : ndarray
        production rate
    """
    time = np.asarray(time)
    return rate_init / (1 + d_rate * time)

def har_cumprod_rate(rate: list, rate_init: float, d_rate: float):
    """Harmonic Model.
    This function calculates cumulative production from rate.

    Parameters
    ---
    rate : list, ndarray
        production rate
    rate_init : float
        initial rate
    d_rate : float
        decline rate
    Return
    ---
    cumprod : ndarray
        cumulative production
    """
    rate = np.asarray(rate)
    return np.log(rate_init/rate)*rate_init/d_rate

def har_cumprod_time(time: list, rate_init: float, d_rate: float):
    """Harmonic Model.
    This function calculates cumulative production from time.

    Parameters
    ---
    time : list, ndarray
        time series
    rate_init : float
        initial rate
    d_rate : float
        decline rate
    Return
    ---
    cumprod : ndarray
        cumulative production
    """
    raise NotImplementedError()

def har_time_rate(rate: list, rate_init: float, d_rate: float):
    """Harmonic Model.
    This function calculates time from rate.

    Parameters
    ---
    rate : list, ndarray
        production rate
    rate_init : float
        initial rate
    d_rate : float
        decline rate
    Return
    ---
    time : ndarray
        time series
    """
    rate = np.asarray(rate)
    return (1./rate - 1./rate_init) * rate_init/d_rate

def har_time_cumprod(cumprod: list, rate_init: float, d_rate: float):
    """Harmonic Model.
    This function calculates time from cumulative production.

    Parameters
    ---
    cumprod : list, ndarray
        cumulative production
    rate_init : float
        initial rate
    d_rate : float
        decline rate
    Return
    ---
    time : ndarray
        time series
    """
    raise NotImplementedError()

def hyp_rate_cumprod(cumprod: list, rate_init: float, d_rate: float, b_exp: float):
    """Hyperbolic Model.
    This function calculates rate from cumulative production.

    Parameters
    ---
    cumprod : list, ndarray
        cumulative production
    rate_init : float
        initial rate
    d_rate : float
        decline rate
    Return
    ---
    rate : ndarray
        production rate
    """
    cumprod = np.asarray(cumprod)
    const = rate_init/(d_rate * (1 - b_exp))
    return np.exp(np.log(1 - cumprod/const)/(1 - b_exp) + np.log(rate_init))

def hyp_rate_time(time: list, rate_init: float, d_rate: float, b_exp: float):
    """Hyperbolic Model.
    This function calculates rate from time.

    Parameters
    ---
    time : list, ndarray
        time series
    rate_init : float
        initial rate
    d_rate : float
        decline rate
    Return
    ---
    rate : ndarray
        production rate
    """
    time = np.asarray(time)
    return rate_init / np.power(1 + b_exp*d_rate*time, 1/b_exp)

def hyp_cumprod_rate(rate: list, rate_init: float, d_rate: float, b_exp: float):
    """Hyperbolic Model.
    This function calculates cumulative production from rate.

    Parameters
    ---
    rate : list, ndarray
        production rate
    rate_init : float
        initial rate
    d_rate : float
        decline rate
    Return
    ---
    cumprod : ndarray
        cumulative production
    """
    rate = np.asarray(rate)
    const = rate_init/(d_rate * (1 - b_exp))
    return const * (1 - np.power(rate/rate_init, (1 - b_exp)))

def hyp_cumprod_time(time: list, rate_init: float, d_rate: float, b_exp: float):
    """Hyperbolic Model.
    This function calculates cumulative production from time.

    Parameters
    ---
    time : list, ndarray
        time series
    rate_init : float
        initial rate
    d_rate : float
        decline rate
    Return
    ---
    cumprod : ndarray
        cumulative production
    """
    raise NotImplementedError()

def hyp_time_rate(rate: list, rate_init: float, d_rate: float, b_exp: float):
    """Hyperbolic Model.
    This function calculates time from rate.

    Parameters
    ---
    rate : list, ndarray
        production rate
    rate_init : float
        initial rate
    d_rate : float
        decline rate
    Return
    ---
    time : ndarray
        time series
    """
    rate = np.asarray(rate)
    return (np.exp(b_exp*(np.log(rate_init) - np.log(rate))) - 1)/(b_exp * d_rate)

def hyp_time_cumprod(cumprod: list, rate_init: float, d_rate: float, b_exp: float):
    """Harmonic Model.
    This function calculates time from cumulative production.

    Parameters
    ---
    cumprod : list, ndarray
        cumulative production
    rate_init : float
        initial rate
    d_rate : float
        decline rate
    Return
    ---
    time : ndarray
        time series
    """
    raise NotImplementedError()


import os
import numpy as np
from scipy.io import loadmat
import scipy.optimize as op
from ex3_utils import displayData, sigmoid

## Machine Learning Online Class - Exercise 3 | Part 1: One-vs-all

def lrCostFunction(theta, X, y, _lambda):
    m = y.size
    reg = _lambda/m * theta; reg[0] = 0
    hx = sigmoid(X @ theta)
    J = (- y @ np.log(hx) - (1 - y) @ np.log(1 - hx))/m + (reg @ reg)/2
    grad = X.T @ (hx - y) / m + reg
    return (J, grad)

def oneVsAll(X, y, num_labels, _lambda):
    X = np.c_[np.ones(X.shape[0]), X]
    theta = np.zeros((num_labels, X.shape[1]))
    for i in range(1, num_labels+1):
        label = (y == i).astype(int) #y is loaded from mat file and values from 1 to 10
        Result = op.minimize(fun = lrCostFunction, x0 = np.zeros(X.shape[1]), args = (X, label, _lambda), method = 'TNC', jac = True)
        theta[i-1:] = Result.x
    return theta

def predictOneVsAll(all_theta, X):
    X = np.c_[np.ones(X.shape[0]), X]
    pred = np.argmax(sigmoid(X @ all_theta.T), axis=1)
    return pred + 1
# =========== Part 1: Loading and Visualizing Data =============
#  We start the exercise by first loading and visualizing the dataset.
#  You will be working with a dataset that contains handwritten digits.

# Load Training Data
print('Loading and Visualizing Data ...')
scriptdir = os.path.dirname(os.path.realpath(__file__))
data = loadmat(scriptdir + '//ex3data1.mat') # training data stored in arrays X, y
X = data['X']
y = data['y'].ravel()
num_labels = 10          # 10 labels, from 1 to 10
                         # note that we have mapped "0" to label 10
m, input_layer_size = X.shape # 5000 , 400 = 20x20 Input Images of Digits

# Randomly select 100 data points to display
sel = np.random.choice(m, 100, replace=False)
displayData(X[sel, :])

input('Program paused. Press enter to continue.\n')

# ============ Part 2a: Vectorize Logistic Regression ============

# Test case for lrCostFunction
print('\nTesting lrCostFunction() with regularization')

theta_t = np.array([-2, -1, 1, 2])
X_t = np.c_[np.ones(5), np.arange(1, 16).reshape((5,3), order='F')/10]
y_t = np.array([1, 0, 1, 0, 1])
lambda_t = 3
J, grad = lrCostFunction(theta_t, X_t, y_t, lambda_t)

print(f'\nCost: {J}')
print('Expected cost: 2.534819')
print('Gradients:\n')
print(grad)
print('Expected gradients:')
print(' 0.146561\n -0.548558\n 0.724722\n 1.398003')
input('Program paused. Press enter to continue.\n')

# ============ Part 2b: One-vs-All Training ============
print('\nTraining One-vs-All Logistic Regression...')
_lambda = 0.1
all_theta = oneVsAll(X, y, num_labels, _lambda)
input('Program paused. Press enter to continue.\n')

# ================ Part 3: Predict for One-Vs-All ================

pred = predictOneVsAll(all_theta, X)

print(f'Training Set Accuracy: {np.mean(pred == y) * 100}\n')


__author__ = 'Petar Ojdrovic'

import numpy
import scipy.stats
import time

#Options Pricing

"""
S: initial stock price
k: strike price
T: expiration time
sigma: volatility
r: risk-free rate
"""

##What is d1, d2, and pricer? d1 is the first differential of the underlying pr
def d1(S0, K, r, siga, T):
    return (numpy.log(S0/K) + (r + siga**2/2) * T)/(siga * numpy.sqrt(T))

def d2(S0, K, r, siga, T):
    return (numpy.log(S0/K) + (r - siga**2/2) * T)/(siga * numpy.sqrt(T))

def OptionsPricing(type, S0, K, r, sigma, T):
    if type=="C":
        return S0 * scipy.stats.norm.cdf(d1(S0, K, r, sigma, T)) - K * numpy.exp(-r * T) * scipy.stats.norm.cdf(d2(S0, K, r, sigma, T))
    else:
        return K * numpy.exp(-r * T) * scipy.stats.norm.cdf(-d2(S0, K, r, sigma, T)) - S0 * scipy.stats.norm.cdf(-d1(S0, K, r, sigma, T))

S0 = 104
K = 105
r = .15
sigma = 0.45
T = 0.05
type = "C"

print("Stock price is", S0)
print("Strike price is", K)
print("interest rate is", r)
print("volatility is", sigma)
print("time to maturity in years", T)

t=time.time()
c_OP = OptionsPricing(type, S0, K, r, sigma, T)
elapsed = time.time()-t
print("options price:", c_OP, elapsed)


import numpy as np
from scipy.io import wavfile

sampleRate = 44100

length = 5

t = np.linspace(0, length, sampleRate * length)  #  Produces a 5 second Audio-File
y1 =100*np.sin(500 * 2 * np.pi * t)  #  Has frequency of 440Hz

y3 =100*np.sin(2500 * 2 * np.pi * t)

y5 =100*np.sin(5000 * 2 * np.pi * t)

y7 =100*np.sin(7000 * 2 * np.pi * t)

y9 =100*np.sin(9000 * 2 * np.pi * t)

y11=100*np.sin(11500 * 2 * np.pi * t)

y13=100*np.sin(13000 * 2 * np.pi * t)

y15=100*np.sin(15000 * 2 * np.pi * t)

y17=100*np.sin(17000 * 2 * np.pi * t)

y19=100*np.sin(19000 * 2 * np.pi * t)

y21=100*np.sin(21000 * 2 * np.pi * t)

Y_Total = y1 + y3+ y5 + y7 + y9 + y11 + y13  + y15 + y17 + y19 + y21
# wavfile.write('Sine.wav', sampleRate, Y_Total)
wavfile.write('Sine2.wav',sampleRate, np.int16(Y_Total))


import numpy as np

class Perceptron(object):

    def __init__(self, input_num, activator):

        self.activator = activator
        self.weights = np.zeros((input_num))
        self.bias = 0.0

    def __str__(self):

        return 'weights\t:%s\nbias\t:%f\n' % (self.weights, self.bias)

    def predict(self, input_vec):

        return self.activator(np.dot(input_vec, self.weights) + self.bias)

    def train(self, input_vecs, labels, iteration, rate):

        for _ in range(iteration):
            self._one_iteration(input_vecs, labels, rate)

    def _one_iteration(self, input_vecs, labels, rate):

        samples = zip(input_vecs, labels)

        for input_vec, label in samples:

            output = self.predict(input_vec)

            self._update_weight(input_vec, output, label, rate)

    def _update_weight(self, input_vec, output, label, rate):

        delat = label - output
        self.weights += rate * delat * input_vec
        self.bias += rate * delat

def f(x):

    if x > 0: return 1
    else:  return 0

def get_train_dataset():

    vecs = np.array([[1, 1], [1, 0], [0, 1], [0, 0]])
    labels = np.array([1, 0, 0, 0])

    return vecs, labels

def train_and_perceptron():

    p = Perceptron(2, f)

    input_vecs, labels = get_train_dataset()
    p.train(input_vecs, labels, 10, 0.1)

    return p

if __name__ == "__main__":

    and_perceptron = train_and_perceptron()
    print(and_perceptron)

    print ('1 and 1 = ' , and_perceptron.predict([1, 1]))
    print ('1 and 0 = ' , and_perceptron.predict([1, 0]))
    print ('0 and 1 = ' , and_perceptron.predict([0, 1]))
    print ('0 and 0 = ' , and_perceptron.predict([0, 0]))


# coding: utf-8

# # Anatomy of a learning algorithm
#
# * Loss functions
#   * Types of loss function
# * Cost function
# * Optimization function
#   * Least Mean Squares
#   * Gradient Descent
#   * Stochastic Gradient Descent
# * The probabilistic interpretation
#   * Logistic Regression
# * Parametric vs. Non-Parametric

# ## Loss
#
# [Partially from here](https://heartbeat.fritz.ai/5-regression-loss-functions-all-machine-learners-should-know-4fb140e9d4b0)
#
# All the algorithms in machine learning rely on minimizing or maximizing a function, which we call “objective function”. The group of functions that are minimized are called “loss functions”. A loss function is a measure of how good a prediction model does in terms of being able to predict the expected outcome. A most commonly used method of finding the minimum point of function is “gradient descent”.

# To perform supervised learning, we must decide how we’re going to represent the objective function $h$ in a computer. As an initial choice, let’s say we decide to approximate $y$ as a linear function of $x$, which is the simplest approach, followed by the _vanilla linear regression_:
#
# $$ h_{\theta}(x) = \theta_0 + \theta_1 x_1 + \theta_2 x_2 $$
#
# Here, the $\theta_i$’s are the parameters (also called weights) parameterizing the space of linear functions mapping from $X$ to $Y$. We can rewrite the expression above as:
#
# $$ h(x) = \sum_{i=0}^n \theta_i x_i = \theta^T x $$

# The training examples and the target variable may also be represented as a matrix:
#
# $$ X = \begin{bmatrix} (x^{(1)})^T \\
# (x^{(2)})^T \\
# \vdots \\
# (x^{(m)})^T \\
# \end{bmatrix}  \hspace{1cm} \vec{y} = \begin{bmatrix} y^{(1)} \\
# (y^{(2)} \\
# \vdots \\
# y^{(m)} \\
# \end{bmatrix} $$

# Now, our **goal** is to find the parameters $\theta$.
#
# Given that we determined that the **loss** is a measure of how good is our approximation to $y$, it should be defined as (for a given sample or measure of $x^{(i)}$:
#
# $ \text{loss} \approx h_\theta(x^{(i)}) - y^{(i)} $

# In[11]:

# Plotting Values and Regression Line
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib import collections as mc
from sklearn import linear_model

# Read the data
url="http://www-bcf.usc.edu/~gareth/ISL/Advertising.csv"
adv = pd.read_csv(url)
data = pd.DataFrame({'X': list(adv.TV.values), 'Y': list(adv.sales)}).            sort_values(by=['X'])
X = pd.DataFrame(data.X)
Y = pd.DataFrame(data.Y)

max_x = np.max(X)
min_x = np.min(X)

# Fit a linear regression to the sample entries
lm = linear_model.LinearRegression()
lm.fit(X, Y)
t0 = lm.intercept_
t1 = lm.coef_[0]

# Calculating line values x and y
x = np.linspace(min_x, max_x, data.X.shape[0])
y = t0 + t1 * x

# Ploting the regression Line and the scattered points
ax = plt.figure(figsize=(8,6)).gca()
plt.scatter(data.X, data.Y, c='#aaaaaa', label='Scatter Plot')
plt.plot(x, y, color='#58b970', lw=3, label='Regression Line')

lines = [[(X.iloc[i,0], Y.iloc[i,0]),
          (X.iloc[i,0], y[i])]
         for i in range(X.shape[0])]
lc = mc.LineCollection(lines, color="red", lw=0.5, label="loss")
ax.add_collection(lc)
plt.xlabel('X')
plt.ylabel('Y')
plt.legend()
plt.show();

# ## Cost Function
#
# We define the **cost function** as a function of the parameters that we need to learn $\theta$, as:
#
# $$ J(\theta) = \frac{1}{2} \sum_{i=1}^{m} (h_\theta(x^{(i)}) - y^{(i)})^2 $$
#
# You might find this equation with a similar notation where $\beta$ is used instead of $\theta$.

# ## Optimization function
#
# Since we want to choose $\theta$ so as to minimize $J(\theta)$ we need an algorithm (or function) that will search throughout all possible values of $\theta$ to find those that **minimize the cost**.

# ### Normal Equations
#
# First way of solving the minimization problem for $\theta$ is to use a direct computation of them, after developing a little bit the cost function equation in matix form, which leads to a single expression like this:
#
# $$ \theta = (X^T X)^{-1} X^T \vec{y} $$

# ### Gradient Descent
#
# Supose that instead of developing a lot of matrix algebra we think of an algorithm that iteratively **searches** for the optimal values of $\theta$, like this:
#
# 1. start with some random values for $\theta$
# 2. for each of the parameters $j = 1, ..., n$, and a given example:
#     1. update $\theta_j$ proportionally to the loss
#
# This can also be expressed as:
#
# $$ \theta_j := \theta_j - \alpha \Big( y^{(i)} - h_{\theta}(x^{(i)}) \Big) x_{j}^{(i)} $$
#
# The new parameter $\alpha$ is the _learning rate_ and controls the amount by which we update our $\theta$ proportionally to the error, and each sample ($x_{j}^{(i)}$). This update rule is called the **LMS** update rule (Least Mean Squares), and is also known as the **Widrow-Hoff** learning rule.

# ### Batch Gradient descent
#
# If we execute the update rule, for every training examples in our training set, but only after it has been measured the error that all of them produce, the algorithm becomes (for every $j$):
#
# $\text{Repeat until convergence \{} $
#
# $ \hspace{1cm} \theta_j := \theta_j - \alpha \sum_{i=1}^{m}(y^{(i)} - h_{\theta}(x^{(i)})) \ x_{j}^{(i)} $
#
# $\text{\}}$

# ### Stochastic Gradient descent
#
# If we execute the update rule, each time we encounter a training sample in our training set, the algorithm becomes (for every $j$):
#
# $ \text{Repeat until convergence \{} $
#
#      $ \text{for i=1 to m, \{} $
#
# $ \hspace{1.25cm} \theta_j := \theta_j - \alpha \ (y^{(i)} - h_{\theta}(x^{(i)})) \ x_{j}^{(i)} $
#
#     $\text{\}}$
#
# $\}$

# ### Intuition on gradient descent
#
# Why the expression?
#
# $$ (y^{(i)} - h_{\theta}(x^{(i)})) x_{j}^{(i)} $$
#
# It is the result of solving the equation:
#
# $$ \frac{\delta}{\delta \theta_j} J(\theta) $$
#
# also known as the **gradient**, or $ \nabla J(\theta) $. The derivative is the slope of a line tangent to the curve at each point. The _curve_ is the one defined by our cost function ($J(\theta)$)

# ### Intuition (cont.)
#
# The **gradient** ($\frac{\delta}{\delta \theta_j}$) is the **slope** of the tangent of the cost function ($ J(\theta)$) at a given point.

# In[4]:

X = np.linspace(-3., 3., num=100)
Y = np.power(X, 2)
dx = np.linspace(.75, 3., num=100)

def y(x):
    return np.power(x, 2.0)

def m(x):
    return 2.0 * x

def yp(x, p):
    return ((m(p) * x) - (m(p) * p) + y(p))

def zero(p):
    return (m(p) * p - y(p))/m(p)

plt.box(False)
plt.yticks([])
plt.xticks([])
plt.plot(X, Y)
point = 2.5
plt.plot(X, np.zeros(X.shape[0]), color='black', lw=0.5)
plt.plot(point, np.power(point, 2.), marker='o', markersize=5, color="red")
plt.plot([zero(point), 3.],[0., yp(3., point)], 'g-', lw=0.75)
plt.xlabel(r'$ \theta $')
plt.ylabel(r'$ J( \theta ) $')
plt.show()

# When that value is positive, the gradient descent equation lowers $\theta$ (because of the negative sign), indicating that the minimum of $J(\theta)$ corresponds to a lower value of $\theta$.
#
# $$ \theta_j := \theta_j - \alpha \frac{\delta}{\delta \theta_j} J(\theta) $$

# In[5]:

plt.box(False)
plt.yticks([])
plt.xticks([])
plt.plot(X, Y)
plt.plot(X, np.zeros(X.shape[0]), color='black', lw=0.5)

for point in np.arange(0.5, 3.0, 0.5):
    plt.plot(point, np.power(point, 2.), marker='o', markersize=5, color="red")
    plt.plot([zero(point), 3.],[0., yp(3., point)], 'g-', lw=0.75)

plt.xlabel(r'$ \theta $')
plt.ylabel(r'$ J( \theta ) $')
plt.show()

# ## Probabilities
#
# Assume now that the actual values that we want to predict are related to our predictions via the following expression:
#
# $$ y^{(i)} = \theta^T x^{(i)} + \epsilon^{(i)} $$
#
# where $\epsilon^{(i)}$ is the error in our predictions, because of not having enough features, or including noise in our data, ....
#
# If we assume that $\epsilon^{(i)}$ follows a normal distribution $ \epsilon^{(i)} \sim \mathcal{N}(0, \sigma)$, then the density of $\epsilon^{(i)}$ is given by
#
# $$ p(\epsilon^{(i)}) = \frac{1}{\sqrt{2 \pi} \sigma} exp \Big( - \frac{(\epsilon^{(i)})^2}{2 \sigma^2} \Big) $$

# ...which automatically leads to assume that the other part of the expression
#
# $$ y^{(i)} - \theta^T x^{(i)} = \epsilon^{(i)} $$
#
# also follows a normal distribution, and its density also follows the equation from the normal distribution.

# In[6]:

import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm

x_axis = np.arange(-5, 5, 0.001)
plt.plot(x_axis, norm.pdf(x_axis, 0, 1))
plt.show()

# $$ p(y^{(i)} | x^{(i)};\theta) = \frac{1}{\sqrt{2 \pi} \sigma} exp \Big( - \frac{(y^{(i)} - \theta^T x^{(i)})^2}{2 \sigma^2} \Big) $$

# This quantity is a function of $\vec{y}$ and $X$, for a fixed value of $\theta$. We can turn this into a function of $\theta$, and in that case it's called **likelihood**.
#
# $$ p(\vec{y} | X; \theta) = L(\theta; X, \vec{y}) = L(\theta) $$
#
# Given that the probabilities are independent from each other, as a basis from our assumption, we can re-write $L(\theta)$ as follows:
#
# $$ L(\theta) = \prod_{i=1}^m p(y^{(i)} | x^{(i)};\theta) $$
#
# How to solve the equation? We should select out values for $\theta$ so as to make the data as highly probable as possible. We should chose $\theta$ to maximize $L(\theta)$. We're in a slightly different problem here: we jumped from minimizing the cost, to maximizing the probability, according to the **maximum likelihood**.

#
#
# Given that our normal distribution assumption introduces exponents, which are complicated to solve, we modify the problem, without modifying the basic principles (instead of maximizing $L(\theta)$ we maximize any strictly increasing function of $L(\theta)$.
#
# $$ \ell(\theta) = log \ L(\theta) $$
#
# which result, after solving the equation, in minimizing the expression:
#
# $$ \frac{1}{2} \sum_{i=1}^{m} (y^{(i)} - \theta^T x^{(i)})^2 $$
#
# ... our original cost function. So maximizing probabilities is equivalent to minimizing the least-squares function.

# ## Logistic Regression
#
# We can solve a classification problem using the same tools as in regression, but we want our approximation function $\ h_{\theta}(x)\ $ to have values within $(0, 1)$, instead of any continuous value. To do so:
#
# $$ h_\theta(x) = g(\theta^T \ x) = \frac{1}{1 + e^{-\theta^T\ x}} $$

# In[7]:

x = np.linspace(-10, 10, 100)
y = np.power((1. + np.exp(-x)), -1.)
plt.box(False)
plt.xticks([])
plt.yticks([])
plt.plot(x, y, lw=4)
plt.axhline(y=0.0, color='k', linestyle='-', lw=0.5)
plt.axhline(y=1.01, color='red', linestyle='--')
plt.axvline(x=0.0, color='k', linestyle='-', lw=0.5)
plt.text(6.0, 0.9, "y=TRUE (1.0)")
plt.text(-9.5, 0.05, "y=FALSE (0.0)")
plt.show();

# $g(x)$ tends towards 1 as $x \to \infty$, and $g(x)$ tends towards 0 as $x \to -\infty$.

# Following the logistic regression model, we can use a maximum likelihood estimator (under certain assumptions), to find the parameters we're looking for ($\theta$). Our problem will be re-stated as follows, to accomodate the probabilitic interpretation:
#
# $ \hspace{2cm} P(y=1\ |\ x;\theta) = h_\theta(x) \\
# \hspace{2cm} P(y=0\ |\ x;\theta) = 1 - h_\theta(x) $
#
# which can also be written as:
#
# $$ p(y\ |\ x;\theta) = (h_\theta(x))^y \ (1 - h_\theta(x))^{1-y} $$

# If we solve the previous equation for $\ell (\theta)$, we will surprisingly end up in an expression called **gradient ascent**:
#
# $$ \theta_j := \theta_j + \alpha ( y^{(i)} - h_{\theta}(x^{(i)}) ) x_{j}^{(i)} $$
#
# Notice the sign before the $\alpha$, as we're now in a maximization problem. Notice also that in this case, $h_\theta(x^{(i)})$ is a non-linear function of $\theta^T x^{(i)}$.

# ## Parametric vs. non-parametric
#
# In our original problem with linear regression we quickly realize that a straight line approach is not the optimal solution. Instead, adding a single quadratic coefficient to the equation that collects all our parameters ($\theta$) produces a better result.

# In[8]:

from sklearn.pipeline import Pipeline, make_pipeline
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
from sklearn.model_selection import cross_validate, cross_val_score, train_test_split
from sklearn.model_selection import learning_curve, validation_curve
from sklearn import datasets, linear_model
from sklearn.model_selection import cross_val_score, ShuffleSplit

def split_datasets(X, y, seed,
                   split_test_size=0.2,
                   validation_split=True):
    """
    Split X and y dataframes into training, test and validation datasets, using the provided random seed.
    Returns a dictionary with the datasets
    """
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=split_test_size, random_state=seed)

    split = dict()
    if validation_split is True:
        X_train, X_val, y_train, y_val = train_test_split(
            X_train, y_train, test_size=split_test_size, random_state=seed)
        split['X_val'] = X_val
        split['y_val'] = y_val

    split['X_train'] = X_train
    split['X_test'] = X_test
    split['y_train'] = y_train
    split['y_test'] = y_test

    return split

def prepare_datasets(data, features, target,
                     seed=1024,
                     test_size=0.2,
                     validation_split=False):
    """
    From an input dataframe, separate features from target, and
    produce splits (with or without validation).
    """
    X = pd.DataFrame(adv, columns=features)
    y = pd.DataFrame(adv.loc[:, target])
    split = split_datasets(X, y, seed=seed, split_test_size=test_size, validation_split=validation_split)
    return split

def xy_values(pipeline, X):
    """ Returns the x and y values sorted by X in a dataframe """
    y_pred = pipeline.predict(X)
    return pd.DataFrame({'x': list(X.values), 'y': list(y_pred)}).                        sort_values(by=['x'])

def polynomial_pipeline(X, y, degree):
    """
    Build a pipeline with polinomial expressions and linear regression over it.
    """
    cv = ShuffleSplit(n_splits=100, test_size=0.2, random_state=555)
    poly = PolynomialFeatures(degree=degree, interaction_only=False, include_bias=False)
    lm = linear_model.LinearRegression()
    pipeline = make_pipeline(poly, lm)
    pipeline.fit(X, y)
    return pipeline

split = prepare_datasets(adv, ['TV'], 'sales')
pipeline1 = polynomial_pipeline(split['X_train'], split['y_train'], degree=1)
pipeline2 = polynomial_pipeline(split['X_train'], split['y_train'], degree=2)

f, (ax1, ax2) = plt.subplots(nrows=1, ncols=2)
f.set_figwidth(11)

ax1.scatter(split['X_train'].values, split['y_train'].values, color='#aaaaaa')
pred1 = xy_values(pipeline1, split['X_test'])
ax1.plot(pred1.x.values, pred1.y.values, label='prediction',
         linewidth=3, color='green')
ax1.set_title('1-degree polynomial prediction')

ax2.scatter(split['X_train'].values, split['y_train'].values, color='#aaaaaa')
pred2 = xy_values(pipeline2, split['X_test'])
ax2.plot(pred2.x.values, pred2.y.values, label='prediction',
         linewidth=3, color='green')
ax2.set_title('2-degree polynomial prediction')

plt.show();

# ...But the challenge is that in more complicated spaces (higer number of dimensions, or in plain english, with more features involved), it would be tricky to find what is the correct polynomial degree that best fit our target variable.
#
# This is the main reason why the choice of features is so important to ensure a proper behavior of the model.

# The original structure of the optimization algorithm suggested in the regression algorithm is as follows:
#
# 1. Fit $\theta$ to minimize $\sum_i (y^{(i)}\ -\ \theta^T x^{(i)})^2$
# 2. Produce a prediction by computing $\theta^T x$.
#
# but we could make our problem a bit more interesting if instead of focusing only in the _parameters_ of the function, we also consider that the algorithm must find additional values, not related to the function itself:
#
# 1. Fit $\theta$ to minimize $\sum_i \omega^{(i)}(y^{(i)}\ -\ \theta^T x^{(i)})^2$
# 2. Produce a prediction by computing $\theta^T x$
#
# This new expression introduces $\omega^{(i)}$: **weights**.

# This example shows how a **non-parametric** algorithm works. The traditional version of linear regression is an example of **parametric** algorithm.
#
# Parametric means fixed number of parameters, and strong assumptions on the function to be used in our model.
#
# Non-parametric means no assumption over the function used by the model and the need to keep the training data _in memory_ to make predictions. Locally weighted linear regression uses the following expression to determine each weight:
#
# $$ \omega^{(i)} = exp \Big( - \frac{(x^{(i)} - x)^2}{2 \tau^2} \Big) $$
#
# The weights depend on the point $x$ at which we're trying to evaluate $x$.

# Using the normal equations (closed form of how to solve least squares with matrices):
#
# $$ \theta = (X^T\ X)^{-1} \ X^T \ \vec{y} $$
#
# $$ \omega^{(i)} = exp \Big( - \frac{(x^{(i)} - x)^2}{2 \tau^2} \Big) $$

# In[10]:

def radial_kernel(x0, X, tau):
    return np.exp(np.sum((X - x0) ** 2, axis=1) / (-2 * tau * tau))

def local_regression(x0, X, Y, tau):
    # add bias term
    x0 = np.r_[1, x0]
    X = np.c_[np.ones(len(X)), X]

    # fit model: normal equations with kernel
    xw = X.T * radial_kernel(x0, X, tau)
    theta = np.linalg.pinv(xw @ X) @ xw @ Y

    return x0 @ theta

# In[9]:

# Implementation in [this page](https://medium.com/100-days-of-algorithms/day-97-locally-weighted-regression-c9cfaff087fb).
def radial_kernel(x0, X, tau):
    return np.exp(np.sum((X - x0) ** 2, axis=1) / (-2 * tau * tau))

def local_regression(x0, X, Y, tau):
    # add bias term
    x0 = np.r_[1, x0]
    X = np.c_[np.ones(len(X)), X]

    # fit model: normal equations with kernel
    xw = X.T * radial_kernel(x0, X, tau)
    theta = np.linalg.pinv(xw @ X) @ xw @ Y

    # predict value
    return x0 @ theta

def lwlr(x, y, tau):
    max_x = np.max(x)
    min_x = np.min(x)
    # prediction
    domain = np.linspace(min_x, max_x, num=len(Y))
    prediction = [local_regression(x0, X, Y, tau) for x0 in domain]
    return prediction

# Initialize data
X = pd.DataFrame(data.X)['X'].values
Y = pd.DataFrame(data.Y)['Y'].values

# Plot the approximation
yest = lwlr(X, Y, tau=5)

plt.figure(figsize=(8,6))
plt.title('Locally weighted linear regression')
plt.scatter(X, Y, label='y noisy', alpha=0.3)
for tau_ in [1., 5., 10.]:
    yest = lwlr(X, Y, tau=tau_)
    plt.plot(X, yest,
            label=r'$\tau={}$'.format(tau_), lw=1, alpha=1)
plt.legend()
plt.show()

scripts/Anatomy of a learning algorithm.py


import sympy as sp
import random

import sys, os
sys.path.append(os.path.abspath('..'))
from cyllene import *
from IPython.display import Markdown, Latex

def show_values(f,r):
    display(Markdown("Current values: "))
    display(Latex("$f(x)= "+sp.latex(f(x))+"$"))
    display(Latex("r =  "+sp.latex(r)))

def show_limit_estimate(f,r):

    display(Markdown("As the values of x get closer and closer to
"))
    display(Markdown("`"+str(r)+"`"))
    display(Markdown("the values f(x) seem to get close to somewhere around"))
    display(Markdown("`"+str((sp.N(f(r-0.001),4)+sp.N(f(r+0.001),4))/2)+"`"))

def limit(f,r):

    return sp.limit(f(x),x,r)

def lim_left(f,r):

    return sp.limit(f(x),x,r, dir='-')

def lim_right(f,r, dir='-'):

    return sp.limit(f(x),x,r)

def show_limit_analysis(f,r):
    display(Markdown("We have set: "))
    display(Latex("$f(x)= "+sp.latex(f(x))+"$"))
    display(Latex("$r =  "+sp.latex(r)+"$"))

    display(Markdown("
"))
    display(Markdown("**Problem**: *Estimate*  "))
    display(Latex("$\large \lim_{x \\to r} f(x)$"))
    display(Markdown("---"))

    display(Markdown("We generate a table of values of f around (but *not exactly at*) r"))

    display(show_table(f, [r-0.1, r-0.01, r-0.001, r+0.001, r+0.01, r+0.1]))

    display(Markdown("
"))

    display(Markdown("What do the values suggest a reasonable estimate for the limit would be?"))
    # display(Markdown("As the values of x get closer and closer to
"))
    # display(Markdown("`"+str(r)+"`"))
    # display(Markdown("the values f(x) seem to get close to somewhere around"))
    # display(Markdown("`"+str((sp.N(f(r-0.001),4)+sp.N(f(r+0.001),4))/2)+"`"))

def generate_example():

    s = random.randint(-30,30)/10
    b = (random.randint(-10,10)+5)**2/10
    g1 = function('quadratic')
    g2 = function(g1(x)+b)
    g = function(sp.Piecewise((g1(x), x<s),  (g2(x),x>s), (g1(s)+2*b, True)))

    display(Latex("$\\text{New $g(x), s$ generated}$"))
    display(Latex("$s = " + str(s) + "$"))
    # display(Markdown('
'))
    display(Markdown("**Problem**: *Estimate the one-sided limits*  "))
    display(Latex("$ \lim_{x \\to s^-} g(x) \\text{ and } \lim_{x \\to s^+} g(x)$"))

    return g, s

display(Markdown("Initialization successful"))


'''
calc_performance_metrics_for_binary_predictions

Provides implementation of common metrics for assessing a binary classifier's
hard decisions against true binary labels, including:
* accuracy
* true positive rate and true negative rate (TPR and TNR)
* positive predictive value and negative predictive value (PPV and NPV)
'''

import numpy as np

def calc_TP_TN_FP_FN(ytrue_N, yhat_N):
    ''' Count the four possible states of true and predicted binary values.

    Args
    ----
    ytrue_N : 1D array, shape (n_examples,) = (N,)
        All values must be either 0 or 1. Will be cast to int dtype.
        Each entry represents the binary 'true' label of one example
        One entry per example in current dataset
    yhat_N : 1D array, shape (n_examples,) = (N,)
        All values must be either 0 or 1. Will be cast to int dtype.
        Each entry represents a predicted label for one example
        One entry per example in current dataset.
        Needs to be same size as ytrue_N.

    Returns
    -------
    TP : int
        Number of true positives
    TN : int
        Number of true negatives
    FP : int
        Number of false positives
    FN : int
        Number of false negatives

    Examples
    --------

    '''
    # Cast input to integer just to be sure we're getting what's expected
    ytrue_N = np.asarray(ytrue_N, dtype=np.int32)
    yhat_N = np.asarray(yhat_N, dtype=np.int32)

    # TODO fix by calculating the number of true pos, true neg, etc.
    TP_arr = np.bitwise_and(ytrue_N, yhat_N)
    TN_arr = np.bitwise_and(np.logical_not(ytrue_N), np.logical_not(yhat_N))

    NP = np.count_nonzero(yhat_N == 1)
    NN = np.count_nonzero(yhat_N == 0)

    TP  = np.count_nonzero(TP_arr == 1)
    TN = np.count_nonzero(TN_arr == 1)
    FP = NP - TP
    FN = NN - TN
    return TP, TN, FP, FN

def calc_ACC(ytrue_N, yhat_N):
    ''' Compute the accuracy of provided predicted binary values.

    Args
    ----
    ytrue_N : 1D array of floats, shape (n_examples,) = (N,)
        All values must be either 0.0 or 1.0.
        Each entry represents the binary 'true' label of one example
        One entry per example in current dataset
    yhat_N : 1D array of floats, shape (n_examples,) = (N,)
        All values must be either 0.0 or 1.0.
        Each entry represents a predicted label for one example
        One entry per example in current dataset.
        Needs to be same size as ytrue_N.

    Returns
    -------
    acc : float
        Accuracy = ratio of number correct over total number of examples

    Examples
    --------
    >>> N = 8
    >>> ytrue_N = np.asarray([0., 0., 0., 0., 1., 1., 1., 1.])
    >>> yhat_N  = np.asarray([0., 0., 1., 0., 1., 1., 0., 0.])
    >>> acc = calc_ACC(ytrue_N, yhat_N)
    >>> print("%.3f" % acc)
    0.625
    '''

    TP, TN, FP, FN = calc_TP_TN_FP_FN(ytrue_N, yhat_N);
    # TODO compute accuracy
    # You should *use* your calc_TP_TN_FP_FN function from above
    # Hint: make sure denominator will never be exactly zero

    # by adding a small value like 1e-10
    return (TP + TN) / (TP + TN + FN + FP + 1e-10)

def calc_TPR(ytrue_N, yhat_N):
    ''' Compute the true positive rate of provided predicted binary values.

    Also known as the recall.

    Args
    ----
    ytrue_N : 1D array of floats, shape (n_examples,) = (N,)
        All values must be either 0.0 or 1.0.
        Each entry represents the binary 'true' label of one example
        One entry per example in current dataset
    yhat_N : 1D array of floats, shape (n_examples,) = (N,)
        All values must be either 0.0 or 1.0.
        Each entry represents a predicted label for one example
        One entry per example in current dataset.
        Needs to be same size as ytrue_N.

    Returns
    -------
    tpr : float
        TPR = ratio of true positives over total labeled positive

    Examples
    --------
    >>> N = 8
    >>> ytrue_N = np.asarray([0., 0., 0., 0., 1., 1., 1., 1.])
    >>> yhat_N  = np.asarray([0., 0., 1., 0., 1., 1., 0., 0.])
    >>> tpr = calc_TPR(ytrue_N, yhat_N)
    >>> print("%.3f" % tpr)
    0.500

    # Verify what happens with empty input
    >>> empty_val = calc_TPR([], [])
    >>> print("%.3f" % empty_val)
    0.000
    '''
    # You should *use* your calc_TP_TN_FP_FN function from above
    # Hint: make sure denominator will never be exactly zero
    TP, TN, FP, FN = calc_TP_TN_FP_FN(ytrue_N, yhat_N);

    # by adding a small value like 1e-10
    return (TP) / (TP + FN + 1e-10)

def calc_TNR(ytrue_N, yhat_N):
    ''' Compute the true negative rate of provided predicted binary values.

    Args
    ----
    ytrue_N : 1D array of floats, shape (n_examples,) = (N,)
        All values must be either 0.0 or 1.0.
        Each entry represents the binary 'true' label of one example
        One entry per example in current dataset
    yhat_N : 1D array of floats, shape (n_examples,) = (N,)
        All values must be either 0.0 or 1.0.
        Each entry represents a predicted label for one example
        One entry per example in current dataset.
        Needs to be same size as ytrue_N.

    Returns
    -------
    tnr : float
        TNR = ratio of true negatives over total labeled negative.

    Examples
    --------
    >>> N = 8
    >>> ytrue_N = np.asarray([0., 0., 0., 0., 1., 1., 1., 1.])
    >>> yhat_N  = np.asarray([0., 0., 1., 0., 1., 1., 0., 0.])
    >>> tnr = calc_TNR(ytrue_N, yhat_N)
    >>> print("%.3f" % tnr)
    0.750

    # Verify what happens with empty input
    >>> empty_val = calc_TNR([], [])
    >>> print("%.3f" % empty_val)
    0.000
    '''
    # TODO compute TNR
    # You should *use* your calc_TP_TN_FP_FN function from above
    # Hint: make sure denominator will never be exactly zero
    # by adding a small value like 1e-10
    TP, TN, FP, FN = calc_TP_TN_FP_FN(ytrue_N, yhat_N);

    # by adding a small value like 1e-10
    return (TN) / (FP + TN + 1e-10)

def calc_PPV(ytrue_N, yhat_N):
    ''' Compute positive predictive value of provided predicted binary values.

    Also known as the precision.

    Args
    ----
    ytrue_N : 1D array of floats, shape (n_examples,) = (N,)
        All values must be either 0.0 or 1.0.
        Each entry represents the binary 'true' label of one example
        One entry per example in current dataset
    yhat_N : 1D array of floats, shape (n_examples,) = (N,)
        All values must be either 0.0 or 1.0.
        Each entry represents a predicted label for one example
        One entry per example in current dataset.
        Needs to be same size as ytrue_N.

    Returns
    -------
    ppv : float
        PPV = ratio of true positives over total predicted positive.

    Examples
    --------
    >>> N = 8
    >>> ytrue_N = np.asarray([0., 0., 0., 0., 1., 1., 1., 1.])
    >>> yhat_N  = np.asarray([0., 0., 1., 0., 1., 1., 0., 0.])
    >>> ppv = calc_PPV(ytrue_N, yhat_N)
    >>> print("%.3f" % ppv)
    0.667

    # Verify what happens with empty input
    >>> empty_val = calc_PPV([], [])
    >>> print("%.3f" % empty_val)
    0.000
    '''
    # TODO compute PPV
    # You should *use* your calc_TP_TN_FP_FN function from above
    # Hint: make sure denominator will never be exactly zero
    # by adding a small value like 1e-10
    TP, TN, FP, FN = calc_TP_TN_FP_FN(ytrue_N, yhat_N);

    return (TP) / (TP + FP + 1e-10)

def calc_NPV(ytrue_N, yhat_N):
    ''' Compute negative predictive value of provided predicted binary values.

    Args
    ----
    ytrue_N : 1D array of floats, shape (n_examples,) = (N,)
        All values must be either 0.0 or 1.0.
        Each entry represents the binary 'true' label of one example
        One entry per example in current dataset
    yhat_N : 1D array of floats, shape (n_examples,) = (N,)
        All values must be either 0.0 or 1.0.
        Each entry represents a predicted label for one example
        One entry per example in current dataset.
        Needs to be same size as ytrue_N.

    Returns
    -------
    npv : float
        NPV = ratio of true negative over total predicted negative.

    Examples
    --------
    >>> N = 8
    >>> ytrue_N = np.asarray([0., 0., 0., 0., 1., 1., 1., 1.])
    >>> yhat_N  = np.asarray([0., 0., 1., 0., 1., 1., 0., 0.])
    >>> npv = calc_NPV(ytrue_N, yhat_N)
    >>> print("%.3f" % npv)
    0.600

    # Verify what happens with empty input
    >>> empty_val = calc_NPV([], [])
    >>> print("%.3f" % empty_val)
    0.000
    '''
    # TODO compute NPV
    # You should *use* your calc_TP_TN_FP_FN function from above
    # Hint: make sure denominator will never be exactly zero
    # by adding a small value like 1e-10
    TP, TN, FP, FN = calc_TP_TN_FP_FN(ytrue_N, yhat_N);

    # by adding a small value like 1e-10
    return (TN) / (TN + FN + 1e-10)

# N = 8
# ytrue_N = np.asarray([0., 0., 0., 0., 1., 1., 1., 1.])
# yhat_N  = np.asarray([0., 0., 1., 0., 1., 1., 0., 0.])
# TP, TN, FP, FN = calc_TP_TN_FP_FN(ytrue_N, yhat_N)
# print(TP)
# #2
# print(TN)
# #3
# print(FP)
# #1
# print(FN)
# #2
# print(np.allclose(TP + TN + FP + FN, N))
# #True

# N = 8
# ytrue_N = np.asarray([0., 0., 0., 0., 1., 1., 1., 1.])
# yhat_N  = np.asarray([0., 0., 1., 0., 1., 1., 0., 0.])
# acc = calc_ACC(ytrue_N, yhat_N)
# print("%.3f" % acc)
# #0.625

# N = 8
# ytrue_N = np.asarray([0., 0., 0., 0., 1., 1., 1., 1.])
# yhat_N  = np.asarray([0., 0., 1., 0., 1., 1., 0., 0.])
# tpr = calc_TPR(ytrue_N, yhat_N)
# print("%.3f" % tpr)
# # 0.500
#
# # Verify what happens with empty input
# empty_val = calc_TPR([], [])
# print("%.3f" % empty_val)
# # 0.000
#
# N = 8
# ytrue_N = np.asarray([0., 0., 0., 0., 1., 1., 1., 1.])
# yhat_N  = np.asarray([0., 0., 1., 0., 1., 1., 0., 0.])
# tnr = calc_TNR(ytrue_N, yhat_N)
# print("%.3f" % tnr)
# # 0.750
#
# # Verify what happens with empty input
# empty_val = calc_TNR([], [])
# print("%.3f" % empty_val)
# # 0.000

# N = 8
# ytrue_N = np.asarray([0., 0., 0., 0., 1., 1., 1., 1.])
# yhat_N  = np.asarray([0., 0., 1., 0., 1., 1., 0., 0.])
# ppv = calc_PPV(ytrue_N, yhat_N)
# print("%.3f" % ppv)
# # 0.667
#
# # Verify what happens with empty input
# empty_val = calc_PPV([], [])
# print("%.3f" % empty_val)
# # 0.000

# N = 8
# ytrue_N = np.asarray([0., 0., 0., 0., 1., 1., 1., 1.])
# yhat_N  = np.asarray([0., 0., 1., 0., 1., 1., 0., 0.])
# npv = calc_NPV(ytrue_N, yhat_N)
# print("%.3f" % npv)
# # 0.600
#
# # Verify what happens with empty input
# empty_val = calc_NPV([], [])
# print("%.3f" % empty_val)
# # 0.000


import numpy as np
from scipy.optimize import minimize
import matplotlib.pyplot as plt

def linearRegCostFunction(X, y, theta, lambda_val, return_grad=False):
    #   [J, grad] = LINEARREGCOSTFUNCTION(X, y, theta, lambda_val) computes the
    #   cost of using theta as the parameter for linear regression to fit the
    #   data points in X and y. Returns the cost in J and the gradient in grad

    # Initialize some useful values
    m = len(y) # number of training examples

    # force to be 2D vector
    theta = np.reshape(theta, (-1,y.shape[1]))

    # You need to return the following variables correctly
    J = 0
    grad = np.zeros(theta.shape)
    # cost function
    J = (1. / (2 * m)) * np.power((np.dot(X, theta) - y) , 2).sum() + (float(lambda_val) / (2*m)) * np.power(theta[1:theta.shape[0]],2).sum()

    # regularized gradient
    grad = (1. / m) * np.dot(X.T, np.dot(X,theta) - y) + (float(lambda_val) / m) * theta

    # unregularize first gradient
    grad_no_regularization = (1. / m) * np.dot(X.T, np.dot(X, theta) - y)
    grad[0] = grad_no_regularization[0]

    if return_grad == True:
        return J, grad.flatten()
    elif return_grad == False:
        return J

def trainLinearReg(X, y, lambda_val):
    #   [theta] = TRAINLINEARREG (X, y, lambda_val) trains linear regression using
    #   the dataset (X, y) and regularization parameter lambda_val. Returns the
    #   trained parameters theta.

    # Initialize Theta
    initial_theta = np.zeros((X.shape[1], 1))

    # Short hand for cost function to be minimized
    def costFunc(theta):
        return linearRegCostFunction(X, y, theta, lambda_val, True)

    # Now, costFunction is a function that takes in only one argument
    maxiter = 200
    results = minimize(costFunc, x0=initial_theta, options={'disp': True, 'maxiter':maxiter}, method="L-BFGS-B", jac=True)

    theta = results["x"]

    return theta

def learningCurve(X, y, Xval, yval, lambda_val):
    #   [error_train, error_val] = ...
    #       LEARNINGCURVE(X, y, Xval, yval, lambda_val) returns the train and
    #       cross validation set errors for a learning curve. In particular,
    #       it returns two vectors of the same length - error_train and
    #       error_val. Then, error_train(i) contains the training error for
    #       i examples (and similarly for error_val(i)).
    #
    #   In this function, you will compute the train and test errors for
    #   dataset sizes from 1 up to m. In practice, when working with larger
    #   datasets, you might want to do this in larger intervals.

    # Number of training examples
    m = len(X)

    # You need to return these values correctly
    error_train = np.zeros((m, 1))
    error_val = np.zeros((m, 1))

    for i in range(1, m + 1):
        # define training variables for this loop
        X_train = X[:i]
        y_train = y[:i]

        # learn theta parameters with current X_train and y_train
        theta = trainLinearReg(X_train, y_train, lambda_val)

        # fill in error_train(i) and error_val(i)
        #   note that for error computation, we set lambda_val = 0 in the last argument
        error_train[i - 1] = linearRegCostFunction(X_train, y_train, theta, 0)
        error_val[i - 1] = linearRegCostFunction(Xval, yval, theta, 0)

    return error_train, error_val

def polyFeatures(X, p):
    #   [X_poly] = POLYFEATURES(X, p) takes a data matrix X (size m x 1) and
    #   maps each example into its polynomial features where
    #   X_poly(i, :) = [X(i) X(i).^2 X(i).^3 ...  X(i).^p];
    #

    # You need to return the following variables correctly.
    # X_poly = np.zeros((X.size, p))

    # initialize X_poly to be equal to the single-column X
    X_poly = X

    # if p is equal or greater than 2
    if p >= 2:

        # for each number between column 2 (index 1) and last column
        for k in range(1, p):
            # add k-th column of polynomial features where k-th column is X.^k
            X_poly = np.column_stack((X_poly, np.power(X, k + 1)))

    return X_poly

def featureNormalize(X):
    #   FEATURENORMALIZE(X) returns a normalized version of X where
    #   the mean value of each feature is 0 and the standard deviation
    #   is 1. This is often a good preprocessing step to do when
    #   working with learning algorithms.
    mu = np.mean(X, axis=0)
    X_norm = X - mu
    sigma = np.std(X_norm, axis=0)
    X_norm = X_norm / sigma

    return X_norm, mu, sigma

def plotFit(min_x, max_x, mu, sigma, theta, p):
    #   PLOTFIT(min_x, max_x, mu, sigma, theta, p) plots the learned polynomial
    #   fit with power p and feature normalization (mu, sigma).

    # Hold on to the current figure
    plt.hold(True)

    # We plot a range slightly bigger than the min and max values to get
    # an idea of how the fit will vary outside the range of the data points
    x = np.array(np.arange(min_x - 15, max_x + 25, 0.05)) # 1D vector

    # Map the X values
    X_poly = polyFeatures(x, p)
    X_poly = X_poly - mu
    X_poly = X_poly / sigma

    # Add ones
    X_poly = np.column_stack((np.ones((x.shape[0],1)), X_poly))

    # Plot
    plt.plot(x, np.dot(X_poly, theta), '--', linewidth=2)

    # Hold off to the current figure
    plt.hold(False)

def validationCurve(X, y, Xval, yval):
    #   [lambda_vec, error_train, error_val] = ...
    #       VALIDATIONCURVE(X, y, Xval, yval) returns the train
    #       and validation errors (in error_train, error_val)
    #       for different values of lambda. You are given the training set (X,
    #       y) and validation set (Xval, yval).

    # Selected values of lambda (you should not change this)
    lambda_vec = np.array([0, 0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 1, 3, 10])

    # You need to return these variables correctly.
    error_train = np.zeros((len(lambda_vec), 1))
    error_val = np.zeros((len(lambda_vec), 1))

    for i in range(len(lambda_vec)):
        lambda_val = lambda_vec[i]
        # learn theta parameters with current lambda value
        theta = trainLinearReg(X, y, lambda_val)
        # fill in error_train[i] and error_val[i]
        #   note that for error computation, we set lambda = 0 in the last argument
        error_train[i] = linearRegCostFunction(X, y, theta, 0)
        error_val[i] = linearRegCostFunction(Xval, yval, theta, 0)

    return lambda_vec, error_train, error_val


from __future__ import division
import numpy as np
from matplotlib import animation
from matplotlib import cm
from matplotlib import pyplot as plt
from matplotlib.artist import setp
plt.switch_backend('tkagg')
from mpl_toolkits.mplot3d.axes3d import Axes3D
from scipy.sparse import spdiags, coo_matrix, bmat, identity
from scipy.sparse.linalg import spsolve

def heatexplicit(init_conditions,x_subintervals,t_subintervals,x_interval=[-10,10],T=1.,flag3d="off",nu=1.):
	'''
	Parameters
	nu: diffusive constant
	L, T: Solve on the Cartesian rectangle (x,t) in x_interval x [0,T]
	x_subintervals: Number of subintervals in spatial dimension
	t_subintervals: Number of subintervals in time dimension
	CFL condition: nu delta_t/delta_x**2 <= 1/2. Where delta_t = T/t_subintervals,
	delta_x = (b-a)/x_subintervals,
	a, b = x_interval[0], x_interval[1]
	In terms of our constants, this means that t_subintervals >=(2.*nu)*(T*x_subintervals**2/(b-a)**2.) or
	equivalently that (nu/4.)*(T/L**2.)*(x_subintervals**2./t_subintervals) <= 1/2
	'''
	a, b = x_interval[0], x_interval[1]

	delta_x, delta_t = (b-a)/x_subintervals, T/t_subintervals
	if nu*delta_t/delta_x**2 > 1/2:
		print "The CFL condition is not satisfied"
		print "Must have nu*delta_t/delta_x**2 <= 1/2, i.e."+str(nu*delta_t/delta_x**2) + "<= 1/2"
		print "Recommend t_subintervals = "+str(t_subintervals)+">" +str((2.*nu)*(T*x_subintervals**2/(b-a)**2.))

	K = nu*delta_t/delta_x**2.
	# print str(J)+" subintervals of Space domain.\n"
	# print str(int(N))+" subintervals of Time domain."

	D0,D1,diags = (1.-2.*K)*np.ones((1,(x_subintervals-1))), K*np.ones((1,(x_subintervals-1))), np.array([0,-1,1])
	data = np.concatenate((D0,D1,D1),axis=0) # This stacks up rows
	A=spdiags(data,diags,(x_subintervals-1),(x_subintervals-1)).asformat('csr')

	U = np.zeros((x_subintervals+1,t_subintervals+1)) #[:,0:-1]
	U[:,0] = init_conditions #np.maximum(1.-np.linspace(a,b,J+1)**2,0.)	# Initial Conditions
	for j in range(0,int(t_subintervals)+1):
		if j>0: U[1:-1,j] =  A*U[1:-1,j-1]

	return np.linspace(a,b,x_subintervals+1), U

def heat_Crank_Nicolson(init_conditions,x_subintervals,t_subintervals,x_interval=[-10,10],T=1.,flag3d="off",nu=1.):
	'''
	Parameters
	nu: diffusive constant
	L, T: Solve on the Cartesian rectangle (x,t) in x_interval x [0,T]
	x_subintervals: Number of subintervals in spatial dimension
	t_subintervals: Number of subintervals in time dimension
	a, b = x_interval[0], x_interval[1]
	'''
	a, b = x_interval[0], x_interval[1]
	delta_x, delta_t = (b-a)/x_subintervals, T/t_subintervals

	K = .5*nu*delta_t/delta_x**2.

	D0,D1,diags = (1-2.*K)*np.ones((1,(x_subintervals-1))), K*np.ones((1,(x_subintervals-1))), np.array([0,-1,1])
	data = np.concatenate((D0,D1,D1),axis=0) # This stacks up rows
	A=spdiags(data,diags,(x_subintervals-1),(x_subintervals-1)).asformat('csr')
	# print K
	# print A.todense()
	D0,D1,diags = (1.+2.*K)*np.ones((1,(x_subintervals-1))), -K*np.ones((1,(x_subintervals-1))), np.array([0,-1,1])
	data = np.concatenate((D0,D1,D1),axis=0) # This stacks up rows
	B=spdiags(data,diags,(x_subintervals-1),(x_subintervals-1)).asformat('csr')

	U = np.zeros((x_subintervals+1,t_subintervals+1))
	U[:,0] = init_conditions
	for j in range(0,int(t_subintervals)+1):
		if j>0: U[1:-1,j] =  spsolve(B,A*U[1:-1,j-1] )
	return np.linspace(a,b,x_subintervals+1), U

a = 0
def math_animation(Data,time_steps,view,interval_length):
	X,Array = Data

	fig = plt.figure()
	ax = plt.axes(xlim=tuple(view[0:2]), ylim=tuple(view[2:]) )
	line, = ax.plot([], [], lw=2)

	def initialize_background():
		line.set_data([], [])
		return line,

	def animate_function(i):
		global a
		if a<time_steps:
			line.set_data(X, Array[i,:])
			setp(line, linewidth=2, color='k')
			# line = Line2D(X, Array[:,i], color='red', linewidth=2)
		else:
			line.set_data(X, Array[-1,:])
		a+=1

		return line

	# call the animator.  blit=True means only re-draw the parts that have changed.
	anim = animation.FuncAnimation(fig, animate_function, init_func=initialize_background,
                               frames=time_steps, interval=interval_length)#, blit=True)
	# frames must be a generator, an iterable, or a number of frames
	# Draws a new frame every interval milliseconds.

	plt.show()

# Solving u_t = nu u_{xx}, t \in [0,T], x \in [-L, L]
# import numpy as np
# from solution import heatexplicit, math_animation
# import matplotlib.pyplot as plt
# from matplotlib import cm

def plot3d():
	T, L, J, nu = 1., 10., 320, 1.
	flag3d = "3d_plot"

	X,U = heatexplicit(J,nu,L,T,flag3d)
	N = U.shape[1]-1
	# #Produce 3D plot of solution
	xv,tv = np.meshgrid(np.linspace(-L,L,J/4+1), np.linspace(0,T,N/8+1))
	Z = U[::4,::8].T

	fig = plt.figure()
	ax = fig.gca(projection='3d')
	surf = ax.plot_surface(tv, xv, Z, rstride=1, cstride=1, cmap=cm.coolwarm,
	    								linewidth=0, antialiased=False)
	ax.set_xlabel('Y'); ax.set_ylabel('X'); ax.set_zlabel('Z')
	plt.show()

def animate_heat1d():
	x_subintervals, t_subintervals = 20,1600
	x_interval,t_final = [0,1],2
	init_conditions = np.sin(2.*np.pi*(np.linspace(0,1,x_subintervals+1)))
	Data = heatexplicit(init_conditions,x_subintervals,t_subintervals,x_interval,T=t_final,flag3d="on",nu=1.)

	Data = Data[0], Data[1].T[0:-1,:]
	time_steps, view = Data[1].shape[0], [-.1, 1.1,-1.1, 1.1]
	interval=40
	math_animation(Data,time_steps,view,interval)

def plot_error():
	# Compare solutions for various discretizations
	X, U, L = [], [], 10.
	List, LineStyles = [40,80,160,320,640,1280,2560], ['--k','-.k',':k','-k','-k','-k']
	for points in List:
		x,u = heatexplicit(J=points,nu=1.,L=10.,T=1.,flag3d="off")
		X.append(x);
		U.append(u[:,-1])
		del x, u
		print points
	# # Plot of solutions for varying discretizations
	# for j in range(0,len(LineStyles)): plt.plot(X[j],U[j],LineStyles[j],linewidth=1)
	# plt.axis([-10,10,-.1,.4])
	# plt.show()
	# plt.clf()
	h = [2.*L/item for item in List[:-1]]

	def graph(X,U,List,h):
		# # Error Chart 1: Plot approximate error for each solution
		# for j in range(0,len(List)-1): plt.plot(X[j],abs(U[-1][::2**(6-j)]-U[j]),'-k',linewidth=1.2)
		for j in range(0,len(List)-1): plt.plot(X[j],abs(U[-1][::List[-1]/List[j]]-U[j]),'-k',linewidth=1.2)
		plt.savefig("ApproximateError.pdf")
		plt.clf()

		# # Error Chart 2: Create a log-log plot of the max error of each solution

		# MaxError = [max(abs(U[-1][::2**(6-j)]-U[j] )) for j in range(0,len(List)-1)]
		MaxError = [max(abs(U[-1][::List[-1]/List[j]]-U[j] )) for j in range(0,len(List)-1)]
		plt.loglog(h,MaxError,'-k',h,MaxError,'ko')
		plt.ylabel("Max Error")
		plt.xlabel('$\Delta x$')
		h, MaxError = np.log(h)/np.log(10),np.log(MaxError)/np.log(10)
		print '\n'*2, "Approximate order of accuracy = ", (MaxError[-1]-MaxError[0])/(h[-1]-h[0])
		plt.savefig("MaximumError.pdf")
		# plt.show()

	graph(X,U,List,h)

if __name__=="__main__":

	# plot3d()
	# plot_error()
	animate_heat1d()


"""Credits to:
    https://scipy-cookbook.readthedocs.io/items/LoktaVolterraTutorial.html
"""
import seaborn as sns
import numpy as np
import matplotlib.pyplot as plt
from scipy import integrate

def dX_dt(X, t=0):
    """Returns the growth rate of bears and fishes."""

    res = np.array([
        a * X[0] - b * X[0] * X[1],
        (c * X[0] - d) * X[1] ])
    return res

# Set seaborn theme.
sns.set_theme(style='darkgrid')

# Parameters.
a = 1.
b = 0.1
c = 0.075
d = 1.5

# Equilibrium points.
X_f0 = np.array([0. , 0.])
X_f1 = np.array([d / c, a / b])

# Time.
t = np.linspace(0, 15,  1000)

# Initials conditions.
X0 = np.array([10, 5])

# Integrate the system.
X = integrate.odeint(dX_dt, X0, t)
fishes, bears = X.T

#
# Plot the timeline of populations.
#
a4_dims = (11.7, 8.27)
fig, ax = plt.subplots(figsize=a4_dims)
plt.plot(t, fishes, 'r-', label = 'Fishes')
plt.plot(t, bears, 'b-', label = 'Bears')
plt.grid(True)
plt.legend(loc='best')
plt.xlabel('Time')
plt.ylabel('Population')
plt.title('Time evolution')
fig.savefig('populations.png')

#
# Plot the phase plot.
#
fig, ax = plt.subplots(figsize = a4_dims)

# Scale factor for initial conditions.
values  = np.linspace(0.2, 1, 5)

# Colors for each trajectory.
vcolors = plt.cm.autumn_r(np.linspace(0.2, 1., len(values)))

# Plot all the trajectories.
for value, col in zip(values, vcolors):

    # Solve a new system.
    X0 = value * X_f1
    X = integrate.odeint(dX_dt, X0, t)

    # Plot the solution.
    plt.plot( X[:,0], X[:,1], lw = 3.5 * value, color = col,
        label = f'X0=({X0[0]:.2f}, {X0[1]:.2f})')

# Define a grid and compute direction for each point.
n_points = 20
ymax = plt.ylim(ymin = 0)[1]
xmax = plt.xlim(xmin = 0)[1]
x = np.linspace(0, xmax, n_points)
y = np.linspace(0, ymax, n_points)
X1, Y1 = np.meshgrid(x, y)

# Growth rate for the whole grid.
DX1, DY1 = dX_dt([X1, Y1])

# Normalize and stabilize.
M = np.hypot(DX1, DY1)
M[M == 0] = 1.
DX1 /= M
DY1 /= M

plt.title('Phase plot')
Q = plt.quiver(X1, Y1, DX1, DY1, M, pivot = 'mid', cmap = plt.cm.jet)
plt.xlabel('Fishes')
plt.ylabel('Bears')
plt.legend()
plt.grid()
plt.xlim(0, xmax)
plt.ylim(0, ymax)
fig.savefig('phases.png')


from scipy import *
from numpy import *
from matplotlib.pyplot import *
from scipy.linalg import *
from numpy.linalg import *

def polyinterp(t,y):
    """
    polyinterp(t,y)

    Return a callable polynomial interpolant through the points in vectors `t`,`y`. Uses
    the barycentric interpolation formula.
    """
    n = len(t)-1
    C = (t[-1]-t[0]) / 4       # scaling factor to ensure stability
    tc = t/C

    # Adding one node at a time, compute inverses of the weights.
    omega = ones(n+1)
    for m in range(n):
        d = tc[:m+1] - tc[m+1]          # vector of node differences
        omega[:m+1] = omega[:m+1]*d     # update previous
        omega[m+1] = prod( -d )         # compute the new one
    w = 1 / omega                   # go from inverses to weights

    def p(x):
        # Compute interpolant.
        z = where(x==t)[0]
        if len(z)>0:       # avoid dividing by zero
            # Apply L'Hopital's Rule exactly.
             f = y[z[0]]
        else:
            terms = w / (x - t)
            f = sum(y*terms) / sum(terms)
        return f

    return vectorize(p)

def triginterp(t,y):
    """
        triginterp(t,y)

    Return trigonometric interpolant for points defined by vectors `t` and `y`.
    """
    N = len(t)

    def trigcardinal(x):
        if x==0:
            tau = 1.
        elif mod(N,2)==1:      # odd
            tau = sin(N*pi*x/2) / (N*sin(pi*x/2))
        else:                # even
            tau = sin(N*pi*x/2) / (N*tan(pi*x/2))
        return tau

    def p(x):
        return sum( [y[k]*trigcardinal(x-t[k]) for k in range(N)] )

    return vectorize(p)

def ccint(f,n):
    """
    ccint(f,n)

    Perform Clenshaw-Curtis integration for the function `f` on `n`+1 nodes in [-1,1]. Return
    integral and a vector of the nodes used. Note: `n` must be even.
    """
    # Find Chebyshev extreme nodes.
    theta = linspace(0,pi,n+1)
    x = -cos(theta)

    # Compute the C-C weights.
    c = zeros(n+1)
    c[[0,n]] = 1/(n**2-1)
    v = ones(n-1)
    for k in range(1,int(n/2)):
        v -= 2*cos(2*k*theta[1:-1])/(4*k**2-1)
    v -= cos(n*theta[1:-1])/(n**2-1)
    c[1:-1] = 2*v/n

    # Evaluate integrand and integral.
    I = dot(c,f(x))   # use vector inner product
    return I,x

def glint(f,n):
    """
    glint(f,n)

    Perform Gauss-Legendre integration for the function `f` on `n` nodes in (-1,1). Return
    integral and a vector of the nodes used.
    """
    # Nodes and weights are found via a tridiagonal eigenvalue problem.
    beta = 0.5/sqrt(1-(2.0*arange(1,n))**(-2))
    T = diag(beta,1) + diag(beta,-1)
    ev,V = eig(T)
    ev = real_if_close(ev)
    p = argsort(ev)
    x = ev[p]               # nodes
    c = 2*V[0,p]**2         # weights

    # Evaluate the integrand and compute the integral.
    I = dot(c,f(x))         # vector inner product
    return I,x

def intde(f,h,M):
    """
    intde(f,h,M)

    Perform doubly-exponential integration of function `f` over (-Inf,Inf), using
    discretization size `h` and truncation point `M`. Return integral and a vector of the
    nodes used.
    """
    # Find where to truncate the trapezoid sum.
    K = ceil( log(4/pi*log(2*M))/h )

    # Integrate by trapezoids in a transformed variable t.
    t = h*arange(-K,K+1)
    x = sinh(pi/2*sinh(t))
    dxdt = pi/2*cosh(t)*cosh(pi/2*sinh(t))

    I = h*dot(f(x),dxdt)
    return I,x

def intsing(f,h,delta):
    """
    intsing(f,h,delta)

    Integrate function `f` (possibly singular at 1 and -1) over [-1+`delta`,1-`delta`]
    using discretization size `h`. Return integral and a vector of the
    nodes used.
    """
    # Find where to truncate the trapezoid sum.
    K = ceil(log(-2/pi*log(delta/2))/h)

    # Integrate over a transformed variable.
    t = h*arange(-K,K+1)
    x = tanh(pi/2*sinh(t))
    dxdt = pi/2*cosh(t) / (cosh(pi/2*sinh(t))**2)

    I = h*dot(f(x),dxdt)
    return I,x


#%% [markdown]
# ## Simulated Baseline Generation
# The simulated baseline curve will be constructed using a simple polynomial curve that will
# be randomly adjusted between a minimum and maximum exponent value.  The varying exponent in the baseline
# curve is used to simulate samples having different baselines independent of the concentration level.

#%%
import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np
from scipy.stats import norm

mpl.style.use('seaborn-notebook')
plt.rcParams["figure.figsize"] = (12, 5)

#%% [markdown]
# The following plot shows the baseline curves using both the minimum and maximum exponent values.

#%%
xnum = 600
np.random.seed(42)
x = np.arange(0, xnum, 1.0)
E1_ = (-1e-7*x**2.1)
E1F_ = E1_ + np.min(E1_)*-1.0
E2_ = (-1e-7*x**2.2)
E2F_ = E2_ + np.min(E2_)*-1.0

fig, axs = plt.subplots()
axs.plot(x, E1F_, label='min')
axs.plot(x, E2F_, label='max')
fig.suptitle('Baseline Curve Base Shapes')
plt.legend()

#%% [markdown]
# ## Baseline Curves for Dataset Generation
# This project will vary the baseline curve exponent to generate the simulated datasets.  The goal
# of this project is to determine how well each algorithm can handle signals with varying baselines
# and concentration levels. The following plots show the pure signal combined with each of the minimum
# and maximum baseline curves at 25% and 75% concentration level.

#%%
S_1 = norm.pdf(x, loc=310.0, scale=40.0)
S_2 = norm.pdf(x, loc=390.0, scale=20.0)
S_true = np.vstack((S_1, S_2))
C_true = np.array([[0.25, 0.75], [0.75, 0.25]])
signal = np.dot(C_true, S_true)

fig, axs = plt.subplots()
for i, level in enumerate(C_true):
    axs.plot(x, signal[i], label='{0:.2f}-signal'.format(C_true[i, 0]))
    axs.plot(x, signal[i]+E1F_, label='{0:.2f}-min'.format(C_true[i, 0]))
    axs.plot(x, signal[i]+E2F_, label='{0:.2f}-max'.format(C_true[i, 0]))
fig.suptitle('Baseline Range')
plt.legend()


import numpy as np
from math import fabs

def robust_min(img, p=5):
    return np.percentile(img.ravel(), p)

def robust_max(img, p=95):
    return np.percentile(img.ravel(), p)

def normalize(img, m=10, M=90):
    return np.clip((img - robust_min(img, m)) / (robust_max(img, M) - robust_min(img, m)), 0.0, 1.0)

def first_element_greater_than(values, req_value):
    """Returns the pair (i, values[i]) such that i is the minimum value that satisfies values[i] >= req_value.
    Returns (-1, None) if there is no such i.
    Note: this function assumes that values is a sorted array!"""
    i = np.searchsorted(values, req_value)
    val = values[i] if i < len(values) else None
    return (i, val)

def last_element_less_than(values, req_value):
    """Returns the pair (i, values[i]) such that i is the maximum value that satisfies values[i] <= req_value.
    Returns (-1, None) if there is no such i.
    Note: this function assumes that values is a sorted array!"""
    i = np.searchsorted(values, req_value, side='right') - 1
    val = values[i] if i >= 0 else None
    return (i, val)

def closest_element_to(values, req_value):
    """Returns the tuple (i, values[i], diff) such that i is the closest value to req_value,
    and diff = |values(i) - req_value|
    Note: this function assumes that values is a sorted array!"""
    assert(len(values) > 0)

    i = np.searchsorted(values, req_value, side='left')
    if i > 0 and (i == len(values) or fabs(req_value - values[i - 1]) < fabs(req_value - values[i])):
        idx = i - 1
        val = values[i - 1]
    else:
        idx = i
        val = values[i]

    diff = fabs(val - req_value)
    return (idx, val, diff)


## This is course material for Introduction to Python Scientific Programming
## Example code: gradient_descent_line_search.py
## Author: Allen Y. Yang
##
## (c) Copyright 2020. Intelligent Racing Inc. Not permitted for commercial use

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d

fig = plt.figure()

sample_count = 100
x_sample = 10*np.random.random(sample_count)-5
y_sample = 2*x_sample - 1 + np.random.normal(0, 1.0, sample_count)

# plots the parameter space
ax2 = fig.add_subplot(1,1,1, projection = '3d')

def penalty(para_a, para_b):
    global x_sample, y_sample, sample_count

    squares = (y_sample - para_a*x_sample - para_b)**2
    return 1/2/sample_count*np.sum(squares)

a_arr, b_arr = np.meshgrid(np.arange(-5, 5, 0.1), np.arange(-5, 5, 0.1) )

func_value = np.zeros(a_arr.shape)
for x in range(a_arr.shape[0]):
    for y in range(a_arr.shape[1]):
            func_value[x, y] = penalty(a_arr[x, y], b_arr[x, y])

ax2.plot_surface(a_arr, b_arr, func_value, color = 'red', alpha = 0.8)
ax2.set_xlabel('a parameter')
ax2.set_ylabel('b parameter')
ax2.set_zlabel('f(a, b)')

# Plot the gradient descent

def grad(aa):
    global sample_count, y_sample, x_sample

    grad_aa = np.zeros(2)
    update_vector = 1/sample_count * (y_sample - aa[0] * x_sample - aa[1])
    grad_aa[0] = - x_sample.dot(update_vector)
    grad_aa[1] = - np.ones(sample_count).dot(update_vector)
    return grad_aa

aa = np.array([-4, 4])
value = penalty(aa[0],aa[1])
ax2.scatter(aa[0], aa[1], penalty(aa[0],aa[1]), c='b', s=100, marker='*')
epsilon = 0.00001
learn_rates = [0.2, 0.1, 0.05, 0.01]
max_iteration = 100
delta = value
iter = 0
# Update vector aa
while delta > epsilon and iter < max_iteration:
    delta = 0
    aa_next = aa
    for rate in learn_rates:
        aa_try = aa - rate * grad(aa)
        value_next = penalty(aa_try[0],aa_try[1])
        if value_next<value and value - value_next > delta:
            delta = value - value_next
            aa_next = aa_try

    ax2.plot([aa[0],aa_next[0]],[aa[1], aa_next[1]],\
        [penalty(aa[0],aa[1]), penalty(aa_next[0],aa_next[1]) ], 'ko-')
    aa = aa_next
    value = value - delta
    iter +=1
    fig.canvas.draw_idle()
    plt.pause(0.1)

print('Optimal result: ', aa)
print('Step Count:', iter)
ax2.scatter(aa[0], aa[1], penalty(aa[0],aa[1]), c='b', s=100, marker='*')
plt.show()


import numpy as np
import matplotlib.pyplot as plt
import matplotlib.pylab as pylab
from monorotor import Monorotor
import plotting
import testing
import trajectories

class PDController:
    def __init__(self, k_p, k_d, m):
        self.k_p = k_p
        self.k_d = k_d
        self.vehicle_mass = m
        self.g = 9.81
        self.last_error = 0

    def thrust_control(self,
                       z_target,
                       z_actual,
                       z_dot_target,
                       z_dot_actual,
                       z_dot_dot_ff=0.0):

        error = z_target - z_actual
        error_dot = z_dot_target - z_dot_actual

        self.last_error = error

        # u_bar is what we want vertical acceleration to be
        u_bar = self.k_p * error + self.k_d * error_dot + z_dot_dot_ff

        #u is the thrust command which will cause u_bar
        u = self.vehicle_mass * (self.g - u_bar)

        return u

testing.pd_controller_test(PDController)

def oscillating_target():
    '''
    returns sampling time, target path, target path velocity, target path acceleration
    '''

    AMPLITUDE = 0.5
    OSCILLATION_FREQUENCY = 5

    PERIOD = 2 * np.pi / OSCILLATION_FREQUENCY

    t, z_path, z_dot_path, z_dot_dot_path = trajectories.cosine(AMPLITUDE,
                                                                PERIOD,
                                                                duration=6.0)

    return t, z_path, z_dot_path, z_dot_dot_path

def constant_target():
    '''
    returns sampling time, target path, target path velocity
    '''
    t, z_path, z_dot_path, z_dot_dot_path = trajectories.step()

    return t, z_path, z_dot_path, z_dot_dot_path

def proceed(monorotor, controller, target_func, feed_forward = False):
    '''
    :param monorotor, controller
    :param target_func function which generates target path and its related
    :returns traversed path by monorotor
    '''
    t, z_path, z_dot_path, z_dot_dot_path = target_func()
    dt = t[1] - t[0]

    history = []
    for z_target, z_dot_target, z_dot_dot_ff in zip(z_path, z_dot_path, z_dot_dot_path):
        z_actual = monorotor.z
        z_dot_actual = monorotor.z_dot

        u = controller.thrust_control(z_target, z_actual, z_dot_target, z_dot_actual, z_dot_dot_ff) if feed_forward else controller.thrust_control(z_target, z_actual, z_dot_target, z_dot_actual)
        monorotor.thrust = u

        monorotor.advance_state(dt)
        history.append(monorotor.X)

    actual_path = [h[0] for h in history]

    return t, z_path, actual_path

if __name__ == '__main__':
    MASS_ERROR = 1.0
    K_P = 20.0
    K_D = 2.0

    drone = Monorotor()
    controller = PDController(K_P, K_D, drone.m * MASS_ERROR)

    t, target_path, actual_path = proceed(drone, controller, oscillating_target)

    drone = Monorotor()
    controller = PDController(K_P, K_D, drone.m * MASS_ERROR)

    t, target_path, actual_path_ff = proceed(drone, controller, oscillating_target, True)
    plotting.compare_planned_to_actual(actual_path, target_path, t, actual_path_ff)

# This code simulates TWO drones. One uses the feed forward
# acceleration and the other doesn't. Note the difference in
# trajectories.


import numpy as np
import radvel.kepler

def timetrans_to_timeperi(tc, per, ecc, omega):
    """
    Convert Time of Transit to Time of Periastron Passage

    Args:
        tc (float): time of transit
        per (float): period [days]
        ecc (float): eccentricity
        omega (float): longitude of periastron (radians)

    Returns:
        float: time of periastron passage

    """
    try:
        if ecc >= 1:
            return tc
    except ValueError:
        pass

    f = np.pi/2 - omega
    ee = 2 * np.arctan(np.tan(f/2) * np.sqrt((1-ecc)/(1+ecc)))  # eccentric anomaly
    tp = tc - per/(2*np.pi) * (ee - ecc*np.sin(ee))      # time of periastron

    return tp

def timeperi_to_timetrans(tp, per, ecc, omega, secondary=False):
    """
    Convert Time of Periastron to Time of Transit

    Args:
        tp (float): time of periastron
        per (float): period [days]
        ecc (float): eccentricity
        omega (float): argument of peri (radians)
        secondary (bool): calculate time of secondary eclipse instead

    Returns:
        float: time of inferior conjunction (time of transit if system is transiting)

    """
    try:
        if ecc >= 1:
            return tp
    except ValueError:
        pass

    if secondary:
        f = 3*np.pi/2 - omega                                       # true anomaly during secondary eclipse
        ee = 2 * np.arctan(np.tan(f/2) * np.sqrt((1-ecc)/(1+ecc)))  # eccentric anomaly

        # ensure that ee is between 0 and 2*pi (always the eclipse AFTER tp)
        if isinstance(ee, np.float64):
            ee = ee + 2 * np.pi
        else:
            ee[ee < 0.0] = ee + 2 * np.pi
    else:
        f = np.pi/2 - omega                                         # true anomaly during transit
        ee = 2 * np.arctan(np.tan(f/2) * np.sqrt((1-ecc)/(1+ecc)))  # eccentric anomaly

    tc = tp + per/(2*np.pi) * (ee - ecc*np.sin(ee))         # time of conjunction

    return tc

def true_anomaly(t, tp, per, e):
    """
    Calculate the true anomaly for a given time, period, eccentricity.

    Args:
        t (array): array of times in JD
        tp (float): time of periastron, same units as t
        per (float): orbital period in days
        e (float): eccentricity

    Returns:
        array: true anomoly at each time
    """

    # f in Murray and Dermott p. 27
    m = 2 * np.pi * (((t - tp) / per) - np.floor((t - tp) / per))
    eccarr = np.zeros(t.size) + e
    e1 = radvel.kepler.kepler(m, eccarr)
    n1 = 1.0 + e
    n2 = 1.0 - e
    nu = 2.0 * np.arctan((n1 / n2)**0.5 * np.tan(e1 / 2.0))

    return nu


import numpy as np
from scipy.special import expit

class LogisticRegression(object):
    """
    Logistic Regression Classifier

    Parameters
    ----------
    learning_rate : int or float, default=0.1
        The tuning parameter for the optimization algorithm (here, Gradient Descent)
        that determines the step size at each iteration while moving toward a minimum
        of the cost function.

    max_iter : int, default=100
        Maximum number of iterations taken for the optimization algorithm to converge

    penalty : None or 'l2', default='l2'.
        Option to perform L2 regularization.

    C : float, default=0.1
        Inverse of regularization strength; must be a positive float.
        Smaller values specify stronger regularization.

    tolerance : float, optional, default=1e-4
        Value indicating the weight change between epochs in which
        gradient descent should terminated.
    """

    def __init__(self, learning_rate=0.1, max_iter=100, regularization='l2', C = 0.1, tolerance = 1e-4):
        self.learning_rate  = learning_rate
        self.max_iter       = max_iter
        self.regularization = regularization
        self.C              = C
        self.tolerance      = tolerance

    def fit(self, X, y):
        """
        Fit the model according to the given training data.

        Parameters
        ----------
        X : {array-like, sparse matrix} of shape (n_samples, n_features)
            Training vector, where n_samples is the number of samples and
            n_features is the number of features.
        y : array-like of shape (n_samples,)
            Target vector relative to X.
        Returns
        -------
        self : object
        """
        self.theta = np.zeros(X.shape[1] + 1)
        X = np.concatenate((np.ones((X.shape[0], 1)), X), axis=1)

        for _ in range(self.max_iter):
            y_hat = self.__sigmoid(X @ self.theta)
            errors = y - y_hat
            N = X.shape[1]

            if self.regularization is not None:
                delta_w = self.learning_rate * ((self.C * (X.T @ errors)) + np.sum(self.theta))
            else:
                delta_w = self.learning_rate * (X.T @ errors)

            if np.all(abs(delta_w) >= self.tolerance):
                self.theta -= delta_w / N
            else:
                break

        return self

    def predict_proba(self, X):
        """
        Probability estimates for samples in X.

        Parameters
        ----------
        X : array-like of shape (n_samples, n_features)
            Vector to be scored, where `n_samples` is the number of samples and
            `n_features` is the number of features.
        Returns
        -------
        probs : array-like of shape (n_samples,)
            Returns the probability of each sample.
        """
        return self.__sigmoid((X @ self.theta[1:]) + self.theta[0])

    def predict(self, X):
        """
        Predict class labels for samples in X.

        Parameters
        ----------
        X : array_like or sparse matrix, shape (n_samples, n_features)
            Samples.
        Returns
        -------
        labels : array, shape [n_samples]
            Predicted class label per sample.
        """
        return np.round(self.predict_proba(X))

    def __sigmoid(self, z):
        """
        The sigmoid function.

        Parameters
        ------------
        z : float
            linear combinations of weights and sample features
            z = w_0 + w_1*x_1 + ... + w_n*x_n
        Returns
        ---------
        Value of logistic function at z
        """
        return 1 / (1 + expit(-z))

    def get_params(self):
        """
        Get method for models coeffients and intercept.

        Returns
        -------
        params : dict
        """
        try:
            params = dict()
            params['intercept'] = self.theta[0]
            params['coef'] = self.theta[1:]
            return params
        except:
            raise Exception('Fit the model first!')


#!/usr/bin/python

import getopt
import numpy as np
import re
import sys

def NumberOfDivisors(n):
    """Return the number of divisors for a given number"""
    if n <= 1:
        return 1
    limit = n
    divisor = 1
    num_divisors = 0
    while divisor < limit:
        #print("Iterating ... limit = %d, divisor = %d, numdivs = %d" % (limit, divisor, num_divisors))
        if n % divisor == 0:
            limit = n / divisor
            #print("Found factor: %d" % divisor)
            num_divisors = num_divisors + 2
        divisor = divisor + 1
    return num_divisors

def TriangleNumber(n):
    """Returns the nth triangle number"""
    return n * (n + 1) / 2

def solve12():
    """Find the first triangle number to have more than 500 divisors"""
    n = 1
    maxdivs = 1
    maxn = 1
    numdivs = 1
    while True:
        numdivs = NumberOfDivisors(TriangleNumber(n))
        #print("Investigating %dth triangle number   -- %d [Maximum so far was %d with %d]" % (n, numdivs, maxn, maxdivs))
        if numdivs >= 500:
            break
        if numdivs > maxdivs:
            maxdivs = numdivs
            maxn = n
        n = n + 1
    print("Found %d with %d" % (TriangleNumber(n), numdivs))

def solve13():
    """Find the first 10 digits of the sum of one hundred 50-digit numbers.
       Data is stored in data/problem13.
       [Use the fact that python automatically handles large numbers --
       see http://www.python.org/dev/peps/pep-0237/]"""
    sum = 0
    f = open('data/problem13', 'r')
    for line in f:
        val = int(line.strip())
        sum += val
    print("sum is %d" % sum)

def get_hotpo_length(num, memoized_lengths):
    if num == 2:
        return 2  # 2->1
    if num < 100000 and memoized_lengths[num] > 0:
        #print("Found precalculated length %d for %d" % (memoized_lengths[num], num))
        return memoized_lengths[num]
    if num % 2 == 0:
        return 1 + get_hotpo_length(num/2, memoized_lengths)
    else:
        return 1 + get_hotpo_length(3 * num + 1, memoized_lengths)

def solve14():
    """Find the number less than a million which produces the longes chain for the HalfOrTriplePlusOne chain"""
    longest_sequence = (1, 1)
    # Memoize the first 100,000 numbers
    memo_limit = 100000
    memoized_lengths = np.zeros(100000)
    for num in xrange(2, 1000000):
        length = get_hotpo_length(num, memoized_lengths)
        #print("Length is %d" % length)
        if num < 100000:
            memoized_lengths[num] = length
        if (length, num) > longest_sequence:
            longest_sequence = (length, num)
            #print("Longest sequence is (%d, %d)" % (longest_sequence[0], longest_sequence[1]))

    print("Finally, longest sequence was : %d for %d" %
            (longest_sequence[0], longest_sequence[1]))

def factorial(num):
    if num == 1:
        return 1
    else:
        return num * factorial(num-1)

def solve15():
    """Find the number of ways to traverse a 20x20 grid across a diagonal"""
    # 2N C N is the number of ways to select a path across the diagonal (here N = 20)

    result = factorial(40) / factorial(20) / factorial(20)
    print("The answer is: %d" % result)

def sum_of_digits(num):
    sum = 0
    while num > 0:
        sum += (num % 10)
        num = num / 10
    return sum

def solve16():
    """Find the sum of  the digits in 2 ^ 1000"""
    two_to_thousand = 2**1000
    result = sum_of_digits(two_to_thousand)
    print("The answer is: %d" % result)

def solve17():
    """Find the number of words used in writing out all numbers from 1 to 1000"""
    one_len = len("one")
    digits = ["", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" ]
    tens = ["", "ten", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"] # all except ten
    teens = ["", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"]

    all_numbers = []
    # Start with the single digits
    for hundred in range(10):
        hundred_prefix = ""
        if hundred > 0:
            all_numbers.append(digits[hundred] + " hundred ")
            hundred_prefix = digits[hundred] + " hundred and "
        for ten in range(10):
            if ten > 0:
                all_numbers.append(hundred_prefix + tens[ten])
            ten_prefix = ""
            if ten > 1:
                ten_prefix = tens[ten] + " "
            for digit in range(1, 10):
                if ten == 0:
                    all_numbers.append(hundred_prefix + digits[digit])
                elif ten == 1:
                    all_numbers.append(hundred_prefix + teens[digit])
                else:
                    all_numbers.append(hundred_prefix + ten_prefix + digits[digit])
    all_numbers.append("one thousand")

    # Print them all out, while adding them up
    result = 0
    for number in all_numbers:
        # print(number)     ## Uncomment this for debug
        tempstring = re.sub(r' ', '', number)
        result = result + len(tempstring)
    print("\n\nThe answer is: %d" % result)

euler_problems = {
        12: solve12,
        13: solve13,
        14: solve14,
        15: solve15,
        16: solve16,
        17: solve17,
        }

def main():
    optlist, args = getopt.getopt(sys.argv[1:], "hp:", ["help", "problem_number"])
    for o, a in optlist:
        if o == "-p":
           euler_problems[int(a)]()
        else:
            print("Usage: %s -p <problem-number>")
            sys.exit()

if __name__ == '__main__':
    main()


import numpy as np
import imageio
from skimage.feature import canny
import cv2

DEG = np.pi / 180.0

def rgb2gray(rgb):
    return np.dot(rgb[..., :3], [0.299, 0.587, 0.114]).astype(np.uint8)

def fast_hough_line(img, angle_step=1, lines_are_white=True, value_threshold=5):
    """
    hough line using vectorized numpy operations,
    may take more memory, but takes much less time

    Input:
    img - 2D binary image with nonzeros representing edges
    angle_step - Spacing between angles to use every n-th angle
                 between -90 and 90 degrees. Default step is 1.
    lines_are_white - boolean indicating whether lines to be detected are white
    value_threshold - Pixel values above or below the value_threshold are edges

    Returns:
    accumulator - 2D array of the hough transform accumulator
    theta - array of angles used in computation, in radians.
    rhos - array of rho values. Max size is 2 times the diagonal
           distance of the input image.
    """
    # Rho and Theta ranges
    thetas = np.deg2rad(np.arange(-90.0, 90.0, angle_step)) #can be changed
    #width, height = col.size  #if we use pillow
    width, height = img.shape
    diag_len = int(np.ceil(np.sqrt(width * width + height * height)))   # max_dist
    rhos1 = np.linspace(-diag_len, diag_len, diag_len * 2)

    # Cache some resuable values
    cos_theta = np.cos(thetas)
    sin_theta = np.sin(thetas)
    num_thetas = len(thetas)

    # Hough accumulator array of theta vs rho
    accumulator = np.zeros((2 * diag_len, num_thetas))
    are_edges = img > value_threshold if lines_are_white else img < value_threshold
    #are_edges = cv2.Canny(img,50,150,apertureSize = 3)
    y_idxs, x_idxs = np.nonzero(are_edges)  # (row, col) indexes to edges
    # Vote in the hough accumulator
    xcosthetas = np.dot(x_idxs.reshape((-1,1)), cos_theta.reshape((1,-1)))
    ysinthetas = np.dot(y_idxs.reshape((-1,1)), sin_theta.reshape((1,-1)))
    rhosmat = np.round(xcosthetas + ysinthetas) + diag_len
    rhosmat = rhosmat.astype(np.int16)
    for i in range(num_thetas):
        rhos, counts = np.unique(rhosmat[:,i], return_counts=True)
        accumulator[rhos,i] = counts
    return accumulator, thetas, rhos1, np.arange(0,2 * diag_len)

def hough_peaks(H, num_peaks, thetas, rhos):
    H_values_sorted = np.sort(np.unique(H))[::-1]
    H_values_num_peaks = H_values_sorted[:num_peaks]
    peaks = []
    for pi in H_values_num_peaks:
        indexes = np.argwhere(H == pi)
        for find_indexes in indexes:
            rho = rhos[find_indexes[0]]
            theta = thetas[find_indexes[1]]
            peaks.append([rho, theta])
    return np.array(peaks[0:num_peaks])

def peak_votes(accumulator, thetas, rhos):
    """ Finds the max number of votes in the hough accumulator """
    idx = np.argmax(accumulator)
    rho = rhos[int(idx / accumulator.shape[1])]
    theta = thetas[idx % accumulator.shape[1]]
    return idx, theta, rho

def hough_lines_draw(image, peaks, rhos1):
    x = np.arange(0, image.shape[0]-1)
    H = np.zeros(image.shape)
    for i in range(peaks.shape[0]):
        d = rhos1[int(peaks[i, 0])]
        theta = peaks[i, 1]
        y = ((d - x*np.cos(theta))/np.sin(theta)).astype(int)
        index = np.where(y<=image.shape[1]-1)[0]
        y1 = y[index]
        x1 = x[index]
        index1 = np.where(y1>=0)[0]
        y2 = y1[index1]
        x2 = x1[index1]
        H[x2, y2] = 1
    return H

def show_hough_line(img, accumulator, lines_images, peaks, thetas, rhos, rhos1, save_path=None):
    import matplotlib.pyplot as plt

    fig, ax = plt.subplots(1, 2, figsize=(10, 10))

    ax[0].imshow(img+lines_images, cmap=plt.cm.gray)
    ax[0].set_title('Input image')
    ax[0].axis('image')

    ax[1].imshow(accumulator, cmap='jet',
                   extent=[np.rad2deg(thetas[0]), np.rad2deg(thetas[-1]), rhos1[-1], rhos1[0]])
    for ii in range(len(peaks[:, 0])):
        ax[1].scatter(np.rad2deg(peaks[ii,1]), rhos1[int(peaks[ii, 0])], color='r')
    ax[1].set_aspect('equal', adjustable='box')
    ax[1].set_title('Hough transform')
    ax[1].set_xlabel('Angles (degrees)')
    ax[1].set_ylabel('Distance (pixels)')
    ax[1].axis('image')
    if save_path is not None:
        plt.savefig(save_path, bbox_inches='tight')
    plt.show()

def ComputeGnomon_2(TwiceTheta_Chi, CenterProjection=(45 * DEG, 0 * DEG)):
    data_theta = TwiceTheta_Chi[0] / 2.0
    data_chi = TwiceTheta_Chi[1]
    lat = np.arcsin(np.cos(data_theta * DEG) * np.cos(data_chi * DEG))  # in rads
    longit = np.arctan(
        -np.sin(data_chi * DEG) / np.tan(data_theta * DEG))  # + ones(len(data_chi))*(np.pi)
    centerlat, centerlongit = CenterProjection
    slat0 = np.ones(len(data_chi)) * np.sin(centerlat)
    clat0 = np.ones(len(data_chi)) * np.cos(centerlat)
    longit0 = np.ones(len(data_chi)) * centerlongit
    slat = np.sin(lat)
    clat = np.cos(lat)
    cosanguldist = slat * slat0 + clat * clat0 * np.cos(longit - longit0)
    _gnomonx = clat * np.sin(longit0 - longit) / cosanguldist
    _gnomony = (slat * clat0 - clat * slat0 * np.cos(longit - longit0)) / cosanguldist
    return _gnomonx, _gnomony

def computeGnomonicImage(TwiceTheta,Chi):
    # CenterProjectionAngleTheta = 50#45
    TwiceTheta_Chi = TwiceTheta,Chi
    Xgno,Ygno = ComputeGnomon_2(TwiceTheta_Chi, CenterProjection=(45 * DEG, 0 * DEG))
    pts =(np.array([Xgno,Ygno]).T)
    nbpeaks=len(pts)
    NbptsGno = 300
    maxsize = max(Xgno.max(),Ygno.max(),-Xgno.min(),-Ygno.min())+.0
    xgnomin,xgnomax,ygnomin,ygnomax=(-0.8,0.8,-0.5,0.5)
    xgnomin,xgnomax,ygnomin,ygnomax=(-maxsize,maxsize,-maxsize,maxsize)

    halfdiagonal = np.sqrt(xgnomax**2+ygnomax**2)*NbptsGno
    XGNO = np.array((Xgno-xgnomin)/(xgnomax-xgnomin)*NbptsGno, dtype=int)
    YGNO = np.array((Ygno-ygnomin)/(ygnomax-ygnomin)*NbptsGno, dtype=int)
    imageGNO=np.zeros((NbptsGno+1,NbptsGno+1), dtype=int)
    imageGNO[XGNO,YGNO]=1
    return imageGNO, nbpeaks, halfdiagonal

def InverseGnomon(_gnomonX, _gnomonY):
    """ from x,y in gnomonic projection gives lat and long
    return theta and chi of Q (direction of Q)

    WARNING: assume that center of projection is centerlat, centerlongit = 45 deg, 0
    """
    lat0 = np.ones(len(_gnomonX)) * np.pi / 4
    longit0 = np.zeros(len(_gnomonX))
    Rho = np.sqrt(_gnomonX ** 2 + _gnomonY ** 2) * 1.0
    CC = np.arctan(Rho)

    # the sign should be - !!
    lalat = np.arcsin(np.cos(CC) * np.sin(lat0) + _gnomonY / Rho * np.sin(CC) * np.cos(lat0))
    lonlongit = longit0 + np.arctan2(_gnomonX * np.sin(CC),
        Rho * np.cos(lat0) * np.cos(CC) - _gnomonY * np.sin(lat0) * np.sin(CC))

    Theta = np.arcsin(np.cos(lalat) * np.cos(lonlongit))
    Chi = np.arctan(np.sin(lonlongit) / np.tan(lalat))

    return 2.*np.rad2deg(Theta), np.rad2deg(Chi)

if __name__ == '__main__':
    # ### or load directly the peaks from cor file
    # import LaueTools.IOLaueTools as IOLT
    # filename_ = r"C:\Users\purushot\Desktop\github_version_simple\lauetoolsnn\misc\img\HS261120b_SH2_S5_B_0000.cor"
    # data_theta, data_chi = IOLT.readfile_cor(filename_)[1:3]
    # l_tth = data_theta * 2.
    # l_chi = data_chi
    # img, _, _ = computeGnomonicImage(l_tth, l_chi)

    import matplotlib.pyplot as plt
    from LaueTools import imageprocessing as ImProc
    import LaueTools.dict_LaueTools as dictLT
    import LaueTools.LaueGeometry as Lgeo

    imgpath = 'img/HS261120b_SH2_S5_B_0000.tif'
    img = imageio.imread(imgpath)
    if img.ndim == 3:
        img = rgb2gray(img)

    backgroundimage = ImProc.compute_autobackground_image(img, boxsizefilter=10)
    img = ImProc.computefilteredimage(img, backgroundimage, "sCMOS", usemask=False,
                                                        formulaexpression="A-1.1*B")
    img = canny(img, )
    img = img.astype(int)

    # img2 = np.copy(img)
    # img2[img2==0] = 1000
    # plt.imshow(img2, cmap='gray')

    XX,YY = np.meshgrid(np.arange(img.shape[1]),np.arange(img.shape[0]))
    table = np.vstack((img.ravel(),XX.ravel(),YY.ravel())).T
    ## remove zero values pixels from peak
    keep_ind = np.where(table[:,0]!=0)[0]
    table = np.take(table, keep_ind, axis=0)

    peak_XY = table[:,1:]
    twicetheta, chi = Lgeo.calc_uflab(peak_XY[:,0], peak_XY[:,1], [79.554,979.45,932.54,0.387,0.44],
                                        returnAngles=1,
                                        pixelsize=dictLT.dict_CCD["sCMOS"][1],
                                        kf_direction='Z>0')

    ## check Gnomonic function
    TwiceTheta_Chi = twicetheta, chi
    Xgno,Ygno = ComputeGnomon_2(TwiceTheta_Chi, CenterProjection=(45 * DEG, 0 * DEG))
    twicetheta_back, chi_back = InverseGnomon(Xgno,Ygno)

    assert np.all(np.round(chi_back,4)==np.round(chi,4))
    assert np.all(np.round(twicetheta_back,4)==np.round(twicetheta,4))

    imgGN, _, _ = computeGnomonicImage(twicetheta, chi)

    fig, ax = plt.subplots()
    plt.imshow(imgGN,cmap='gray')
    plt.savefig("prefix.png", bbox_inches='tight',format='png', dpi=1000)

    accumulator, thetas, rhos1, rhos = fast_hough_line(imgGN, angle_step=1,
                                                       lines_are_white=True, value_threshold=0.1)
    peaks = hough_peaks(accumulator, 20, thetas, rhos)
    lines_images = hough_lines_draw(imgGN, peaks, rhos1)

    show_hough_line(imgGN, accumulator, lines_images, peaks, thetas, rhos, rhos1, save_path=None)


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Dec  2 15:37:10 2020

Copyright 2020 by Hadrien Montanelli.
"""
# %% Imports.

# Standard library imports:
from math import pi
import matplotlib.pyplot as plt
import numpy as np
import time

# Chebpy imports:
from chebpy.trig import coeffs2vals, diffmat, trigpts, vals2coeffs

# %% First-order differentiation on [-pi, pi].

# Function:
w = 100
f = lambda x: np.cos(w*x)
dfex = lambda x: -w*np.sin(w*x)

# Grid:
n = 4*w
dom = [-pi, pi]
x = trigpts(n, dom)

# Compute coeffs of f:
F = vals2coeffs(f(x))

# Differentiation matrix in coefficient space:
start = time.time()
D = diffmat(n, 1, dom)
end = time.time()
print(f'Time   (setup): {end-start:.5f}s')
plt.figure()
plt.spy(D)

# Differentiate:
start = time.time()
DF = D @ F
end = time.time()
print(f'Time (product): {end-start:.5f}s')

# Convert to value space:
df = coeffs2vals(DF)

# Error:
error = np.max(np.abs(df - dfex(x)))/np.max(np.abs(dfex(x)))
print(f'Error  (L-inf): {error:.2e}')

# %% Second-order differentiation on [-pi, pi].

# Function:
w = 100
f = lambda x: np.cos(w*x)
d2fex = lambda x: -w**2*np.cos(w*x)

# Grid:
n = 4*w
dom = [-pi, pi]
x = trigpts(n, dom)

# Compute coeffs of f:
F = vals2coeffs(f(x))

# Differentiation matrix in coefficient space:
start = time.time()
D2 = diffmat(n, 2, dom)
end = time.time()
print(f'Time   (setup): {end-start:.5f}s')
plt.figure()
plt.spy(D)

# Differentiate:
start = time.time()
D2F = D2 @ F
end = time.time()
print(f'Time (product): {end-start:.5f}s')

# Convert to value space:
d2f = coeffs2vals(D2F)

# Error:
error = np.max(np.abs(d2f - d2fex(x)))/np.max(np.abs(d2fex(x)))
print(f'Error  (L-inf): {error:.2e}')