from math import cos
from numpy.lib.shape_base import tile
import torch
import torch.nn as nn
import numpy as np
import plotly.graph_objects as go

def plot_ackley(axis_npoints = 1000):
    coords = torch.linspace(-40, 40, axis_npoints)
    grid = torch.cartesian_prod(coords, coords)
    cost = Ackley()
    z = cost(grid)
    fig = go.Figure(data=[go.Surface(z=z.reshape(axis_npoints, axis_npoints), x=coords.numpy(), y=coords.numpy())])
    fig.update_layout(title="Ackley function a=20, b=0.2, c=2pi", autosize=False,
                    width=1000, height=1000,
                    margin=dict(l=65, r=50, b=65, t=90))
    fig.show()

def plot_rastrigin(axis_npoints = 1000):
    coords = torch.linspace(-5.12, 5.12, axis_npoints)
    grid = torch.cartesian_prod(coords, coords)
    cost = Rastrigin()
    z = cost(grid)
    fig = go.Figure(data=[go.Surface(z=z.reshape(axis_npoints, axis_npoints), x=coords.numpy(), y=coords.numpy())])
    fig.update_layout(title="Rastrigin function A=10", autosize=False,
                    width=1000, height=1000,
                    margin=dict(l=65, r=50, b=65, t=90))
    fig.show()

def plot_griewank(axis_npoints = 1000):
    coords = torch.linspace(-5, 5, axis_npoints)
    grid = torch.cartesian_prod(coords, coords)
    cost = Griewank()
    z = cost(grid)
    fig = go.Figure(data=[go.Surface(z=z.reshape(axis_npoints, axis_npoints), x=coords.numpy(), y=coords.numpy())])
    fig.update_layout(title="Griewank function", autosize=False,
                    width=1000, height=1000,
                    margin=dict(l=65, r=50, b=65, t=90))
    fig.show()

def plot_zakharov(axis_npoints = 1000):
    coords = torch.linspace(-5, 10, axis_npoints)
    grid = torch.cartesian_prod(coords, coords)
    cost = Zakharov()
    z = cost(grid)
    fig = go.Figure(data=[go.Surface(z=z.reshape(axis_npoints, axis_npoints), x=coords.numpy(), y=coords.numpy())])
    fig.update_layout(title="Zakharov function", autosize=False,
                    width=1000, height=1000,
                    margin=dict(l=65, r=50, b=65, t=90))
    fig.show()

class Ackley:
    def __init__(self, a=20, b=0.2, c=2*np.pi):
        self.a = a
        self.b = b
        self.c = c

    def __call__(self, x):
        _, d = x.shape
        f = -self.a * torch.exp(-self.b * torch.linalg.norm(x, ord=2, dim=1) / np.sqrt(d)) - torch.exp(torch.sum(torch.cos(self.c * x), dim=1) / d) + self.a + np.exp(1.0)
        return f

class Rastrigin:
    def __init__(self, A=10):
        self.A = A

    def __call__(self, x):
        _, d = x.shape
        return self.A * d + torch.sum(torch.square(x) - 10 * torch.cos(2 * np.pi * x), dim=1)

class Griewank:
    def __call__(self, x):
        n, d = x.shape
        denom = torch.repeat_interleave(torch.arange(1, d+1).view(1, -1), repeats=n, dim=0)
        denom = torch.sqrt(denom.float())
        return torch.sum(x / 4000, dim=1) - torch.prod(torch.cos(x / denom), dim=1) + 1

class Zakharov:
    def __call__(self, x):
        n, d = x.shape
        multiplier = torch.repeat_interleave(torch.arange(1, d+1).view(1, -1), repeats=n, dim=0)
        square = torch.square(torch.sum(0.5 * multiplier * x, dim=1))
        return torch.sum(torch.square(x), dim=1) + square + torch.square(square)


from svd import find_u_from_v, svd, singular_value_expansion
import math
from pytest import approx
import numpy as np
from numpy.testing import assert_allclose

class TestVectorU:
    def test_find_u_from_v(self):
        matrix = np.array([[3, -2], [-3, 2]])
        eigenvalue = 25.999999999999996
        singular_value = math.sqrt(eigenvalue)
        eigenvector = np.array([[0.8320502943378436], [-0.5547001962252291]])

        result = find_u_from_v(matrix, v=eigenvector, singular_value=singular_value)

        assert len(result) == 2
        assert result[0][0] == approx(0.707106, rel=1e-5)
        assert result[1][0] == approx(-0.707106, rel=1e-5)

class TestSVD:
    def test_svd(self):
        matrix = np.array([[2, -1], [-1, 2]])

        result = svd(matrix, max_eigenvalues=2, iterations=10)

        assert len(result) == 2

        assert_allclose(result[0][0], [[0.7071067812541463], [-0.7071067811189488]])
        assert result[0][1] == approx(3)
        assert_allclose(result[0][2], [[0.7071067813893437], [-0.7071067809837513]])

        assert_allclose(result[1][0], [[0.7071067805781592], [0.7071067817949359]])
        assert result[1][1] == approx(1)
        assert_allclose(result[1][2], [[0.7071067809837512], [0.7071067813893438]])

    def test_svd_with_zero_singular_values(self):
        matrix = [[3, -2], [-3, 2]]

        result = svd(matrix, max_eigenvalues=2, iterations=10)

        assert len(result) == 1

        assert_allclose(result[0][0], [[0.7071067811865475], [-0.7071067811865475]])
        assert result[0][1] == approx(5.099019513592785)
        assert_allclose(result[0][2], [[0.8320502943378437], [-0.5547001962252291]])

class TestSingularValueExpansion:
    def test_singular_value_expansion(self):
        svd_data = [
            (
                np.array([[0.7071067812541464], [-0.7071067811189488]]),
                2.9999999999999996,
                np.array([[0.7071067813893437], [-0.7071067809837512]])
             ),
            (
                np.array([[0.7071067805781589], [0.7071067817949361]]),
                0.9999999999999998,
                np.array([[0.7071067809837509], [0.707106781389344]])
            )
        ]

        result = singular_value_expansion(svd_data)

        assert_allclose(result, [
                [1.9999999999999996, -0.9999999999999998],
                [-0.9999999999999999, 1.9999999999999996]
            ])

    def test_singular_value_expansion_zero_singular_value(self):
        svd_data = [
            (
                np.array([[0.7071067811865476], [-0.7071067811865476]]),
                5.0990195135927845,
                np.array([[0.8320502943378436], [-0.5547001962252291]])
            )
        ]

        result = singular_value_expansion(svd_data)

        assert_allclose(result, [[3, -2], [-3, 2]])


import gym
import numpy as np
import math as math

# Init environment
env = gym.make("FrozenLake-v0")
# you can set it to deterministic with:
# env = gym.make("FrozenLake-v0", is_slippery=False)

# If you want to try larger maps you can do this using:
#random_map = gym.envs.toy_text.frozen_lake.generate_random_map(size=5, p=0.8)
#env = gym.make("FrozenLake-v0", desc=random_map)

# Init some useful variables:
n_states = env.observation_space.n
n_actions = env.action_space.n

def value_iteration():
    V_states = np.zeros(n_states)  # init values as zero
    theta = 1e-8
    gamma = 0.8
    # TODO: implement the value iteration algorithm and return the policy
    # Hint: env.P[state][action] gives you tuples (p, n_state, r, is_terminal), which tell you the probability p that you end up in the next state n_state and receive reward r
    nsteps = 0
    while True:
        delta = 0
        for s in range(n_states):
            v = V_states[s]
            V_states[s] = max([math.fsum([p * (r + gamma * V_states[n_state]) for p, n_state, r, is_terminal in env.P[s][a]]) for a in range(n_actions)])
            delta = max(delta, abs(v - V_states[s]))
        nsteps += 1
        if delta < theta:
            break
    print("Number of steps: {}".format(nsteps))
    return V_states

def make_policy(value):
    policy = np.zeros(n_states)  # init values as zero
    theta = 1e-8
    gamma = 0.8

    for s in range(n_states):
        policy[s] = np.argmax([math.fsum([p * (r + gamma * value[n_state]) for p, n_state, r, is_terminal in env.P[s][a]]) for a in range(n_actions)])

    return policy

def main():
    # print the environment
    print("current environment: ")
    env.render()
    print("")

    # run the value iteration
    value = value_iteration()
    print("Computed value:")
    print(value)
    policy = make_policy(value)
    print("Computed policy:")
    print(policy)

    # This code can be used to "rollout" a policy in the environment:
    """print ("rollout policy:")
    maxiter = 100
    state = env.reset()
    for i in range(maxiter):
        new_state, reward, done, info = env.step(policy[state])
        env.render()
        state=new_state
        if done:
            print ("Finished episode")
            break"""

if __name__ == "__main__":
    main()


"""Functions for working with lines in spectrum"""

import numpy as np
from scipy.optimize import curve_fit

def gauss_function(x, a, x0, sigma):
    """
    Gaussian
    _________
    Arguments:

    x (float or float array) - argument
    a (float) - amplitude
    x0 (float) - mathematical expectation
    sigma (float) - standard deviation
    __________________________________
    Returns:

    Gauss function by formula
    a * np.exp(-(x - x0)**2 / (2 * sigma**2))
    """

    return a * np.exp(-(x - x0) ** 2 / (2 * sigma ** 2))

def calc_cont(x, y, wln, deg):
    """
    Trying to find continuum in current spectrum
    ____________________________________________
    Arguments:

    points (float x float array) - selected points
    deg - polynomial degree
    _____________________
    Returns:

    Continuum curve
    """

    cont_coef = np.polyfit(x, y, deg)
    cont = np.poly1d(cont_coef)(wln)

    return cont

def calc_masc(wln_sel, flx_sel):
    """
    Function for calculation of mass center of selected area
    ________________________________________________________
    Arguments:

    wln_sel (float array) - wavelength in selected area
    flx_sel (float array) - flux in selected area
    _____________________________________________
    Returns:

    Mass center of selected area
    """

    mx = np.sum(wln_sel * flx_sel)
    m = np.sum(flx_sel)
    mc = mx / m

    return mc

def calc_int(wln_sel, flx_sel, cnt_sel):
    """
    Function for calculation area between spectrum and continuum
    in selected area
    ________________
    Arguments:

    wln_sel (float array) - wavelength in selected area
    flx_sel (float array) - flux in selected area
    cnt_sel (float array) - continuum curve in selected area
    ________________________________________________________
    Returns:

    Intensity (area between spectrum and continuum)
    """

    intensity = np.trapz(flx_sel - cnt_sel, wln_sel)

    return intensity

def fit_gauss(wln_sel, flx_sel, cnt_lvl):
    """
    Function to fitting gaussian in selected area
    _____________________________________________
    Arguments:

    wln_sel (float array) - wavelength in selected area
    flx_sel (float array) - flux in selected area
    cnt_lvl (float array) - median continuum level in
                            selected area
    _________
    Returns:

    Coefficients of gauss curve
    """

    # Initial approximation
    amp = np.max(flx_sel) - np.min(flx_sel)
    mean = calc_masc(wln_sel, flx_sel)
    sigma = np.std(flx_sel)

    popt, pcov = curve_fit(
        gauss_function,
        wln_sel,
        flx_sel - cnt_lvl,
        p0=[amp, mean, sigma]
    )

    return popt


import numpy as np
from scipy.stats import norm
import xlwings as xw

# https://en.wikipedia.org/wiki/Greeks_(finance)
@xw.func
def bsm(S, K, T = 1.0, r = 0.0, q = 0.0, sigma = 0.16, CP='call'):
    d1 = (np.log(S / K) + (r - q + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
    d2 = (np.log(S / K) + (r - q - 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))

    result = 0.0
    if CP.lower() == 'call':
        result = (S * np.exp(-q * T) * norm.cdf(d1, 0.0, 1.0) - K * np.exp(-r * T) * norm.cdf(d2, 0.0, 1.0))
    if CP.lower() == 'put':
        result = (K * np.exp(-r * T) * norm.cdf(-d2, 0.0, 1.0) - S * np.exp(-q * T) * norm.cdf(-d1, 0.0, 1.0))

    return result

@xw.func
def bsm_delta(S, K, T = 1.0, r = 0.0, q = 0.0, sigma = 0.16, CP='call'):
    d1 = (np.log(S / K) + (r - q + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))

    result = 0.0
    if CP.lower() == 'call':
        result = np.exp(-q * T) * norm.cdf(d1, 0.0, 1.0)
    if CP.lower() == 'put':
        result = - np.exp(-q * T) * norm.cdf(-d1, 0.0, 1.0)

    return result

@xw.func
def bsm_vega(S, K, T = 1.0, r = 0.0, q = 0.0, sigma = 0.16):
    d1 = (np.log(S / K) + (r - q + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
    d2 = (np.log(S / K) + (r - q - 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))

    result = S * np.exp(-q * T) * norm.pdf(d1, 0.0, 1.0) * np.sqrt(T)
    # result2 = K * np.exp(-r * T) *  norm.pdf(d2, 0.0, 1.0) * np.sqrt(T)
    # assert result == result2, 'vega failed'
    return result

@xw.func
def bsm_theta(S, K, T = 1.0, r = 0.0, q = 0.0, sigma = 0.16, CP='call'):
    d1 = (np.log(S / K) + (r - q + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
    d2 = (np.log(S / K) + (r - q - 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))

    result = 0.0
    if CP.lower() == 'call':
        result = - np.exp(-q * T) * S * norm.pdf(d1, 0.0, 1.0) * sigma / 2 / np.sqrt(T) \
                 - r * K * np.exp(-r * T) * norm.cdf(d2, 0.0, 1.0) \
                 + q * S * np.exp(-q * T) * norm.cdf(d1, 0.0, 1.0)
    if CP.lower() == 'put':
        result = - np.exp(-q * T) * S * norm.pdf(-d1, 0.0, 1.0) * sigma / 2 / np.sqrt(T) \
                 + r * K * np.exp(-r * T) * norm.cdf(-d2, 0.0, 1.0) \
                 - q * S * np.exp(-q * T) * norm.cdf(-d1, 0.0, 1.0)

    return result

@xw.func
def bsm_rho(S, K, T = 1.0, r = 0.0, q = 0.0, sigma = 0.16, CP='call'):
    d1 = (np.log(S / K) + (r - q + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
    d2 = (np.log(S / K) + (r - q - 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))

    result = 0.0
    if CP.lower() == 'call':
        result = K * T * np.exp(-r*T) * norm.cdf(d2, 0.0, 1.0)
    if CP.lower() == 'put':
        result = -K * T * np.exp(-r*T) * norm.cdf(-d2, 0.0, 1.0)

    return result

@xw.func
def bsm_gamma(S, K, T = 1.0, r = 0.0, q = 0.0, sigma = 0.16):
    d1 = (np.log(S / K) + (r - q + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))

    result = np.exp(-q*T) * norm.pdf(d1) / S / sigma / np.sqrt(T)
    # result = K * np.exp(-r*T) * np.pdf(d2) / S / S / sigma / np.sqrt(T)
    return result

@xw.func
def bsm_vanna(S, K, T = 1.0, r = 0.0, q = 0.0, sigma = 0.16):
    """d^2V/dS/dsigma"""
    d1 = (np.log(S / K) + (r - q + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
    d2 = (np.log(S / K) + (r - q - 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))

    result = -np.exp(-q*T) * norm.pdf(d1) * d2 / sigma
    return result

@xw.func
def bsm_volga(S, K, T = 1.0, r = 0.0, q = 0.0, sigma = 0.16):
    """d^2V/dsigma^2"""
    d1 = (np.log(S / K) + (r - q + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
    d2 = (np.log(S / K) + (r - q - 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))

    result = S*np.exp(-q*T) * norm.pdf(d1) * np.sqrt(T) * d1 * d2 / sigma
    return result

@xw.func
def black76(F, K, T, r, sigma, CP='call'):
    d1 = (np.log(F / K) + (0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
    d2 = (np.log(F / K) - (0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))

    result = 0.0
    if CP.lower() == 'call':
        result = np.exp(-r*T)*( F*norm.cdf(d1) - K*norm.cdf(d2) )
    else:
        result = np.exp(-r*T)*( K*norm.cdf(-d2) - F*norm.cdf(-d1) )

    return result

@xw.func
def black76_delta(F, K, T, r, sigma, CP='call'):
    d1 = (np.log(F / K) + (0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
    d2 = (np.log(F / K) - (0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))

    result = 0.0
    if CP.lower() == 'call':
        result = np.exp(-r*T)*norm.cdf(d1)
    else:
        result = -np.exp(-r*T)*norm.cdf(-d1)

    return result

@xw.func
def black76_vega(F, K, T, r, sigma):
    d1 = (np.log(F / K) + (0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
    d2 = (np.log(F / K) - (0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))

    result = F * np.exp(-r * T) * norm.pdf(d1) * np.sqrt(T)
    # result = K * np.exp(-r*T) * norm.pdf(d2)*np.sqrt(T)

    return result

@xw.func
def black76_theta(F, K, T, r, sigma, CP='call'):
    d1 = (np.log(F / K) + (0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
    d2 = (np.log(F / K) - (0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))

    result = 0.0
    if CP.lower() == 'call':
        result = -F*np.exp(-r*T)*norm.pdf(d1)*sigma/2/np.sqrt(T) \
                 - r*K*np.exp(-r*T)*norm.cdf(d2) \
                 + r*F*np.exp(-r*T)*norm.cdf(d1)
    else:
        result = -F * np.exp(-r * T) * norm.pdf(-d1) * sigma / 2 / np.sqrt(T) \
                 + r * K * np.exp(-r * T) * norm.cdf(-d2) \
                 - r * F * np.exp(-r * T) * norm.cdf(-d1)

    return result

@xw.func
def black76_rho(F, K, T, r, sigma, CP='call'):
    d1 = (np.log(F / K) + (0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
    d2 = (np.log(F / K) - (0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))

    result = 0.0
    if CP.lower() == 'call':
        result = K*T*np.exp(-r*T)*norm.cdf(d2)
    else:
        result = -K*T*np.exp(-r*T)*norm.cdf(-d2)
    return result

@xw.func
def black76_gamma(F, K, T, r, sigma):
    d1 = (np.log(F / K) + (0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
    d2 = (np.log(F / K) - (0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))

    result = np.exp(-r*T)*norm.pdf(d1)/F/sigma/np.sqrt(T)
    # result = k*np.exp(-r*T)*norm.pdf(d2)/F/F/sigma/np.sqrt(T)

    return result

@xw.func
def black76_vanna(F, K, T, r, sigma):
    d1 = (np.log(F / K) + (0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
    d2 = (np.log(F / K) - (0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))

    result = -np.exp(-r*T)*norm.pdf(d1)*d2/sigma

    return result

@xw.func
def black76_volga(F, K, T, r, sigma):
    d1 = (np.log(F / K) + (0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
    d2 = (np.log(F / K) - (0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))

    result = F*np.exp(-r*T)*norm.pdf(d1)*np.sqrt(T)*d1*d2/sigma

    return result


from math import sin, cos, sqrt, atan2, radians
import numpy as np

# approx. radius of earth
R = 6373.0

#computes distance in km between to given points with lat and long
def computeDistance(p1,p2):

#adds a distance column to a table

    lat1 = radians(p1[0])
    lon1 = radians(p1[1])
    lat2 = radians(p2[0])
    lon2 = radians(p2[1])

    dlon = lon2 - lon1
    dlat = lat2 - lat1

    a = sin(dlat / 2)**2 + cos(lat1) * cos(lat2) * sin(dlon / 2)**2
    c = 2 * atan2(sqrt(a), sqrt(1 - a))

    distance = R * c

    return distance

def getDistances(table):
    result = []

    curOwl = "" # current Owl. Changes when reaching a new tag ID
    p1 = []
    p2 = []

    for f in table:

        if curOwl == "" :
            curOwl = f[0]
            p1 = [f[2], f[3]]
            distanceFeature = np.array(np.append(f,  0 ))
            result.append(distanceFeature)

        else:
            if curOwl == f[0]:

                p2 = [f[2], f[3]]

                distance = computeDistance(p1, p2)

                distanceFeature = np.array(np.append(f, distance))
                p1 = p2
                result.append(distanceFeature)

            else:
                curOwl = ""
                distanceFeature = np.array(np.append(f, 0))
                result.append(distanceFeature)

    return result


import numpy as np
import pandas as pd
import math

def get_kuramoto_params(x):
    """
    Calculates the two Kuramoto parameters: r for measuring synchrony and ψ for the average phase.

    Arguments:
    x = 1D numpy array or pandas series, shape (n) where n is number of timepoints, expects unit of time to be hours

    Returns:
    mag = the magnitude of synchrony (r) between the set of oscillators, between [0-1]
    ang = the average phase (ψ) of the set of oscillators

    If you use this code, consider citing https://www.biorxiv.org/content/10.1101/2022.01.11.475783v1
    """
    if isinstance(x, (pd.Series, pd.DataFrame)):
        x = x.values
    if x.ndim > 1:
        print("Input data must be 1 dimensional")
        return np.nan, np.nan
    # Convert from hours to radians
    x = x * np.pi / 12
    scale_factor = 1/x.shape[0]
    cosine_term, sine_term = 0, 0
    for i in range(x.shape[0]):
        cosine_term += np.cos(x[i])
        sine_term += np.sin(x[i])
    # Get magnitude of combined vector through square root of sum of squares
    mag = scale_factor * np.sqrt(cosine_term**2 + sine_term**2)
    # Calculate angle of combined vector, set to be from positive real axis
    ang = math.atan2(cosine_term, sine_term)
    ang += 2*np.pi
    ang = ang % (2*np.pi)
    return mag, ang


import math
from scipy.spatial import distance

def gauss_bell(x_vector, t_vector, std):
    return math.exp(-((distance.euclidean(x_vector, t_vector) / std) ** 2))

def calculate_cell_std(t_image, images):
    return min(list(map(lambda x: distance.euclidean(x, t_image), images)))

Radial Basis Function(RBF) Network/rbf_cell.py


import numpy as np
from sklearn.decomposition import TruncatedSVD

def cosine(x, y):
    """Cosine similarity between x and y."""
    return np.dot(x, y) / (np.linalg.norm(x) * np.linalg.norm(y))

def compute_pc(X, npc=1):
    """
    Compute the principal components. DO NOT MAKE THE DATA ZERO MEAN!
    :param X: X[i,:] is a data point
    :param npc: number of principal components to remove
    :return: component_[i,:] is the i-th pc
    Function taken from https://github.com/PrincetonML/SIF
    Copyright (c) 2017 PrincetonML
    This function is licensed under the MIT license found at
    https://github.com/PrincetonML/SIF/blob/master/LICENSE.
    """
    svd = TruncatedSVD(n_components=npc, n_iter=7, random_state=0)
    svd.fit(X)
    return svd.components_

def normalise_rows(X):
    """Normalise the rows of the matrix X, using L2 norm."""
    return X / np.linalg.norm(X, axis=1)[:, np.newaxis]


from scipy.special import sph_harm, assoc_laguerre
import math
FACTORIAL_LIMIT = 170  # the limit over which factorial cannot be converted to float

def Y(l, m, theta, phi):
    '''
    Spherical harmonics in the convention of physics.

    :param l: orbital quantum number.
    :param m: magnetic quantum number.
    :param theta: azimuthal angle (angle with z axis).
    :param phi: polar angle (angle with x axis in the xy plane).

    :return: the spherical harmonics Y_m^l(theta, phi).
    '''

    if l < 0:
        raise ValueError(f'l must be larger than 0. Found: l = {l}')
    if m < -l or m > l:
        raise ValueError(f'm must be between -l and l. Found: m = {m}, l = {l}')

    return sph_harm(m, l, phi, theta)

def R(n: int, l: int, r: float, a: float = 1) -> float:
    '''
    Radial wave function of the hydrogen atom :math:R_{n \ell}(r)

    :param n: principal quantum number.
    :param l: orbital quantum number.
    :param r: dimensionless radius.
    :param a: bohr radius.

    :return: normalized radial wave function at r.
    '''
    n = int(n)

    if n < 1:
        raise ValueError(f'n must be greater than 0. Found: n = {n}')
    if l < 0 or l >= n:
        raise ValueError(
            f'l must be greater than or equal to 0 and less than n. Found: n = {n}, l = {l}.'
        )

    norm = math.sqrt((2 / (n * a)) ** 3)

    if n - l - 1 <= FACTORIAL_LIMIT:
        norm *= math.sqrt(math.factorial((n - l - 1)) / (2 * n) / math.factorial(n + l))
    else:
        norm /= math.sqrt((2 * n) * prod(n-l, n+l))

    norm *= math.exp(- r / (n * a))
    norm *= (2*r / (n * a)) ** l

    rad_func = assoc_laguerre(x=2*r/(n*a), n=n-l-1, k=2*l+1)

    return norm * rad_func

def psi(
    n: int, l: int, m: int,
    r: float, theta: float, phi: float,
    a: float = 1
) -> float:
    '''
    Normalized wave function of the hydrogen atom.

    :param n: principal quantum number.
    :param l: orbital quantum number.
    :param m: magnetic quantum number.
    :param r: radius.
    :param theta: azimuthal angle [0, pi].
    :param phi: polar angle [0, 2pi].
    :param a: Bohr radius.

    :return: normalized wave function psi_{n, l, m} at r, theta, phi.
    '''

    n = int(n)

    if n < 1:
        raise ValueError(f'n must be greater than 0. Found: n = {n}')
    if l < 0 or l >= n:
        raise ValueError(f'l must be between 0 and n - 1. Found: l = {l}, n = {n}')
    if m < -l or m > l:
        raise ValueError(f'm must be between -l and l. Found: m = {m}, l = {l}')

    return R(n, l, r, a) * Y(l, m, theta, phi)

def prod(start, end):
    p = 1
    for i in range(start, end+1):
        p *= i
    return p


import numpy as np
import matplotlib.pyplot as plt
from loadData import *
from scipy.special import expit

# logistic regression
def one_hot(data):
    shape = (data.shape[0], N_class)
    one_hot = np.zeros(shape)
    rows = np.arange(data.shape[0])
    one_hot[rows, data[:,0]] = 1
    return one_hot

def cross_entropy(p, y):
    m = y.shape[0]
    log_likelihood = -y * np.log(p+epsilon)
    loss = np.sum(log_likelihood) / m
    return loss

def predict(X, W, t = None):
    # X_new: Nsample x (d+1)
    # W: (d+1) x K

    Z = np.dot(X, W)
    y = expit(Z)
    t_hat = one_hot(t)
    loss = cross_entropy(y, t_hat)

    predicted = np.argmax(y, 1).reshape(X.shape[0],1)
    correct = (predicted == t).sum()
    acc = correct / X.shape[0]

    return y, t_hat, loss, acc

def train(X_train, y_train, X_val, y_val):
    N_train = X_train.shape[0]
    N_val   = X_val.shape[0]

    # init weight
    w = np.zeros([X_train.shape[1], 1])

    # init return val
    epoch_best = 0
    acc_best = 0
    W_best = None
    track_loss_train = []
    track_acc_val = []

    for epoch in range(MaxIter):
        loss_this_epoch = 0
        for b in range(int(np.ceil(N_train/batch_size)) ):
            X_batch = X_train[b*batch_size : (b+1)*batch_size]
            y_batch = y_train[b*batch_size : (b+1)*batch_size]

            y, t_hat, loss, _ = predict(X_batch, w, y_batch)

            loss_this_epoch += loss

            # calculate gradient
            gradient = np.dot(X_batch.transpose(), (y-t_hat))/batch_size + decay * w
            decay_alpha = np.power(0.99, epoch) * alpha # learning rate decay
            w = w - decay_alpha * gradient
        avg_loss_this_epoch = loss_this_epoch / (N_train/batch_size)
        _, _, loss, acc = predict(X_val, w, y_val)
        print('epoch:', epoch)
        print('avg loss this epoch:', avg_loss_this_epoch)
        print('valid loss:',loss, '\nvalid accuracy:',acc)
        track_loss_train.append(avg_loss_this_epoch)
        track_acc_val.append(acc)
        if acc > acc_best:
            acc_best = acc
            epoch_best = epoch
            W_best = w

    return epoch_best, acc_best, W_best, track_loss_train, track_acc_val

X_train, y_train, X_val, y_val, X_test, y_test = loadSpamData()
print(X_train.shape, y_train.shape, X_val.shape, y_val.shape, X_test.shape, y_test.shape)

N_class = 2

alpha   = 3e-4          # learning rate
batch_size  = 10        # batch size
MaxIter = 1000          # Maximum iteration
decay = 0.01            # weight decay
epsilon = 1e-12         # avoid error message

epoch_best, acc_best, W_best, track_loss_train, track_acc_val = train(X_train, y_train, X_val, y_val)

_, _, _, acc_test = predict(X_test, W_best, y_test)

print('\nAt epoch {}.\nvalidation accuracy: {:.2f}%.\ntest accuracy: {:.2f}%'.format(epoch_best, acc_best*100, acc_test*100))
plt.figure(1)
plt.plot(track_loss_train)
plt.xlabel("epochs")
plt.ylabel("training loss")
plt.figure(2)
plt.plot(track_acc_val)
plt.xlabel("epochs")
plt.ylabel("validation acc")
plt.show()


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Sep 27 19:05:27 2017

@author: sitibanc
"""
import math
from scipy.stats import norm
import numpy as np

# Black-Sholes定價模型

def blscall(s, l, t, r, sigma):
    d1 = math.log(s/l) + ( r + 0.5 * sigma ** 2) * t / sigma * math.sqrt(t)
    #d2 = math.log(s/l) + ( r - 0.5 * sigma ** 2) * t / sigma * math.sqrt(t)
    d2 = d1 - sigma * math.sqrt(t)
    return s * norm.cdf(d1) - l * math.exp(-r * t) * norm.cdf(d2)

s = 50.0
l = 40.0
t = 2.0
r = 0.08
sigma = 0.2

print('Black-Scholes Call price: ', blscall(s, l, t, r, sigma))
print('Parity: ', blscall(s, l, t, r, sigma) + l * math.exp(-r * t) - s)

# 差分代替偏微分

d1 = math.log(s/l) + ( r + 0.5 * sigma ** 2) * t / sigma * math.sqrt(t)
print('偏微分：', norm.cdf(d1))
print('差分：', (blscall(s + 0.01, l, t, r, sigma) - blscall(s - 0.01, l, t, r, sigma)) / 0.02)

# Monte-Carlo Method

n = 100
dt = t / n

p = np.zeros([10000, n+1])
for i in range(10000):
    p[i,0] = s
    for j in range(n):
        p[i,j+1] = p[i,j] * math.exp((r - 0.5 * sigma ** 2) * dt + np.random.normal(0, 1, 1) * sigma * math.sqrt(dt))

# 以Mote-Carlo算出的結果回推目前call option的合理價格
c = 0
for i in range(10000):
    if p[i, 100] > l:
        c += (p[i, 100] - l) / 10000
print('Monte-Carlo: ', c * math.exp(-r * t))


import numpy as np
from ._base import Activation

class SigmoidActivation(Activation):
    """Sigmoid activation layer."""

    def forward(self, in_arr: np.ndarray) -> np.ndarray:
        out = 1.0 / (1.0 + np.exp(-in_arr))
        return out

    def backward(self, out: np.ndarray) -> np.ndarray:
        grad = out * (1 - out)
        return grad

class ReLUActivation(Activation):
    """ReLU activation layer."""

    def forward(self, in_arr: np.ndarray) -> np.ndarray:
        out = np.max(in_arr, 0)
        return out

    def backward(self, out: np.ndarray) -> np.ndarray:
        grad = np.zeros_like(out)
        grad[out > 0] = 1
        return grad

class TanhActivation(Activation):
    """Tanh activation layer."""

    def forward(self, in_arr: np.ndarray) -> np.ndarray:
        out = np.tanh(in_arr)
        return out

    def backward(self, out: np.ndarray) -> np.ndarray:
        grad = 1.0 - np.power(out, 2)
        return grad


import numpy as np
import matplotlib.pyplot as plt
from sklearn import linear_model
from sklearn.svm import SVR

plt.close('all')
# create dataset for a semi-circle
N=1000
x=np.linspace(-1,1,N)
ymodel=np.sqrt(1-x**2)
ymodel=np.sin(2*np.pi*x)
y=ymodel+np.random.normal(0,0.001,N)
noise=np.zeros(N)
noise[10:N-10:20]=1;
y[noise==1]=5

### Linear regrassion fiting
model=50
X=np.zeros((N,model))
for i in range(1,model):
    X[:,i-1]=pow(x,i)

modelLinear = linear_model.LinearRegression()
modelLinear.fit(X,y)
coefsLinear=modelLinear.coef_

### L2 regularization regression
n_alphas = 200
alphas = np.logspace(5, -5, n_alphas)

modelL2 = linear_model.Ridge(fit_intercept=False)

coefsL2 = np.zeros((n_alphas,model))
errorL2 = np.zeros(n_alphas)
modelDeviationL2=np.zeros(n_alphas)
for a,k in zip(alphas,range(0,n_alphas)):
    modelL2.set_params(alpha=a)
    modelL2.fit(X, y)
    coefsL2[k,:]=modelL2.coef_
    yest=modelL2.predict(X)
    errorL2[k]=np.mean((y-yest)**2)
    modelDeviationL2[k]=np.mean(((ymodel-yest)**2))

## L1 regularization regression
modelL1 = linear_model.Lasso(fit_intercept=False)

coefsL1 = np.zeros((n_alphas,model))
errorL1 = np.zeros(n_alphas)
modelDeviationL1=np.zeros(n_alphas)

for a,k in zip(alphas,range(0,n_alphas)):
    modelL1.set_params(alpha=a)
    modelL1.fit(X, y)
    coefsL1[k,:]=modelL1.coef_
    yest=modelL1.predict(X)
    errorL1[k]=np.mean((y-yest)**2)
    modelDeviationL1[k]=np.mean(((ymodel-yest)**2))

## Plot L1 and L2 weights for different alphas
fig,subplot=plt.subplots(2,1,sharex=True)

ax =subplot[0]

ax.plot(alphas, coefsL2)
ax.set_xscale('log')
ax.set_title('L2 weights as a function of the regularization')
ax.set_ylabel('weights')

ax=subplot[1]

ax.plot(alphas, coefsL1,'--')
ax.set_xscale('log')
ax.set_xlabel('alpha')
ax.set_ylabel('weights')

ax.set_title('L1 weights as a function of the regularization')
plt.axis('tight')
plt.show()

## Plot L1 and L2 estimation error for different alphas
plt.figure()
plt.plot(alphas,errorL1,color='r',label='L1 deviation from data')
plt.plot(alphas,errorL2,color='b', label='L2 deviation from data')
plt.plot(alphas,modelDeviationL1,color='r',ls='--',label='L1 deviation from model')
plt.plot(alphas,modelDeviationL2,color='g',ls='--', label='L2 deviation from model')
plt.xscale('log')
plt.xlabel('Alphas')
plt.ylabel('Error')
plt.title('L1 and L2 estimation error for different alphas')
plt.legend()
plt.show()

## Plot Number of zeros weights of L1 and L2 regularization
plt.figure()
L1zeros=np.sum(abs(coefsL1)<1e-15,axis=1)
L2zeros=np.sum(abs(coefsL2)<1e-15,axis=1)
plt.plot(alphas,L1zeros,'r',alphas,L2zeros,'b')
plt.xscale('log')
plt.xlabel('Alphas')
plt.ylabel('Number of zeros')
plt.title('Plot number of zeros weights of L1 and L2 regularization')
plt.show()

## Estimate using all three models
yestL0=modelLinear.predict(X)
yestL1=modelL1.predict(X)
yestL2=modelL2.predict(X)
plt.figure()
plt.scatter(x,y,alpha=0.2,color='darkorange',label='data')
plt.plot(x,ymodel,color='k',label='Model')
plt.plot(x,yestL0, color='r',ls='--',label='Linear regression')
plt.plot(x,yestL1,color='g',ls='--',label='L1 regularization')
plt.plot(x,yestL2,color='b',ls='--',label='L2 regularization')
plt.xlabel('data')
plt.ylabel('target')
plt.title('Estimation using linear Regression')
plt.legend()
plt.show()

print("\nError from data L0: ",(np.sqrt(np.mean((y-yestL0)**2))))
print("Error from data L1: ",(np.sqrt(np.mean((y-yestL1)**2))))
print("Error from data L2: ",(np.sqrt(np.mean((y-yestL2)**2))))

print("\nError from model L0: ",(np.sqrt(np.mean((ymodel-yestL0)**2))))
print("Error from model L1: ",(np.sqrt(np.mean((ymodel-yestL1)**2))))
print("Error from model L2: ",(np.sqrt(np.mean((ymodel-yestL2)**2))))

## Estimation using SVM regression
plt.figure()
svr_rbf = SVR(kernel='rbf', C=1e3, gamma=0.1)
svr_lin = SVR(kernel='linear', C=1e3)
svr_poly = SVR(kernel='poly', C=1e3, degree=2)
y_rbf = svr_rbf.fit(X, y).predict(X)
y_lin = svr_lin.fit(X, y).predict(X)
y_poly = svr_poly.fit(X, y).predict(X)

print("\nError from data RBF: ",(np.sqrt(np.mean((y-y_rbf)**2))))
print("Error from data Linear: ",(np.sqrt(np.mean((y-y_lin)**2))))
print("Error from data Poly: ",(np.sqrt(np.mean((y-y_poly)**2))))

print("\nError from model RBF: ",(np.sqrt(np.mean((ymodel-y_rbf)**2))))
print("Error from model Linear: ",(np.sqrt(np.mean((ymodel-y_lin)**2))))
print("Error from model Poly: ",(np.sqrt(np.mean((ymodel-y_poly)**2))))

lw = 2
plt.scatter(x, y, color='darkorange', label='data')
plt.hold('on')
plt.plot(x, y_rbf, color='navy', lw=lw, label='RBF model')
plt.plot(x, y_lin, color='c', lw=lw, label='Linear model')
plt.plot(x, y_poly, color='cornflowerblue', lw=lw, label='Polynomial model')
plt.xlabel('data')
plt.ylabel('target')
plt.title('Estimation using Support Vector Regression')
plt.legend()
plt.show()


# Simulate Matern hard-core point processes (Type I/II) on a rectangle.
# Author: H. Paul Keeler, 2019.
# Website: hpaulkeeler.com
# Repository: github.com/hpaulkeeler/posts
# For more details, see the post:
# hpaulkeeler.com/simulating-matern-hard-core-point-processes/

import numpy as np;  # NumPy package for arrays, random number generation, etc
import matplotlib.pyplot as plt  # For plotting

plt.close('all');  # close all figures

numbSim=10**3; #number of simulations

# Simulation window parameters
xMin = -.5;
xMax = .5;
yMin = -.5;
yMax = .5;

#Parameters for the parent and daughter point processes
lambdaPoisson=50;#density of underlying Poisson point process
radiusCore=0.1;#radius of hard core

#Extended simulation windows parameters
rExt=radiusCore; #extension parameter -- use core radius
xMinExt=xMin-rExt;
xMaxExt=xMax+rExt;
yMinExt=yMin-rExt;
yMaxExt=yMax+rExt;
#rectangle dimensions
xDeltaExt=xMaxExt-xMinExt;
yDeltaExt=yMaxExt-yMinExt;
areaTotalExt=xDeltaExt*yDeltaExt; #area of extended rectangle

###START Simulations START####
#initialize arrays for collecting statistics
numbPointsAll=np.zeros(numbSim); #number of Poisson points
numbPointsAll_I=np.zeros(numbSim); #number of Matern I points
numbPointsAll_II=np.zeros(numbSim); #number of Matern II points
#loop through for each simulation
for ss in range(numbSim):

    #Simulate Poisson point process for the parents
    numbPointsExt= np.random.poisson(areaTotalExt*lambdaPoisson);#Poisson number
    #x and y coordinates of Poisson points for the parent
    xxPoissonExt=xMinExt+xDeltaExt*np.random.rand(numbPointsExt);
    yyPoissonExt=yMinExt+yDeltaExt*np.random.rand(numbPointsExt);

    #thin points if outside the simulation window
    booleWindow=((xxPoissonExt>=xMin)&(xxPoissonExt<=xMax)&(yyPoissonExt>=yMin)&(yyPoissonExt<=yMax));
    indexWindow=np.arange(numbPointsExt)[booleWindow];
    #retain points inside simulation window
    xxPoisson=xxPoissonExt[booleWindow];
    yyPoisson=yyPoissonExt[booleWindow];

    numbPoints=len(xxPoisson); #number of Poisson points in window
    #create random marks for ages
    markAge=np.random.rand(numbPointsExt);

    ###START Removing/thinning points START###
    booleRemoveI=np.zeros(numbPoints, dtype=bool);#Index for removing points -- Matern I
    booleKeepII=np.zeros(numbPoints,dtype=bool);#Index for keeping points -- Matern II
    for ii in range(numbPoints):
        distTemp=np.hypot(xxPoisson[ii]-xxPoissonExt,yyPoisson[ii]-yyPoissonExt);  #distances to other points
        booleInDisk=(distTemp<radiusCore)&(distTemp>0); #check if inside disk

        #Matern I
        booleRemoveI[ii]=any(booleInDisk);

        #Matern II
        #keep the younger points
        if len(markAge[booleInDisk])==0:
            booleKeepII[ii]=True;
            #Note: if markAge(booleInDisk) is empty, keepBooleII[ii]=True.
        else:
            booleKeepII[ii]=all(markAge[indexWindow[ii]]<markAge[booleInDisk]);

    ###END Removing/thinning points END###

    #Remove/keep points to generate Matern hard-core processes
    #Matérn I
    booleKeepI=~(booleRemoveI);
    xxMaternI=xxPoisson[booleKeepI];
    yyMaternI=yyPoisson[booleKeepI];

    #Matérn II
    xxMaternII=xxPoisson[booleKeepII];
    yyMaternII=yyPoisson[booleKeepII];

    #Update statistics
    numbPointsAll[ss]=numbPoints;
    numbPointsAll_I[ss]=len(xxMaternI);
    numbPointsAll_II[ss]=len(xxMaternII);

###END Simulations END####

##Plotting
markerSize=12; #marker size for the Poisson points
plt.plot(xxPoisson,yyPoisson, 'ko',markerfacecolor="None",markersize=markerSize);
plt.plot(xxMaternI,yyMaternI, 'rx',markersize=markerSize/2);
plt.plot(xxMaternI,yyMaternI, 'b+',markersize=markerSize);
plt.legend(('Underlying Poisson','Matern I','Matern II'));

#Compare statistics
diskArea=np.pi*radiusCore**2; #area of disk
areaWindow=(xMax-xMin)*(yMax-yMin); #area of simulation window

#underlying Poisson point process
lambdaExact=lambdaPoisson
print("lambdaExact = ",lambdaExact)
lambdaEmp=np.mean(numbPointsAll/areaWindow)
print("lambdaEmp = ",lambdaEmp)

#Matern I
lambdaExactI=lambdaPoisson*np.exp(-lambdaPoisson*diskArea) #exact
print("lambdaExactI = ",lambdaExactI)
lambdaEmpI=np.mean(numbPointsAll_I/areaWindow) #empirical
print("lambdaEmpI = ",lambdaEmpI)

#Matern II
lambdaExactII=1/(diskArea)*(1-np.exp(-lambdaPoisson*diskArea))
print("lambdaExactII = ",lambdaExactII)
lambdaEmpII=np.mean(numbPointsAll_II/areaWindow)
print("lambdaEmpII = ",lambdaEmpII)


import numpy as np

# Bellman-Ford algorithm
def bellman_ford1(graph, start):
    # distances and parents init
    distance = {}
    parent = {}
    for node in graph:
        distance[node] = 'inf'
        parent[node] = 'nil'

    distance[start] = 0

    for i in range(len(graph) - 1):
        changes = False
        for v in graph:
            if not distance[v] == 'inf':
                for n in graph[v]:
                    if distance[n] == 'inf' or distance[n] > distance[v] + graph[v][n]:
                        changes = True
                        distance[n] = distance[v] + graph[v][n]
                        parent[n] = v

        if not changes:
            break

    return distance, parent

def bellman_ford2(graph, start):
    # distances and parents init
    distance = {}
    parent = {}
    for node in graph:
        distance[node] = 'inf'
        parent[node] = 'nil'

    distance[start] = 0

    c = set()
    c.add(start)

    while c:
        changed = set()
        for v in graph:
            if v in c or v in changed:
                if v in changed:
                    changed.remove(v)
                for n in graph[v]:
                    if distance[n] == 'inf' or distance[n] > distance[v] + graph[v][n]:
                        distance[n] = distance[v] + graph[v][n]
                        parent[n] = v
                        changed.add(n)

        c = set(changed)

    return distance, parent

def bellman_ford3(graph, start):
    # distances and parents init
    distance = {}
    parent = {}
    for node in graph:
        distance[node] = 'inf'
        parent[node] = 'nil'

    distance[start] = 0

    order = list(graph)
    order = list(np.random.permutation(order))

    index = order.index(start)
    temp = order[index]
    order[index] = order[0]
    order[0] = temp

    r_order = list(order)
    r_order.reverse()

    graph_plus = dict()
    graph_minus = dict()

    for v in graph:
        neighbours_p = dict()
        for e in graph[v]:
            if order.index(e) > order.index(v):
                neighbours_p[e] = graph[v][e]
        graph_plus[v] = neighbours_p

    for v in graph:
        neighbours_m = dict()
        for e in graph[v]:
            if order.index(e) < order.index(v):
                neighbours_m[e] = graph[v][e]
        graph_minus[v] = neighbours_m

    c = set()
    c.add(start)

    while c:
        changed = set()
        for v in order:
            if v in c or v in changed:
                for n in graph_plus[v]:
                    if distance[n] == 'inf' or distance[n] > distance[v] + graph_plus[v][n]:
                        distance[n] = distance[v] + graph_plus[v][n]
                        parent[n] = v
                        changed.add(n)

        for v in r_order:
            if v in c or v in changed:
                for n in graph_minus[v]:
                    if distance[n] == 'inf' or distance[n] > distance[v] + graph_minus[v][n]:
                        distance[n] = distance[v] + graph_minus[v][n]
                        parent[n] = v
                        changed.add(n)

        c = set(changed)

    return distance, parent


#!/usr/bin/env python
# coding: utf-8

# # Simple nonlinear complementarity problem
#
# **Randall Romero Aguilar, PhD**
#
# This demo is based on the original Matlab demo accompanying the  Computational Economics and Finance 2001 textbook by Mario Miranda and Paul Fackler.
#
# Original (Matlab) CompEcon file: **demslv13.m**
#
# Running this file requires the Python version of CompEcon. This can be installed with pip by running
#
#     !pip install compecon --upgrade
#
# Last updated: 2021-Oct-01
#

# ## About
#
# The problem is to solve
#
# \begin{equation}
# f(x, y) = \begin{bmatrix}1+xy -2x^3-x\\ 2x^2-y\end{bmatrix}
# \end{equation}
#
# subject to $0 \leq x, y \leq 1$

# In[1]:

from compecon import MCP, jacobian
import numpy as np

x0 = [0.5, 0.5]

# ### Solving the problem without the Jacobian
# To solve this problem we create a **MCP** object using a lambda function.

# In[2]:

def func(z):
    x, y = z
    return np.array([1 + x*y - 2*x**3 - x, 2*x**2 - y])

F = MCP(func, [0, 0], [1,1])

# Solve for initial guess $x_0 = [0.5, 0.5]$

# In[3]:

x = F.zero(x0, transform='minmax', show=True)
#x = F.zero(x0, transform='ssmooth', show=True) # FIXME: generates error
print(f'Solution is {x=}.')

# ### Solving the problem with the Jacobian

# In[4]:

def func2(z):
    x, y = z
    f = [1 + x*y - 2*x**3 - x, 2*x**2 - y]
    J = [[y-6*x**2-1, x],[4*x, -1]]
    return np.array(f), np.array(J)

F2 = MCP(func2, [0, 0], [1,1])

# In[5]:

x = F2.zero(x0, transform='minmax', show=True)
print(f'Solution is {x=}.')

_build/jupyter_execute/notebooks/slv/13 Simple nonlinear complementarity problem.py


#!/usr/bin/env python

from numpy import *

# sample
print ('#' * 15 + ' sample ' + '#' * 15)

a = arange(15).reshape(3, 5)

print a

print a.shape

print a.ndim

print a.dtype.name

print a.itemsize

print a.size

print type(a)

b = array([6, 7, 8])

print b

print type(b)

# create ndarray
print ('#' * 15 + ' create ndarray ' + '#' * 15)

a = array([2,3,4])
print a
print a.dtype
b = array([1.2, 3.5, 5.1])
print b.dtype

c = array([(1.5, 2, 3), (4, 5, 6)])
print c

d = array([[1, 2], [3, 4]], dtype = complex)
print d

print zeros((3, 4))
print ones((2,3,4), dtype=int16)
print empty((2, 3))

print arange(10, 30, 5)

print arange(0, 2, 0.3)

# print ndarray
# 1d array
a = arange(6)
print a

# 2d array
b = arange(12).reshape(4,3)
print b

# 3d array
c = arange(24).reshape(2,3,4)
print c

# 4d array
d = arange(2*3*4*5).reshape(2,3,4,5)
print d

print arange(10000)

print arange(10000).reshape(100, 100)

#set_printoptions(threshold='nan')
#print arange(10000)

# basic operation
print ('#' * 15 + ' basic operation ' + '#' * 15)
a = array([20, 30, 40, 50])
b = arange(4)
print b
c = a - b
print c
print b**2
print 10 * sin(a)
print a < 35

A = array([[1,1],
[0,1]])
B = array([[2,0],
[3,4]])

# elementwise product
print A*B

# matrix product
print dot(A,B)

a = ones((2,3), dtype=int)
b = random.random((2,3))
a *= 3
print a
b += a
print b

# b is converted to integer type
a += b

print a

# upcast


import numpy as np

a = np.arange(15).reshape(3, 5)

# print np array
print a
# print shape of the array
print a.shape
# print #of dimensions - rank
print a.ndim
# print data type
print a.dtype.name
# print item size
print a.itemsize
# print actual size (how many elements)
print a.size
# print array type
print type(a)

# Data type of array is choose automatically when different type of data given
a = np.array([2, 3, 4])
print a.dtype

b = np.array([1.2, 3.5, 5.1])
print b.dtype

# sequences of sequences into two-dimensional arrays
b = np.array([(1.5, 2, 3), (4, 5, 6)])
print b

# Data type can be also define at the initiation time
c = np.array([[1, 2], [3, 4]], dtype=complex)
print c.dtype.name

# can create array with zeros , ones , and randoms (data type is float 64)
np.zeros((3, 4))

np.ones((2, 3, 4), dtype=np.int16)  # dtype can also be specified

np.empty((2, 3))  # uninitialized, output may vary

# can create sequence of numbers (start , end , increment )
np.arange(10, 30, 5)

# 9 numbers from 0 to 2
np.linspace(0, 2, 9)

# array types
a = np.arange(6)  # 1d array
print(a)

b = np.arange(12).reshape(4, 3)  # 2d array
print(b)

c = np.arange(24).reshape(2, 3, 4)  # 3d array
print(c)

# if array is too large to display it will skip showing middle content
print(np.arange(10000).reshape(100, 100))
# If need to see all
# np.set_printoptions(threshold='nan')

# basic arithmetic operation will return a new array
a = np.array([20, 30, 40, 50])
b = np.arange(4)
print b

c = a - b
print c

print b ** 2

print 10 * np.sin(a)

print a < 35

# * will works on element wise if need to get matrix product use dot

A = np.array([[1, 1],
              [0, 1]])
B = np.array([[2, 0],
              [3, 4]])
print A * B  # element wise product

print A.dot(B)  # matrix product

print np.dot(A, B)  # another matrix product


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

def ramuh(x,y,choices,plotTypes):
    """This is the user interface
    'x' is your list of x-axis variables
    'y' is your list of y-axis variables
    'choices' is an integer of how many top choices you want to view.
    'plotType' controls how the trendlines are shown in the end.
        0: all on the same plot
        1: all on different plots in the same window
        2: all on different windows that pop up all at once
        3: all on different windows that pop up one by one.

    Example Input: ramuh(np.array([0.5,0.9,1.7,2.4]),np.array([8.7,9.3,10.6,12.1]),2,0)
    """

#[lineList,r2Max,nList,recomendation]
    print('Welcome to ramuh. I will be your guide for our journey in curve fitting.')
    print('I see you have given me some inputs. Let me check that they are indeed numpy arrays.')
    result = check(x,y,choices,plotTypes)
    print('Looks good. You asked for the best {0:2d} trendlines for this data. I would be happy to oblige.\n'.format(choices))
    answer = optimizer(x,y,choices)
    #i = 0
    #print('\nlineList',answer[0][i])
    #print('\nr2Max',answer[1][i])
    #print('\nnList',answer[2][i])
    #print('\nrecomendation',answer[3][i])
    #print('')
    for i in range(choices):
        print('Choice number {0:2d} comes from a '.format(i+1)+answer[2][i]+' trendline')
        print('The r2 value for it is, {0:1.5f}, which is '.format(answer[1][i])+answer[3][i])
        print('This trendline is: '+answer[0][i],'\n')
    print("Before I go, I'll let you check over the trendlines I choose. Here is a plot that contains them all.")
    showplots(x,y,answer,choices,plotTypes)

#The Black Box
def check(x,y,choices,plotTypes):
    """This checks that the inputs will work.

    Example Input: optimizer(np.array([0.5,0.9,1.7,2.4]),np.array([8.7,9.3,10.6,12.1]),2,0)
    """

    if len(x) > len(y):
        print('Your x-list is larger than your y-list.')
    elif len(y) > len(x):
        print('Your y-list is larger than your x-list.')

    if type(x) != np.ndarray:
        print('Your x-list is not an np.array')
    if type(y) != np.ndarray:
        print('Your y-list is not an np.array')
    #Check for multi-dimensionsal
    #Check that there are not too many choices

    #Have it return wether or not it is good. If not, the whole thing stops.

def showplots(xlist,ylist,answer,choices,plotType):
    """This creates a dynamic plot(s) for the trendlines.

    Example Input: optimizer(np.array([0.5,0.9,1.7,2.4]),np.array([8.7,9.3,10.6,12.1]),2,0)
    """

    h = 50 #step scalar
    x = np.arange(xlist.min(),xlist.max(),(xlist.max()-xlist.min())/h)
    if plotType == 0:
        plt.figure(1)
        plt.plot(xlist,ylist,'k-')
        for j in range(choices):
            if j<6:
                if j%2 == 0:
                    if j == 0:
                        lnStl = 'Purple'
                    elif j == 2:
                        lnStl = 'Orange'
                    elif j == 4:
                        lnStl = 'g-'
                else:
                    if j == 1:
                        lnStl = 'r-'
                    elif j == 3:
                        lnStl = 'y-'
                    elif j == 5:
                        lnStl = 'b-'
            else:
                lnStl = 'w--'
            plt.plot(x,eval(answer[0][j].strip('y=')),lnStl)
        plt.show()

def optimizer(x,y,choices):
    """This runs through the various trendlines and evaluates them.
    myList = [linear,power_law,logrtithmic,poly2,poly3,poly4]

    Example Input: optimizer(np.array([0.5,0.9,1.7,2.4]),np.array([8.7,9.3,10.6,12.1]),2)
    """

    myList,solvedMax,r2Max,nList,recomendation,lineList = [],[],[],[],[],[]
    myList.append(trendline(x,y,'linear'))
    myList.append(trendline(x,y,'power_law'))
    myList.append(trendline(x,y,'exponential'))
    myList.append(trendline(x,y,'logrithmic'))
    myList.append(trendline(x,y,3))
    myList.append(trendline(x,y,4))
    myList.append(trendline(x,y,5))
    myList = np.transpose(myList)[:]
    for i in range(choices):
        n = myList[1].argmax()
        solvedMax.append(myList[0][i].tolist())
        r2Max.append(myList[1][i])
        if n == 0:
            nList.append('linear')
            lineList.append('y='+str(solvedMax[0][0][0])+'+'+str(solvedMax[0][1][0])+'*x')
        elif n == 1:
            nList.append('power law')
            lineList.append('y='+str(solvedMax[0][0][0])+'*x**('+str(solvedMax[0][1][0]))
        elif n == 2:
            nList.append('exponential')
            lineList.append('y='+str(solvedMax[0][0][0])+'*np.exp('+str(solvedMax[0][1][0])+'*x)')
        elif n == 3:
            nList.append('logrithmic')
            lineList.append('y='+str(solvedMax[0][0][0])+'+'+str(solvedMax[0][1][0])+'*np.log(x)')
        else:
            if n == 4:
                nList.append('2nd order polynomial')
                lineList.append('y='+str(solvedMax[0][0][0])+'+'+str(solvedMax[0][1][0])+'*x'+str(solvedMax[0][2][0])+'*x**2')
            elif n == 5:
                nList.append('3rd order polynomial')
                lineList.append('y='+str(solvedMax[0][0][0])+'+'+str(solvedMax[0][1][0])+'*x'+str(solvedMax[0][2][0])+'*x**2'+str(solvedMax[0][3][0])+'*x**3')
            else:
                nList.append(str(n-1)+'th order polynomial')
                temp = 'y='+str(solvedMax[0][0][0]+'+'+str(solvedMax[0][1][0])+'*x')
                for i in range(n-1):
                    temp += '+'+str(solvedMax[0][i+2][0])+'*x**'+str(i+2)
                lineList.append(tempStr)

        if r2Max[i] >= 0.999:
            recomendation.append('extremely good. I recommend using it.')
        elif r2Max[i] >= 0.99:
            recomendation.append('very good. I recommend using it.')
        elif r2Max[i] >= 0.98:
            recomendation.append('great. I recommend using it.')
        elif r2Max[i] >= 0.95:
            recomendation.append('good. You could use it.')
        elif r2Max[i] >= 0.90:
            recomendation.append('alright. You could still use it.')
        elif r2Max[i] >= 0.80:
            recomendation.append('not that good. You should re-take your data so I can provide you with a better trendline.')
        elif r2Max[i] >= 0.50:
            recomendation.append('bad. You should re-take your data so I can provide you with a useable trendline.')
        else:
            recomendation.append("horrible. I'm sorry it did not work out.")

        myList[1][n] = 0

    return [lineList,r2Max,nList,recomendation]

def trendline(x,y,eqnType):
    """This creates various types of trendlines from lists of data in the form of numpy arrays.

    Example Input: trendline(np.array([0.5,0.9,1.7,2.4]),np.array([8.7,9.3,10.6,12.1]),'linear')
    """
    if type(eqnType)==str:
        n = len(x)
        if eqnType == 'linear':
            sumx = np.sum(x)
            sumy = np.sum(y)
            sumxy = np.sum(x*y)
            sumx2 = np.sum(x**2)
            sumy2 = np.sum(y**2)

        elif eqnType == 'power_law':
            sumx = np.sum(np.log(x))
            sumy = np.sum(np.log(y))
            sumxy = np.sum(np.log(x)*np.log(y))
            sumx2 = np.sum(np.log(x)**2)
            sumy2 = np.sum(np.log(y)**2)

        elif eqnType == 'exponential':
            sumx = np.sum(x)
            sumy = np.sum(np.log(y))
            sumxy = np.sum(x*np.log(y))
            sumx2 = np.sum(x**2)
            sumy2 = np.sum(np.log(y)**2)

        elif eqnType == 'logrithmic':
            sumx = np.sum(np.log(x))
            sumy = np.sum(y)
            sumxy = np.sum(x*y)
            sumx2 = np.sum(x**2)
            sumy2 = np.sum(y**2)

        A = np.array([[n,sumx],[sumx,sumx2]])
        b = np.array([[sumy],[sumxy]])
        solved = np.linalg.solve(A,b)
        aAns = solved[0,0]
        #print(aAns)
        bAns = solved[1,0]
        #print(bAns)
        r2 = (aAns*sumy+bAns*sumxy-1/n*sumy**2)/(sumy2-1/n*sumy**2)

    else:
        #The eqnType is the order that the polynomial is.
        sumxList,sumyList,b = [],[],[]
        n = eqnType
        A = [[n]]
        for i in range(eqnType*2): #Create a list that ranges from x^1 to x^n
            sumxList.append(np.sum(x**(i+1)))
            sumyList.append(np.sum(x**(i)*y))

        for i in range(eqnType-1): #Initialize the A
            A.append([sumxList[i]])

        for j in range(eqnType):#Set up the A and b
            for i in range(eqnType-1):
                A[j].append(sumxList[i+j])
            b.append(sumyList[j])

        A = np.array(A)
        b = np.array(b).transpose()

        #Solve it
        solved = np.linalg.solve(A,b)

        for i in range(eqnType):
            r2 = solved[i]*sumyList[i]
        r2 = (r2-solved[0]**2/n)/(np.sum(y**2)-1/n*sumyList[0]**2)
    #return [0,1]
    return [solved,r2]


#!/usr/bin/python3
# 8.3 - Perspective Projections
# http://learnwebgl.brown37.net/08_projections/projections_perspective.html
from math import *
from sympy import *
left,right,bottom,top,near,far = symbols('left right bottom top near far')

# near-far range
# OpenGL -1 to 1
# D3D, webGPU 0 to 1
# Solve system
# c1/z + c2
# When z = near, the equation must calculate 0.
# When z = far, the equation must calculate 1.
Z = Matrix([
    [1/near, 1,  0],
    [1/far,  1,  1]
]).rref()

z1 = simplify(Z[0][0,2])
z2 = simplify(Z[0][1,2])
#print(z1)
#print(z2)

# Scale the 2D (x’,y’) values
# in the viewing window to a
# 2-by-2 unit square; (-1,-1) to (+1,+1)
A = Matrix([
    [2/(right-left),             0, 0, 0],
    [             0,2/(top-bottom), 0, 0],
    [             0,             0, 1, 0],
    [             0,             0, 0, 1]
])
# Perform the perspective calculation
B = Matrix([
    [near,   0, 0, 0],
    [   0,near, 0, 0],
    [   0,   0, 1, 0],
    [   0,   0, 0, 1]
])
# Scale the depth values (z)
# into a normalized range (0,+1)
# (and setup for division by (z)).
# c1, c2, x, y, z = symbols('c1 c2 x y z')
# C = Matrix([
#     [1, 0,    0,    0],
#     [0, 1,    0,    0],
#     [0, 0,   c2,   c1],
#     [0, 0,    1,    0]
# ])
# print(C*Matrix([[x],[y],[z],[1]]))
C = Matrix([
    [1, 0,    0,    0],
    [0, 1,    0,    0],
    [0, 0,   z2,   z1],
    [0, 0,    1,    0]
])

# Translate the apex of the frustum to the origin.
D = Matrix([
    [1, 0, 0, -(left+right)/2],
    [0, 1, 0, -(bottom+top)/2],
    [0, 0, 1,               0],
    [0, 0, 0,               1]
])

R = A*B*C*D

P = R.evalf(subs={left:-1,right:1,bottom:-1,top:1,near:2,far:40})
M1 = P*Matrix([[ 1],[ 1],[ 2],[1]]); M1 = M1/M1[3]; #print(M1)
M2 = P*Matrix([[-1],[-1],[ 2],[1]]); M2 = M2/M2[3]; #print(M2)
M3 = P*Matrix([[ 1],[ 1],[40],[1]]); M3 = M3/M3[3]; #print(M3)
M4 = P*Matrix([[-1],[-1],[40],[1]]); M4 = M4/M4[3]; #print(M4)

assert (isclose(M1[0], 1)), "Must be at far right"
assert (isclose(M1[1], 1)), "Must be at up"
assert (isclose(M1[2], 0)), "Must be at zero"
assert (isclose(M2[0], -1)), "Must be at far left"
assert (isclose(M2[1], -1)), "Must be at bottom"
assert (isclose(M2[2],  0)), "Must be at ozerone"
# Perfect center in infinite and not the "far" point
assert (isclose(M3[0], 0.05)), "Must be near center, but +x+y quadrant"
assert (isclose(M3[1], 0.05)), "Must be near center, but +x+y quadrant"
assert (isclose(M3[2], 1)), "Must be at one"
assert (isclose(M4[0],-0.05)), "Must be near center, but -x-y quadrant"
assert (isclose(M4[1],-0.05)), "Must be near center, but -x-y quadrant"
assert (isclose(M4[2], 1)), "Must be at one"

[
    print("R[{0}] = {1};".format(
        j * 4 + i,
        ccode(simplify(R[i,j])))
    )
    for j in range(4)
    for i in range(4)
]


# script to show realation of binomial and poisson distribution in photon counting
import numpy as np
import math
import matplotlib.pyplot as plt

from scipy.stats import binom, poisson

n_exp    = 100000   # number of experiments to run
chunk_size = np.uint(1e3)
mu       = 7
seed     = 0

#---------------------------------------------------------------------------------------------------
np.random.seed(seed)

#for n_nuclei in [10,20,100,1000,10000]:    # number of nuclei to start with
for n_nuclei in [10,20,100,1000]:    # number of nuclei to start with

  p = mu/n_nuclei  # probability that an event (decay + detection) occurs

  if (n_exp) < chunk_size:
    r = np.random.rand(n_exp, n_nuclei)
    # number of events in every experiment
    n = (r<=p).sum(1)
  else:
    nchunks = math.ceil(n_exp / chunk_size)

    n = []
    for i in range(nchunks):
      print(f'{i+1} / {nchunks}')
      r = np.random.rand(chunk_size, n_nuclei)
      n.append((r<=p).sum(1))

    n = np.array(n).flatten()

  # histogram the number of detected events per experiement
  h = np.histogram(n, np.arange(n.max() + 2) - 0.5)

  # generate x and y for plots to make life easier
  x = (0.5*(h[1][:-1] + h[1][1:])).astype(np.uint) # the bin centers
  y = h[0]

  # calculate binom and poisson pmfs
  xx   = np.arange(n_nuclei+1)
  bpmf = binom.pmf(xx, n_nuclei, p)
  ppmf = poisson.pmf(xx, n_nuclei*p)

  # print max abs and relative error
  abs_error     = ppmf - bpmf
  rel_error     = abs_error / bpmf
  print(f'max abs error P/B {abs_error.max()}')
  print(f'max rel error P/B {rel_error.max()}')

  fig, ax = plt.subplots(1,1, figsize = (7,4))
  ax.bar(x, y/y.sum(), width = 1, color = 'silver')
  ax.plot(xx, bpmf, 'o-', color = 'tab:blue', label = f'Binomial pmf (N={n_nuclei:.1e}, p = {p:.1e})')
  ax.plot(xx, ppmf, '.:', color = 'tab:orange', label = f'Poisson pmf (mu = N*p = {mu})')
  ax.set_xlim(-0.2,19)
  ax.set_ylim(-0.002,0.3)
  ax.set_xlabel('n (number of events)')
  ax.set_title(f'histogram N = {n_nuclei}, p = {p}, {n_exp} exps.')
  ax.grid(ls = ':')
  ax.legend()
  fig.tight_layout()
  fig.savefig(f'mu_{mu}_p_{p:.1e}.png')
  fig.show()


# # Linear Regression using Gradient Descent

# ## Import Packages

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

# ## Import Data set

train_data = pd.read_csv('apartment_data_train.csv')
train_data.head()

train_data.info()

train_data.describe()

test_data = pd.read_csv('apartment_data_test.csv')
test_data.head()

# ## Feature Separation and Normalization

X_train = train_data.iloc[:, :-1].values
X_test = test_data.iloc[:, :].values
y_train = train_data.iloc[:, -1:].values
m = y_train.shape[0]

train_data_mean = train_data.mean(0)
train_data_std = train_data.std(0)

X_train_mean = train_data_mean[0:2].values
X_train_std = train_data_std[0:2].values
X_train_norm = (X_train - X_train_mean) / X_train_std

X_test_norm = (X_test - X_train_mean) / X_train_std

# ## Add Intercept Term to Features

train_ones = np.ones((X_train_norm.shape[0], 1))
test_ones = np.ones((X_test_norm.shape[0], 1))
X_train_norm = np.column_stack((train_ones, X_train_norm))
X_test_norm = np.column_stack((test_ones, X_test_norm))

# ## Perform Gradient Descent

# ### Compute Cost

def compute_cost(X, y, theta):
    hx = np.matmul(X, theta)
    error_values = hx - y
    squared_error = np.square(error_values)
    cost = np.sum(squared_error, 0)
    return cost

# ### Gradient Descent

def gradient_descent(X, y, theta, alpha, iterations):
    iteration_array = np.array([itr for itr in range(iterations)])
    cost_history = []
    for iteration in range(iterations):
        hx = np.matmul(X, theta)
        error_value = hx - y
        error_value_multi = np.matmul(error_value.T, X)
        delta = np.multiply(error_value_multi.T, (alpha / m))
        theta = theta - delta
        cost_history.append(compute_cost(X, y, theta))
    return [theta, np.column_stack((iteration_array, np.asarray(cost_history)))]

# ### Use Gradient Descent

alpha = 0.01
num_iterations = 400
theta = np.zeros((X_train_norm.shape[1], 1))
theta, cost_history_result = gradient_descent(X_train_norm, y_train, theta, alpha, num_iterations)
result = np.matmul(X_test_norm, theta)
print(result)

# ### Cost Function

plt.plot(cost_history_result)
plt.xlabel("Iteration")
plt.ylabel("Cost")
plt.show()


from numpy.matlib import *
import numpy
import time
import scipy.sparse.linalg

def gausselim(A, B, pivoting = 0, ztol = 1.0e-8, debug = False):

    size = len(A)
    X    = [0.0] * size
    R    = range(size)
    C    = range(size)
    code = 0

    # Triangularization.
    for pivot in range(size-1):
        absm    = abs(A[pivot][pivot])
        exchrow = pivot
        exchcol = pivot

        if pivoting == 1: # partial pivoting.
            for row in range(pivot + 1, size):
                atestm  = abs(A[row][pivot])
                if atestm > absm:
                   absm    = atestm
                   exchrow = row

        elif pivoting == 2: #full pivoting.
            for row in range(pivot + 1, size):
                for col in range(pivot, size):
                   atestm  = abs(A[row][col])
                   if atestm > absm:
                      absm    = atestm
                      exchrow = row
                      exchcol = col

        elif pivoting == 3: # rook pivoting.

           absm  = 0.0
           Done = False
           while not Done:
               Done = True
               #Find row with maximum element in column exchcol.
               for row in range(exchrow, size):
                   atest = abs(A[row][exchcol])
                   if atest > absm:
                      absm  = atest
                      exchrow = row
                      Done = False

               #Find col with maximum element in row exchrow.
               for col in range(pivot, size):
                   atest = abs(A[exchrow][col])
                   if atest > absm:
                      absm = atest
                      exchcol = col
                      Done = False

        #Exchange columns ?
        if pivot != exchcol:
            if debug: print "exhanging columns", pivot, exchcol
            for row in range(pivot, size):
                A[row][pivot], A[row][exchcol] = A[row][exchcol], A[row][pivot]
            #B[pivot], B[exchcol] = B[exchcol],B[pivot]
            C[pivot] = exchcol

        #Exchange rows?
        if pivot != exchrow:
            if debug:
               print "Exchanging rows:",  pivot,  exchrow
            A[exchrow],A[pivot] = A[pivot], A[exchrow]
            B[exchrow],B[pivot] = B[pivot], B[exchrow]
            R[pivot] = exchrow

        if absm > ztol:
           if debug:
               print "pre reduction for row",pivot
               print "current augmented matrix:"
               # mataugprint(A,B)
           m = float(A[pivot][pivot])
           for row in range(pivot +1, size):
               kmul = float(A[row][pivot])/m
               # Apply rectangular rule.(Reduction)
               for col  in range(size -1, pivot, -1):
                    A[row][col] = float(A[row][col]) - kmul * A[pivot][col]
               B[row] = float(B[row]) - kmul * B[pivot]
               A[row][pivot] = 0.0

           if debug:
               print "post reduction:"
               print "current augmented matrix:"
               # mataugprint(A,B)
               print

        else:
           code = 1

    # Perform Back substitution.
    if debug:
       print "Back substitution: row, rhs sum, pivot"
    if code == 0:
        for row in range(size-1, -1, -1):
           sum = B[row]
           for k in range(row+1, size):
                sum -= (X[k] * A[row][k])
           #print row, sum, A[row][row]
           X[row] = sum/A[row][row]
        reorder(X,C)
        if debug: print "Computed value of X = ",  X
    return (code,R,C, A,X,B)

def reorder(X, C):
    for i, c in enumerate(C):
        if i != c:
           #exchange elements at positions i and c.
           X[i], X[c] =  X[c], X[i]
    return X

def Solve(A,b):

    (code, R,C, A, X, B) = gausselim(A, b)
    if code==0:
        return X
    else:
        return None


import numpy as np

##########################################################################
# Modeling: what we want to capture

# points = [(np.array([2]),4), (np.array([4]),2)]
# d = 1

# Generate artificial data
true_w = np.array([1,2,3,4,5])
d = len(true_w)
points = []
iterationCount = 500
for i in range(iterationCount):
    x = np.random.randn(d)
    y = true_w.dot(x) + np.random.randn()
    #  print(x, y)
    points.append((x,y))

def F(w):
    return sum((w.dot(x) - y)**2 for x, y in points) / len(points)

def dF(w):
    return sum(2*(w.dot(x) - y)*x  for x, y in points) / len(points)

def sF(w, i):
    x,y = points[i]
    return (w.dot(x) - y)**2

def sdF(w, i):
    x,y = points[i]
    return 2*(w.dot(x) - y)*x

##########################################################################
# Algorithms: how we compute it

def gradientDescent(F, dF, d):
    # gradient descent
    w = np.zeros(d)
    eta = 0.01
    for t in range(iterationCount):
        value = F(w)
        gradient = dF(w)
        w = w - eta * gradient
        print('iteration {}: w = {}, F(w) = {}'.format(t, w, value))

def stochasticGradientDescent(sF, sdF, d, n):
    # stochastic gradient descent
    w = np.zeros(d)
    eta = 1
    numUpdates = 0
    for t in range(iterationCount):
        for i in range(n):
            value = sF(w, i)
            gradient = sdF(w, i)
            numUpdates += 1
            eta = 1 / numUpdates
            w = w - eta * gradient
        print('iteration {}: w = {}, F(w) = {}'.format(t, w, value))

##########################################################################
# gradientDescent(F, dF, d)
stochasticGradientDescent(sF, sdF, d, len(points))


from sympy import diag, sqrt, symbols, tanh

from einsteinpy.symbolic import constants
from einsteinpy.symbolic.metric import MetricTensor

def AlcubierreWarp(
    x_s=symbols("x_s"), sigma=symbols("sigma"), R=symbols("R"), v=symbols("v")
):
    """
    Alcubierre Warp Drive Metric (G = c = 1) [1]_

    .. [1] Classical and Quantum Gravity,
    "The warp drive: hyper-fast travel within general relativity",
    Miguel Alcubierre, 1994, **11(5)**, pp. 73-77

    arXiv: `<https://arxiv.org/abs/gr-qc/0009013>`_)

    Journal: `<https://doi.org/10.1088%2F0264-9381%2F11%2F5%2F001>`_

    Parameters
    ----------
    x_s : ~sympy.core.basic.Basic or int or float
        Coordinate (a function of time, `t`)
    sigma : ~sympy.core.basic.Basic or int or float
        Arbitrary Parameter (See paper [1]_)
    R : ~sympy.core.basic.Basic or int or float
        Arbitrary Parameter (See paper [1]_)
    v : ~sympy.core.basic.Basic or int or float
        Coordinate Velocity

    Returns
    -------
    ~einsteinpy.symbolic.metric.MetricTensor
        Alcubierre Warp Drive Metric Tensor

    Alcubierre Warp Drive Metric (G = c = 1) [1]_

    """
    coords = symbols("t x y z")
    t, x, y, z = coords
    r_s = sqrt((x - x_s) ** 2 + y + z)
    f = (tanh(sigma * (r_s + R)) - tanh(sigma * (r_s - R))) / (2 * tanh(sigma * R))

    v2 = v ** 2
    f2 = f ** 2

    metric = diag(v2 * f2 - 1, 1, 1, 1).tolist()
    metric[0][1] = metric[1][0] = -v * f

    return MetricTensor(metric, coords, "ll", name="AlcubierreWarpMetric")


"""
Calculate eigenvalues and eigenvectors of a matrix using the power method.
"""

from linear_algebra import matrix_size, dot_product

import numpy as np

ZERO_NUMBER = 1e-20  # Values smaller than this numbers are considered to be zero

def dominant_eigen_system(matrix, iterations):
    """
    Calculates the dominant eigenvalue and a dominant unit eigenvector
    of a symmetric matrix using the power method.

    Parameters
    ----------
    matrix : numpy.ndarray
        A symmetric matrix.

    iterations : int
        The number of iterations of the power method.

    Returns
    -------
    tuple of (Int, numpy.ndarray)
        Dominant eigenvalue and a dominant unit eigenvector.
        The list is empty if the dominant eigenvalue is zero.
    """

    row_count, col_count = matrix_size(matrix)

    # Create an initial unit vector
    unit_vector = np.zeros([col_count, 1])
    unit_vector[0, 0] = 1

    # Calculate dominant unit eigenvector
    for _ in range(iterations):
        product = matrix @ unit_vector
        vec_length = np.linalg.norm(product)

        # Reached zero eigenvalue
        if vec_length < ZERO_NUMBER:
            return (0, [])

        unit_vector = product / vec_length

    # Calculate dominant eigenvalue
    eigenvalue = dot_product(matrix @ unit_vector, unit_vector)

    return (eigenvalue, unit_vector)


"""

The script declare the functions used in prepare_data.py

"""
import pandas as pd
import numpy as np

from logzero import logger
import matplotlib.pyplot as plt

from statsmodels.tsa.stattools import adfuller

from statsmodels.tsa.seasonal import seasonal_decompose

from utility import parse_config

config_path = "../config/config.yaml"
config = parse_config(config_path)   # read config file

def stationarity_test(df):

    #Determining rolling statistics
    rolstd = df.rolling(12).std()

    # Plot rolling statistics:
    plt.plot(df, color='blue',label='Original')
    plt.plot(df, color='red', label='Rolling Mean')
    plt.plot(rolstd, color='black', label = 'Rolling Std')
    plt.legend(loc='best')
    plt.title(config["prepare_data"]["stationarity_test"]["title"] , fontsize = 14)
    plt.xlabel('Date')
    plt.savefig("stationarity_test.png")

    logger.info("Results of Dickey Fuller Test")
    adft = adfuller(df, autolag='AIC')

    output = pd.Series(adft[0:4],index=['Test Statistics','p-value','No. of lags used','Number of observations used'])
    for key,values in adft[4].items():
        output['critical value (%s)'%key] =  values
    logger.info(output)

def decompose_series(df):

    result = seasonal_decompose(df, model='multiplicative', period = 30)
    fig = plt.figure()
    fig = result.plot()
    fig.set_size_inches(12, 19)
#    fig.suptitle("Series Decomposition of House Prices", fontsize = 18)

    fig.suptitle(config["prepare_data"]["decompose_series"]["title"], fontsize = 18)
    plt.xlabel(config["prepare_data"]["decompose_series"]["xlabel"])
    plt.savefig("decompose_series.png")

def remove_trend(df):

    df_log = np.log(df)
    moving_avg = df_log.rolling(12).mean()
    std_dev = df_log.rolling(12).std()
    fig = plt.figure()
    fig.set_size_inches(12, 9)

    plt.legend(loc='best')
    plt.title('Moving Average', fontsize = 14)
    plt.plot(std_dev, color ="black", label = "Standard Deviation")
    plt.plot(moving_avg, color="red", label = "Mean")
    plt.legend()
    plt.savefig("moving_average.png")
    return df_log

def split_data(df_log):
    train_data, test_data = df_log[3:int(len(df_log)*0.9)], df_log[int(len(df_log)*0.9):]
    plt.figure(figsize=(10,6))
    plt.grid(True)
    plt.title(config["prepare_data"]["split_data"]["title"], fontsize = 14)
    plt.xlabel(config["prepare_data"]["split_data"]["xlabel"])
    plt.ylabel(config["prepare_data"]["split_data"]["ylabel"])
    plt.plot(df_log, 'green', label='Train data')
    plt.plot(test_data, 'blue', label='Test data')
    plt.legend()
    plt.savefig("train-test-series.png")

    return train_data, test_data


'''
This file holds scheduling classes for the epsilon-greedy exploration strategy
'''

# imports:
import numpy as np

class ExponentialSchedule():
    '''
    Exponential scheduling strategy.
    eps(t) = end_val + (start_val - end_val) * exp(-1.0 * t / decay_rate)
    Parameters:
        start_val: initial value (float)
        end_val: final value (float)
        decay_rate: rate of exponential decaying (int), usually steps or episodes
    '''
    def __init__(self, start_val = 1.0, end_val = 0.05, decay_rate = 200):
        self.start = start_val
        self.end = end_val
        self.decay = decay_rate

    def value(self, t):
        '''
        Calculates the current value at time t
        Parameters:
            t: current time/step/episode (int)
        '''
        return (self.end + (self.start - self.end) * np.exp(-1.0 * t / self.decay))

class LinearSchedule():
    def __init__(self, start_val = 1.0, end_val = 0.05, total_timesteps = 200):
        '''
        Linear scheduling strategy.
        eps(t) = start_val + (end_val - start_val) * min(t / total_time_steps, 1)
        Parameters:
            start_val: initial value (float)
            end_val: final value (float)
            total_timesteps: time steps to get from start to end (until saturation)
        '''

        self.start = start_val
        self.end = end_val
        self.total_timesteps = total_timesteps

    def value(self, t):
        '''
        Calculates the current value at time t
        Parameters:
            t: current time/step/episode (int)
        '''
        return (self.start + (self.end - self.start) * min(1.0 * t / self.total_timesteps, 1.0))


from scipy.optimize import linprog

# fx = [-3, -5]                             ;Funcion a optimizar igualada a cero
# restrictions = [[1, 0], [0, 2], [3, 2]]   ;Lado izquierdo de las restricciones
# [4, 12, 18]                               ;Lado derecho de las restricciones
def simplex(fx, restrictions, result_restrictions):
    x0_bounds = (0, None)
    x1_bounds = (0, None)
    # Solve the problem by Simplex method in Optimization (method='revised simplex', method='simplex', )
    res = linprog(fx, A_ub=restrictions, b_ub=result_restrictions,  bounds=(x0_bounds, x1_bounds), method='simplex', options={"disp": True})
    print(res)
    # X = Funcion objetivo
    # Fun = resultado de optimizacion
    return res

# Probando el metodo simplex
fx = [-3, -5]
restrictions = [[1, 0], [0, 2], [3, 2]]
result_restrictions = [4, 12, 18]
simplex(fx, restrictions, result_restrictions)


"""Collection of math modules."""

import numpy as np
import math

def distance(x1, y1, x2, y2):
    """Compute distnace between two points."""
    return math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)

def point_distance(u, v):
    """Compute distance."""
    return distance(u[0], u[1], v[0], v[1])

def get_direction(x, y):
    """Compute direction between two points."""
    dx = x[0] - y[0]
    dy = x[1] - y[1]
    return np.arctan2(dy, dx)

def round_angles(direction):
    """Round angles such that direction is less that 2*np.pi"""
    return direction % (2 * np.pi)

"""
From Algorithm tutor
https://algorithmtutor.com/Computational-Geometry/Determining-if-two-consecutive-segments-turn-left-or-right/
"""

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def subtract(self, p):
    	return Point(self.x - p.x, self.y - p.y)

    def __str__(self):
        return '(' + str(self.x) + ', ' + str(self.y) + ')'

def cross_product(p1, p2):
	return p1.x * p2.y - p2.x * p1.y

def direction(p1, p2, p3):
	return  cross_product(p3.subtract(p1), p2.subtract(p1))

def collinear(p1, p2, p3):
	return direction(p1, p2, p3) == 0

def right(p1, p2, p3):
	return direction(p1, p2, p3) > 0

def left(p1, p2, p3):
	return direction(p1, p2, p3) < 0

# checks if p lies on the segment p1p2
def on_segment(p1, p2, p):
    return min(p1.x, p2.x) <= p.x <= max(p1.x, p2.x) and min(p1.y, p2.y) <= p.y <= max(p1.y, p2.y)

# checks if line segment p1p2 and p3p4 intersect
def check_intersect(p1, p2, p3, p4):
    p1 = Point(p1[0], p1[1])
    p2 = Point(p2[0], p2[1])
    p3 = Point(p3[0], p3[1])
    p4 = Point(p4[0], p4[1])
    d1 = direction(p3, p4, p1)
    d2 = direction(p3, p4, p2)
    d3 = direction(p1, p2, p3)
    d4 = direction(p1, p2, p4)

    if ((d1 > 0 and d2 < 0) or (d1 < 0 and d2 > 0)) and \
        ((d3 > 0 and d4 < 0) or (d3 < 0 and d4 > 0)):
        return True

    elif d1 == 0 and on_segment(p3, p4, p1):
        return True
    elif d2 == 0 and on_segment(p3, p4, p2):
        return True
    elif d3 == 0 and on_segment(p1, p2, p3):
        return True
    elif d4 == 0 and on_segment(p1, p2, p4):
        return True
    else:
        return False


import os
import pandas as pd
import matplotlib.ticker as ticker
from sklearn.metrics import mean_squared_error
import numpy as np
from scipy.stats import norm,normaltest,probplot
from scipy import optimize
import matplotlib.pyplot as plt
from matplotlib import style
from sklearn.linear_model import LinearRegression
from statistics import pstdev
import pylab

path = """path_to_folder"""

os.chdir(path)

data = pd.read_csv('temperature_data.csv')

x =np.array(list(data["Temp"].index))
y = np.array(list(data["Temp"]))

###

# TEMPERATURE DATA

plt.plot(x,y)
plt.show()

###
def form(x,A,B,C,phi):
    return A+B*x+C * np.sin(np.radians(x - phi))

param, covariance = optimize.curve_fit(form, x, y,[ 0, 0, 0 ,0])

fit_cosin = form(x, *param)

plt.scatter(x, y, label='data',alpha=0.5)
plt.plot(x, fit_cosin, 'r-')
plt.ticklabel_format(style='sci', axis='y', scilimits=(0,0))
plt.axes().xaxis.set_major_locator(ticker.MultipleLocator(45))
plt.show()

###

projection_mean = y-fit_cosin
plt.plot(projection_mean)
plt.show()

###

plt.hist(projection_mean,bins=500)
plt.show()

###

probplot(projection_mean, dist="norm", plot=pylab)
pylab.show()
###

absc = projection_mean[:-1].reshape((-1, 1))
ordo = projection_mean[1:]
model = LinearRegression(fit_intercept=True)
model.fit(absc, ordo)
b = model.coef_[0]
a = model.intercept_
x_test=np.linspace(min(absc[:-1]),max(absc[:-1]),num=len(absc)).reshape((-1, 1))
y_pred = model.predict(x_test)

se = mean_squared_error(ordo, y_pred)
print("b : ",b)
print("a : ", a)
print("se : ", se)

plt.scatter(absc,ordo,alpha=0.5)
plt.plot(x_test,y_pred,c="red")
plt.show()

###

# OU MODEL

K = -np.log(b)
theta = a/(1-b)
sigma = se*np.sqrt(-2*np.log(b)/(1-b**2))/10

print("K = ", K)
print("theta : ",theta)
print("sigma : ",sigma)

def OU(x,t,K,theta,sigma):
    res=[x]
    for i in range(t):
        ou_t = res[-1]*np.exp(-K)+theta*(1-np.exp(-K))+sigma*np.sqrt((1-np.exp(-2*K))/(2*K))*np.random.normal(0,1)
        res.append(ou_t)
    return np.array(res)

res= OU(projection_mean[0],len(projection_mean)-1,K,theta,sigma)
plt.plot(res,alpha=0.5,c="red")
plt.plot(projection_mean[:-1],alpha=0.5,c="blue")
plt.show()

###

#Autocorrelation

def autocorr(x):
    result = np.correlate(x, x, mode='full')
    return result[int(result.size/2):]

plt.plot(autocorr(res),c="red")
plt.plot(autocorr(projection_mean[:-1]),c="blue")
plt.show()

###

plt.hist(res,bins=500,color="red",alpha=0.3)
plt.hist(projection_mean[:-1],bins=500,color="blue",alpha=0.3)
plt.show()

###

#Prediction

def pred_T(n,x0,t0):
    new_x = np.array([x0+i for i in range(N)])
    fit_cosin_future = form(new_x, *param)
    ou_test= OU(projection_mean[-1],n-1,K,theta,sigma)
    temprerature_prediction = fit_cosin_future + ou_test
    return temprerature_prediction

N = 30*6
temprerature_prediction = pred_T(N,x[-1],y[-1])
plt.plot([i for i in range(len(x)+N)],list(y)+list(temprerature_prediction),c="red")
plt.plot(x,y,c="blue")
plt.show()

###

# MC Simulation
referential =y[-1]
def H(n,x0,t0):
    return (referential*n-np.sum(pred_T(n,x0,t0)))

n_sim=1000

# Average diff between our reference point and the our future prediction
sim = np.array([H(N,x[-1],y[-1]) for i in range(n_sim)])

mu_n = np.mean(sim)
sigma_n=pstdev(sim)
print("mu_n : ",mu_n)
print("sigma_n : ",sigma_n)

plt.plot(sim)
plt.show()

###

# PRICING

#risk free rate
r = 0.05/365

def call_hdd(t,strike,r):
    return( np.exp(-r*t)*((strike-mu_n)*(norm.cdf(alpha_n)-norm.cdf(-mu_n/sigma_n))+sigma_n/np.sqrt(2*np.pi)*(np.exp((-alpha_n**2)/2)-np.exp(-((mu_n/sigma_n)**2)/2))) )

strike_array = np.linspace(0,1000,num=10000)
prime_aray=[]
for strike in strike_array:
    alpha_n=(strike-mu_n)/sigma_n
    print("alpha_n : ",alpha_n)

    print("N : ", N)
    print("mu_n", mu_n)
    prime_aray.append(call_hdd(N,strike,r))
    print("CALL HDD Strike ", strike," : ",prime_aray[-1])

plt.plot(strike_array,prime_aray)
plt.axvline(mu_n,c="red")
plt.show()


# sympy_examples.py
# -------------------------------------------------------------------------
# Demonstrate some useful methods available in the SymPy module.
# Run each command separately at an IPython command line.
# -------------------------------------------------------------------------
from sympy import *
init_session()

expand( (x + y)**5 )
factor( x**6 - 1 )
pi.n(100)
plot(besselj(0, x), besselj(1, x), (x, 0, 10))
diff( x*sin(y), x, y )
integrate(cos(x)**2, x)
integrate(exp(-x**2), (x, -oo, oo))
Sum(k**3, (k, 0, m)).doit().factor()
dsolve(f(x).diff(x) + f(x) - x, f(x)).simplify()


##############################################################################################################
## This file contains functions to calculate derivative of the product spherical harmonics and derivative
## of the product Wigner D-functions.
##
##############################################################################################################
from matrix import SphericalHarmonics
from matrix import WignerDfunctions
from itertools import combinations
import numpy as np
import numpy.matlib as npmat

def DerivProdSH(theta,phi,B,update):
    #########################################################################################################
    ## Function to calculate derivative of product spherical harmonics
    ## input : m dimensions of theta and phi, bandlimited B, update "all" mean we derive the product spherical
    ## harmonics with respect to theta and phi (elevation and azimuth). Update "partial" only calculate
    ## derivative of the product spherical harmonics w.r.t phi and fix the theta with equispaced samples.
    ##
    ## Partial is used to verify the lower bound coherence of spherical harmonics matrix if we fix sampling on
    ## theta
    ##
    ##
    ## Since we want to optimize the coherence of the matrix (inner product of difference columns), we have
    ## to calculate the gradient w.r.t theta, or phi
    ##
    ## input : theta, phi m dimension samples on sphere, bandlimited B, and update
    ## output : gr_theta,gr_phi -- gradient with respect to (theta,phi) for update all,
    ##          gr_phi -- gradient with respect to (phi) for partial
    ##########################################################################################################
    m = len(theta)
    N=B**2
    phi_0 = np.zeros(m) #Generate only matrix associated Legendre by assigning phi = 0
    A = np.real(SphericalHarmonics(theta,phi_0,B)[1])
    lk = SphericalHarmonics(theta,phi_0,B)[2]
    Aderiv = np.real(SphericalHarmonics(theta,phi_0,B)[3])
    col_comb = np.array(list(combinations(range(N),2)))
    comb_lk = [lk[col_comb[:,0],:],lk[col_comb[:,1],:]]
    ## Product of combination of degree and order associated Legendre
    ProductasLeg = np.multiply(A[:,col_comb[:,0]],A[:,col_comb[:,1]])

    k = comb_lk[0][:,1] - comb_lk[1][:,1]
    ##########################################################################################################
    ## Derivative of q-norm
    ##########################################################################################################
    q = 8.0
    ##########################################################################################################
    ## Matrix-based derivative w.r.t phi
    ##########################################################################################################
    mat_cos = np.cos(np.outer(phi,k))
    mat_sin = np.sin(np.outer(phi,k))

    Qnorm = (q/2.0)*np.power(np.sqrt( np.power(abs(sum(np.multiply(ProductasLeg,mat_cos))),2)\
            + np.power( abs(sum(np.multiply(ProductasLeg,mat_sin))),2  )),(q-2))

    Qnorm1 = (1/q)*np.power(sum(np.power(np.sqrt( np.power(abs(sum(np.multiply(ProductasLeg,mat_cos))),2) \
            +  np.power(abs(sum(np.multiply(ProductasLeg,mat_sin))),2  )),q)),((1/q)-1))

    if update == 'all':
        ########################################################################################################
        ## Direct calculation of derivativei with respect to theta
        #######################################################################################################

        dPlktotal = np.multiply(A[:,col_comb[:,0]],Aderiv[:,col_comb[:,1]]) + np.multiply(Aderiv[:,col_comb[:,0]],A[:,col_comb[:,1]])

        ############################################################################################################
        ## Matrix-based derivative w.r.t theta
        ############################################################################################################
        gr_temp_theta1 = np.multiply((2.0*npmat.repmat(sum(np.multiply(ProductasLeg,mat_cos)),m,1)),(np.multiply(dPlktotal,mat_cos)))
        gr_temp_theta2 = np.multiply((2.0*npmat.repmat(sum(np.multiply(ProductasLeg,mat_sin)),m,1)),(np.multiply(dPlktotal,mat_sin)))
        gr_temp_theta = gr_temp_theta1 + gr_temp_theta2
        gr_theta = Qnorm1*np.sum(np.multiply(npmat.repmat(Qnorm,m,1),gr_temp_theta),axis = 1)

        ####################################################################################################
        ## Matrix-based derivative w.r.t phi
        ###################################################################################################
        Grad_temp1 =  2.0*npmat.repmat(sum(np.multiply(ProductasLeg,mat_cos)),m,1)
        Grad_temp2 = np.multiply(np.multiply(ProductasLeg,mat_sin),npmat.repmat(-k,m,1))
        Grad1 = np.multiply(Grad_temp1,Grad_temp2)

        Grad_temp3 = 2.0*npmat.repmat(sum(np.multiply(ProductasLeg,mat_sin)),m,1)
        Grad_temp4 = np.multiply(np.multiply(ProductasLeg,mat_cos),npmat.repmat(k,m,1))
        Grad2 = np.multiply(Grad_temp3,Grad_temp4)
        Grad_temp = Grad1 + Grad2

        gr_phi = Qnorm1*np.sum(np.multiply(npmat.repmat(Qnorm,m,1),Grad_temp),axis =1)

        return gr_theta, gr_phi

    else:

        Grad_temp1 =  2.0*npmat.repmat(sum(np.multiply(ProductasLeg,mat_cos)),m,1)
        Grad_temp2 = np.multiply(np.multiply(ProductasLeg,mat_sin),npmat.repmat(-k,m,1))
        Grad1 = np.multiply(Grad_temp1,Grad_temp2)

        Grad_temp3 = 2.0*npmat.repmat(sum(np.multiply(ProductasLeg,mat_sin)),m,1)
        Grad_temp4 = np.multiply(np.multiply(ProductasLeg,mat_cos),npmat.repmat(k,m,1))
        Grad2 = np.multiply(Grad_temp3,Grad_temp4)
        Grad_temp = Grad1 + Grad2

        gr_phi = Qnorm1*np.sum(np.multiply(npmat.repmat(Qnorm,m,1),Grad_temp),axis =1)

        return gr_phi

def DerivProdWigner(theta,phi,chi,B,update):
    #########################################################################################################
    ## Function to calculate derivative of product Wigner D-functions
    ## input : m dimensions of theta,phi, and chi, bandlimited B, update "all" mean we derive the product
    ## Wigner D-functions  with respect to theta, phi, chi (elevation, azimuth and polarization).
    ## Update "partial" only calculate derivative of the product Wigner D-functions  w.r.t phi and chi
    ##
    ## Partial is used to verify the lower bound coherence of Wigner D-functions if we fix sampling on theta
    ##
    ## Since we want to optimize the coherence of the matrix (inner product of difference columns), we have
    ## to calculate the gradient w.r.t theta, phi and chi
    ##
    ## input : theta, phi, chi in m-dimension samples on the rotation group, bandlimited B, and update
    ## output : gr_theta,gr_phi, gr_chi -- gradient with respect to (theta,phi,chi) for update all,
    ##          gr_phi,gr_chi -- gradient with respect to (phi,chi) for partial
    ##########################################################################################################

    m = len(theta)
    N = B*(2*B-1)*(2*B+1)//3

    phi_0 = np.zeros(m) #Generate only matrix Wigner d-functions by setting azimuth (phi) = 0
    chi_0 = np.zeros(m) #Generate only matrix Wigner d-functions by setting polarization (chi) = 0
    A = np.real(WignerDfunctions(theta,phi_0,chi_0,B)[1])
    lkn = WignerDfunctions(theta,phi_0,chi_0,B)[2]
    Aderiv = np.real(WignerDfunctions(theta,phi_0,chi_0,B)[3])
    col_comb = np.array(list(combinations(range(N),2)))
    comb_lkn = [lkn[col_comb[:,0],:],lkn[col_comb[:,1],:]]

    ## Product of combination degree and orders Wigner d-functions
    ProductWignerd = np.multiply(A[:,col_comb[:,0]],A[:,col_comb[:,1]])

    ########################################################################
    ## Matrix-based phi and chi
    #######################################################################
    k = comb_lkn[0][:,1] - comb_lkn[1][:,1]
    n = comb_lkn[0][:,2] - comb_lkn[1][:,2]

    ## Derivative of q norm
    q = 8.0

    mat_cos = np.cos(np.outer(phi,k) + np.outer(chi,n))
    mat_sin = np.sin(np.outer(phi,k) + np.outer(chi,n))

    Qnorm = (q/2.0)*np.power(np.sqrt( np.power(abs(sum(np.multiply(ProductWignerd,mat_cos))),2)\
                + np.power( abs(sum(np.multiply(ProductWignerd,mat_sin))),2  )),(q-2))
    Qnorm1 = (1/q)*np.power(sum(np.power(np.sqrt( np.power(abs(sum(np.multiply(ProductWignerd,mat_cos))),2) \
                 +  np.power(abs(sum(np.multiply(ProductWignerd,mat_sin))),2  )),q)),((1/q)-1))

    if update == 'all':

        ##################################################################################
        ## Direct derivative with respect to theta
        ##################################################################################
        dWignerd = np.multiply(A[:,col_comb[:,0]],Aderiv[:,col_comb[:,1]]) + np.multiply(Aderiv[:,col_comb[:,0]],A[:,col_comb[:,1]])

        ############################################################################################################
        ## Matrix-based derivative w.r.t to the q-norm
        ############################################################################################################
        gr_temp_theta1 = np.multiply((2.0*npmat.repmat(sum(np.multiply(ProductWignerd,mat_cos)),m,1)),(np.multiply(dWignerd,mat_cos)))
        gr_temp_theta2 = np.multiply((2.0*npmat.repmat(sum(np.multiply(ProductWignerd,mat_sin)),m,1)),(np.multiply(dWignerd,mat_sin)))
        gr_temp_theta = gr_temp_theta1 + gr_temp_theta2
        gr_theta = Qnorm1*np.sum(np.multiply(npmat.repmat(Qnorm,m,1),gr_temp_theta),axis = 1)

        ######################################################################
        ## Matrix-based phi
        #####################################################################
        Grad_temp1_phi =  2.0*npmat.repmat(sum(np.multiply(ProductWignerd,mat_cos)),m,1)
        Grad_temp2_phi =  np.multiply(np.multiply(ProductWignerd,mat_sin),npmat.repmat(-k,m,1))
        Grad1_phi = np.multiply(Grad_temp1_phi,Grad_temp2_phi)

        Grad_temp3_phi =  2.0*npmat.repmat(sum(np.multiply(ProductWignerd,mat_sin)),m,1)
        Grad_temp4_phi = np.multiply(np.multiply(ProductWignerd,mat_cos),npmat.repmat(k,m,1))
        Grad2_phi = np.multiply(Grad_temp3_phi,Grad_temp4_phi)
        Grad_temp_phi = Grad1_phi + Grad2_phi

        ######################################################################
        ## Matrix-based chi
        ######################################################################

        Grad_temp1_chi =  2.0*npmat.repmat(sum(np.multiply(ProductWignerd,mat_cos)),m,1)
        Grad_temp2_chi =  np.multiply(np.multiply(ProductWignerd,mat_sin),npmat.repmat(-n,m,1))
        Grad1_chi = np.multiply(Grad_temp1_chi,Grad_temp2_chi)

        Grad_temp3_chi =  2.0*npmat.repmat(sum(np.multiply(ProductWignerd,mat_sin)),m,1)
        Grad_temp4_chi = np.multiply(np.multiply(ProductWignerd,mat_cos),npmat.repmat(n,m,1))
        Grad2_chi = np.multiply(Grad_temp3_chi,Grad_temp4_chi)
        Grad_temp_chi = Grad1_chi + Grad2_chi

        gr_phi = Qnorm1*np.sum(np.multiply(npmat.repmat(Qnorm,m,1),Grad_temp_phi),axis = 1)
        gr_chi = Qnorm1*np.sum(np.multiply(npmat.repmat(Qnorm,m,1),Grad_temp_chi),axis = 1)

        return gr_theta, gr_phi, gr_chi
    else:

        ######################################################################
        ## Matrix-based phi
        #####################################################################
        Grad_temp1_phi =  2.0*npmat.repmat(sum(np.multiply(ProductWignerd,mat_cos)),m,1)
        Grad_temp2_phi =  np.multiply(np.multiply(ProductWignerd,mat_sin),npmat.repmat(-k,m,1))
        Grad1_phi = np.multiply(Grad_temp1_phi,Grad_temp2_phi)

        Grad_temp3_phi =  2.0*npmat.repmat(sum(np.multiply(ProductWignerd,mat_sin)),m,1)
        Grad_temp4_phi = np.multiply(np.multiply(ProductWignerd,mat_cos),npmat.repmat(k,m,1))
        Grad2_phi = np.multiply(Grad_temp3_phi,Grad_temp4_phi)
        Grad_temp_phi = Grad1_phi + Grad2_phi

        ######################################################################
        ## Matrix-based chi
        ######################################################################

        Grad_temp1_chi =  2.0*npmat.repmat(sum(np.multiply(ProductWignerd,mat_cos)),m,1)
        Grad_temp2_chi =  np.multiply(np.multiply(ProductWignerd,mat_sin),npmat.repmat(-n,m,1))
        Grad1_chi = np.multiply(Grad_temp1_chi,Grad_temp2_chi)

        Grad_temp3_chi =  2.0*npmat.repmat(sum(np.multiply(ProductWignerd,mat_sin)),m,1)
        Grad_temp4_chi = np.multiply(np.multiply(ProductWignerd,mat_cos),npmat.repmat(n,m,1))
        Grad2_chi = np.multiply(Grad_temp3_chi,Grad_temp4_chi)
        Grad_temp_chi = Grad1_chi + Grad2_chi

        gr_phi = Qnorm1*np.sum(np.multiply(npmat.repmat(Qnorm,m,1),Grad_temp_phi),axis = 1)
        gr_chi = Qnorm1*np.sum(np.multiply(npmat.repmat(Qnorm,m,1),Grad_temp_chi),axis = 1)

        return gr_phi,gr_chi