# this program is aiming to implement Ricker Wavelet and oberve its shape and parametric influecne regarding to shape
# input args:
# 1.fm:the frquency of Ricker wavelet
# 2,time_slot: discretized time array
# output args:
# 1. ricker_amp=ampltide of Ricker wavelet in time-domain

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

# maximum simulation time unit:s
max_time = 0.3
# peak frequency (Hz)
fm = 200
# time shift to let Ricker wavelet starts at zero
time_shift = np.sqrt(2) / (2 * np.pi * fm)
time_slot = np.linspace(-time_shift, max_time, num=2e3)

# function to plot ricker wavelet and its spectrum
# input args:
#     fm: peak frequency
#     time_slot: time interavl for plot the ricker wavelet
#output args:
   #  ricker_amp: Ricker wavelet waveform
def Ricker_wavelet(fm, time_slot):
    ricker_amp = []
    spectrumVal = []
    # convert frequency into radians
    fm_radian = 2 * np.pi * fm

    # calculate amplitude
    for i in time_slot:
        amplitude_value = (1 - 0.5 * fm_radian ** 2 * i ** 2) * (np.exp((-0.25 * fm_radian ** 2 * i ** 2)))
        ricker_amp.append(amplitude_value)
    # calculate spectrum

    spectrum = np.fft.fft(ricker_amp)
    # frequency range to plot spectrum
    frq = np.linspace(-0 * np.pi * fm, 6 * np.pi * fm)
    for j in frq:
        spectrumVal.append((2 * j ** 2) / (np.sqrt(np.pi) * fm_radian ** 3) * np.exp(-j ** 2 / fm_radian ** 2))
    # plot for spectrum
    spectrum_flag = 0
    if spectrum_flag == 1:
        plt.plot(frq / (2 * np.pi), spectrumVal)

        plt.xlabel('Frequency (Hz)')
        plt.ylabel('$R(\omega)$')
        plt.title('Spectrum of Ricker Wavelet')

    return ricker_amp

# here we calculate amplitude of ricker wavelet at given time t
# try to combine Ricker wavelet as input impulse for shot source and estimate arrival time at receiver array

# input args:
#      fm: peak frequncy of ricker wavelet
#      t: time point
def Ricker_response(fm, t):
    # convert frequency into radians
    fm_radian = 2 * np.pi * fm
    # shift the ricker wavelet and let it start at zero
    amplitude_value = (1 - 0.5 * fm_radian ** 2 * t ** 2) * (np.exp((-0.25 * fm_radian ** 2 * t ** 2)))
    return amplitude_value


import numpy as np
import itertools
np.random.seed(42)

def preprocess(X, y):
    """
    Perform mean normalization on the features and divide the true labels by
    the range of the column.

    Input:
    - X: Inputs  (n features over m instances).
    - y: True labels.

    Returns a two vales:
    - X: The mean normalized inputs.
    - y: The scaled labels.
    """

    ###########################################################################
    # TODO: Implement the normalization function.                             #
    ###########################################################################

    X = (X - np.mean(X, axis=0)) / (np.max(X, axis=0) - np.min(X, axis=0))
    y = (y - np.mean(y)) / (np.max(y) - np.min(y))
    ###########################################################################
    #                             END OF YOUR CODE                            #
    ###########################################################################
    return X, y

def compute_cost(X, y, theta):
    """
    Computes the average squared difference between an observation’s actual and
    predicted values for linear regression.

    Input:
    - X: Inputs  (n features over m instances).
    - y: True labels (1 value over m instances).
    - theta: The parameters (weights) of the model being learned.

    Returns a single value:
    - J: the cost associated with the current set of parameters (single number).
    """

    J = 0  # Use J for the cost.
    ###########################################################################
    # TODO: Implement the MSE cost function.                                  #
    ###########################################################################
#     hypothesis = np.dot(X, theta)
#     diff = np.subtract(hypothesis,y)
#     J = np.sum(np.power(diff, 2)) / (2 * len(y))
    return np.sum(np.square(np.dot(X, theta) - y)) / ( 2 * len(y) )
#     ###########################################################################
    #                             END OF YOUR CODE                            #
    ###########################################################################
 #   return J

def gradient_descent(X, y, theta, alpha, num_iters):
    """
    Learn the parameters of the model using gradient descent. Gradient descent
    is an optimization algorithm used to minimize some (loss) function by
    iteratively moving in the direction of steepest descent as defined by the
    negative of the gradient. We use gradient descent to update the parameters
    (weights) of our model.

    Input:
    - X: Inputs  (n features over m instances).
    - y: True labels (1 value over m instances).
    - theta: The parameters (weights) of the model being learned.
    - alpha: The learning rate of your model.
    - num_iters: The number of updates performed.

    Returns two values:
    - theta: The learned parameters of your model.
    - J_history: the loss value for every iteration.
    """

    J_history = [] # Use a python list to save cost in every iteration
    ###########################################################################
    # TODO: Implement the gradient descent optimization algorithm.            #
    ###########################################################################
    for i in range(num_iters):
        J_history.append(compute_cost(X, y, theta))
        theta = theta - ((np.dot(X.T, (np.dot(X, theta) - y))) * (alpha / len(y)))
    ###########################################################################
    #                             END OF YOUR CODE                            #
    ###########################################################################
    return theta, J_history

def pinv(X, y):
    """
    Calculate the optimal values of the parameters using the pseudoinverse
    approach as you saw in class.

    Input:
    - X: Inputs  (n features over m instances).
    - y: True labels (1 value over m instances).

    Returns two values:
    - theta: The optimal parameters of your model.

    ########## DO NOT USE numpy.pinv ##############
    """
    ###########################################################################
    # TODO: Implement the pseudoinverse algorithm.                            #
    ###########################################################################
    pinv = np.dot(np.linalg.inv(np.dot(X.T, X)), X.T)
    pinv_theta = np.dot(pinv, y)
    ###########################################################################
    #                             END OF YOUR CODE                            #
    ###########################################################################
    return pinv_theta

def efficient_gradient_descent(X, y, theta, alpha, num_iters):
    """
    Learn the parameters of your model, but stop the learning process once
    the improvement of the loss value is smaller than 1e-8. This function is
    very similar to the gradient descent function you already implemented.

    Input:
    - X: Inputs  (n features over m instances).
    - y: True labels (1 value over m instances).
    - theta: The parameters (weights) of the model being learned.
    - alpha: The learning rate of your model.
    - num_iters: The number of updates performed.

    Returns two values:
    - theta: The learned parameters of your model.
    - J_history: the loss value for every iteration.
    """

    J_history = [] # Use a python list to save cost in every iteration
    ###########################################################################
    # TODO: Implement the gradient descent optimization algorithm.            #
    ###########################################################################
    prev_j = 0
    for i in range(num_iters):
        j = compute_cost(X, y, theta)
        J_history.append(j)
        # same as abs, looking for performance upgrades
        if np.abs(j - prev_j) < 1e-8:
            return theta, J_history
        theta = theta - ((np.dot(X.T, (np.dot(X, theta) - y))) * (alpha / len(y)))
        prev_j = j
    ###########################################################################
    #                             END OF YOUR CODE                            #
    ###########################################################################
    return theta, J_history

    ###########################################################################
    #                             END OF YOUR CODE                            #
    ###########################################################################

def find_best_alpha(X, y, iterations):
    """
    Iterate over provided values of alpha and maintain a python dictionary
    with alpha as the key and the final loss as the value.

    Input:
    - X: a dataframe that contains all relevant features.

    Returns:
    - alpha_dict: A python dictionary that hold the loss value after training
    for every value of alpha.
    """

    alphas = [0.00001, 0.00003, 0.0001, 0.0003, 0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 1, 2, 3]
    alpha_dict = {}
    ###########################################################################
    # TODO: Implement the function.                                           #
    ###########################################################################
    theta = np.random.random(size=2)
    alpha_dict = {alpha: efficient_gradient_descent(X, y, theta,alpha, iterations)[1][-1] for alpha in alphas}

    ###########################################################################
    #                             END OF YOUR CODE                            #
    ###########################################################################
    return alpha_dict

def generate_triplets(X):
    """
    generate all possible sets of three features out of all relevant features
    available from the given dataset X. You might want to use the itertools
    python library.

    Input:
    - X: a dataframe that contains all relevant features.

    Returns:
    - A python list containing all feature triplets.
    """

    triplets = []
    ###########################################################################
    # TODO: Implement the function.                                           #
    ###########################################################################
    triplets = list(itertools.combinations(X, 3))
    ###########################################################################
    #                             END OF YOUR CODE                            #
    ###########################################################################
    return triplets

def find_best_triplet(df, triplets, alpha, num_iter):
    """
    Iterate over all possible triplets and find the triplet that best
    minimizes the cost function. For better performance, you should use the
    efficient implementation of gradient descent. You should first preprocess
    the data and obtain a array containing the columns corresponding to the
    triplet. Don't forget the bias trick.

    Input:
    - df: A dataframe that contains the data
    - triplets: a list of three strings representing three features in X.
    - alpha: The value of the best alpha previously found.
    - num_iters: The number of updates performed.

    Returns:
    - The best triplet as a python list holding the best triplet as strings.
    """
    best_triplet = None
    min_J = float('inf')
    ###########################################################################
    # TODO: Implement the function.                                           #
    ###########################################################################
    for triplet in triplets:
        X = np.array(df[list(triplet)])
        y = np.array(df.get('price'))
        X, y = preprocess(X,y)
        ones = np.ones(np.size(X,0)).reshape(-1,1)
        X = np.hstack((ones, X))
        mock_theta = np.ones(np.size(X,1))
        current_j = efficient_gradient_descent(X, y, mock_theta, alpha, num_iter)[1][-1]
        if (current_j < min_J):
            min_J = current_j
            best_triplet = triplet

    ###########################################################################
    #                             END OF YOUR CODE                            #
    ###########################################################################
    return best_triplet


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

class GamblersProblem():

    _plot_colors = ['b', 'g', 'r', 'c', 'm', 'y', 'k', 'w']

    def __init__(self, win_probability=0.4):
        self._win_probability = win_probability

    def get_possible_next_states(self, state, action):
        ret = []
        # Either we win
        ret.append(state + action)
        # or we lose
        ret.append(state - action)
        return list(set(ret))

    def probability_next_state(self, state, action, next_state):
        # Special "sink" states
        if state == 0:
            if next_state == 0:
                return 1.0
            return 0.0
        if state == 100:
            if next_state == 100:
                return 1.0
            return 0.0

        # Loss
        if next_state == (state - action):
            return 1 - self._win_probability
        # Win
        elif next_state == (state + action):
            return self._win_probability
        else:
            # Should never actually make it here
            return 0.0

    def reward(self, state, action, next_state):
        if next_state == 100:
            return 1.0
        else:
            return 0.0

    def value_iteration(self, convergence=0.0001):
        diff = np.inf
        value = np.zeros(101)
        temp = np.copy(value)
        ret = []
        while diff > convergence:
            for state in range(1, value.shape[0] - 1):
                action_space = np.arange(0, min(state, 100 - state) + 1)
                best_value = None
                for action in action_space:
                    possible_next_states = self.get_possible_next_states(state, action)
                    gain = 0.0
                    for next_state in possible_next_states:
                        gain += self.probability_next_state(state, action, next_state) * (
                            self.reward(state, action, next_state) + temp[next_state]
                        )
                    if best_value is None or gain > best_value:
                        best_value = gain
                value[state] = best_value
            diff = np.max(np.fabs(np.subtract(temp, value)))
            temp = np.copy(value)
            ret.append(temp)
        return ret

    def get_greedy_policy(self, value):
        policy = np.zeros(101)
        for state in np.arange(1, 100):
            action_space = np.arange(0, min(state, 100 - state) + 1)
            best_action = [None, -np.inf]
            for action in action_space:
                possible_next_states = self.get_possible_next_states(state, action)
                gain = 0.0
                for next_state in possible_next_states:
                    gain += self.probability_next_state(state, action, next_state) * (
                        self.reward(state, action, next_state) + value[next_state]
                    )
                if best_action[0] is None:
                    best_action[0] = action
                    best_action[1] = gain
                elif math.isclose(gain, best_action[1]):
                    # Tie breaking strategy
                    # Choose more conservative action
                    if action < best_action[0]:
                        best_action[0] = action
                elif gain > best_action[1]:
                    best_action[0] = action
                    best_action[1] = gain
            policy[state] = best_action[0]
        return policy

    def plot_value_functions(self, value_functions):
        for i in range(len(value_functions)):
            plt.plot(value_functions[i][0:-1], self._plot_colors[i%len(self._plot_colors)], label=f'Value Function {i}')
        plt.title(f"Gambler's Problem Value Iteration (Win Probability = {self._win_probability})")
        plt.xlabel('Capital')
        plt.ylabel('Value')
        plt.legend(loc=4)

    def plot_policy(self, policy):
        plt.plot(np.arange(0, 101), policy)
        plt.title(f'Optimal Policy for Gambler (Win Probability = {self._win_probability})')
        plt.xlabel('Captial')
        plt.ylabel('Stake')

    def plot_results(self, value_functions, policy, figure=1):
        plt.figure(figure)
        self.plot_value_functions(value_functions)
        plt.figure(figure + 1)
        self.plot_policy(policy)
        return figure + 2

if __name__ == '__main__':
    gmb = GamblersProblem()
    values = gmb.value_iteration(convergence=0.001)
    policy = gmb.get_greedy_policy(values[-1])
    gmb.plot_value_functions(values)
    gmb.plot_policy(policy)


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

tLimits = [0, 5]
mu = 0
sigma = 0.75

def lognorm(t):
    return (np.exp(-((np.log(t) - mu) ** 2) / (2 * sigma ** 2))) / (t * sigma * np.sqrt(2 * np.pi))

tValues = np.arange(0.01, 5, 0.01)
yValues = np.array(list(map(lognorm, tValues)))
plt.figure()
plt.plot(tValues, yValues, '-b')
plt.ylabel('y')
plt.xlabel('t')
plt.title("Log-Normal Distribution")
plt.grid()
plt.show()

##############################################################################

M = np.ceil(max(yValues))
print("M = %f" % M)

binRange = tLimits[1] - tLimits[0]
binCount = 100

rand_lognorm = np.zeros(binCount)           # Array of 'binCount' bins
bins = np.linspace(0, binRange, binCount)   # Array of 'binCount' bin limits from 0 to 'binRange'

# Generate uniform random nos. and convert them to a lognormal dist. using accept-reject
def generatePoint():
    u = random.uniform(tLimits[0], tLimits[1])
    v = random.uniform(0, M)
    if v < lognorm(u):
        for j in range(0, len(bins)):
            if u < bins[j]:
                rand_lognorm[j] += 1
                break

for i in range(10000):
    generatePoint()

print(rand_lognorm)

##############################################################################

binWidth = binRange / binCount
area = sum(binWidth * rand_lognorm)

plt.figure()
plt.plot(bins, rand_lognorm / area, 'bo', label="By Von-Neumann Method")
plt.plot(bins, stats.lognorm([sigma],loc=mu).pdf(bins), '-r', label="By Standard Library")
plt.ylabel('Number of counts')
plt.xlabel('X, the log-normally distributed random variable')
plt.title("Probability Density Function")
plt.grid()
plt.legend()
plt.show()


from abc import ABC, abstractmethod
import numpy as np

class Metric(ABC):
    def __init__(self, name='PlaceHolder'):
        self.name = name
        self.value = None

    @abstractmethod
    def calculate(self, Y, Yhat):
        pass

    def __str__(self):
        return f'{self.name} metric : {self.value}'

class MSE(Metric):
    def __init__(self):
        super().__init__(name='MSE')

    def calculate(self, Y, Yhat):
        self.value = np.square(Y - Yhat).mean()
        return self.value

class RMSE(Metric):
    def __init__(self):
        super().__init__(name='RMSE')

    def calculate(self, Y, Yhat):
        self.value = np.sqrt(np.square(Y - Yhat).mean())
        return self.value

class CategoricalAccuracy(Metric):
    '''I've invented it. Made it exists, but be careful'''
    def __init__(self):
        super().__init__(name='CategoricalAccuracy')

    def calculate(self, Y, Yhat):

        self.value = (np.argmax(Y, axis=0) == np.argmax(Yhat, axis=0)).sum() / Y.shape[1]
        return self.value


"""
Code for integer only arithmetic inferencing for quantization aware trained model,
limited to Dense, Conv1D layers

Original Source Code:
https://github.com/google/gemmlowp/blob/master/fixedpoint/fixedpoint.h
https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/kernels/internal/common.h
"""

import numpy as np

# Returns the integer that represents the product of two fixed-point
# numbers, interpreting all integers as fixed-point values in the
# interval [-1, 1), rounding to the nearest value, and saturating
# -1 * -1 to the maximum value (since 1 is not in the half-open
# interval [-1, 1)).
#
# [The explanation below specializes to std::int32_t for example purpose.]
#
# The mapping between IntegerType and the interval [-1, 1) is unique and
# implied by IntegerType, which is assumed to be signed. For example,
# for IntegerType==std::int32_t, the mapping is
# real_value = integer_value / 2^31.

# So in this case, and leaving aside rounding and saturating, this
# function computes ((a / 2^31) * (b / 2^31)) * 2^31, which simplifies to
# (a * b) / 2^31.
#
# The 'doubling' part in the name of this function comes from the fact that
# this operation is very close to a "multiply-high" operation, keeping only
# the top half bits, except that that would be effectively computing
# (a * b) / 2^32, so here we are computing 2x that, since
# 1/2^31 = 2 * 1/2^32. The idea is to use all of the available 32 bits
# in the destination int32 value.
# Good to write C code for? YES
def SaturatingRoundingDoublingHighMul(a, b):
    overflow = (a == b) & (a == -2147483648)
    a_64 = np.int64(a)
    b_64 = np.int64(b)

    ab_64 = a_64 * b_64
    if ab_64 >= 0:
        nudge = np.int32(1 << 30)
    else:
        nudge = np.int32(1 - (1 << 30))
    ab_x2_high32 = np.int32((ab_64 + nudge) / (np.int64(1 << 31)))

    if overflow:
        return np.int32(2147483647)
    else:
        return ab_x2_high32

# Good to write C code for? YES
def MultiplyByQuantizedMultiplierGreaterThanOne(x, quant_mul, left_shift):
    return SaturatingRoundingDoublingHighMul(x * (1 << left_shift), quant_mul)

# Good to write C code for? YES
def MultiplyByQuantizedMultiplierSmallerThanOne(x, quant_mul, right_shift):
    return RoundingDivideByPOT(SaturatingRoundingDoublingHighMul(x, quant_mul), right_shift)

# Correctly-rounded-to-nearest division by a power-of-two.
# Also known as a rounding arithmetic right shift.
# Good to write C code for? YES
def RoundingDivideByPOT(x, exponent):
    if (exponent < 0) | (exponent > 31):
        raise ValueError('Inputs incorrect')
    mask = np.int32((1 << exponent) - 1)
    zero = np.int32(0)
    one = np.int32(1)
    remainder = x & mask
    if x < zero:
        maskiflessthan = x & zero
    else:
        maskiflessthan = x

    threshold = (mask >> 1) + (maskiflessthan & one)

    if remainder > threshold:
        maskifgreaterthan = remainder & threshold
    else:
        maskifgreaterthan = remainder

    return (x >> exponent) + (maskifgreaterthan & one)

# Integer math for FullyConnected or Dense layer
# Good to write C code for? YES
def FullyConnected(quantized_inputs, input_offset, quantized_weights, weight_offset, quantized_bias,
                   output_offset, M_0, right_shift, output_shape, num_skip_dyn, num_skip_static, total_exc):
    output_full_conn_arr = np.zeros(shape=output_shape, dtype=np.uint8)
    rows, cols = quantized_weights.shape
    for i in range(rows):
        acc = np.int32(0)
        for j in range(cols):
            input_val = np.int32(quantized_inputs[0][j])
            weight_val = np.int32(quantized_weights[i][j])
            if input_val - input_offset == 0:
                num_skip_dyn += 1
            if weight_val - weight_offset == 0:
                num_skip_static += 1
            total_exc += 1
            acc += (input_val - input_offset) * (weight_val - weight_offset)
        acc += quantized_bias[i]
        acc = MultiplyByQuantizedMultiplierSmallerThanOne(acc, M_0, right_shift)
        acc += output_offset  # activation offset
        acc = np.max([acc, np.int32(0)])
        acc = np.min([acc, np.int32(255)])
        output_full_conn_arr[0][i] = np.uint8(acc)
    return output_full_conn_arr, num_skip_dyn, num_skip_static, total_exc

# Integer math for Conv1D
# Good to write C code for? YES
def Conv(quantized_inputs, input_offset, quantized_weights, weight_offset, quantized_bias,
                   output_offset, M_0, right_shift, output_shape, num_skip_dyn, num_skip_static, total_exc):
    output_conv_arr = np.zeros(shape=output_shape, dtype=np.uint8)
    kernel_shape = quantized_weights.shape[2]
    rows = quantized_weights.shape[3]
    cols = quantized_inputs.shape[1]
    for i in range(rows):
        acc = np.int32(0)
        for j in range(cols):
            if j + 1 <= cols - 1:
                for k in range(kernel_shape):
                    input_val = np.int32(quantized_inputs[0, j - 1 + k, 0])
                    weight_val = np.int32(quantized_weights[0, 0, k, i])
                    if input_val - input_offset == 0:
                        num_skip_dyn += 1
                    if weight_val - weight_offset == 0:
                        num_skip_static += 1
                    total_exc += 1
                    acc += (input_val - input_offset) * (weight_val - weight_offset)
            acc += quantized_bias[i]
            acc = MultiplyByQuantizedMultiplierSmallerThanOne(acc, M_0, right_shift)
            acc += output_offset  # activation offset
            acc = np.max([acc, np.int32(0)])
            acc = np.min([acc, np.int32(255)])
            output_conv_arr[j][i] = np.uint8(acc)
            #print("[%d][%d]: %d" % (j, i, output_conv_arr[j][i]))
    return output_conv_arr, num_skip_dyn, num_skip_static, total_exc

# Returns the product of a run-time integer value by a compile-time power
# of two, with either a positive exponent (equivalent to an arithmetic
# left shift, saturating) or a negative exponent (equivalent to an arithmetic
# right shift, rounding to nearest).
# Good to write C code for? NO
def SaturatingRoundingMultiplyByPOT(x, exponent):
    return RoundingDivideByPOT(x, -exponent)

# I don't know if we will need this
# Good to write C code for? NO
def SaturatingAdd(a, b):
    a32 = np.int32(a)
    b32 = np.int32(b)
    sum = np.int64(a32 + b32)
    sum = np.max([-32768, sum])
    sum = np.min([32767, sum])
    return np.int16(sum)

# Good to write C code for? NO
def exp_on_interval_between_negative_one_quarter_and_0_excl(x, num_int_bits):
    if x < float_to_q(-0.25, 5):
        raise ValueError('Must be > -0.25')
    elif x > 0:
        raise ValueError('Must be < 0')
    mask = (1 << (32 - num_int_bits - 1)) - 1
    mask_shift = np.int32((x & mask) << num_int_bits)
    rescaled_x = np.int32(mask_shift | (1 << 31))
    scaled_x = np.int32(rescaled_x + (1 << 28))
    x2 = SaturatingRoundingDoublingHighMul(scaled_x, scaled_x)
    x3 = SaturatingRoundingDoublingHighMul(x2, scaled_x)
    x4 = SaturatingRoundingDoublingHighMul(x2, x2)
    x4_over_4 = SaturatingRoundingMultiplyByPOT(x4, -2)
    constant_1_over_3 = float_to_q(1/3, 0)
    constant_term = float_to_q(np.exp(-1.0 / 8.0), 0)
    x4_over_24_plus_x3_over_6_plus_x2_over_2 = SaturatingRoundingMultiplyByPOT(SaturatingRoundingDoublingHighMul(x4_over_4 + x3, constant_1_over_3) + x2, -1)
    x4_over_24_plus_x3_over_6_plus_x2_over_2_mul_constant_term = SaturatingRoundingDoublingHighMul(constant_term, x4_over_24_plus_x3_over_6_plus_x2_over_2)
    result = SaturatingAdd(constant_term, x4_over_24_plus_x3_over_6_plus_x2_over_2_mul_constant_term)

    if result < 0:
        raise ValueError('Negative Output')
    return result

# Good to write C code for? NO
def CountLeadingZeros(x):
    count = 0
    for i in range(32):
        if (x >> (31 - i)) == 0:  # ignore sign? i think so
            count += 1
        else:
            break
    return count

# Returns (a+b)/2, rounded to the nearest integer.
# Equivalent to VRHADD in the ARM NEON instruction set.
# Good to write C code for? NO
def RoundingHalfSum(a, b):
    a64 = np.int64(a)
    b64 = np.int64(b)
    sum = a64 + b64
    if sum >= 0:
        sign = 1
    else:
        sign = -1
    return np.int32((sum + sign) / 2)

# returns 1 / (1 + x) for x (0, 1)
# Good to write C code for? NO
def one_over_one_plus_x_for_x_in_0_1(a, num_int_bits):
    if (change_to_float(a, num_int_bits) > 1) | (change_to_float(a, num_int_bits) < 0):
        raise ValueError('input not between 0 and 1')
    half_denominator = RoundingHalfSum(a, 2147483647)
    constant_48_over_17 = float_to_q(48.0/17.0, 2)
    constant_neg_32_over_17 = float_to_q(-32.0/17.0, 2)
    constant_one = float_to_q(1.0, 2)
    half_denominator_mul_constant_neg_32_over_17 = SaturatingRoundingDoublingHighMul(half_denominator, constant_neg_32_over_17)
    x = constant_48_over_17 + half_denominator_mul_constant_neg_32_over_17
    for i in range(3):
        half_denominator_times_x = SaturatingRoundingDoublingHighMul(half_denominator, x)
        one_minus_half_denominator_times_x = constant_one - half_denominator_times_x
        x = x + SaturatingRoundingDoublingHighMul(x, one_minus_half_denominator_times_x)
    return np.int32(x << 1)

# Good to write C code for? NO
def exp_on_negative_values(a, num_int_bits):
    # change to 0 bit int rep
    mask = (1 << 24) - 1
    one_quarter = 1 << 24
    a_mod_quarter_minus_one_quarter = (input_diff_rescaled & mask) - one_quarter
    result = exp_on_interval_between_negative_one_quarter_and_0_excl(a_mod_quarter_minus_one_quarter, 5)
    remainder = a_mod_quarter_minus_one_quarter - a
    result = SelectUsingMask(MaskIfZero(a), (1 << 31) - 1, result)
    return result
    # constant_one = 2147483647
    # constant_half = float_to_q(0.5, num_int_bits)
    # constant_1_over_6 = float_to_q(1 / 6, num_int_bits)
    # constant_1_over_24 = float_to_q(1 / 24, num_int_bits)
    # one_plus_x = constant_one + x
    # x2 = FixedPointMul(x, x, num_int_bits)
    # x2_over_2 = FixedPointMul(x2, constant_half, num_int_bits)
    # x3 = FixedPointMul(x2, x, num_int_bits)
    # x3_over_6 = FixedPointMul(x3, constant_1_over_6, num_int_bits)
    # x4 = FixedPointMul(x2, x2, num_int_bits)
    # x4_over_24 = FixedPointMul(x4, constant_1_over_24, num_int_bits)
    # return one_plus_x + x2_over_2 + x3_over_6 + x4_over_24

# For each input scalar, the corresponding bits of the result are set if the
# input scalar is non-zero.
# Good to write C code for? NO
def MaskIfNonZero(a):
    if a:
        return ~np.int32(0)
    else:
        return np.int32(0)

# For each input scalar, the corresponding bits of the result are set if the
# input scalar is zero.
# Good to write C code for? NO
def MaskIfZero(a):
    return MaskIfNonZero(~a)

# Each bit of the result is set to the corresponding bit of either then_val or
# else_val depending on whether the corresponding bit of if_mask is set.
# Equivalent to the VBSL instruction in ARM NEON.
# Good to write C code for? NO
def SelectUsingMask(if_mask, then_val, else_val):
    return (if_mask & then_val) ^ ((~if_mask) & else_val)


# simple_nn.py

# A simple neural network with one node that has two inputs and one output.

import math
import random as rd
import numpy as np

train_x = [1, 2, 3, 5, 8]
train_y = [1, 3, 2, 6, 12]

def initialize_parameters():
	m = rd.random()
	b = rd.random()
	return [m, b]

# Note the cost function is function of two variables m and b.
# The plot will be a surface.   Since we are looking to minimize the cost,
#
def compute_cost(X,Y,m,b):
	n = len(X)
	J = 0
	for i, x in enumerate(X):
		y_hat = m * x + b
		y = Y[i]
		J += (y - y_hat) ** 2
	return J/n

def compute_gradients(X, Y, m, b):
	n = float(len(X))
	dm = 0
	db = 0

	for i, x in enumerate(X):
		y = Y[i]
		y_hat = m*x + b
		dm += x * (y - y_hat) # partial with respect to m of cost function
		db += y - y_hat # partial with respect to b of cost function

	return [-2*dm/n, -2*db/n]

def optimize(X, Y, num_iterations=10, learning_rate=0.1):
	m, b = [0,0] # initialize parameters
	# m, b = initialize_parameters() # Use for random initialization
	line_list = []

	for i in range(1, num_iterations+1):
		# gradients are used to update the parameters (backward propagation)
		dm, db = compute_gradients(X, Y, m, b)
		m = m - learning_rate * dm
		b = b - learning_rate * db
		graph_count = 5

		# Pick 5 lines to graph as algorithm learns regression line
		# Could be improved by pick lines at an exponential rate
		if i in [1, 10, 100, 1000, 10000, 100000, 100000]:
		# if i % (num_iterations//graph_count) == 0:
			line_list.append([m,b])
			print(f"iterations = {i}")
		# recompute the cost (forward propagation)
		cost = compute_cost(X, Y, m, b)
		# print(f"{cost=}, {m=},{b=},{dm=},{db=}")
	return line_list

num = 1000000
alpha = 0.0001
learned_line_list = optimize(train_x, train_y, num, alpha)

print(f"The learned line list is {learned_line_list}")

import matplotlib.pyplot as plt
plt.scatter(train_x, train_y, color="red")
x_list = []
y_list = []
for i, line in enumerate(learned_line_list):
	m, b = line
	for x in np.arange(0,10,0.1):
		y = m*x + b
		x_list.append(x)
		y_list.append(y)
		plt.plot(x_list,y_list, linewidth=1.0, color="blue")
plt.show()


from numpy import log,vectorize,delete,array
from collections import defaultdict

def safe_xlogx(s,cutoff=1.0e-08):
    '''
    Element-wise function that computes x*log(x) and (correctly) returns zero if x
    is small; 0*log(0) should be interpreted as zero.
    x changed to s
    '''
    if s < cutoff:
        return 0.0
    return s*log(s)

def natstobits(s):
    '''
    Applies the log change of base formula to convert nat units to bit units.
    '''
    return s/log(2)

def entropyd(x,est='ML'):
    '''
    Computes the entropy of discrete (integer) data.

    INPUT:
        x: array-like, required
            data for which to compute H[x]

        est: string, optional
            estimator. current options

                'ML' : maximum-likelihood (plugin)
                'MM' : Miller-Maddow corrected
                'JK' : Jackknife estimator (can be slow)

    OUTPUT:
        H[x]: entropy of x, measured in nats
    '''
    vec_xlogx = vectorize(safe_xlogx)
    # do the frequency counting
    counts = defaultdict(int)
    for xi in x:
        counts[xi] += 1
    # convert to frequencies
    sumpofx = 1.0*sum(list(counts.values()))
    pofx = array(list(counts.values()))/sumpofx
    # ML estimator
    H_ML = -1*vec_xlogx(pofx).sum()
    if est == 'ML':
        H = H_ML
    elif est == 'MM':
        # nonzero bins have already been removed from pofx
        H = H_ML + (len(pofx) - 1.0)/(2.0*len(x))
    elif est == 'JK':
        Sc = 0
        for i in range(0,len(x)):
            newx = delete(x,i)
            Sc += entropy(newx,bins,'ML')
        H_JK = len(x)*H_ML - ((len(x) - 1.0)/len(x))*Sc
        H = H_JK
    return H


import numpy as np

def report_points_range(points):
    points_center = points.mean(axis=0)
    points_radii_wrt_center = np.linalg.norm(points - points_center, axis=1)
    points_radii_wrt_origin = np.linalg.norm(points, axis=1)

    print(f"center             : {points_center}")
    print(f"radius w.r.t center: {points_radii_wrt_center.max()}")
    print(f"radius w.r.t origin: {points_radii_wrt_origin.max()}")
    print(f"range              : {points.min(axis=0)} to {points.max(axis=0)}")


# Copyright 2021 BlackRock, Inc.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#     http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations

from typing import Iterable, Union, cast, overload

import numpy as np
import numpy.typing as npt

@overload
def _rastrigin_term(x: float) -> float:
    ...

@overload
def _rastrigin_term(x: Iterable[float]) -> npt.NDArray[np.floating]:
    ...

def _rastrigin_term(x: Union[float, Iterable[float]]) -> Union[float, npt.NDArray[np.floating]]:
    x_array: Union[float, npt.NDArray[np.floating]] = np.array(x) if isinstance(x, Iterable) else x
    return x_array**2 - 10 * np.cos(2 * np.pi * x_array)

def rastrigin(x: Union[float, Iterable[float]]) -> float:
    """https://www.sfu.ca/~ssurjano/rastr.html."""
    if isinstance(x, Iterable):
        x = np.array(x)  # This may consume the iterable (if it's a generator for example)
        n_dimensions = x.size
        if len(x.shape) != 1:
            raise AssertionError("Invalid shape")
    else:
        n_dimensions = 1
    return cast(float, 10 * n_dimensions + np.sum(_rastrigin_term(x)))


import numpy as np

#_Score Measure
def Gini_index(input_vec): ## This may be checked later for error in the result
    counts = input_vec
    gini_index_val = 1
    for i in range(counts.shape[0]):
        gini_index_val -= (counts[i]/sum(counts))**2
    return gini_index_val

from scipy.stats import entropy
def Entropy(input_vec):
    counts = input_vec
    entropy_val = entropy(counts, base=2)
    return entropy_val

def purity(true_set_classes, false_set_classes, impurity_measure='entropy'):
    """Checks the purity of sets passed to it based on their labels
    information gain of the criterion used to divide the dataset
    input: classes for the true and false sets along with
    the specification of impurity measure
    output: the gain or purity of such division"""

    All_classes = np.array(true_set_classes + false_set_classes)

    if impurity_measure == 'gini':
        purity = Gini_index(All_classes) - \
                 (sum(true_set_classes) / sum(All_classes)) * Gini_index(true_set_classes) - \
                 (sum(false_set_classes) / sum(All_classes)) * Gini_index(false_set_classes)
    else:
        # if purity measure is information gain
        purity = Entropy(All_classes) - \
                 (sum(true_set_classes) / sum(All_classes)) * Entropy(true_set_classes) - \
                 (sum(false_set_classes) / sum(All_classes)) * Entropy(false_set_classes)

    return purity


from __future__ import division

import numpy as np

def lsqfitma(X,Y):

    '''
    original function and documentation
    % lsqfitma.m                                     by:  Edward T Peltzer, MBARI
    %                                                revised:  2016 Mar 17.
    %
    % M-file to calculate a "MODEL-2" least squares fit.
    %
    %     The line is fit by MINIMIZING the NORMAL deviates.
    %
    %     The equation of the line is:     y = mx + b.
    %
    %     This line is called the MAJOR AXIS.  All points are given EQUAL
    %       weight.  The units and range for X and Y must be the same.
    %     Equations are from York (1966) Canad. J. Phys. 44: 1079-1086;
    %       re-written from Kermack & Haldane (1950) Biometrika 37: 30-41;
    %       after a derivation by Pearson (1901) Phil. Mag. V2(6): 559-572.
    %
    %     Data are input and output as follows:
    %
    %	    [m,b,r,sm,sb] = lsqfitma(X,Y)
    %
    %             X    =    x data (vector)
    %             Y    =    y data (vector)
    %
    %             m    =    slope
    %             b    =    y-intercept
    %             r    =    correlation coefficient
    %             sm   =    standard deviation of the slope
    %             sb   =    standard deviation of the y-intercept
    %
    %     Note that the equation passes through the centroid:  (x-mean, y-mean)

    function [m,b,r,sm,sb]=lsqfitma(X,Y)

    '''

    # Determine the size of the vector
    n = len(X)

    # Calculate sums and other re-used expressions
    Sx = np.sum(X)
    Sy = np.sum(Y)
    xbar = Sx/n
    ybar = Sy/n
    U = X - xbar
    V = Y - ybar

    Suv = np.sum(U*V)
    Su2 = np.sum(U**2)
    Sv2 = np.sum(V**2)

    sigx = np.sqrt(Su2/(n-1))
    sigy = np.sqrt(Sv2/(n-1))

    # Calculate m, b, r, sm, and sb
    m = (Sv2 - Su2 + np.sqrt(((Sv2 - Su2)**2) + (4*Suv**2)))/(2*Suv)
    b = ybar - m*xbar
    r = Suv/np.sqrt(Su2*Sv2)

    sm = (m/r)*np.sqrt((1 - r**2)/n)
    sb1 = (sigy - sigx*m)**2
    sb2 = (2*sigx*sigy) + ((xbar**2*m*(1 + r))/r**2)
    sb = np.sqrt((sb1 + ((1 - r)*m*sb2))/n)

    return m,b,r,sm,sb


import cv2; import numpy as np; import auxiliary as aux
import matplotlib.pyplot as plt
from scipy.fftpack import dct, idct

#%% JPG Lite - a (pretty much) simplified version of the standard
#   still image transform coding compression algorithm

##  Useful routines
#   Tile DCT 2D transform
def dct2(img, nrm = 'ortho'):
    return dct(dct(img.T, norm = nrm).T, norm = nrm)
#   Tile Inverse DCT 2D transform
def idct2(img, nrm = 'ortho'):
    return idct(idct(img.T, norm = nrm).T, norm = nrm)

##  Quantization of the DCT 2D coefficients
#   When 'block_size == 8', then the standard JPG luminance channel (Y)
#   quantization matrix is applied with a 'quality factor' Q.
#   Otherwise the scalar quantization, 'x = Q⁻¹⌊Q⋅x + .5⌋', is performed
def quantize(X, Q = 1):
    if X.shape[0] == 8:
        QT = np.array(aux.JPG_QT_Y)/Q
        X /= QT; X = np.floor(X + .5); X *= QT; X = X.astype(int)
    else:
        X = np.floor(Q*X + .5)/Q
    return X

## Image loading (if 'B == 8' then by default a JPG quantization scheme is
#   applied (note we assume for simplicity the fixed image size [being a power of two])
org = cv2.cvtColor(cv2.imread('GrassHopper.PNG'), cv2.COLOR_BGR2YCrCb)
N = 1024; org = cv2.resize(org[..., 0], (N, N))
B = 32;  tiles, blocks = range(0, N, B), range(int(N/B))

## Transforming each tile/block using DCT 2D
trns = [[dct2(org[n:n + B, m:m + B]) for m in tiles] for n in tiles]

## Compression vs. image quality
#  If B ≠ 8, then the standard scalar quantization is applied and 'Q == 1'
#  means no scalar quantization (other than conversion to the 'int' type).
#  For B == 8 the JPG quatization matrix is applied. Then 'Q == 0.1' results
#  usually in a poor quality image while 'Q == 10' yields a visually indistinguishable image.
Q = 1/64
## Coefficients quantization and inverse transformation
qntz = [[quantize(trns[n][m], Q)          for n in blocks] for m in blocks]
img  = [[idct2(qntz[n][m]).astype(np.int) for n in blocks] for m in blocks]
#  Note that the 'trns' argument is not deep-copied when passed to
#  'quantize' function and thus is modified there!

## Presentation
img, qntz = np.block(img), np.block(qntz)
nz  = sum((qntz != 0).flat)
aux.displayImages([org, qntz, img, org - img],
                  ['original', 'DCT 2D', f'Q = {Q}', f'{nz} non-zeros'])


# don't create a parent class here. There is nothing to be gained doing that.

from scipy.stats import norm
from math import exp, log, sqrt

class BsmNode:
    '''Data container for Black-Scholes-Merton calculations

    Given the type of option (Call or Put), the option's underlying asset price,
    the contract's strike price, the annual volatility of the underlying, a
    continuously compounded annual risk-free rate, and a length of time (in years)
    the class will generate and store all intermediate calculation values as well
    as various greek values associated with the option.

    Primary calculation is the option price.

    Currently does not support options where the underlying has intermediate cash
    flows such as a stock with a dividend payment. It will be implemented in the
    future.'''
    def __init__(self, op_type, underlying, strike, volatility, risk_free, time_in_years):
        self.op_type = op_type
        self.underlying = underlying
        self.strike = strike
        self.volatility = volatility
        self.risk_free = risk_free
        self.time_in_years = time_in_years

        # Internal Calculations.
        self.d1 = self.d1_calc()
        self.d2 = self.d2_calc()
        self.n1 = self.normcdf_calc()[0]
        self.n2 = self.normcdf_calc()[1]

        self.price = self.price_calc()

        # greeks
        self.delta = self.delta_calc()
        self.gamma = self.gamma_calc()
        self.theta = self.theta_calc()
        #self.vega = self.vega_calc()
        #self.rho = self.rho_calc()

        # dev note:
        # Additional values can be calculated such as the discount factor: exp(-self.risk_free * self.time_in_years),
        # but that wouldn't be a 'true' annual discount factor... which might confuse people.
        # Additionally, saving the sqrt(self.time_in_years) because it's used multiple times.
        # The efficiency gains might be substantial though.
        # But the cost is a bigger data container...
        #
        # On another note:
        # Currently the class parameters can be changed. However the calculations will not update. Consider fixing this.

    def __repr__(self):
        text = '''\nData node of a Black-Scholes-Merton Model for a {op} option where the underlying is $ {under_p},
with a strike price of $ {strike_p}, an annual volatility of {vol}, a continuously-compounded
risk-free rate of {rf}. The option expires in {years} years.'''.format(
                                                                    op = self.op_type,
                                                                    under_p = self.underlying,
                                                                    strike_p = self.strike,
                                                                    vol = self.volatility,
                                                                    rf = self.risk_free,
                                                                    years = self.time_in_years
                                                                    )
        return text

    # internal class calculations.
    def d1_calc(self):
        return ((log(self.underlying/self.strike) + (self.risk_free + (self.volatility**2)/2)*self.time_in_years) /
        (self.volatility * sqrt(self.time_in_years)))

    def d2_calc(self):
        return self.d1 - (self.volatility * sqrt(self.time_in_years))

    def normcdf_calc(self):
        if self.op_type == 'Call':
            n1 = norm.cdf(self.d1)
            n2 = norm.cdf(self.d2)
        else:
            # must be a put
            n1 = norm.cdf(-self.d1)
            n2 = norm.cdf(-self.d2)
        return [n1, n2]

    def price_calc(self):
        if self.op_type == 'Call':
            price = self.underlying * self.n1 - self.strike * exp(-self.risk_free * self.time_in_years) * self.n2
        else:
            #must be a put
            price = -self.underlying * self.n1 + self.strike * exp(-self.risk_free * self.time_in_years) * self.n2
        return price

    # the greeks
    def delta_calc(self):
        if self.op_type == 'Call':
            delta = norm.cdf(self.d1)
        else:
            #must be a put
            delta = -norm.cdf(-self.d1)
        return delta

    def gamma_calc(self):
        return (1/(self.underlying*self.volatility*sqrt(self.time_in_years))) * norm.pdf(self.d1)

    def theta_calc(self):
        if self.op_type == 'Call':
            theta = (-((self.underlying * norm.pdf(self.d1) * self.volatility)/(2 * sqrt(self.time_in_years)))
            - self.risk_free * self.strike * exp(-self.risk_free*self.time_in_years) * norm.cdf(self.d2))
        else:
            #must be a put
            theta = (-((self.underlying * norm.pdf(-self.d1) * self.volatility)/(2 * sqrt(self.time_in_years)))
            + self.risk_free * self.strike * exp(-self.risk_free*self.time_in_years) * norm.cdf(-self.d2))
        return theta

    # recommend not using vega
    def vega_calc(self):
        if self.op_type == 'Call':
            vega = self.underlying * norm.pdf(self.d1) * sqrt(self.time_in_years)
        else:
            #must be a put
            vega = self.strike * exp(-self.risk_free * self.time_in_years) * norm.pdf(self.d2) * sqrt(self.time_in_years)
        return vega

    # recommend not using rho
    def rho_calc(self):
        if self.op_type == 'Call':
            pass
        else:
            pass
        return NotImplemented

    def print_calc_values(self, rounding = 2, hide_greeks = False, hide_d_calc = False, hide_n_calc = False, hide_delta = False, hide_gamma = False, hide_theta = False):
        '''A nice terminal display for checking internal values

        Mainly used for debugging. Can also be used to show answers for problems.
        The greeks vega and rho are not implemented at this time.'''

        #hiding greeks
        if hide_greeks:
            hide_delta = True
            hide_gamma = True
            hide_theta = True

        print(self.__repr__())

        # rounding and heads up message
        if hide_d_calc or hide_n_calc or hide_delta or hide_gamma or hide_theta:
            print('Display values are rounded to {} decimals.'.format(rounding))
            print('Additionally, some values are hidden!')
        else:
            print('Display values are rounded to {} decimals'.format(rounding))

        print('----------------')
        print('Internal Values')
        print('----------------')

        print('{} option price : ${}'.format(self.op_type, round(self.price, rounding)))

        if hide_d_calc:
            pass
        else:
            print('d1 = {}'.format(round(self.d1, rounding)))
            print('d2 = {}'.format(round(self.d2, rounding)))

        if hide_n_calc:
            pass
        else:
            print('cumulative normal distribution using d1 = {}'.format(round(self.n1, rounding)))
            print('cumulative normal distribution using d2 = {}'.format(round(self.n2, rounding)))

        if hide_delta:
            pass
        else:
            print('greek delta = {}'.format(round(self.delta, rounding)))

        if hide_gamma:
            pass
        else:
            print('greek gamma = {}'.format(round(self.gamma, rounding)))

        if hide_theta:
            pass
        else:
            print('greek theta = {}'.format(round(self.theta, rounding)))

example = BsmNode('Call', 100, 110, 0.14247, 0.05, 1)
print(example.delta)
print(example.gamma)
example = BsmNode('Put', 100, 110, 0.14247, 0.05, 1)
print(example.delta)
print(example.gamma)
print(example.theta_calc())
example.print_calc_values()


import numpy as np

def pca(
        features,
        dim_remain=2):
    # singular value decomposition factorises your data matrix such that:
    #
    #   features = U * S * V.T     (where '*' is matrix multiplication)
    #   features * V = U * S
    #
    # * U and V are the singular matrices, containing orthogonal vectors of
    #   unit length in their rows and columns respectively.
    #
    # * S is diagonal matrix containing the singular values of features - these
    #   values squared divided by the number of observations will give the
    #   variance explained by each PC.
    #
    # * if features is considered to be an (observations, features) matrix, the PCs
    #   themselves would correspond to the rows of S^(1/2)*V.T. if features is
    #   (features, observations) then the PCs would be the columns of
    #   U*S^(1/2).
    #
    # * since U and V both contain orthonormal vectors, U*V.T is equivalent
    #   to a whitened version of features.

    U, s, Vt = np.linalg.svd(features, full_matrices=False)
    V = Vt.T
    S = np.diag(s)

    # PCs are already sorted by descending order
    # of the singular values (i.e. by the
    # proportion of total variance they explain)
    # Method 3 dimention reduction:
    # if we use only the first few PCs the reconstruction is less accurate,
    # the dimension is also recuded to (or to say projected on) into another
    # low dimenional space.
    Mhat = np.dot(U[:, :dim_remain], S[:dim_remain, :dim_remain])

    return Mhat, V[:, :dim_remain]


import numpy as np

class KalmanFilter:
    # For an explanation of this algorithm, see Welch & Bishop, 'An
    # Introduction to the Kalman Filter', University of North Carolina,
    # Jul 2006
    def __init__(self, x_prior, P_prior, A, B, H, Q, R):
        # State equation:
        # x_next = Ax + Bu + w
        # where:
        #    x = state
        #    u = input
        #    w ~ N(0, Q) process noise
        #
        # Measurement equation:
        # z = Hx + v
        # where:
        #    v ~ N(0, R) measurement noise
        #
        # P_prior = E[e e^T]
        # where:
        #    e = x_actual - x_prior
        self.x_prior = np.asarray(x_prior)
        self.x = self.x_prior
        self.P_prior = np.asarray(P_prior)
        self.P = self.P_prior
        self.A = np.asarray(A)
        self.B = np.asarray(B)
        self.H = np.asarray(H)
        self.Q = np.asarray(Q)
        self.R = np.asarray(R)

    def update(self, u_input, z_measurement):
        self.u = np.asarray(u_input)
        self.z = np.asarray(z_measurement)

        ## Predict step:
        # Project state forward one step
        # x_prior = Ax + Bu
        self.x_prior = np.asarray(self.A.dot(self.x) + self.B.dot(self.u))
        # Project covariance forward one step
        # P_prior = APA^T + Q
        self.P_prior = np.asarray(self.A.dot(self.P.dot(self.A.T)) + self.Q)

        ## Update step:
        # Compute Kalman gain
        # K = P_prior H^T (H P_prior H^T + R)^-1
        # We have to do the inverse differently if it's a scalar
        denominator = self.H.dot(self.P_prior.dot(self.H.T) + self.R)
        if denominator.shape is ():
            self.K = np.asarray(self.P_prior.dot(self.H.T) / denominator)
        else:
            self.K = self.P_prior.dot(self.H.T.dot(np.linalg.inv(denominator)))
        # Incorporate measurement
        # x = x_prior + K(z - H x_prior)
        self.x = self.x_prior + self.K.dot(self.z - self.H.dot(self.x_prior))
        # Obtain posterior covariance
        # P = (I - KH) P_prior
        # We have to do the I differently if we're working with scalars
        KH = self.K.dot(self.H)
        if KH.shape is ():
            self.P = np.asarray((1 - KH)*self.P_prior)
        else:
            self.P = (np.eye(KH.shape) - KH).dot(self.P_prior)

        return self.x, self.P

if __name__=='__main__':
    # This example is taken from Welch & Bishop, 'An Introduction to the
    # Kalman Filter', University of North Carolina, Jul 2006

    import matplotlib.pyplot as plt

    filt = KalmanFilter(0, 1, 1, 0, 1, 1e-5, 1)
    n = 100
    x = -0.37727
    z = np.random.normal(x, 0.1, size=n)
    x_out = np.zeros(n)
    P = np.zeros(n)

    for k in range(1, n):
        x_out[k], P[k] = filt.update(0, z[k])

    plt.figure()
    plt.plot(z, 'k+', label='Measurements')
    plt.plot(x_out, 'b-', label='Estimates')
    plt.axhline(x, color='g', label='True value')
    plt.legend()
    plt.title("Estimate of x")
    plt.xlabel('Iteration')
    plt.ylabel('Value')

    plt.figure()
    plt.plot(P[1:])
    plt.title("Estimated a priori error")
    plt.xlabel('Iteration')
    plt.ylabel('Error')

    plt.show()


import numpy as np
import sympy

from einsteinpy.symbolic import ChristoffelSymbols, MetricTensor, RiemannCurvatureTensor

def schwarzschild_metric():
    symbolstr = "t r theta phi"
    syms = sympy.symbols(symbolstr)
    G, M, c, a = sympy.symbols("G M c a")
    # using metric values of schwarschild space-time
    # a is schwarzschild radius
    list2d = np.zeros((4, 4), dtype=int).tolist()
    list2d[0][0] = 1 - (a / syms[1])
    list2d[1][1] = -1 / ((1 - (a / syms[1])) * (c ** 2))
    list2d[2][2] = -1 * (syms[1] ** 2) / (c ** 2)
    list2d[3][3] = -1 * (syms[1] ** 2) * (sympy.sin(syms[2]) ** 2) / (c ** 2)
    sch = MetricTensor(list2d, syms)
    # print (sch.tensor())
    return sch

def spherical_metric():
    symbolstr = "r theta phi"
    syms = sympy.symbols(symbolstr)
    list2d = (np.zeros(shape=(3, 3), dtype=int)).tolist()
    list2d[0][0] = 1
    list2d[1][1] = syms[0] ** 2
    list2d[2][2] = (syms[0] ** 2) * (sympy.sin(syms[1]) ** 2)
    metric = MetricTensor(list2d, syms)
    return metric

def test_Riemann_is_zero_for_spherical_space():
    metric = spherical_metric()
    dims = metric.dims
    T = RiemannCurvatureTensor.from_metric(metric)
    arr = T.tensor()
    for i in range(dims ** 4):
        n = i % dims
        r = (int(i / dims)) % (dims)
        s = (int(i / (dims ** 2))) % (dims)
        t = (int(i / (dims ** 3))) % (dims)
        assert arr[t, s, r, n] == 0

def test_TypeError():
    testarr = np.ones((4, 4, 4, 4), dtype=int).tolist()
    syms = 0
    try:
        obj = RiemannCurvatureTensor(testarr, syms)
        assert False
    except TypeError:
        assert True

def test_circular_index_config_same():
    sch = schwarzschild_metric()
    rm0 = RiemannCurvatureTensor.from_metric(sch)
    rm1 = rm0.change_config("lulu")
    rm2 = rm1.change_config("uuuu")
    rm3 = rm2.change_config("ulll")
    dims = sch.dims
    for i in range(dims ** 4):
        # t,s,r,n each goes from 0 to (dims-1)
        # hack for codeclimate. Could be done with 4 nested for loops
        n = i % dims
        r = (int(i / dims)) % (dims)
        s = (int(i / (dims ** 2))) % (dims)
        t = (int(i / (dims ** 3))) % (dims)
        assert sympy.simplify(rm3[n, r, s, t] - rm0[n, r, s, t]) == 0
    assert rm0.parent_metric == rm0._parent_metric

def test_no_exception_raised_passing_christoffel_with_unconventional_indices():
    sch = schwarzschild_metric()
    ch = ChristoffelSymbols.from_metric(sch)
    ch2 = ch.change_config("uuu")
    try:
        rm = RiemannCurvatureTensor.from_christoffels(ch2)
        assert True
    except:
        assert False


import numpy as np
import pandas as pd

def fscore_precision_recall_specificity(tp, fp, tn, fn):
    fscore=0
    precision=0
    recall=0
    specificity=0

    if tp+fn != 0:
        recall=float(tp)/(tp+fn)

    if tp+fp != 0:
        precision=float(tp)/(tp+fp)

    if tn+fp != 0:
        specificity=float(tn)/(tn+fp)

    if precision+recall !=0:
        fscore=float(2*(precision*recall))/(precision+recall)

    return [fscore,precision,recall,specificity]

def tp_fp_tn_fn(predicted, labels):
    tp=0
    fp=0
    tn=0
    fn=0

    for i in range(0,len(predicted)):
        predicted_=predicted[i]
        label_=labels[i]
        if predicted_==label_:
            if predicted_==1:
                tp=tp+1
            else:
                tn=tn+1
        else:
            if predicted_==1:
                fp=fp+1
            else:
                fn=fn+1

    return [tp,fp,tn,fn]

def roc(predicted, labels, number_of_points=100):
    min_value=min(predicted)
    max_value=max(predicted)

    values=np.linspace(min_value,max_value,number_of_points)

    ROC=pd.DataFrame(index=range(0,number_of_points),columns=['threshold', 'tp', 'fp', 'tn', 'fn', 'precision', 'recall', 'specificity', 'fscore'])
    for i in range(0,len(values)):
        [TP,FP,TN,FN]=tp_fp_tn_fn(predicted>values[i],labels)
        [fscore,precision,recall,specificity]=fscore_precision_recall_specificity(TP, FP, TN, FN)

        ROC.iloc[i,:]=[values[i],TP,FP,TN,FN,precision,recall,specificity,fscore]
    return ROC

def maximize_roc(roc, maximization_criteria):
    to_be_maximized=roc[maximization_criteria].values
    maximum_index=np.argmax(to_be_maximized)
    return roc.iloc[[maximum_index]]


# Jon Kleinberg's HITS (Hyperlink-Induced Topic Search) algorithm

import numpy as np
import networkx as nx
from graphflow import L1norm

def hits(adjMatrix, eps=1e-4, maxstep=1000):
    """

    :param adjMatrix:
    :param eps:
    :param maxstep:
    :return:
    """
    nbnodes = adjMatrix.shape[0]
    # hub vector
    i = np.random.uniform(size=nbnodes).reshape((nbnodes, 1))
    # authority vector
    p = np.random.uniform(size=nbnodes).reshape((nbnodes, 1))

    step = 0
    converged = False

    while step < maxstep and not converged:
        newp = np.matmul(adjMatrix.T, i)
        newi = np.matmul(adjMatrix, p)

        newp = newp / np.linalg.norm(newp)
        newi = newi / np.linalg.norm(newi)

        converged = (L1norm(newp, p) < eps) and (L1norm(newi, i) < eps)
        i, p = newi, newp

    return i.reshape(nbnodes), p.reshape(nbnodes)

def CalculateHITS(digraph, eps=1e-4, maxstep=1000):
    """

    :param digraph:
    :param eps:
    :param maxstep:
    :return:
    """
    A = nx.adj_matrix(digraph).toarray()
    nodes = list(digraph.nodes())
    hubvec, authvec = hits(A, eps=eps, maxstep=maxstep)
    hubdict = {nodes[i]: hubvec[i] for i in range(len(hubvec))}
    authdict = {nodes[i]: authvec[i] for i in range(len(authvec))}
    return hubdict, authdict


import Matrix_2 as M2
import imp
imp.reload(M2)
import numpy as np
import scipy as sp

A = np.matrix([[2,1,0,0,0],[3,8,4,0,0],[0,9,20,10,0],[0,0,22,51,-25],[0,0,0,-55,60]],dtype=float)

print(M2.LU(A, False))

#[[  2.           1.           0.           0.           0.        ]
# [  1.5          6.5          4.           0.           0.        ]
# [  0.           1.38461538  14.46153846  10.           0.        ]
# [  0.           0.           1.5212766   35.78723404 -25.        ]
# [  0.           0.           0.          -1.53686088  21.578478  ]]

L, U = M2.LU(A)
print(L, U)

#[[ 1.          0.          0.          0.          0.        ]
# [ 1.5         1.          0.          0.          0.        ]
# [ 0.          1.38461538  1.          0.          0.        ]
# [ 0.          0.          1.5212766   1.          0.        ]
# [ 0.          0.          0.         -1.53686088  1.        ]]
#[[  2.           1.           0.           0.           0.        ]
# [  0.           6.5          4.           0.           0.        ]
# [  0.           0.          14.46153846  10.           0.        ]
# [  0.           0.           0.          35.78723404 -25.        ]
# [  0.           0.           0.           0.          21.578478  ]]

# Check to see if L*U = A

print(np.dot(L, U))

#[[  2.   1.   0.   0.   0.]
# [  3.   8.   4.   0.   0.]
# [  0.   9.  20.  10.   0.]
# [  0.   0.  22.  51. -25.]
# [  0.   0.   0. -55.  60.]]

# Check with scipy function (permute_l = True means the permutation matrix is
# multiplied into L so A = (PL)*U)

sp.linalg.lu(A, permute_l = True)

# Using this function, the L and U matrices returned multiply to give the actual
# matrix. Although using scipy.linalg.lu gives a different result because there
# is pivoting. After selecting permute_l = True, the two matrices matched.

print(M2.det(A))

print(np.linalg.det(A))

# Returns 145180.0 for given matrix, which matches the value calculated by numpy.

b = np.array([2,5,-4,8,9], dtype=float)
x = M2.solve(A, b)
print(x)

# x = [ 0.33764981  1.32470037 -1.6526381   1.71304587  1.72029205]

print(np.dot(A, x))

# [[ 2.  5. -4.  8.  9.]]

Ain = M2.inv(A)
print(Ain)

# Returns array([[ 0.71180603, -0.14120402,  0.04642513, -0.0165312 , -0.006888  ],
#       [-0.42361207,  0.28240805, -0.09285025,  0.03306241,  0.013776  ],
#       [ 0.31336961, -0.20891307,  0.15088166, -0.05372641, -0.022386  ],
#       [-0.24548836,  0.16365891, -0.1181981 ,  0.07769665,  0.03237361],
#       [-0.225031  ,  0.15002066, -0.10834826,  0.07122193,  0.04634247]])

print(np.dot(A, Ain))

#[[  1.00000000e+00   0.00000000e+00   0.00000000e+00   0.00000000e+00
#    0.00000000e+00]
# [ -2.22044605e-16   1.00000000e+00   0.00000000e+00  -2.77555756e-17
#    0.00000000e+00]
# [  3.33066907e-16   1.11022302e-16   1.00000000e+00   0.00000000e+00
#   -1.38777878e-17]
# [  7.49400542e-16  -1.38777878e-15   9.99200722e-16   1.00000000e+00
#   -2.35922393e-16]
# [  3.33066907e-16   1.55431223e-15  -1.33226763e-15   4.44089210e-16
#    1.00000000e+00]]

# All off-diagonal elements are of order e-15 to e-17 so negligible
# so this has multiplied with A to give the identity matrix.


# exec(open(".//04_NumPy_Arrays.py"))

import numpy as np

# First 20 countries with employment data
countries = np.array([
    'Afghanistan', 'Albania', 'Algeria', 'Angola', 'Argentina',
    'Armenia', 'Australia', 'Austria', 'Azerbaijan', 'Bahamas',
    'Bahrain', 'Bangladesh', 'Barbados', 'Belarus', 'Belgium',
    'Belize', 'Benin', 'Bhutan', 'Bolivia', 'Bosnia and Herzegovina'
])

# Employment data in 2007 for those 20 countries
employment = np.array([
    55.70000076,  51.40000153,  50.5       ,  75.69999695,
    58.40000153,  40.09999847,  61.5       ,  57.09999847,
    60.90000153,  66.59999847,  60.40000153,  68.09999847,
    66.90000153,  53.40000153,  48.59999847,  56.79999924,
    71.59999847,  58.40000153,  70.40000153,  41.20000076
])

#
# Accessing elements
#
print countries[0]
print countries[3]

#
# Slicing
#

print countries[0:3]
# ['Afghanistan' 'Albania' 'Algeria']

print countries[:3]
# ['Afghanistan' 'Albania' 'Algeria']

print countries[:10]

print countries[17:]
# ['Bhutan' 'Bolivia' 'Bosnia and Herzegovina']

print countries[:]
#['Afghanistan' 'Albania' 'Algeria' 'Angola' 'Argentina' 'Armenia'
# 'Australia' 'Austria' 'Azerbaijan' 'Bahamas' 'Bahrain' 'Bangladesh'
# 'Barbados' 'Belarus' 'Belgium' 'Belize' 'Benin' 'Bhutan' 'Bolivia'
# 'Bosnia and Herzegovina']

#
# Element Types
#

print(countries.dtype)
# S22

print(employment.dtype)
# float64

print np.array([0, 1, 2 ,3]).dtype
# int32

print np.array([1.0, 1.5, 2.0, 2.5]).dtype
# float64

print np.array([True, False, True]).dtype
# bool

print np.array(['AL', 'AK', 'AZ', 'AR', 'CA']).dtype
# S2

#
# Looping
#

for country in countries:
    print 'Examining country {}'.format(country)

for i in xrange(len(countries)):
    country = countries[i]
    country_employment = employment[i]
    print 'Country {} has employment {}'.format(country,
                                                country_employment)

#
# Numpy functions
#
print employment.mean()
print employment.std()
print employment.max()
print employment.sum()

'''
Below is my own answer to the assignment.
'''

# def max_employment(countries, employment):
#    '''
#    Fill in this function to return the name of the country
#    with the highest employment in the given employment
#    data, and the employment in that country.
#    '''
#    max_country = None
#    max_employment = 0

#    max_value = employment.max()
#    max_country = countries[max(np.where(employment == max_value)[0])]

#    return (max_country, max_value)

def max_employment(countries, employment):
    max_country = None
    max_employment = 0

    # loop through each element of the two
    # numPy arrays. Each array has the same length
    for i in range(len(countries)):
        country = countries[i]
        country_empoloyment = employment[i]

        # check if the current employment is greater
        # than the employment we've seen so far
        if country_employment > max_employment:
            max_country = country
            max_employment = country_employment

    return (max_country, max_employment)

'''
Another way to code max_employment
'''
def max_employment2(countries, employment):
    i = employment.argmax()
    return (countries[i], employment[i])

'''
Arithmetic operations between two NumPy arrays
'''

a = np.array([1,2,3,4])
b = np.array([1,2,1,2])

print a + b
print a - b
print a * b
print a / b
print a ** b

'''
Arithmetic operations between a NumPy array and a single number
'''

a = np.array([1, 2, 3, 4])
b = 2

print a + b
print a - b
print a * b
print a / b
print a ** b


# Author: Arrykrishna Mootoovaloo
# Collaborators: Prof. Alan Heavens, Prof. Andrew Jaffe, Dr. Florent Leclercq
# Email : arrykrish@gmail.com/a.mootoovaloo17@imperial.ac.uk
# Affiliation : Imperial Centre for Inference and Cosmology
# Status : Under Development

'''
Setup for the redshift distributions
'''

import numpy as np
import scipy.stats as ss

# our scripts
import settings as st

class nz_dist(object):

    def __init__(self, zmin: float = None, zmax: float = None, nzmax: int = None):

        if zmin is None:
            self.zmin = st.survey_zmin

        else:
            self.zmin = zmin

        if zmax is None:
            self.zmax = st.survey_zmax

        else:
            self.zmax = zmax

        if nzmax is None:
            self.nzmax = st.nzmax

        else:
            self.nzmax = nzmax

        # n(z) redshift
        self.nz_z = np.linspace(self.zmin, self.zmax, self.nzmax)

        # shift to midpoint
        self.mid_z = 0.5 * (self.nz_z[1:] + self.nz_z[:-1])

        self.mid_z = np.concatenate((np.zeros(1), self.mid_z))

    def nz_model_1(self, zm: float) -> np.ndarray:
        '''
        Calculate the analytic function

        :math:`n(z)=z^{2}\\text{exp}(-\\frac{z}{z_{0}})`
        '''
        z0 = zm / 3.

        nz = self.nz_z**2 * np.exp(-self.nz_z / z0)

        # find normalisation factor
        fact = np.trapz(nz, self.nz_z)

        # calculate n(z) at mid points and normalise n(z)
        nz_new = self.mid_z**2 * np.exp(-self.mid_z / z0) / fact

        return nz_new

    def nz_model_2(self, z0: float, alpha: float = 2, beta: float = 1.5):
        '''
        https://arxiv.org/pdf/1502.05872.pdf

        Calculate the analytic function

        :math:`n(z)=z^{\\alpha}\\text{exp}(-(\\frac{z}{z_{0}})^{\\beta})`
        '''

        nz = self.nz_z**alpha * np.exp(-(self.nz_z / z0)**beta)

        # find normalisation factor
        fact = np.trapz(nz, self.nz_z)

        # calculate n(z) at mid points and normalise n(z)
        nz_new = self.mid_z**alpha * np.exp(-(self.mid_z / z0)**beta) / fact

        return nz_new

    def nz_gaussian(self, z0: float, sigma: float) -> np.ndarray:
        '''
        Gaussian n(z) distribution for the tomographic bin

        :math:`n(z)=\\frac{1}{2\pi\sigma}\\text{exp}(-\\frac{1}{2}\\frac{(z-z_{0})^{2}}{\sigma^{2}})`
        '''

        nz_dist = ss.norm(z0, sigma)

        nz = nz_dist.pdf(self.nz_z)

        return nz


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

#  Define our ODEs
def equations(s, theta):
    theta_prime = np.zeros(2)

    c = 8.360795454545455e-07

    theta_prime[0] = theta[1]
    theta_prime[1] = -c * np.cos(theta[0])

    return theta_prime

#  Constants
c = 8.360795454545455e-07
K = 0.0006693734347137342
L = 600

#  Define range of integration
x = np.linspace(0, L, 100)
xspan = (x[0],x[-1])

#  We use the small angle approximation for our initial guess
initial_guess = c * L + K

#  Then we adjust our guess until the derivative boundary condition is matched
#theta_0 = [0, initial_guess]
theta_0 = [0, 0.001145]

#  Solve the system
sol = solve_ivp(equations, xspan, theta_0, t_eval=x)

#  Plot the derivative and desired boundary value
plt.plot(x, sol.y[1,:])
plt.plot(L, K, 'o')
plt.title('Confirm Derivative Boundary Condition')
plt.grid(True)
plt.xlabel('s')
plt.ylabel(r'$d\theta/ds$')
plt.savefig('Derivative_Boundary_condition.png')
#  Check to see of our approximate solution is close.  I multiplied both bu 1000 to make the numbers easier to compare by eye.
print('Desired boundary = ', 10000 * K)
print('fFrom solver: ', 10000 * sol.y[1, -1])

plt.plot(x, sol.y[0,:])
plt.grid(True)
plt.xlabel('s')
plt.ylabel(r'$\theta$')
plt.title('Solution')
plt.savefig('Solution.png')

#  Desired tolerance
tol = 1e-6

#  Correct derivative is between these two numbers
low =  0.001
high = 0.002

#  Counter and maximum numer of iterations to avoid infinite loops
max_iters = 100
count = 0

while count <= max_iters:
    count = count + 1

    #  Derivative guess is average of bounds
    theta_prime0 = np.mean([low, high])
    theta_0 = [0, theta_prime0 ]

    #  Solve the system
    sol = solve_ivp(equations, xspan, theta_0, t_eval = x)

    #  For ease of use, extract the function derivative values from the solution object.
    theta_prime = sol.y[1, :]

    #  Calculate percent error and break if within tolerance
    err = np.abs(theta_prime[-1] - K) / K
    if np.abs(err) <= tol:
        break

    #  Adjust our bounds if we are not within tolerance
    if theta_prime[-1] > K:
        high = theta_prime0
    else:
        low = theta_prime0

print('Correct initial derivative = ', theta_prime0)

#  solve and plot the final results

theta_0 = [0, theta_prime0]
sol = solve_ivp(equations, xspan, theta_0, t_eval=x)

fig, (ax1, ax2) = plt.subplots(1,2)

ax1.plot(x, sol.y[0,:], 'k')
ax1.grid(True)
ax1.set_xlabel('s')
ax1.set_ylabel(r'$\theta$')
ax1.set_title('Solution')

ax2.plot(x, sol.y[1,:], 'k')
ax2.plot(x[-1], K, 'o')
ax2.grid(True)
ax2.set_xlabel('s')
ax2.set_ylabel(r'$d\theta/ds$')
ax2.set_title('Check Derivative Boundary')

plt.tight_layout()
plt.savefig('final.png')


import matplotlib
matplotlib.use('Agg')

import numpy as np
import matplotlib.pyplot as plt

def model(param):
    """
    Modelo de alcance maximo dado param[0] (angulo) y param[1] (velocidad)
    """
    y = (np.sin(2.0*param[0])*param[1]**2)/9.8
    return y

def loglikelihood(y_obs, sigma_y_obs, param):
    """
    Log de la verosimilitud de los alcances observados (y_obs, sigma_y_obs)
    dados los parametros param[0] (angulo) y param[1] (velocidad).
    """
    d = y_obs -  model(param)
    d = d/sigma_y_obs
    d = -0.5 * np.sum(d**2)
    return d

def logprior(param):
    """
    Logprior para param[0] (angulo) y param[1] (velocidad)
    """
    if param[0] > 0 and param[0] < np.pi/2.0 and param[1]>0 and param[1]<1000:
        p = 0.0
    else:
        p = -np.inf
    return p

y_obs = np.array([880.0, 795.0, 782.0, 976.0, 178.0])
sigma_y_obs = 5.0 * np.ones(len(y_obs))

N = 100000
l_param = [np.array([np.pi/4.0, 10.0])]
sigma_param = np.array([0.1, 1.0])
n_param = len(sigma_param)
for i in range(1,N):
    propuesta  = l_param[i-1] + np.random.normal(size=n_param)*sigma_param
    logposterior_viejo = loglikelihood(y_obs, sigma_y_obs, l_param[i-1]) + logprior(l_param[i-1])
    logposterior_nuevo = loglikelihood(y_obs, sigma_y_obs, propuesta) + logprior(propuesta)

    r = min(1,np.exp(logposterior_nuevo-logposterior_viejo))
    alpha = np.random.random()
    if(alpha<r):
        l_param.append(propuesta)
    else:
        l_param.append(l_param[i-1])

l_param = np.array(l_param)
l_param = l_param[N//10:,:] # descartamos el primer 10% de la cadena

v_medio  = np.mean(l_param[:,1])
v_sigma = np.std(l_param[:,1])
title = "$v = {:.1f} \pm {:.1f} $ km/s".format(v_medio, v_sigma)

_ = plt.hist(l_param[:,1], bins=20, density=True)
plt.title(title)
plt.xlabel("$v_0$ (km/s)")
plt.ylabel("PDF ($v_0$|observaciones)")
plt.savefig('Ejercicio8.pdf')
plt.clf()

plt.scatter(l_param[:,0], l_param[:,1], s=1.0, alpha=0.1)
plt.xlabel(r'$\theta$ (radianes)')
plt.ylabel(r'$v_0$ (m/s)')
plt.savefig('vel_theta.pdf')


# -*- coding: utf-8 -*-
"""Some helper functions used in implementation.py."""
import numpy as np

def compute_loss_LS(y, tx, w):
    """compute the MSE loss of least squares model.

    Input:
        - y     = the label
        - tx    = the samples
        - w     = the weights for LS model

    Output:
        - loss = the loss for given (y,tx,w)
    """
    e = y - tx.dot(w)
    loss =  1/2*np.mean(e**2)
    return loss

def compute_gradient_LS(y, tx, w):
    """
    compute the gradient of least squares model.

    Input:
        - y     = the label
        - tx    = the samples
        - w     = the weights for LS model

    Output:
        - gradient = the gradient for given (y,tx,w)
    """
    e = y - tx.dot(w)
    n_sample = y.shape[0]
    gradient = -1/n_sample*tx.T.dot(e)
    return gradient

def batch_iter(y, tx, batch_size, num_batches=1, shuffle=True):
    """
    Generate a minibatch iterator for a dataset.
    Takes as input two iterables (here the output desired values 'y' and the input data 'tx')
    Outputs an iterator which gives mini-batches of `batch_size` matching elements from `y` and `tx`.
    Data can be randomly shuffled to avoid ordering in the original data messing with the randomness of the minibatches.
    Example of use :
    for minibatch_y, minibatch_tx in batch_iter(y, tx, 32):
        <DO-SOMETHING>
    """
    data_size = len(y)

    if shuffle:
        shuffle_indices = np.random.permutation(np.arange(data_size))
        shuffled_y = y[shuffle_indices]
        shuffled_tx = tx[shuffle_indices]
    else:
        shuffled_y = y
        shuffled_tx = tx
    for batch_num in range(num_batches):
        start_index = batch_num * batch_size
        end_index = min((batch_num + 1) * batch_size, data_size)
        if start_index != end_index:
            yield shuffled_y[start_index:end_index], shuffled_tx[start_index:end_index]

def sigmoid(t):
    """
    apply the sigmoid function on t.
    Output:
        - 1/(1+exp(-t))
    """

    return 1 / (1 + np.exp(-t))

def compute_loss_logistic(y,tx,w):
    """
    compute the binary cross entropy loss of logistic regression model.

    Input:
        - y     = the label
        - tx    = the samples
        - w     = the weights for Logistic model

    Output:
        - gradient = the gradient for given (y,tx,w)
    """

    tmp = tx.dot(w)
    loss = -y.T.dot(np.log(sigmoid(tmp))) + (y - 1).T.dot(np.log(1-sigmoid(tmp)))
    loss = np.asscalar(loss)
    return loss

def compute_gradient_logistic(y, tx, w):
    """
    compute the gradient of logistic regression model. (cross entropy loss)

    Input:
        - y     = the label
        - tx    = the samples
        - w     = the weights for Logistic model

    Output:
        - gradient = the gradient for given (y,tx,w)
    """
    tmp = tx.dot(w)
    gradient = tx.T.dot(sigmoid(tmp) - y)
    return gradient

#def compute_loss_reg_logistic(y, tx, w, lambda_):
#    """compute the train loss of regularized logistic regression model."""
#
#    loss = compute_loss_logistic(y, tx, w) + 0.5 * lambda_ * w.T.dot(w)
#    return loss

def compute_gradient_reg_logistic(y, tx, w, lambda_):
    """
    compute the gradient of regularized logistic regression model.

    Input:
        - y         = the label
        - tx        = the samples
        - w         = the weights for Logistic model
        - lambda_   = the prefactor of L2 regularization term
    Output:
        - gradient  = the gradient for given (y,tx,w,lambda_)
    """

    gradient = compute_gradient_logistic(y, tx, w) + lambda_ * w
    return gradient


# --------------------------------------------------------- #
# ----- Square Matrix Multiplication - divide and conquer - #
# ----- Number of operations:    n**log(2, 7) == n**2.8  -- #
# ----- Running time T(n) =                         ------- #
# --------------------------------------------------------- #

import numpy as np
import sys

sys.path.insert(0, 'C:\\Users\\ARMEN\\Desktop\Algorithms')
sys.setrecursionlimit(5000)

numRows = 16
numColumns = 16
numOfOperations = 0

#matrixA = [[1]*numColumns for row in range(numRows)]
#matrixB = [[2]*numColumns for row in range(numRows)]
#matrixC = [[0]*numColumns for row in range(numRows)]

matrixA = np.zeros((numRows, numColumns))
matrixB = np.zeros((numRows, numColumns))
matrixD = np.zeros((numRows, numColumns))

count = 0
for i in range(numRows):
    for j in range(numRows):
        count += 1
        matrixA[i, j] = count
        matrixB[i, j] = count*10

def SQUARE_MATRIX_MULTIPLY_RECURSIVE(matrixA, matrixB):
    global numOfOperations
    print('A = ', matrixA)
    print('B = ', matrixB)
    n = len(matrixA)
    matrixC = np.zeros((n , n))
    print(len(matrixA), '....', len(matrixB), '.....', len(matrixC))

    if(n==0):
        return matrixC
    if(n==1):
        matrixC[0, 0] = matrixA[0, 0]*matrixB[0, 0]
    else:
        m = n//2

        A11 = matrixA[0:m, 0:m]
        A12 = matrixA[0:m, m:n]
        A21 = matrixA[m:n, 0:m]
        A22 = matrixA[m:n, m:n]

        B11 = matrixB[0:m, 0:m]
        B12 = matrixB[0:m, m:n]
        B21 = matrixB[m:n, 0:m]
        B22 = matrixB[m:n, m:n]

        print('.................A-s')
        print(A11)
        print(A12)
        print(A21)
        print(A22)
        print('.................B-s')
        print(B11)
        print(B12)
        print(B21)
        print(B22)
        matrixC[0:m, 0:m] = SQUARE_MATRIX_MULTIPLY_RECURSIVE(A11, B11) + SQUARE_MATRIX_MULTIPLY_RECURSIVE(A12, B21)
        matrixC[0:m, m:n] = SQUARE_MATRIX_MULTIPLY_RECURSIVE(A11, B12) + SQUARE_MATRIX_MULTIPLY_RECURSIVE(A12, B22)
        matrixC[m:n, 0:m] = SQUARE_MATRIX_MULTIPLY_RECURSIVE(A21, B11) + SQUARE_MATRIX_MULTIPLY_RECURSIVE(A22, B21)
        matrixC[m:n, m:n] = SQUARE_MATRIX_MULTIPLY_RECURSIVE(A21, B12) + SQUARE_MATRIX_MULTIPLY_RECURSIVE(A22, B22)
        n = n//2
    print(matrixC)
    numOfOperations += 1
    return matrixC

print('Multiplication started')

SQUARE_MATRIX_MULTIPLY_RECURSIVE(matrixA, matrixB)

print('Multiplication finished')
print('Number of operations: ', numOfOperations)


import numpy as np

def build_two_sigma_quantization_table( number_entries, minimum, maximum, standard_deviation ):
    """
    Builds a quantization table, suitable for use with NumPy's digitize(), that emphasizes
    data within two sigma from the mean.  All data outside of the mean, plus or minus two sigma,
    are mapped to two entries, one for values below mean minus two sigma and one for values
    above mean plus two sigma, respectively.  This ensures that the majority of the table
    entries map onto the majority of the data values.

    The quantization table generated looks like so:

                   (number_entries - 1)                              1
            [                |                |                      ]
    -2*standard_deviation   mean      2*standard_deviation        maximum

    NOTE: This assumes the data are zero mean.

    Takes 4 arguments:

      number_entries     - Number of entries in quantization_table.
      minimum            - Minimum value of the data to quantize.
      maximum            - Maximum value of the data to quantize.
      standard_deviation - Standard deviation of the data to quantize.

    Returns 1 value:

      quantization_table - NumPy array, of type float32, containing the quantization table.

    """

    # attempt to handle non-normal data.  adjust the extrema so that +- one
    # standard deviation is within the data.
    #
    # NOTE: this is a kludge.
    #
    if (-2.0*standard_deviation < minimum):
        minimum = -2.1 * standard_deviation
    if (2.0*standard_deviation > maximum):
        maximum =  2.1 * standard_deviation

    quantization_table = np.empty( (number_entries,), dtype=np.float32 )

    # build the table so that it covers two standard deviations from the mean.
    # the final entry is set to the maximum so all of the data are covered by
    # the table when quantization is done such that x[i-1] < value <= x[i].
    quantization_table[:-1] = np.linspace( -2.0*standard_deviation,
                                            2.0*standard_deviation,
                                            number_entries - 1 )
    quantization_table[-1]  = maximum

    return quantization_table

def build_outliers_quantization_table( number_entries, minimum, maximum, standard_deviation ):
    """
    Builds a quantization table, suitable for use with NumPy's digitize(), that emphasize
    data more than one sigma from the mean.  All data within one sigma of the mean are
    mapped to a single entry in the quantization table ensuring that the majority of the
    table entries map onto the outliers in the data's distribution.

    The quantization table generated looks like so:

         (number_entries - 1)                        (number_entries - number_left_entries - 1)
   floor[--------------------]              1
                  2
       [                      |                           |                      ]
    minimum          -standard_deviation   mean   standard_deviation          maximum

    For tables with even number of entries, the levels are skewed towards the maximum with
    an extra entry.  The generated table is closed from above and open from below,
    which corresponds to digitize()'s right parameter being set to True.

    NOTE: This assumes the data are zero mean.

    Takes 4 arguments:

      number_entries     - Number of entries in quantization_table.
      minimum            - Minimum value of the data to quantize.
      maximum            - Maximum value of the data to quantize.
      standard_deviation - Standard deviation of the data to quantize.

    Returns 1 value:

      quantization_table - NumPy array, of type float32, containing the quantization table.

    """

    # compute the number of entries in each section, starting with the middle.
    number_middle_bins = 1
    number_left_bins   = (number_entries - number_middle_bins) // 2
    number_right_bins  = number_entries - (number_left_bins + number_middle_bins)

    # attempt to handle non-normal data.  adjust the extrema so that +- one
    # standard deviation is within the data.
    #
    # NOTE: this is a kludge.
    #
    if (-1.0*standard_deviation < minimum):
        minimum = -1.1 * standard_deviation
    if (standard_deviation > maximum):
        maximum =  1.1 * standard_deviation

    quantization_table = np.empty( (number_entries,), dtype=np.float32 )

    # build the table as three linearly (different) spaced regions.
    quantization_table[:number_left_bins] = np.linspace( minimum,
                                                         -1.0*standard_deviation,
                                                         number_left_bins,
                                                         endpoint=False )
    quantization_table[number_left_bins:(number_left_bins+number_middle_bins)] = np.linspace( -1.0*standard_deviation,
                                                                                standard_deviation,
                                                                                number_middle_bins,
                                                                                endpoint=False )
    quantization_table[(number_left_bins+number_middle_bins):] = np.linspace( standard_deviation,
                                                                              maximum,
                                                                              number_right_bins,
                                                                              endpoint=True )

    return quantization_table

def build_linear_quantization_table( number_entries, minimum, maximum, standard_deviation ):
    """
    Builds a quantization table, suitable for use with NumPy's digitize(), that uniformly
    maps data between a minimum and maximum.

    The quantization table generated looks like so:

                                       number_entries
       [                                                                         ]
    minimum                                                                   maximum

    Takes 4 arguments:

      number_entries     - Number of entries in quantization_table.
      minimum            - Minimum value of the data to quantize.
      maximum            - Maximum value of the data to quantize.
      standard_deviation - Dummy parameter.  It is required to maintain compatibility with
                           the module's quantization table builder interface.

    Returns 1 value:

      quantization_table - NumPy array, of type float32, containing the quantization table.

    """

    quantization_table = np.linspace( minimum,
                                      maximum,
                                      number_entries,
                                      dtype=np.float32 )

    return quantization_table


# Graphs of the cost C(x), revenue R(x) and the profit P(x), in thousands of dollars, are shown, where x is the number of thousands of items produced.

from sympy import solve, symbols, lambdify, diff
import matplotlib.pyplot as plt
import numpy as np

x1, y1 = symbols( 'x1, y1', positive = True )

g_xlim = [ 1, 13 ]
g_ylim = [ -5, 15 ]

def plot_fun( fun, name, col ):
	x_vals = np.linspace( g_xlim[0], g_xlim[1], 1000, endpoint=True )
	y_vals = fun( x_vals )
	plt.plot( x_vals, y_vals, label = name, color = col )

x = symbols( 'x' )
R = 100*x
P = -x**3 + 15*x**2 - 27*x - 50

# Revenue
lam_R = lambdify( x, R, np )
plot_fun( lam_R, 'R', 'R' )

# Profit
lam_P = lambdify( x, P, np )
plot_fun( lam_P, 'P', 'Black' )

# The profit is given by-x**3 + 15*x**2 - 27*x - 50. What is the formula for the cost function C(x)?
# Cost Function
C = R - P
lam_C = lambdify( x, C, np )
plot_fun( lam_C, 'C', 'Blue' )

# Fixed Cost
C.subs( { x: 0 } )

# Average Cost
avg_cost = C / x
lam_ac = lambdify( x, avg_cost, np )
plot_fun( lam_ac, 'AC', 'Orange' )

margin_cost = diff( C, x )
lam_mc = lambdify( x, margin_cost, np )
plot_fun( lam_mc, 'M', 'G' )

# Report the minimum marginal cost.
min_cost = solve( diff( margin_cost, x ), x )[ 0 ]
margin_cost.subs( { x: min_cost} )

# What is the largest profit possible?
profit = diff( P, x )
solve( profit, x )

P.subs( { x: 9 } )

plt.show()


# This module is intended to determine the probability a triangle is obtuse
# Given that its vertices are determined uniformly in the unit square

from numpy.random import rand
from numpy import mean, std
from scipy.stats import sem, t
import matplotlib.pyplot as plt
from math import sqrt, acos, pi
from itertools import combinations
from sys import argv

def CreateTriangle():
	"""Creates a random triangle in the unit square
	Returns a list with three points of the form (x, y)
	These coordinates are generated uniformly in the unit square"""
	return [(x, y) for x, y in zip(rand(3), rand(3))]

def DistPoints(p1, p2):
	"""Calculate the distance between two points of the form (x, y)"""
	return sqrt(pow(p2[0] - p1[0], 2) + pow(p2[1] - p1[1], 2))

def TriangleAngles(alist):
	"""Determine the angles of a triangle given a list of its vertices
	The list must contain three tuples of the form (x, y)
	This function does not check to ensure this requirement is met"""
	a, b, c = (DistPoints(tup[0], tup[1]) for tup in combinations(alist, 2))
	angles = [acos((a**2 + b**2 - c**2) / (2*a*b)),
			  acos((a**2 + c**2 - b**2) / (2*a*c)),
			  acos((b**2 + c**2 - a**2) / (2*b*c))]
	return angles

def isObtuse(alist):
	"""Determine whether a triangle is an obtuse triangle given a list of its vertices
	The list must contain three tuples of the form (x, y)
	This function does not check to ensure this requirement is met"""
	for angle in TriangleAngles(alist):
		if angle > pi/2:
			return True
	return False

def isRight(alist):
	"""Determine whether a triangle is a right triangle given a list of its vertices
	The list must contain three tuples of the form (x, y)
	This function does not check to ensure this requirement is met
	This function is supplementary and is not used in this module"""
	return any(not (x - pi/2) for x in TriangleAngles(alist))

def isAcute(alist):
	"""Determine whether a triangle is an acute triangle given a list of its vertices
	The list must contain three tuples of the form (x, y)
	This function does not check to ensure this requirement is met
	This function is supplementary and is not used in this module"""
	for angle in TriangleAngles(alist):
		if angle == pi/2 or angle > pi/2:
			return False
	return True

def CI_mean(alist, alpha=.95, round_num=False):
	"""Generate a confidence interval about the mean of a list of numbers with confidence alpha
	Specify a round_num (int) to round the values of your confidence interval to round_num decimal places"""
	mu, s = mean(alist), sem(alist)
	n = len(alist)
	HL = s * t.ppf((1+alpha)/2, n-1)
	return tuple(round(x, 2) for x in (mu - HL, mu + HL)) if type(round_num) is int else (mu - HL, mu + HL)

if __name__ == '__main__':
	iterations = int(argv[2]) if len(argv) >= 3 else 1
	trials_per_iteration = int(argv[1]) if len(argv) >= 2 else 50
	alpha = float(argv[3]) if len(argv) >= 4 else .95
	totalProbs = [mean([isObtuse(CreateTriangle()) for x in range(trials_per_iteration)]) for i in range(iterations)]

	mystr =\
f"""The mean probability of a triangle being obtuse is:  {round(mean(totalProbs), 2)}
The standard deviation is:  {round(std(totalProbs), 2)}
A {100*alpha}% confidence interval for the mean of this probability is:  {CI_mean(totalProbs, alpha, 2)}"""
	print(mystr)

	plt.hist(totalProbs)
	plt.show()


import numpy as np
#Metodo de la falsa posición

#Resolver: 2 - x + 3*sin(x)
def f(x):
    value = 2 - x + 3*np.sin(x)
    return value

#inputs
p_prev_2 = 0.0
p_prev_1 = 5.0
Tol = 1.0e-4
error = 1.0
Nmax = 1000
iteraciones  = 0

#Implementación del algorítmo

while(error > Tol):
    p = p_prev_1 - ((p_prev_1 - p_prev_2)*f(p_prev_1)) / (f(p_prev_1) - f(p_prev_2))

    error = abs(p_prev_1 - p)
    print("pn_2 = %f\t  pn_1 = %f\t  p = %f\t f(p) = %f\t error = %f\n"%(p_prev_2 ,p_prev_1 ,p ,abs(f(p)),error));

    if( f(p)*f(p_prev_1) < 0 ):
        p_prev_2 = p_prev_1
        p_prev_1 = p
    elif ( f(p)*f(p-p_prev_1) > 0 ):
        p_prev_1 = p_prev_2
        p_prev_2 = p

    iteraciones += 1
    if(iteraciones >= Nmax):
        print("El metodo no converge  despues de  %d iteraciones \n"%(Nmax));
        break


import numpy as np

def broadcast_operate():
    a = np.array([0, 1, 2])
    b = np.array([5, 5, 5])
    print('a:', a)
    print('b:', b)
    print('a + b = ', a + b)
    print('a + 5 =', a + 5)

    c = np.ones((3, 3))
    print('c:', c)
    print('c + a', c + a)

    d = np.arange(3)
    e = np.arange(3)[:, np.newaxis]
    print('d = ', d)
    print('e = ', e)
    print('d + e = ', d + e)

def broadcast_operate_example():
    # adding a two-dimensional array to a one-dimensional array:
    a = np.ones((2, 3))
    b = np.arange(3)
    print('a + b = ', a + b)

    # both arrays need to be broadcast:
    a2 = np.arange(3).reshape((3, 1))
    b2 = np.arange(3)
    print('a2 + b2 = ', a2 + b2)

    #  the two arrays are not compatible:
    a3 = np.ones((3, 2))
    b3 = np.arange(3)
    # print('a3 + b3 = ', a3 + b3)  # ValueError: operands could not be broadcast
    b4 = b3[:, np.newaxis]
    print('a3 + b4 = ', a3 + b4)
    np.logaddexp(a3, b4)  # logaddexp(a, b) function, which computes log(exp(a) + exp(b))

    # define a function $z = f(x, y)
    x = np.linspace(0, 5, 50)
    y = np.linspace(0, 5, 50)[:, np.newaxis]
    z = np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x)
    print('z = ', z)

if __name__ == '__main__':
    print('Numpy Version', np.__version__)
    # broadcast_operate()
    broadcast_operate_example()


import numpy as np
import cvxpy as cvx
from sympy.geometry import Segment
import matplotlib.pyplot as plt

r = np.random.rand(10)

def checkValidity(r):
    """
    This function checks if a polygon exists with the given side lengths.
    :param r: Side lengths as a vector.
    :return: True or False.
    """
    s = np.sum(r)/2
    for k in range(len(r)):
        if(s<r[k]):
            return False
    return True

def checkDists(X, r):
    """
    This function checks if the distance between consecutive points are in agreement
    with the given side lengths.
    :param X: Coordinate matrices of the polygon.
    :param r: Side lengths as a vector.
    :return: True or False.
    """
    dists = []
    for k in range(len(r)-1):
        dists = dists + [np.linalg.norm(X[k,:]-X[k+1,:])]
    dists = dists + [np.linalg.norm(X[-1,:]-X[0,:])]

    if(np.linalg.norm(dists-r)<=1e-6):
        return True
    else:
        print('Something went wrong!')

def checkConvexity(A, r):
    """
    This function checks if the given polygon with coordinates is convex.
    :param A: Coordinate matrices of the polygon.
    :param r: Side lengths as a vector.
    :return: True or False
    """
    An = np.vstack((A, A[0,:], A[1,:]))
    t = []
    for k in range(len(r)):
        t = t + [np.cross(An[k+1,:]-An[k,:], An[k+2,:]-An[k+1,:])]

    if((np.array(t)<=0).all() or (np.array(t)>=0).all()):
        return True
    else:
        print('Something went wrong!')

def checkSimple(A,r):
    """
    This function checks if the given polygon with coordinates is simple.
    :param A: Coordinate matrices of the polygon.
    :param r: Side lengths as a vector.
    :return: True or False
    """
    n = len(r)
    An = np.vstack((A, A[0,:], A[1,:]))
    flag = 0
    for k in range(n):
        if(k<n-1):
            for l in range(max(0,k-2)):
                a = Segment(tuple(An[l, :]), tuple(An[l + 1, :]))
                b = Segment(tuple(An[k, :]), tuple(An[k + 1, :]))
                pts = a.intersect(b)
                if (len(pts) == 1):
                    flag = 1
                    break
            if (flag == 1):
                break
        else:
            for l in range(1, max(0,k-2)):
                a = Segment(tuple(An[l, :]), tuple(An[l + 1, :]))
                b = Segment(tuple(An[k, :]), tuple(An[k + 1, :]))
                pts = a.intersect(b)
                if (len(pts) == 1):
                    flag = 1
                    break
            if (flag == 1):
                break

    if(flag==1):
        print('The polygon is not simple!')
        return False
    else:
        return True

def getConvexPolygon(r=np.array([0.43202382, 0.53261951, 0.24482898, 0.37902022, 0.31978369,
                                0.76482198, 0.53154303, 0.22408833, 0.03323694, 0.63061874]),
                     solver='ECOS'):
    """
    This function generates the coordinates of the polygon with the given side lengths.
    :param r: Side lengths as a vector.
    :param solver: One of the solvers in CVXPY. Default set to 'ECOS'.
    :return: The coordinates matrix of the generated polygon and its side lengths.
    """
    if(checkValidity(r)==False):
        print('Such a polygon does not exist!')
        return -1

    X = cvx.Variable((len(r), 2))

    constraints = []
    for k in range(len(r) - 1):
        constraints = constraints + [cvx.norm2(X[k, :] - X[k + 1, :]) <= r[k]]
    constraints = constraints + [cvx.norm2(X[-1, :] - X[0, :]) <= r[-1]]
    constraints = constraints + [np.ones(10) @ X == 0]

    Y = np.zeros(2)
    theta = 2 * np.pi / len(r)
    for k in range(1, len(r)):
        Y = np.vstack((Y, np.array([np.cos(k * theta), np.sin(k * theta)])))

    prob = cvx.Problem(cvx.Maximize(cvx.trace(X @ (Y.T))), constraints)
    prob.solve(solver=solver)

    if(prob.status=='optimal_inaccurate'):
        print('The solution is inaccurate. Please try with another solver!')

    plotPoly(X.value)
    return X.value, r

def plotPoly(A):
    """
    This function plots a polygon with the given coordinates.
    :param A: Coordinate matrices of the polygon.
    :return: Plot of the polygon.
    """
    coord = [[A[k,0],A[k,1]] for k in range(A.shape[0])]
    coord.append(coord[0])
    xs, ys = zip(*coord)

    plt.figure()
    plt.plot(xs, ys)
    plt.scatter(xs, ys)
    plt.show()

if(__name__=='__main__'):
    A, r = getConvexPolygon()
    print(checkDists(A, r))
    print(checkSimple(A, r))
    print(checkConvexity(A, r))