import numpy as np
import numba
from numba import prange

@numba.jit(nopython=True, parallel=True, nogil=True)
def edge_ratio(cells, points):
    edge_ids = np.asarray([[0,1],[0,2],[0,3],[1,2],[1,3],[2,3]], dtype=np.int32)
    ncells = cells.shape[0]
    ratios = np.zeros((ncells,1), dtype=np.float64)
    for id in prange(ncells):
        vtx_coords = points[cells[id]]
        edge_lengths = np.sqrt(np.square(vtx_coords[edge_ids[:,0],:] - vtx_coords[edge_ids[:,1],:]).sum(axis=1))
        ratios[id] = np.amax(edge_lengths)/np.amin(edge_lengths)
    return ratios

@numba.jit(nopython=True, parallel=True, nogil=True)
def aspect_frobenius(cells, points):
    VERDICT_DBL_MIN = 1.0E-30
    VERDICT_DBL_MAX = 1.0E+30
    normal_exp = 1. / 3.

    ncells = cells.shape[0]
    af = np.zeros((ncells,1))
    for id in prange(ncells):
        coordinates = points[cells[id]]

        u = np.array( [[coordinates[1][0] - coordinates[0][0]],
                       [coordinates[1][1] - coordinates[0][1]],
                       [coordinates[1][2] - coordinates[0][2]]] )

        v = np.array( [[coordinates[2][0] - coordinates[0][0]],
                       [coordinates[2][1] - coordinates[0][1]],
                       [coordinates[2][2] - coordinates[0][2]]] )

        w = np.array( [[coordinates[3][0] - coordinates[0][0]],
                       [coordinates[3][1] - coordinates[0][1]],
                       [coordinates[3][2] - coordinates[0][2]]] )

        denominator = 3. * (2. * (u % ( v * w ))**2)**normal_exp

        if denominator < VERDICT_DBL_MIN:
            af[id] = VERDICT_DBL_MAX
            continue

        numerator = 1.5 * ( u[0] * u[0] + u[1] * u[1] + u[2] * u[2]
                          + v[0] * v[0] + v[1] * v[1] + v[2] * v[2]
                          + w[0] * w[0] + w[1] * w[1] + w[2] * w[2] ) \
                    - v[0] * u[0] + v[1] * u[1] + v[2] * u[2] \
                    - w[0] * u[0] + w[1] * u[1] + w[2] * u[2] \
                    - w[0] * v[0] + w[1] * v[1] + w[2] * v[2]

        aspect_frobenius = numerator / denominator

        if aspect_frobenius > 0: af[id] = np.minimum( aspect_frobenius, VERDICT_DBL_MAX )
        else:                    af[id] = np.maximum( aspect_frobenius, -VERDICT_DBL_MAX )

    return af


from math import sqrt
from numba import njit, prange

@njit()
def divisor_counter(number: int) -> int:
    s = 0

    for i in prange(1, int(sqrt(number) + 1)):
        if number % i == 0:
            if number // i == i:
                s += 1
            else:
                s += 2

    return s

@njit()
def triangular_number(divisor_number: int) -> int:
    triangle_number = 0
    s = 0

    while True:
        s += triangle_number

        if divisor_counter(s) >= divisor_number:
            return s
        else:
            triangle_number += 1

if __name__ == '__main__':
    n = triangular_number(500)

    print(n)


import numpy as np

def sigmoid(z):
    """The sigmoid is a squashing function that compresses the entire real line
    into the unit interval.
    """
    return 1. / (1. + np.exp(-z))

def sigmoid_derivative(z):
    """Derivative of the sigmoid function with respect to its input."""
    p = sigmoid(z)
    return p * (1. - p)


from matplotlib import pyplot as plt
import pandas as pd
import numpy as np
import math
import scipy.stats as stats

from . import t
from . import chi2
from . import p
from . import ind
from . import anova
from . import non

"""
Using z statistic for hypothesis testing and confidence intervals.

+ rejection_region_method(x_mean, mu, std, n, alpha, option='left', precision=4, show=True, ignore=False)
+ testing_statistic_method
+ p_value_method
+ power_test
+ sample_size
+ type2_plot
+ power_plot
"""

def rejection_region_method(x_mean, mu, std, n, alpha, option='left', precision=4, show=True, ignore=False):
    """
    Input: x_mean, mu, std, n, alpha, option='left', precision=4, show=True, ignore=False
    Output:
        if opt == 't':
            return x_l, x_u
        else:
            return x_c
    """
    opt = option.lower()[0]
    if opt == 't':
        option = 'Two-Tail Test'
        z_value = stats.norm.ppf(1 - alpha / 2)
        x_u = mu + z_value * std / math.sqrt(n)
        x_l = mu - z_value * std / math.sqrt(n)
        flag = x_mean < x_l or x_mean > x_u
        if not ignore:
            result = f'''======= The Rejection Region Method =======
Significant Level (alpha) = {alpha:.{precision}f}
z_value = {z_value:.{precision}f}

Using {option}:
x̄ =  {x_mean:.{precision}f}
x_l (Lower bound for the critical value) = {x_l:.{precision}f}
x_u (Upper bound for the critical value) = {x_u:.{precision}f}
Reject H_0 → {flag}
            '''
        else:
            result = f'''======= The Rejection Region Method =======
Significant Level (alpha) = {alpha:.{precision}f}
z_value = {z_value:.{precision}f}

Using {option}:
x_l (Lower bound for the critical value) = {x_l:.{precision}f}
x_u (Upper bound for the critical value) = {x_u:.{precision}f}
            '''

    else:
        if opt == 'l':
            # left tail
            option = 'One-Tail Test (left tail)'
            z_value = stats.norm.ppf(alpha)  # negative
            x_c = mu + z_value * std / math.sqrt(n)
            flag = x_mean < x_c
        elif opt == 'r':
            option = 'One-Tail Test (right tail)'
            z_value = stats.norm.ppf(1 - alpha)
            x_c = mu + z_value * std / math.sqrt(n)
            flag = x_mean > x_c
        if not ignore:
            result = f'''======= The Rejection Region Method =======
Significant Level (alpha) = {alpha:.{precision}f}
z_value = {z_value:.{precision}f}

Using {option}:
x̄ =  {x_mean:.{precision}f}
x_c (Critical value) = {x_c:.{precision}f}
Reject H_0 → {flag}
            '''
        else:
            result = f'''======= The Rejection Region Method =======
Significant Level (alpha) = {alpha:.{precision}f}
z_value = {z_value:.{precision}f}

Using {option}:
x_c (Critical value) = {x_c:.{precision}f}
            '''

    if show:
        print(result)

    if opt == 't':
        return x_l, x_u
    else:
        return x_c

def testing_statistic_method(x_mean, mu, std, n, alpha, option='left', precision=4, ignore=False):
    """
    Input: x_mean, mu, std, n, alpha, option='left', precision=4, ignore=False
    Output:
        if opt == 't':
            return z, z_l, z_u
        else:
            return z, z_value
    """
    opt = option.lower()[0]
    z = (x_mean - mu)/(std / math.sqrt(n))
    if opt == 't':
        option = 'Two-Tail Test'
        z_value = stats.norm.ppf(1 - alpha / 2)
        z_u = z_value
        z_l = -z_value
        flag = z < z_l or z > z_u

        if not ignore:
            result = f'''======= Testing Statistic Method =======
Significant Level (alpha) = {alpha:.{precision}f}
z_value = {z_value:.{precision}f}

Using {option}:
z =  {z:.{precision}f}
z_l (Lower bound for the critical value) = {z_l:.{precision}f}
z_u (Upper bound for the critical value) = {z_u:.{precision}f}
Reject H_0 → {flag}
            '''
        else:
            result = f'''======= Testing Statistic Method =======
Significant Level (alpha) = {alpha:.{precision}f}
z_value = {z_value:.{precision}f}

Using {option}:
z_l (Lower bound for the critical value) = {z_l:.{precision}f}
z_u (Upper bound for the critical value) = {z_u:.{precision}f}
            '''

    else:
        if opt == 'l':
            # left tail
            option = 'One-Tail Test (left tail)'
            z_value = stats.norm.ppf(alpha)  # negative
            flag = z < z_value
        elif opt == 'r':
            option = 'One-Tail Test (right tail)'
            z_value = stats.norm.ppf(1 - alpha)
            flag = z > z_value

        if not ignore:
            result = f'''======= Testing Statistic Method =======
Significant Level (alpha) = {alpha:.{precision}f}
z_value = {z_value:.{precision}f}

Using {option}:
z =  {z:.{precision}f}
z_alpha (Critical value) = {z_value:.{precision}f}
Reject H_0 → {flag}
            '''

        else:
            result = f'''======= Testing Statistic Method =======
Significant Level (alpha) = {alpha:.{precision}f}
z_value = {z_value:.{precision}f}

Using {option}:
z_alpha (Critical value) = {z_value:.{precision}f}
            '''

    print(result)
    if opt == 't':
        return z, z_l, z_u
    else:
        return z, z_value

def inter_p_value(p_value):
    # interpretation
    if p_value >= 0 and p_value < 0.01:
        inter_p = 'Overwhelming Evidence'
    elif p_value >= 0.01 and p_value < 0.05:
        inter_p = 'Strong Evidence'
    elif p_value >= 0.05 and p_value < 0.1:
        inter_p = 'Weak Evidence'
    elif p_value >= .1:
        inter_p = 'No Evidence'
    return inter_p

def p_value_method(x_mean, h0_mean, h0_std, samp_num, siglevel, option='left', precision=4):
    """
    Input: x_mean, h0_mean, h0_std (standard deviation of population), samp_num (sample size), siglevel (alpha), option='left', precision=4):
    Output: zcv, p_value
    """
    z_value = (x_mean - h0_mean) / (h0_std/(samp_num ** 0.5))
    alpha = siglevel
    opt = option.lower()[0]
    if opt == 't':
        # two-tail test
        option = 'Two-Tail Test'
        p_value = (1 - stats.norm.cdf(z_value)) * 2
        if (p_value > 1):
            p_value = (stats.norm.cdf(z_value)) * 2
        zcv = stats.norm.ppf(1 - siglevel/2)
        flag = p_value < alpha
        sub_result = f'''Using {option}:
Difference = {x_mean - h0_mean}
z (Critical value) = {-zcv:.{precision}f}, {zcv:.{precision}f}
z (Observed value) = {z_value:.{precision}f}
p-value = {p_value:.{precision}f} ({inter_p_value(p_value)})
Reject H_0 → {flag}
        '''
    else:
        if opt == 'l':
            option = 'One-Tail Test (left tail)'
            p_value = stats.norm.cdf(z_value)
            zcv = stats.norm.ppf(siglevel)
        elif opt == 'r':
            option = 'One-Tail Test (right tail)'
            p_value = stats.norm.sf(z_value)
            zcv = stats.norm.ppf(1 - siglevel)
        flag = p_value < alpha
        sub_result = f'''Using {option}:
Difference = {x_mean - h0_mean}
z (Critical value) = {zcv:.{precision}f}
z (Observed value) = {z_value:.{precision}f}
p-value = {p_value:.{precision}f} ({inter_p_value(p_value)})
Reject H_0 → {flag}
        '''

    result = f"""======= p-value Method =======
Mean = {x_mean:.{precision}f}
Number of Observation = {samp_num:.{precision}f}
Hypothesized Mean (H0 Mean) = {h0_mean:.{precision}f}
Assumed Standard Deviation = {h0_std:.{precision}f}
Significant Level (alpha) = {siglevel:.{precision}f}

""" + sub_result

    print(result)

    return zcv, p_value

def power_test(x_mean, h0_mean, std, n, alpha, h1_mean, option='left', precision=4, show=True, ignore=True):
    """
    Input: x_mean (not necessary if ignore=True), h0_mean, std, n, alpha, h1_mean, option='left', precision=4, show=True, ignore=True
    Output: type2_p (beta), ptest (power of a test)
    """
    opt = option.lower()[0]
    if opt == 't':
        option = 'Two-Tail Test'
        x_l, x_u = rejection_region_method(
            x_mean, h0_mean, std, n, alpha, option=opt, precision=precision, show=show, ignore=ignore)
        z_value = stats.norm.ppf(1 - alpha / 2)
        z_l = -z_value
        z_u = z_value
        z_type2_l = (x_l - h1_mean) / (std / (n ** 0.5))
        z_type2_u = (x_u - h1_mean) / (std / (n ** 0.5))
        type2_p_l = stats.norm.cdf(z_type2_l)
        type2_p_u = stats.norm.cdf(z_type2_u)
        type2_p = type2_p_u - type2_p_l
        ptest = 1 - type2_p
        result = f'''======= Evaluating Type II Errors ({option}) =======
μ = {h1_mean}
z (lower bound) = {z_type2_l:.{precision}f}
z (upper bound) = {z_type2_u:.{precision}f}

z_l (Lower bound for the critical value) = {z_l:.{precision}f}
z_u (Upper bound for the critical value) = {z_u:.{precision}f}

x_l (Lower bound for x critical value) = {x_l:.{precision}f}
x_u (Upper bound for x critical value) = {x_u:.{precision}f}

P(Type II Error) = {type2_p:.{precision}f}
Power of a Test = {ptest:.{precision}f}
        '''
    else:
        x_c = rejection_region_method(
            x_mean, h0_mean, std, n, alpha, option=opt, precision=precision, show=show, ignore=ignore)
        # if x_c > h1_mean:
        #     opt = 'l'
        # else:
        #     opt = 'r'

        if opt == 'l':
            option = 'One-Tail Test (left tail)'
            z_c = -stats.norm.ppf(1 - alpha)
            z_type2 = (x_c - h1_mean) / (std / (n ** 0.5))
            type2_p = 1 - stats.norm.cdf(z_type2)
            ptest = 1 - type2_p

        elif opt == 'r':
            option = 'One-Tail Test (right tail)'
            z_c = stats.norm.ppf(1 - alpha)
            z_type2 = (x_c - h1_mean) / (std / (n ** 0.5))
            type2_p = stats.norm.cdf(z_type2)
            ptest = 1 - type2_p

        result = f'''======= Evaluating Type II Errors ({option}) =======
μ = {h1_mean}
z = {z_type2:.{precision}f}

z critical value = {z_c:.{precision}f}
x critical value = {x_c:.{precision}f}

P(Type II Error) = {type2_p:.{precision}f}
Power of a Test = {ptest:.{precision}f}
'''

    if show:
        print(result)

    return type2_p, ptest

def sample_size(h0_mean, h1_mean, std, alpha, beta):
    """
    Input: h0_mean, h1_mean, std (population), alpha, beta
    Output: desired sample size
    """
    z_a = stats.norm.ppf(1 - alpha)
    z_b = stats.norm.ppf(1 - beta)
    n = (((z_a + z_b) * (std))**2) / ((h0_mean - h1_mean) ** 2)
    return n

def power_plot(h0_mean, psigma, nsizes, alpha, ranges, option='r', figsize=(12, 6), show=True):
    means, betas, xticks, yticks = type2_plot(
        h0_mean, psigma, nsizes, alpha, ranges, option=option, figsize=figsize, pf=True, label=True, show=show)
    if show:
        plt.clf()
        plt.plot(means, 1 - betas)
        plt.xticks(xticks, rotation=45, fontsize=8)
        plt.yticks(yticks, fontsize=8)
        plt.title('Power Function Curve')
        plt.margins(x=.01, tight=False)

def type2_plot(h0_mean, psigma, nsizes, alpha, ranges, option='right', figsize=(12, 6), pf=False, label=True, show=True):
    """
    Caution: 外面要自己 plt.show()

    Input: h0_mean, psigma, nsizes (list or one value), alpha, ranges, option='right', figsize=(12, 6), pf=False, label=True, show=True
    → set show to false to only get the values for powers
    Output: (if pf=True: means, betas, xticks, yticks)
    """

    try:
        _ = iter(nsizes)
    except TypeError as te:
        nsizes = [nsizes]

    opt = option.lower()[0]
    # options
    if opt == 'r':
        zcv = stats.norm.ppf(1-alpha)
    elif opt == 'l':
        zcv = -stats.norm.ppf(1-alpha)
    elif opt == 't':
        zcv = stats.norm.ppf(1 - alpha / 2)

    means = np.arange(ranges[0], ranges[1], 0.1)
    betas = np.zeros(means.shape[0])
    powers = betas.copy()

    if show:
        fig, ax = plt.subplots(figsize=figsize)

    for nsize in nsizes:
        means = np.arange(ranges[0], ranges[1], 0.1)
        betas = np.zeros(means.shape[0])
        powers = betas.copy()
        i = 0
        if opt == 'r':
            x_c = h0_mean + zcv * psigma / (nsize ** 0.5)
            for h1_mean in means:
                z_type2 = (x_c - h1_mean) / (psigma / (nsize ** 0.5))
                type2_p = stats.norm.cdf(z_type2)
                betas[i] = type2_p
                powers[i] = 1 - type2_p
                i += 1
        elif opt == 'l':
            x_c = h0_mean + zcv * psigma / (nsize ** 0.5)
            for h1_mean in means:
                z_type2 = (x_c - h1_mean) / (psigma / (nsize ** 0.5))
                type2_p = 1 - stats.norm.cdf(z_type2)
                betas[i] = type2_p
                powers[i] = 1 - type2_p
                i += 1
        elif opt == 't':
            x_u = h0_mean + zcv * psigma / math.sqrt(nsize)
            x_l = h0_mean - zcv * psigma / math.sqrt(nsize)
            # x_l, x_u = rejection_region_method(_, h0_mean, psigma, nsize, alpha, option=opt, precision=4, show=False, ignore=True)
            for h1_mean in means:
                z_type2_l = (x_l - h1_mean) / (psigma / (nsize ** 0.5))
                z_type2_u = (x_u - h1_mean) / (psigma / (nsize ** 0.5))
                type2_p_l = stats.norm.cdf(z_type2_l)
                type2_p_u = stats.norm.cdf(z_type2_u)
                type2_p = type2_p_u - type2_p_l
                betas[i] = type2_p
                powers[i] = 1 - type2_p
                i += 1

        if pf:
            if show:
                plt.plot(means, betas, label=f'OC ({nsize})')
                plt.plot(means, powers, label=f'PF ({nsize})')
        else:
            if show:
                plt.plot(means, betas, label=f'n = {nsize}')

    if len(ranges) == 3:
        xticks = np.arange(ranges[0], ranges[1] + 1, ranges[2])
    else:  # default
        xticks = np.arange(ranges[0], ranges[1] + 1, 1)
    yticks = np.arange(0, 1.1, .1)

    if show:
        plt.xlabel("H1 Mean")
        plt.xticks(xticks, rotation=45, fontsize=8)
        plt.yticks(yticks, fontsize=8)
        plt.ylabel("Probability of a Type II Error")
        plt.margins(x=.01, tight=False)
        if label:
            plt.legend()

    if pf:
        return means, betas, xticks, yticks

# 2021-04-18

def _single_pop(url=None):
    quit_signal = ['quit', 'q', 'Q', 'Quit', 'QUIT', 'exit']
    print('Describe a single population:')
    cmd = (input(f'''Data Type?
1. Interval
2. Nominal
'''))
    while(cmd not in quit_signal):
        try:
            cmd = int(cmd)
        except:
            pass
        if cmd == 1:
            cmd = (input(f'''Type of descriptive measurements?
1. Central location
2. Variability
'''))
            try:
                cmd = int(cmd)
            except:
                pass
            dic = {1: 't-test and estimator of mu',
                   2: 'chi2-test and estimator of sigma2'}
            print(dic[cmd])
        elif cmd == 2:
            cmd = (input(f'''Number of categories?
1. Two
2. Two or more
'''))
            try:
                cmd = int(cmd)
            except:
                pass
            dic = {1: 'z-test and estimator of p',
                   2: f'chi2-goodness-of-fit test (code: {url}/MGT2002/workflow/Chi-Squared-Tests-Workflow/#goodness-of-fit-test-a-multinomial-population)'}
            print(dic[cmd])

        break

def _experimental_design(url=None):
    quit_signal = ['quit', 'q', 'Q', 'Quit', 'QUIT', 'exit']
    cmd = (input(f'''Experimental design?
1. Independent samples
2. Matched Pairs
'''))
    while(cmd not in quit_signal):
        try:
            cmd = int(cmd)
        except:
            pass
        if cmd == 1:
            cmd = (input(f'''Population distributions?
1. Normal
2. Nonnormal
'''))
            try:
                cmd = int(cmd)
            except:
                pass
            dic = {
                1: _variance, 2: f'Wilcoxon Rank Sum Test ({url}/MGT2002/Chap-19-Nonparametric-Methods/#python-code-for-wilcoxon-rank-sum-test | code: {url}/MGT2002/workflow/Nonparametric-Methods-Workflow/#wilcoxon-rank-sum-test)'}
            if callable(dic[cmd]):
                dic[cmd](url=url)
            else:
                print(dic[cmd])
            # if type(dic[cmd]) == str:
            #     print(dic[cmd])
        elif cmd == 2:
            cmd = (input(f'''Distribution of differences?
1. Normal
2. Nonnormal
'''))
            try:
                cmd = int(cmd)
            except:
                pass
            dic = {1: f't-test and estimator of \mu_D (code: {url}/MGT2002/workflow/Difference-Between-Two-Means/#matched-pair-experiment)',
                   2: f'Wilcoxon Signed Rank Sum Test (code: {url}/MGT2002/workflow/Nonparametric-Methods-Workflow/#wilcoxon-signed-rank-sum-test)'}
            print(dic[cmd])

        break

def _variance(url=None):
    quit_signal = ['quit', 'q', 'Q', 'Quit', 'QUIT', 'exit']
    cmd = (input(f'''Population variances?
1. Equal
2. Unequal
'''))
    while(cmd not in quit_signal):
        try:
            cmd = int(cmd)
        except:
            pass
        dic = {1: f't-test and estimator of \mu_1 - \mu_2 (equal-variances) ({url}/MGT2002/Chap-13-Inference-about-Comparing-Two-Population/#python-code-and-interpretation) \n code: {url}/MGT2002/workflow/Difference-Between-Two-Means/#independent-samples',
               2: f't-test and estimator of \mu_1 - \mu_2 (unequal-variances) ({url}/MGT2002/Chap-13-Inference-about-Comparing-Two-Population/#python-code-and-interpretation) \n code: {url}/MGT2002/workflow/Difference-Between-Two-Means/#independent-samples'}
        print(dic[cmd])
        break

def _two_pop(url=None):
    quit_signal = ['quit', 'q', 'Q', 'Quit', 'QUIT', 'exit']
    cmd = (input(f'''Data Type?
1. Interval
2. Ordinal
3. Nominal
'''))
    while(cmd not in quit_signal):
        try:
            cmd = int(cmd)
        except:
            pass
        if cmd == 1:
            cmd = (input(f'''Type of descriptive measurements?
1. Central location
2. Variability
'''))
            try:
                cmd = int(cmd)
            except:
                pass
            dic = {1: _experimental_design,
                   2: 'chi2-test and estimator of sigma2'}
            if callable(dic[cmd]):
                dic[cmd](url=url)
            else:
                print(dic[cmd])
            # if type(dic[cmd]) == str:
            #     print(dic[cmd])
        elif cmd == 2:
            cmd = (input(f'''Experimental Design?
1. Independent samples
2. Matched pairs
'''))
            try:
                cmd = int(cmd)
            except:
                pass
            dic = {
                1: f'Wilcoxon Rank Sum Test ({url}/MGT2002/Chap-19-Nonparametric-Methods/#python-code-for-wilcoxon-rank-sum-test | code: {url}/MGT2002/workflow/Nonparametric-Methods-Workflow/#wilcoxon-rank-sum-test)', 2: f'Sign Test (code: {url}/MGT2002/workflow/Nonparametric-Methods-Workflow/#sign-test)'}
            print(dic[cmd])
        elif cmd == 3:
            cmd = (input(f'''Number of categories?
1. Two
2. Two or more
'''))
            try:
                cmd = int(cmd)
            except:
                pass
            dic = {
                1: f'z-test and estimator of p_1 - p_2 ({url}/MGT2002/Chap-13-Inference-about-Comparing-Two-Population/#python-code-and-interpretation_2) \n code: {url}/MGT2002/workflow/Difference-Between-Two-Means/#the-difference-between-two-population-proportions', 2: f'chi2-test of a contingency table ({url}/MGT2002/Chap-15-Chi-Squared-Tests/#python-code-for-contingency-test | code: {url}/MGT2002/workflow/Chi-Squared-Tests-Workflow/#tests-of-independence-contingency-table)'}
            print(dic[cmd])

        break

def _pop_dist(url=None):
    quit_signal = ['quit', 'q', 'Q', 'Quit', 'QUIT', 'exit']
    cmd = (input(f'''Population distributions?
1. Normal
2. Nonnormal
'''))
    while(cmd not in quit_signal):
        try:
            cmd = int(cmd)
        except:
            pass
        dic = {1: f'One-way and two-factor analysis of variance \nOne-way: {url}/MGT2002/Chap-14-I-Analysis-of-Variance-ANOVA/#testing-for-the-equality-of-k-population-means | code: {url}/MGT2002/workflow/One-way-ANOVA-workflow/#one-way-anova-workflow \nTwo-factor: | code: {url}/MGT2002/workflow/Two-way-ANOVA-workflow/#two-way-anova-two-factors-workflow',
               2: f'Kruskal-Wallis Test (code: {url}/MGT2002/workflow/Nonparametric-Methods-Workflow/#kruskal-wallis-test)'}
        print(dic[cmd])
        break

def _pop_dist_2(url=None):
    quit_signal = ['quit', 'q', 'Q', 'Quit', 'QUIT', 'exit']
    cmd = (input(f'''Population distributions?
1. Normal
2. Nonnormal
'''))
    while(cmd not in quit_signal):
        try:
            cmd = int(cmd)
        except:
            pass
        dic = {
            1: f'Randomized block analysis of variance ({url}/MGT2002/Chap-14-II-Analysis-of-Variance-ANOVA/#randomized-block-anova-test | code: {url}/MGT2002/workflow/Two-way-ANOVA-Randomized-Block-workflow/#two-way-anova-randomized-block-workflow)', 2: f'Friedman Test (code: {url}/MGT2002/workflow/Nonparametric-Methods-Workflow/#friedman-test)'}
        print(dic[cmd])
        break

def _two_or_more_pop(url=None):
    quit_signal = ['quit', 'q', 'Q', 'Quit', 'QUIT', 'exit']
    cmd = (input(f'''Data Type?
1. Interval
2. Ordinal
3. Nominal
'''))
    while(cmd not in quit_signal):
        try:
            cmd = int(cmd)
        except:
            pass
        if cmd == 1:
            cmd = (input(f'''Experimental design?
1. Independent samples
2. Blocks
'''))
            try:
                cmd = int(cmd)
            except:
                pass
            dic = {1: _pop_dist, 2: _pop_dist_2}
            if callable(dic[cmd]):
                dic[cmd](url=url)
            else:
                print(dic[cmd])

        elif cmd == 2:
            cmd = (input(f'''Experimental Design?
1. Independent samples
2. Blocks
'''))
            try:
                cmd = int(cmd)
            except:
                pass
            dic = {
                1: f'Kruskal-Wallis Test (code: {url}/MGT2002/workflow/Nonparametric-Methods-Workflow/#kruskal-wallis-test)', 2: f'Friedman Test (code: {url}/MGT2002/workflow/Nonparametric-Methods-Workflow/#friedman-test)'}
            print(dic[cmd])
        elif cmd == 3:
            print(
                f'chi2-test of a contingency table ({url}/MGT2002/Chap-15-Chi-Squared-Tests/#python-code-for-contingency-test | code: {url}/MGT2002/workflow/Chi-Squared-Tests-Workflow/#tests-of-independence-contingency-table)')

        break

def _relationship(url=None):
    quit_signal = ['quit', 'q', 'Q', 'Quit', 'QUIT', 'exit']
    cmd = (input(f'''Data Type?
1. Interval
2. Ordinal
3. Nominal
'''))
    while(cmd not in quit_signal):
        try:
            cmd = int(cmd)
        except:
            pass
        if cmd == 1:
            cmd = (input(f'''Population distributions?
1. Error is normal or x and y bivariate normal
2. x and y not bivariate normal
'''))
            try:
                cmd = int(cmd)
            except:
                pass
            dic = {1: f'Simple linear regression and correlation (code: {url}/MGT2002/workflow/Nonparametric-Methods-Workflow/#pearson-correlation-coefficient)',
                   2: f'Spearman rank correlation (code: {url}/MGT2002/workflow/Nonparametric-Methods-Workflow/#spearman-rank-correlation-coefficient)'}
            if type(dic[cmd]) == str:
                print(dic[cmd])
        elif cmd == 2:
            print(
                f'Spearman rank correlation (code: {url}/MGT2002/workflow/Nonparametric-Methods-Workflow/#spearman-rank-correlation-coefficient)')
        elif cmd == 3:
            print(
                f'chi2-test of a contingency table ({url}/MGT2002/Chap-15-Chi-Squared-Tests/#python-code-for-contingency-test | code: {url}/MGT2002/workflow/Chi-Squared-Tests-Workflow/#tests-of-independence-contingency-table)')

        break

def which(location='local'):
    url = ''
    if (location == 'local'):
        url += 'http://127.0.0.1:8000'
    else:
        url = location
    quit_signal = ['quit', 'q', 'Q', 'Quit', 'QUIT', 'exit']
    cmd = input(f'''Declare objective:
1. Describe a single population
2. Comapre two populations
3. Compare two or more populations
4. Analyze relationships between two variables

(type 'quit' to quit the program)
''')
    while(cmd not in quit_signal):
        try:
            cmd = int(cmd)
        except:
            pass
        if cmd == 1:
            _single_pop(url=url)
        elif cmd == 2:
            _two_pop(url=url)
        elif cmd == 3:
            _two_or_more_pop(url=url)
        elif cmd == 4:
            _relationship(url=url)

        cmd = input(f'''=======
Declare objective:
1. Describe a single population
2. Comapre two populations
3. Compare two or more populations
4. Analyze relationships between two variables

(type 'quit' to quit the program)
''')


"""
This script shows how to do linear regression with tensorflow.
"""

import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
import seaborn as sns

# Generating some sample data
train_X = np.array([3.3,4.4,5.5,6.71,6.93,4.168,9.779,6.182,7.59,2.167,7.042,10.791,5.313,7.997,5.654,9.27,3.1])
train_Y = np.array([1.7,2.76,2.09,3.19,1.694,1.573,3.366,2.596,2.53,1.221,2.827,3.465,1.65,2.904,2.42,2.94,1.3])
num_samples = train_X.shape[0]

# Parameters
learning_rate = 0.01
iterations = 2000

### ------ ** Creating the graph ** -------

# Placeholders - where the data can come into the graph
X = tf.placeholder(tf.float32, [None])
Y = tf.placeholder(tf.float32, [None])

# Creating the parameters for y = mx + c
c = tf.Variable(np.random.randn(), name='c')
m = tf.Variable(np.random.randn(), name='m')

# Creating the model: y = mx + c
model = tf.add(c, tf.multiply(m, X))

# Creating the cost function
cost_function = 0.5 * (1.0/num_samples) * tf.reduce_sum(tf.pow(model - Y, 2))

# Defining the method to do the minimisation of the cost function
optimiser = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost_function)

### -------- ** Initialising all the variables ** --------

init = tf.global_variables_initializer()

### -------- ** Starting the session ** ----------

with tf.Session() as sess:
    sess.run(init)

    for i in range(iterations):
        sess.run(optimiser, feed_dict={X: train_X, Y: train_Y})

        if (i+1)%50 == 0:
            cost = sess.run(cost_function, feed_dict={X: train_X, Y: train_Y})
            print("Step:", '%04d' % (i+1), "cost=", "{:.9f}".format(cost), "m=", sess.run(m), "c=", sess.run(c))

    slope = sess.run(m)
    intercept = sess.run(c)

### -------- ** Plotting the results ** ------------

sns.set()
fig, ax = plt.subplots()
ax.scatter(train_X, train_Y)
plot_x = [np.amin(train_X)-2, np.amax(train_X)+2]
plot_y = [slope*plot_x[0] + intercept , slope*plot_x[1] + intercept ]
ax.plot(plot_x, plot_y, color=sns.xkcd_rgb["medium green"])
ax.set_xlabel('x')
ax.set_ylabel('y')

plt.show()


import numpy as np

def softmax_single_array(N = 5):
    """
    Returns the probability of each of the output nodes
    """
    #Final output of a neural network
    a = np.random.randn(5)
    # These values represent the output of the output neurons
    #start by exponetiating the values:
    exp_a = np.exp(a)
    prob_answer_sin = exp_a / exp_a.sum()

    return prob_answer_sin

def softmax_matrix(N = 100, D = 5):
    """
    Returns the probability of each of the output nodes for a matrix of output
    """
    #100 samples in 5 classes
    a = np.random.randn(N, D)

    exp_a = np.exp(a)

    #Use this to sum along the rows, the keepdims is so (100,5) (100,) can be added
    prob_answer_mat = exp_a / exp_a.sum(axis = 1, keepdims = True)

    #shows that each row sums to 1
    print(prob_answer_mat.sum(axis = 1))


import sympy as sp

print('ompute length of an arc of planar curve by SymPy definite integrate')
print('Example 1-04 arclength')
print('Length of arc of parametric curve x(t)=cos^3(t) and y(t)=sin^3(t) from t=0 to t=2pi')

t = sp.Symbol('t')
x = sp.cos(t) ** 3
y = sp.sin(t) ** 3
dx_dt = sp.diff(x, t)
dy_dt = sp.diff(y, t)
print('Symbolic derivative of x(t) is ', dx_dt)
print('Symbolic derivative of y(t) is ', dy_dt)

integrand = sp.sqrt(dx_dt**2 + dy_dt ** 2)
a = 0.
b = 2 * sp.pi
integral = sp.integrate(integrand, (t, a, b))
integral = integral.evalf()
print('Result is ', integral)


import numpy as np
from math import sin, cos, atan, pi
def prvect(v):
	print("( ", end="")
	for i in v:
		print(format(float(i), ".4f"), end=" ")
	print(")")

def prmatr(m):
	print("[")
	for i in m:
		prvect(i)
	print("]")

x0 = np.array([0, 0])

F_str = ["{0}-1/2*sin(({0}-{1})/2)", "{1}-1/2*cos(({0}+{1})/2)"]

F = np.array([
	x0[0]-1/2*sin((x0[0]-x0[1])/2),
	x0[1]-1/2*cos((x0[0]+x0[1])/2)
])

A = np.array([
	[1 - 1/4*cos((x0[0]-x0[1])/2), 1/4*cos((x0[0]-x0[1])/2)],
	[1/4*sin((x0[0]+x0[1])/2), 1+1/4*sin((x0[0]+x0[1])/2)]
])

A1 = np.linalg.inv(A)

x = x0 - A1.dot(F)
x_p = x0.copy()

eps = 10e-8

i = 0

print("Modified Newton")
print(F_str[0].format("x", "y"), "= 0")
print(F_str[1].format("x", "y"), "= 0", end="\n")

print("\nA: ")
prmatr(A)

print("\nF'(x0):")
prvect(F)

print("\n")

while np.linalg.norm(x-x_p) > eps and i < 100:

	x_p = x.copy()
	F[0] = eval(F_str[0].format(x_p[0], x_p[1]))
	F[1] = eval(F_str[1].format(x_p[0], x_p[1]))
	x = x - A1.dot(F)
	i+=1

print("Answer:")
prvect(x)

print("Steps: ", i)

print("\n======================\n")

def find_absmax(A):
	temp = np.absolute(A)
	max_el = -1
	ind = (0, 1)
	for i in range(len(temp)):
		for j in range(len(temp[i])):
			if i == j:
				continue
			if temp[i, j] > max_el:
				max_el = temp[i, j]
				ind = (i, j)
	return ind

def find_max(A):
	temp = np.absolute(A)
	max_el = -1
	for i in range(len(temp)):
		for j in range(len(temp[i])):
			if i == j:
				continue
			if temp[i, j] > max_el:
				max_el = temp[i, j]
	return max_el

def count_non_d(A):
	res = 0
	for i in range(len(A)):
		for j in range(len(A[i])):
			if i == j:
				continue
			res += A[i, j] ** 2
	return res

print("Jacobi\n")

print("A0:")

A = np.array([
	[5, -1, 1],
	[-1, 0, -2],
	[1, -2, 1]
])
prmatr(A)
count = 1
while count < 10000 and find_max(A) > eps:
	count+=1
	V = np.eye(3)

	i0, j0 = find_absmax(A)
	phi = 1/2*atan(2*A[i0, j0]/(A[i0, i0] - A[j0, j0])) if A[i0, i0] != A[j0, j0] else pi/4

	V[i0, i0] = cos(phi)
	V[i0, j0] = -sin(phi)
	V[j0, i0] = sin(phi)
	V[j0, j0] = cos(phi)

	A = V.T.dot(A.dot(V))
print("\nResult:")
prmatr(A)
print("\nSteps: ", count)


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

tn = 1000  # end time
h = 1  # step size
t = np.arange(start=0, stop=tn, step=h, dtype=float)  # time array

m0: float = 1  # initial mass
t12: float = 100  # half life
dc = math.log(2) / t12  # decay constant

m_1 = np.zeros([tn])  # array for analytical solution

m_2 = np.zeros([tn])  # array for numerical solution
m_2[0] = m0

for i in range(0, tn):  # solving decay equation analytically
    m_1[i] = m0 * math.exp(- dc * t[i])

for j in range(1, tn):  # solving decay equation numerically
    m_2[j] = m_2[j-1] + h * (-dc * m_2[j-1])

# mpl.plot(t, m_1) # plotting analytical solution
plt.plot(t, m_2)  # plotting numerical solution
plt.xlabel("seconds")
plt.ylabel("mass (kg)")
plt.title("Spontaneous nuclear decay")
plt.show()  # showing graph


"""
Created on Tue Sep 14 2018
@author: Supratim Haldar
@Description: My implementation of logistic regression (classifier) algorithm
"""
import numpy as np
from scipy import optimize

# =============================================================================
# Function to calculate value Sigmoid Function of any variable z.
# z can be a matrix, vector or scalar
# sigmoid g(z) = 1/(1 + e^-z)
# =============================================================================
def sigmoid(z):
    sig = 1.0/(1.0 + np.exp(-z))

    # Due to floating point presision related issues, e^-z might return very
    # small or very large values, resulting in sigmoid = 1 or 0. Since we will
    # compute log of these values later in cost function, we want to avoid
    # sig = 1 or 0, and hardcode to following values instead.
    sig[sig == 1.0] = 0.9999
    sig[sig == 0.0] = 0.0001

    return sig

# =============================================================================
# Compute cost of Logistic Regression with multiple features
# Vectorized implementation
# Input: data_X = mxn matrix, data_y = m-dim vector, theta = n-dim vector
# Output: cost = 1-dim vector
# =============================================================================
def computeCost(theta, data_X, data_y, lambda_reg = 0):
    m = len(data_X) # No of rows
    n = len(data_X[0]) # No of features
    theta = theta.reshape(n,1)

    # h(x) = g(z) = g(theta0 + theta1*X1 + theta2*X2 + .. + thetan*Xn)
    # h(x) = g(X * theta) = Sigmoid(X * theta) = m-dim vector
    hx = sigmoid(np.dot(data_X, theta))
    cost = - np.dot(data_y.T, np.log(hx)) - np.dot((1 - data_y).T, np.log(1 - hx))

    # This is unregularized cost
    J = cost/m

    # Adding regularization. Setting theta0 to 0, because theta0 will not be
    # regularized
    J_reg = (lambda_reg/(2*m)) * np.dot(theta[1:,:].T, theta[1:,:])
    J = J + J_reg

    return J

# =============================================================================
# Compute gradient or derivative of cost function over parameter, i.e.
# d J(Theta)/d Theta
# =============================================================================
def computeGradient(theta, data_X, data_y, lambda_reg = 0):
    m = len(data_X) # No of rows
    n = len(data_X[0]) # No of features
    theta = theta.reshape(n,1)
    theta_gradient = np.zeros(theta.shape)
    cost = 0
    #print("==== Inside computeGradient() ====", data_X.shape, data_y.shape)

    cost = computeCost(theta, data_X, data_y, lambda_reg)

    hx = sigmoid(np.dot(data_X, theta))
    error = hx - data_y
    theta_gradient = (1/m) * (np.dot(data_X.T, error))

    # Apply regularization
    theta_reg = (lambda_reg/m) * theta[1:,:]
    theta_gradient[1:,:] = theta_gradient[1:,:] + theta_reg

    #print("==== Inside computeGradient() ====", cost)
    return cost.flatten(), theta_gradient.flatten()

# =============================================================================
# Gradient Descent of Linear Regression with multiple features
# Vectorized implementation
# Input: data_X = mxn matrix, data_y = m-dim vector, theta = n-dim vector
# alpha = learning rate, num_iters = no of iterations/steps for GD
# Output: theta = n-dim vector,
# J_history = cost at each iteration, a num_iters-dim vector
# =============================================================================
def gradientDescent(theta, data_X, data_y, alpha, num_iters, lambda_reg = 0):
    m = len(data_X) # No of rows
    J_history = np.zeros([num_iters, 1])

    for i in range(num_iters):
        hx = np.zeros(data_y.shape)
        error = np.zeros(data_y.shape)
        theta_change = np.zeros(theta.shape)

        hx = sigmoid(np.dot(data_X, theta))
        error = hx - data_y
        theta_change = (alpha/m) * (np.dot(data_X.T, error))

        # Apply regularization
        temp = theta[0,0]
        theta[0,0] = 0
        theta_reg = (lambda_reg/m) * theta
        theta[0,0] = temp;
        theta_change = theta_change + theta_reg

        theta = theta - theta_change

        J_history[i] = computeCost(theta, data_X, data_y, lambda_reg)

    return theta, theta_change, J_history

# =============================================================================
# Predict results based on test input feature and parameter values
# Compare with output results, if already available
# =============================================================================
def predict(theta, data_X, data_y):
    prob = sigmoid(np.dot(data_X, theta))
    pred = prob >= 0.5
    accuracy = np.mean((pred == data_y)) * 100
    print("Predict: Prediction Accuracy % =", accuracy)
    return pred, accuracy

# =============================================================================
# One vs All method of logistic regression
# Used for data with multiple clssification outputs
# =============================================================================
def oneVsAll(data_X, data_y, num_labels, lambda_reg):
    n = data_X.shape[1] # No of features
    all_theta = np.zeros([num_labels, n])
    initial_theta = np.zeros([n, 1])
    print("OneVsAll: Shape of X and y: ", data_X.shape, data_y.shape)

    for label in range(num_labels):
        # Calling advanced optimization alogorith to converge gradient
        theta_optimized = optimize.minimize( \
            computeGradient, \
            initial_theta, \
            args=(data_X, data_y == label, lambda_reg), \
            method = "CG",
            jac=True, options={'disp': True, 'maxiter': 100} \
            )
        print("OneVsAll: Optimization Result =", theta_optimized.message, theta_optimized.success)
        theta = theta_optimized.x.reshape(n, 1)
        all_theta[label,:] = theta.T

    return all_theta


'''
Copyright 2019 Agnese Salutari.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License
'''

__author__ = 'Agnese Salutari'

# Dependencies:
from SalutariDiscreteLogProblemSolver import DiscreteLogProblemSolver as DLPS
from random import randint
import sympy

'''
    This file is a SalutariDiscreteLogProblemSolver tester and example of usage.
    The problems SalutariDiscreteLogProblemSolver can solve are Discrete Logarithms Problems:
        a^(x) = b (mod n)
        We know a, b and n; we have to find x.
        Salutari's algorithm solve the problem, step by step, using the following formula:
            a^(x - stepsNumber) = b * a^(-1) (mod n)
        It finds x when it reaches the final form, that is the following:
            a^(x - stepsNumber) = a^(y) (mod n)
            => x = y + stepsNumber
        It works if n is a prime (n = p) and if n is the product of two primes (n = p1 * p2).

        If n is the product of two primes (n = p1 * p2):
            a^(x) = b (mod n);   n = p1 * p2 = product pf two prime numbers
                if a = p1 * k, where k is an integer
                then we have: (p1 * k)^(x) = b (mod p1 * p2).
                So, for the Chinese Remainder Theorem, this can be written as a system of the following two equations:
                    (p1 * k)^(x) = 0^(x) = 0 = b (mod p1)
                    (p1 * k)^(x) = b (mod p2)
                The first equation is 0 = 0.
                We can solve the second equation modulus p2 and then find the solution to the original problem
                (modulus p1 * p2) with Chinese Remainder Theorem:
                    x = x1 + (j * (p1 - 1)) mod (n)
'''

print('PROBLEM 1: Book Problem (n is a prime)')
a = 7
b = 12
n = 41
x = 13
print('a = ' + str(a))
print('b = ' + str(b))
print('n = ' + str(n))
solver = DLPS(a=a, b=b, n=n)
solver.printProblemOnFile(problemTitle='Problem 1 (x=' + str(x) + ').', overwrite=True)
solver.solve()
solver.printSolutionsOnFile(problemTitle='Problem 1 (x=' + str(x) + ').', overwrite=True)

print('###################################################################')

print('PROBLEM 2: Toy Problem (n is a prime)')
a = 2
x = 8
n = 11
b = a ** x % n
print('a = ' + str(a))
print('x = ' + str(x))
print('b = ' + str(b))
print('n = ' + str(n))
solver = DLPS(a=a, b=b, n=n)
solver.printProblemOnFile(problemTitle='Problema 2 (x=' + str(x) + ').')
solver.solve()
solver.printSolutionsOnFile(problemTitle='Problema 2 (x=' + str(x) + ').')

print('###################################################################')

print('PROBLEM 3: Random Problem (n is a prime)')
a = sympy.randprime(10, 100)
x = sympy.randprime(10, 100)
n = sympy.randprime(100, 1000)
b = a ** x % n
print('a = ' + str(a))
print('x = ' + str(x))
print('b = ' + str(b))
print('n = ' + str(n))
solver = DLPS(a=a, b=b, n=n)
solver.printProblemOnFile(problemTitle='Problema 3 (x=' + str(x) + ').')
solver.solve()
solver.printSolutionsOnFile(problemTitle='Problema 3 (x=' + str(x) + ').')

print('###################################################################')

print('PROBLEM 4: Random Problem (n is a prime)')
a = sympy.randprime(100, 1000)
x = sympy.randprime(1000, 10000)
n = sympy.randprime(10000, 100000)
b = a ** x % n
print('a = ' + str(a))
print('x = ' + str(x))
print('b = ' + str(b))
print('n = ' + str(n))
solver = DLPS(a=a, b=b, n=n)
solver.printProblemOnFile(problemTitle='Problema 4 (x=' + str(x) + ').')
solver.solve()
solver.printSolutionsOnFile(problemTitle='Problema 4 (x=' + str(x) + ').')

print('###################################################################')

print('PROBLEM 5: Random Problem (n is a prime)')
a = sympy.randprime(10000, 100000)
x = sympy.randprime(100000, 1000000)
n = sympy.randprime(1000000, 10000000)
b = a ** x % n
print('a = ' + str(a))
print('x = ' + str(x))
print('b = ' + str(b))
print('n = ' + str(n))
solver = DLPS(a=a, b=b, n=n)
solver.printProblemOnFile(problemTitle='Problema 5 (x=' + str(x) + ').')
solver.solve()
solver.printSolutionsOnFile(problemTitle='Problema 5 (x=' + str(x) + ').')

print('###################################################################')

print('PROBLEM 6: Random Problem where: n=2*7, a=k*2, k is a random integer')
p1 = 2
p2 = 7
n = p1 * p2
k = randint(1, 1000) % n
a = (k * p1) % n
x = sympy.randprime(1, n)
b = a ** x % n
print('a = ' + str(a))
print('k = ' + str(k))
print('x = ' + str(x))
print('b = ' + str(b))
print('n = ' + str(n))
solver = DLPS(a=a, b=b, n=n)
solver.printProblemOnFile(problemTitle='Problem 6 (x=' + str(x) + ', n=2*7, a=k*2, k is a random integer).')
solver.solve()
solver.printSolutionsOnFile(problemTitle='Problem 6 (x=' + str(solver.getSolutions()) +
                                        ', n=2*7, a=k*2, k is a random integer).')

print('###################################################################')

print('PROBLEM 7: Random Problem with: n=p1*p2, a=k*p1, p1 and p2 are primes, k is a random integer')
p1 = sympy.randprime(100, 1000)
p2 = sympy.randprime(100, 1000)
n = p1 * p2
k = randint(1, 100)
a = k * p1
x = sympy.randprime(1, n)
n = p1 * p2
b = a ** x % n
print('a = ' + str(a))
print('k = ' + str(k))
print('x = ' + str(x))
print('b = ' + str(b))
print('n = ' + str(n) + ' = ' + str(p1) + ' * ' + str(p2))
solver = DLPS(a=a, b=b, n=n)
solver.printProblemOnFile(problemTitle='Problem 7 (x=' + str(x) + ', n=' + str(p1) + '*' + str(p2) +
                                       ', a=k*p1, p1 e p2 primi, k is a random integer).')
solver.solve()
solver.printSolutionsOnFile(problemTitle='Problem 7 (x=' + str(x) + ', n=' + str(p1) + '*' + str(p2) +
                                       ', a=k*p1, p1 e p2 are primes, k is a random integer).')


import numpy as np
from scipy.optimize import newton_krylov
import matplotlib.pyplot as plt
from scipy import special # special function
from numpy import cosh, zeros_like, mgrid, zeros, ones
# "linspace" return evenly spaced numbers over a specified interval
# "mgrid" returns a dense multi-dimensional mesh grid
# ”zeros_like” return an array of zeros with the same shape and type as a given array
# "zeros" return a new array of given shape and type, filled with zeros
# "ones" return a new array of given shape and type, filled with ones

global source

# simulation parameters
Lx, Ly = 1, 1
nx, ny = 35, 35
hx, hy = Lx/(nx), Ly/(ny+1)
print("Domain length:", Lx, Ly)
print("Number of vertices:", nx, ny)
print("Increments:", hx, hy)

# boundary condition
P_left, P_right = 0, 0
P_top, P_bottom = 0, 0

# set meshgrid
x = np.linspace(0, Lx, nx)
y = np.linspace(hy, Ly - hy, ny)
xv, yv = np.meshgrid(x, y, indexing = 'ij')
#xv, yv = mgrid[0 : Lx : (nx * 1j), hy : Ly - hy : (ny * 1j)]

def d1nc(N):
    d1x = zeros_like(N)
    d1x[:-1] = np.diff(N, axis = 0)/hx
    d1x[:-1] *= (xv[:-1] + xv[1:])/2.0
    #d1x[-1] = 0
    return d1x

def d1cn(C):
    d1x = zeros_like(C)
    d1x[1:] = np.diff(C, axis = 0)/hx
    d1x[1:] /= xv[1:]
    # Apply Neumann Conditions
    d1x[0] = 0
    return d1x

def d2nn(N,N_right):
    d1x = zeros_like(N)
    d1x[:-1] = np.diff(N, axis = 0)/hx
    d1x[-1] = (N_right - N[-1])/hx
    d1x[:-1] *= (xv[:-1] + xv[1:])/2.0

    d2x = zeros_like(d1x)
    d2x[1:] = np.diff(d1x, axis = 0)/hx
    d2x[1:] /= xv[1:]
    # Apply Axis
    d2x[0] = 4 * (N[1] - N[0])/hx/hx
    return d2x

def residual(P):
    d1x = zeros_like(P)
    d2x = zeros_like(P)
    d2y = zeros_like(P)

    #
    #d2x[1:-1] = (P[2:]   - 2*P[1:-1] + P[:-2]) / hx/hx
    #d2x[0]    = (P[1]    - 2*P[0]    + P_left)/hx/hx
    #d2x[-1]   = (P_right - 2*P[-1]   + P[-2])/hx/hx

    #d1x = d1nc(P)
    #d2x = d1cn(d1x)
    #

    d2x = d2nn(P, P_right)

    d2y[:, 1:-1] = (P[:, 2:] - 2*P[:, 1:-1]  + P[:, :-2])/hy/hy
    d2y[:, 0]    = (P[:, 1]  - 2*P[:, 0]     + P_bottom)/hy/hy
    d2y[:, -1]   = (P_top    - 2*P[:, -1]    + P[:, -2])/hy/hy

    return d2x + d2y  + source

def laplacian(P):
    d1x = zeros_like(P)
    d2x = zeros_like(P)
    d2y = zeros_like(P)

    #
    #d2x[1:-1] = (P[2:]   - 2*P[1:-1] + P[:-2])/hx/hx
    #d2x[0]    = (P[1]    - 2*P[0]    + P_left)/hx/hx
    #d2x[-1]   = (P_right - 2*P[-1]   + P[-2])/hx/hx

    #d1x = d1nc(P)
    #d2x = d1cn(d1x)
    #d2x = d2nn(P)
    #

    d1x[:-1] = np.diff(P, axis = 0)/hx
    d1x[:-1] *= (xv[:-1] + xv[1:])/2.0
    #d1x[-1] = 0
    d2x = zeros_like(d1x)
    d2x[1:] = np.diff(d1x, axis = 0)/hx
    d2x[1:] /= xv[1:]

    # Apply Axis
    d2x[0] = 4 * (P[1] - P[0])/hx/hx
    d2x = d2nn(P)
    d2y[:, 0]    = (P[:, 1]  - 2 * P[:, 0]    + P_bottom)/hy/hy
    d2y[:, -1]   = (P_top    - 2 * P[:, -1]   + P[:, -2])/hy/hy

    return d2x + d2y

# solve
kth_zero = special.jn_zeros(0, 1)[-1]
print("kth_zero:", kth_zero)
Bg = pow(np.pi/Ly, 2) + pow(kth_zero/Lx, 2)
source = np.sin(np.pi * yv/Ly) * special.jv(0, kth_zero * xv/Lx)
guess = zeros((nx, ny), float)
sol = newton_krylov(residual, guess, method='lgmres', verbose=1)

print('Residual: %g' % abs(residual(sol)).max())
print('Bg = ', np.max(source)/Bg, '  Numerical = ', np.max(sol))

# visualize
plt.figure(0)
plt.pcolor(xv, yv, sol)
plt.colorbar()

plt.figure(1)
#plt.plot(source.T/Bg/sol)
plt.pcolor(xv, yv, source/Bg)
plt.colorbar()

#plt.figure(2)
#plt.plot(x, sol)
#plt.figure(3)
#plt.plot(x, source/Bg)

plt.show()


import numpy as np

O = np.zeros((2, 3))

A = np.array([
    [2, -2, 5],
    [0, -1, 7],
])

B = np.array([
    [-1, 3, 7],
    [2, -9, 6],
])

C = np.array([
    [9, 5, 8],
    [-6, -1, 6],
])

print("a) A + B")
print(A + B)
print()

print("b) (A + B) + C")
print((A + B) + C)
print()

print("c) A + (B + C)")
print(A + (B + C))
print()

print("d) B + (C + A)")
print(B + (C + A))
print()

print("e) C + O")
print(C + O)

print("f) B + B + B + B + B")
print(B + B + B + B + B)
print()

print("g) 5B")
print(5 * B)
print()

print("h) C + -C")
print(C + -C)
print()


from numpy import *

def compute_error(b, m, points):
    """Compute MSE by given model

    :param b: scalar
    :param m: intercept
    :param points: dataset list
    :return: mean squared error
    """
    sum_error = 0
    N = len(points)

    # compute the sum of squared residuals
    for i in range(N):
        x = points[i, 0]
        y = points[i, 1]
        sum_error += (y - (m * x + b)) ** 2

    # mean squared error
    return sum_error / N

def step_gradient(current_b, current_m, points, learning_rate=0.0001):
    """The gradient step.

    This function update intercept and scalar by using the partial
    derivative of the cost function wrt to b and m.

    :param current_b: current scalar error
    :param current_m: current intercept
    :param points: dataset list
    :param learning_rate: convergence speed
    :return: updated intercept and updated scalar
    """
    gradient_b = 0
    gradient_m = 0
    N = len(points)

    for i in range(N):
        x = points[i, 0]
        y = points[i, 1]

        # partial derivative of the cost function wrt b and m
        gradient_b += -(2 / N) * (y - (current_m * x + current_b))
        gradient_m += -(2 / N) * x * (y - (current_m * x + current_b))

        new_b = current_b - (learning_rate * gradient_b)
        new_m = current_m - (learning_rate * gradient_m)

    return new_b, new_m

def gradient_decent_runner(points, starting_b=0, starting_m=0,
                           learning_rate=0.0001,
                           num_iteration=10000):
    """The gradient descent calculation process

    Results are calculated by gradient steps
    and should converge to optimal (according to number of iteration).

    :param points: dataset list
    :param starting_b: starting scalar
    :param starting_m: starting intercept
    :param learning_rate: convergence speed
    :param num_iteration: number of gradient descent steps
    :return: result intercept and scalar
    """
    # initialization
    b = starting_b
    m = starting_m

    # gradient descent
    for i in range(num_iteration + 1):
        # update b and m
        if i % 100 == 0:
            print('After {} iteration, b = {}, m = {}'.format(i, b, m))
        b, m = step_gradient(b, m, array(points), learning_rate)

    return [b, m]

def run(path, learning_rate=0.0001, initial_b=0, initial_m=0,
        num_iteration=10000):
    """Run a single linear regression process

    :param path: csv file path
    :param learning_rate: convergence speed
    :param initial_b: initial value of scalar
    :param initial_m: initial value of intercept
    :param num_iteration: number of gradient descent steps
    :return: None
    """
    points = genfromtxt(path, delimiter=',')

    print('Starting gradient descent at b = {}, m = {}, error = {}'.format(
        initial_b, initial_m, compute_error(initial_b, initial_m, points)))
    [b, m] = gradient_decent_runner(points, initial_b, initial_m, learning_rate, num_iteration)

    print('Ending point at b = {}, m = {}, error = {}'.format(b, m, compute_error(b, m, points)))

if __name__ == '__main__':
    data_path = 'data.csv'
    learning_rate = 0.0001
    initial_b = 0
    initial_m = 0
    num_iteration = 10000
    run(data_path, learning_rate, initial_b, initial_m, num_iteration)


"""
Home Work 6
Ankit Khandelwal
Exercise 7
15863
"""

from time import perf_counter

import matplotlib.pyplot as plt
import numpy as np
from scipy.linalg import solve_banded
from scipy.stats import linregress

D = 1
NN = [64, 128, 256, 512, 1024]
ans = []
count = 0

for N in NN:
    dx = 1 / N
    dt = 4 * dx ** 2 / D

    bi = 1 + 2 * D * dt / dx ** 2
    ai = -D * dt / dx ** 2
    ci = -D * dt / dx ** 2

    u = np.zeros((N, 1), float)
    v = np.zeros((N, 1), float)
    v[0][0] = 1
    v[N - 1][0] = -ai / bi
    u[0][0] = -bi
    u[N - 1][0] = ci

    A_banded = np.zeros((3, N), float)
    A_banded[0, 1:] = ci
    A_banded[1, :] = bi
    A_banded[2, :N - 1] = ai

    A_banded[1][0] = 2 * bi
    A_banded[1][N - 1] = ai * ci / bi + bi

    tv = np.linspace(0, 1, 1 / dt)
    xv = np.linspace(0, 1, N)
    f = np.zeros((N, 1), float)
    for i in range(N):
        if xv[i] > 0.4 and xv[i] < 0.6:
            f[i][0] = 1
    y = np.ones((N, 1), float)
    q = np.ones((N, 1), float)
    t1 = perf_counter()
    for t in tv:
        y = solve_banded((1, 1), A_banded, f, overwrite_b=True, check_finite=False)
        q = solve_banded((1, 1), A_banded, u, overwrite_b=False, check_finite=False)
        f = y - q @ ((v.T @ y) / (1 + v.T @ q))
        if count == 0 and t >= 0.025 and N == 256:
            plt.plot(xv, f, label='Time 0.025 sec, N = {}'.format(N))
            plt.legend()
            plt.xlabel('X')
            plt.ylabel('f')
            plt.figure()
            count = 1
    ans.append(f)
    t2 = perf_counter()
xpoints = []
ypoints = []
for i in range(len(NN) - 1):
    error = 0
    for j in range(NN[i]):
        error = error + float(abs(ans[i][j] - ans[i + 1][2 * j]))
    plt.scatter(1 / NN[i], error / NN[i], marker=10)
    xpoints.append(1 / NN[i])
    ypoints.append(error / NN[i])
    plt.xscale('log')
    plt.yscale('log')
    plt.xlabel('dx')
    plt.ylabel('Error')
slope = linregress(xpoints, ypoints)[0]
plt.show()

Ankit Khandelwal_HW6/Exercise 7/exercise 7 banded.py


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

def karatsuba(x, y):
    """ Recursive implementation of Karatsuba's Fast Mulciplication Algoritihm

    :param x: The first integer
    :param y: The second integer
    :return: The product of x * y
    """
    if x < 10 or y < 10:
        return x*y

    m = max(len(str(x)), len(str(y))) // 2

    x_high = x // 10**m
    x_low = x % 10**m
    y_high = y // 10**m
    y_low = y % 10**m

    z0 = karatsuba(x_low, y_low)
    z1 = karatsuba(x_low + x_high, y_low + y_high)
    z2 = karatsuba(x_high, y_high)

    return z2 * 10 ** (2 * m) + (z1 - z2 - z0) * 10 ** m + z0

def karat_compare(max_size, tests):
    samples = []
    test_sizes = np.linspace(1,max_size, tests).astype(int)
    standard_results = []
    karatsuba_results = []
    for test_size in test_sizes:
        x_str = ''
        y_str = ''
        for x in range(test_size):
            x_str += str(random.randint(0,9))
            y_str += str(random.randint(0,9))
        samples.append((int(x_str), int(y_str)))
    print(f"Samples Generated: {len(samples)}, with max size: {max_size}")

    for sample, test_size in zip(samples, test_sizes):
        print(f"Attempting numbers of 10^{test_size}")
        x = sample[0]
        y = sample[1]

        t_start = time.perf_counter()
        r = x * y
        standard_results.append(time.perf_counter() - t_start)

        t_start = time.perf_counter()
        r = karatsuba(x, y)
        karatsuba_results.append(time.perf_counter() - t_start)

    plt.plot(test_size, standard_results, label="python native")
    plt.plot(test_size, karatsuba_results, label="karatsuba")
    plt.xlabel("10^x")
    plt.ylabel("Seconds")
    plt.legend()
    plt.show()

def naive_matrix_multiplication_lists(a, b):
    """
    Uses nested loops to calculate AB
    :param a: An MxN matrix of numbers.
    :param b: An NxP matrix of numbers.
    :return: An MxP matrix of numbers which is the product: AB.
    """
    M = len(a)
    N = len(a[0])
    if len(b) != N:
        raise ValueError("The Matrices Provide are not the proper shape.")
    P = len(b[0])

    c = [[0 for i in range(P)] for j in range(M)]

    for i in range(0,M):
        for j in range(0,P):
            for k in range(0,N):
                c[i][j] += a[i][k] * b[k][j]
    return c

def naive_matrix_multiplication_np(a,b):
    M, N = a.shape
    n, P = b.shape

    if N != n:
        raise ValueError("The Matrices Provide are not the proper shape.")

    c = np.zeros((M,P))

    for i in range(0,M):
        for j in range(0,P):
            for k in range(0,N):
                c[i][j] += a[i][k] * b[k][j]
    return c

if __name__ == "__main__":
    a = [[1, 2, 5],
         [3, 4, 6]]

    b = [[5, 6],
         [7, 8],
         [1, 1]]

    c = naive_matrix_multiplication_lists(a, b)
    print("List Results:\n", c)

    A = np.array(a)
    B = np.array(b)
    C = naive_matrix_multiplication_np(A, B)
    print("NP Array Results:\n", C)

    expected_results = np.array([[24, 27], [49, 56]])
    print("Expected Results:\n", expected_results)


import matplotlib.pyplot as plt

import pandas as pd
import numpy as np
#nbr_items = 200_000_000

nbr_items = 99
yp = np.linspace(0.01, 0.99, nbr_items)
yt = np.ones(nbr_items)
answer = -(yt * np.log(yp) + ((1-yt) * (np.log(1-yp))))

yt0 = np.zeros(nbr_items)
answer0 = -(yt0 * np.log(yp) + ((1-yt0) * (np.log(1-yp))))

df = pd.DataFrame({'yp': yp, 'yt': yt, 'cross_entropy': answer, 'cross_entropy0': answer0})

fig, axs = plt.subplots(ncols=2)
ax = axs[0]
df.plot(x='yp', y='cross_entropy', ax=ax, label='Error for yt==1')
df.plot(x='yp', y='cross_entropy0', ax=ax, label='Error for yt==0', linestyle='--')
ax.set_ylabel('Cross Entropy or Error (smaller is better)')
ax.set_xlabel('Predicted Probability (yp)')
ax.set_title('Cross Entropy error for targets yt 0 and 1')

ax = axs[1]
logs = np.log(yp)
df_log = pd.DataFrame({'yp': yp, 'log_yp': logs})
df_log.plot(x='yp', y='log_yp', ax=ax)
ax.set_title('Natural Log for x==[0, 1]')
ax.set_xlabel('Predicted Probability (yp)')

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


# Diagonalizing a Tridiagonal Toeplitz matrix
from math import cos, pi, log10
import numpy as np
#Function for initialization of parameters
def initialize():
    RMin = 0.0
    RMax = 1.0
    Dim = 100
    return RMin, RMax, Dim

#Get the boundary and number of integration points
RMin, RMax, Dim = initialize()

#Initialize constants, step size and the constant values of the diagonal and non-diagonal elements
Step    = RMax/(Dim+1)
DiagConst = 2.0 / (Step*Step)
NondiagConst =  -1.0 / (Step*Step)

#Setting up a tridiagonal matrix
Hamiltonian = np.zeros((Dim,Dim))
Hamiltonian[0,0] = DiagConst
Hamiltonian[0,1] = NondiagConst
for i in range(1,Dim-1):
    Hamiltonian[i,i-1]  = NondiagConst
    Hamiltonian[i,i]    = DiagConst
    Hamiltonian[i,i+1]  = NondiagConst
Hamiltonian[Dim-1,Dim-2] = NondiagConst
Hamiltonian[Dim-1,Dim-1] = DiagConst
# diagonalize and obtain eigenvalues, not necessarily sorted
EigValues, EigVectors = np.linalg.eig(Hamiltonian)
# sort eigenvectors and eigenvalues
permute = EigValues.argsort()
EigValues = EigValues[permute]
EigVectors = EigVectors[:,permute]
# compute  the difference between the numerical and exact eigenvalues
for i in range(Dim):
    lambda_i = DiagConst+2*NondiagConst*cos((i+1)*pi*Step)
    print(abs(EigValues[i]-lambda_i))


#! /usr/bin/env python

from sympy.ntheory.primetest import isprime
from sympy import sieve
import time

def quadratic_primes(max_coefficient):
  '''Calculate the product of the coefficients that generate the most primes
  for a sequence of n starting with n=0 of the form:

  n^2 + an + b

  for |a| < max_coefficient and |b| < max_coefficient.
  '''

  b_lst = list(sieve.primerange(1, max_coefficient))

  max_n = 0
  for a in range(-max_coefficient + 1, max_coefficient):
    for b in b_lst:
      n = 0
      while isprime(n**2 + a * n + b):
        n += 1
      if n > max_n:
        max_n = n
        max_product = a * b

  return max_product

if __name__ == '__main__':
  START_TIME = time.time()
  MAX_COEFFICIENT = 1000
  print "Max coefficient:", MAX_COEFFICIENT
  print "Solution:", quadratic_primes(MAX_COEFFICIENT)
  print "Running time: %.2g" % (time.time() - START_TIME)


import numpy as np
import matplotlib.pyplot as plt

default_kappa= 2
default_exit_tax = .02

#value function for a given state (R,S)
def invariant(R,S,kappa=default_kappa):
    return (S**kappa)/R

#given a value function (parameterized by kappa)
#and an invariant coeficient V0
#return Supply S as a function of reserve R
def reserve(S, V0, kappa=default_kappa):
    return (S**kappa)/V0

#given a value function (parameterized by kappa)
#and an invariant coeficient V0
#return Supply S as a function of reserve R
def supply(R, V0, kappa=default_kappa):
    return (V0*R)**(1/kappa)

#given a value function (parameterized by kappa)
#and an invariant coeficient V0
#return a spot price P as a function of reserve R
def spot_price(R, V0, kappa=default_kappa):
    return kappa*R**((kappa-1)/kappa)/V0**(1/kappa)

#for a given state (R,S)
#given a value function (parameterized by kappa)
#and an invariant coeficient V0
#deposit deltaR to Mint deltaS
#with realized price deltaR/deltaS
def mint(deltaR, R,S, V0, kappa=default_kappa):
    deltaS = (V0*(R+deltaR))**(1/kappa)-S
    if deltaS ==0:
        realized_price = spot_price(R+deltaR, V0, kappa)
    else:
        realized_price = deltaR/deltaS
    return deltaS, realized_price

#for a given state (R,S)
#given a value function (parameterized by kappa)
#and an invariant coeficient V0
#burn deltaS to Withdraw deltaR
#with realized price deltaR/deltaS
def withdraw(deltaS, R,S, V0, kappa=default_kappa):
    deltaR = R-((S-deltaS)**kappa)/V0
    if deltaS ==0:
        realized_price = spot_price(R+deltaR, V0, kappa)
    else:
        realized_price = deltaR/deltaS
    return deltaR, realized_price

def withdraw_with_tax(deltaS, R,S, V0, exit_tax = default_exit_tax, kappa=default_kappa):
    deltaR = R-((S-deltaS)**kappa)/V0
    #print(deltaR)
    quantity_taxed = exit_tax*deltaR
    quantity_recieved = (1-exit_tax)*deltaR

    realized_price = quantity_recieved/deltaS

    return quantity_recieved, quantity_taxed, realized_price

def response_plot(rdf, title):
    plt.subplots(figsize=(10,6))
    ax1=plt.subplot(2,1,1)
    rdf[["spot_price","price"]].plot(ax=ax1)
    plt.legend(["spot price (output: $P_t$)", "price sample (input: $\hat P_t$)" ])
    plt.ylabel("Price, $\hat P_t$, $P_t$")
    plt.title(title+" Response Dynamics")
    ax2=plt.subplot(2,1,2)
    rdf[["abs_err"]].plot(ax=ax2,logy=True, color='green')
    plt.legend(["$Error: |P_t-\hat P_t|$" ])
    plt.xlabel("transaction count (time)")
    plt.ylabel("Absolute Value of Estimation Error")
    plt.show()


#
# Copyright 2022 Ocean Protocol Foundation
# SPDX-License-Identifier: Apache-2.0
#
import numpy

def branin_mesh(X0, X1):
    # b,c,t = 5.1/(4.*(pi)**2), 5./pi, 1./(8.*pi)
    b, c, t = 0.12918450914398066, 1.5915494309189535, 0.039788735772973836
    u = X1 - b * X0**2 + c * X0 - 6
    r = 10.0 * (1.0 - t) * numpy.cos(X0) + 10
    Z = u**2 + r

    return Z

def create_mesh(npoints):
    X0_vec = numpy.linspace(-5.0, 10.0, npoints)
    X1_vec = numpy.linspace(0.0, 15.0, npoints)
    X0, X1 = numpy.meshgrid(X0_vec, X1_vec)
    Z = branin_mesh(X0, X1)

    return X0, X1, Z


#%%
import numpy as np

#%%
# runge-kutta (order four)
# 5.4.1.b
def func1(t, y):
    return 1 + (t - y) ** 2

def sol1(t):
    return t + 1 / (1 - t)

a = 2
b = 3
N = 10
alpha = 1

h = (b - a) / N
t = a
w = alpha
y = sol1(t)
error = abs(y - w)
print('{0:.1f} & {1:.7f} & {2:.7f} & {3} \\\\'.format(t, y, w, error))

for _ in range(N):
    K_1 = h * func1(t, w)
    K_2 = h * func1(t + h / 2, w + K_1 / 2)
    K_3 = h * func1(t + h / 2, w + K_2 / 2)
    K_4 = h * func1(t + h, w + K_3)
    w += (K_1 + 2*K_2 + 2*K_3 + K_4) / 6
    t += h
    y = sol1(t)
    error = abs(y - w)
    print('{0:.1f} & {1:.7f} & {2:.7f} & {3:.7f} \\\\'.format(t, y, w, error))

# 2.0 & 1.0000000 & 1.0000000 & 0.0 \\
# 2.1 & 1.1909091 & 1.1909088 & 0.0000003 \\
# 2.2 & 1.3666667 & 1.3666663 & 0.0000004 \\
# 2.3 & 1.5307692 & 1.5307688 & 0.0000004 \\
# 2.4 & 1.6857143 & 1.6857138 & 0.0000004 \\
# 2.5 & 1.8333333 & 1.8333329 & 0.0000004 \\
# 2.6 & 1.9750000 & 1.9749996 & 0.0000004 \\
# 2.7 & 2.1117647 & 2.1117643 & 0.0000004 \\
# 2.8 & 2.2444444 & 2.2444441 & 0.0000003 \\
# 2.9 & 2.3736842 & 2.3736839 & 0.0000003 \\
# 3.0 & 2.5000000 & 2.4999997 & 0.0000003 \\

#%%
# runge-kutta 4-4
# 5.6.1.c
def func2(t, y):
    return 1 + y / t

def sol2(t):
    return t * np.log(t) + 2 * t

a = 1
b = 2
N = 10
alpha = 2

h = (b - a) / N
t = a
w = alpha
y = sol2(t)
error = abs(y - w)
print('{0:.1f} & {1:.7f} & {2:.7f} & {3} \\\\'.format(t, y, w, error))

tLi = [t]
wLi = [w]
for _ in range(3):
    K_1 = h * func2(t, w)
    K_2 = h * func2(t + h / 2, w + K_1 / 2)
    K_3 = h * func2(t + h / 2, w + K_2 / 2)
    K_4 = h * func2(t + h, w + K_3)
    w += (K_1 + 2*K_2 + 2*K_3 + K_4) / 6
    t += h

    tLi.append(t)
    wLi.append(w)

    y = sol2(t)
    error = abs(y - w)
    print('{0:.1f} & {1:.7f} & {2:.7f} & {3:.7f} \\\\'.format(t, y, w, error))

for _ in range(3, N):
    t += h
    w = wLi[3] + h * (55 * func2(tLi[3], wLi[3]) - 59 * func2(tLi[2], wLi[2]) + 37 * func2(tLi[1], wLi[1]) - 9 * func2(tLi[0], wLi[0])) / 24
    w = wLi[3] + h * (9 * func2(t, w) + 19 * func2(tLi[3], wLi[3]) - 5 * func2(tLi[2], wLi[2]) + func2(tLi[1], wLi[1])) / 24
    y = sol2(t)
    error = abs(y - w)
    print('{0:.1f} & {1:.7f} & {2:.7f} & {3:.7f} \\\\'.format(t, y, w, error))

    tLi.pop(0)
    wLi.pop(0)
    tLi.append(t)
    wLi.append(w)

# 1.0 & 2.0000000 & 2.0000000 & 0.0 \\
# 1.1 & 2.3048412 & 2.3048409 & 0.0000003 \\
# 1.2 & 2.6187859 & 2.6187854 & 0.0000005 \\
# 1.3 & 2.9410735 & 2.9410729 & 0.0000007 \\
# 1.4 & 3.2710611 & 3.2710601 & 0.0000010 \\
# 1.5 & 3.6081977 & 3.6081963 & 0.0000014 \\
# 1.6 & 3.9520058 & 3.9520041 & 0.0000017 \\
# 1.7 & 4.3020680 & 4.3020661 & 0.0000020 \\
# 1.8 & 4.6580160 & 4.6580138 & 0.0000022 \\
# 1.9 & 5.0195224 & 5.0195199 & 0.0000025 \\
# 2.0 & 5.3862944 & 5.3862917 & 0.0000027 \\

#%%
# stiff
# 5.11.1.b
def func3(t, y):
    return -20 * (y - t ** 2) + 2 * t

def func3dy(t, y):
    return -20

def sol3(t):
    return t ** 2 + (1 / 3) * np.exp(-20 * t)

a = 0
b = 1
N = 10
alpha = 1 / 3
TOL = 1e-6
M = 10

h = (b - a) / N
t = a
w = alpha
y = sol3(t)
error = abs(y - w)
print('{0:.1f} & {1:.7f} & {2:.7f} & {3} \\\\'.format(t, y, w, error))

for _ in range(N):
    k_1 = w + h * func3(t, w) / 2
    w_0 = k_1
    j = 1
    FLAG = 0
    while FLAG == 0 and j <= M:
        w = w_0 - (w_0 - h * func3(t + h, w_0) / 2 - k_1) / (1 - h * func3dy(t+h, w_0) / 2)
        if abs(w - w_0) < TOL:
            FLAG = 1
        else:
            j += 1
            w_0 = w

    if j > M:
        print('The maxmum number of iterations exceeded')

    t += h
    y = sol3(t)
    error = abs(y - w)
    print('{0:.1f} & {1:.7f} & {2:.7f} & {3:.7f} \\\\'.format(t, y, w, error))

# 0.0 & 0.3333333 & 0.3333333 & 0.0 \\
# 0.1 & 0.0551118 & 0.0100000 & 0.0451118 \\
# 0.2 & 0.0461052 & 0.0400000 & 0.0061052 \\
# 0.3 & 0.0908263 & 0.0900000 & 0.0008263 \\
# 0.4 & 0.1601118 & 0.1600000 & 0.0001118 \\
# 0.5 & 0.2500151 & 0.2500000 & 0.0000151 \\
# 0.6 & 0.3600020 & 0.3600000 & 0.0000020 \\
# 0.7 & 0.4900003 & 0.4900000 & 0.0000003 \\
# 0.8 & 0.6400000 & 0.6400000 & 0.0000000 \\
# 0.9 & 0.8100000 & 0.8100000 & 0.0000000 \\
# 1.0 & 1.0000000 & 1.0000000 & 0.0000000 \\


import matplotlib.pyplot as plt
import numpy as np

import cvxpy as cp

def loss_fn(X, Y, beta):
    return cp.pnorm(cp.matmul(X, beta) - Y, p=2)**2

def regularizer(beta):
    return cp.pnorm(beta, p=2)**2

def objective_fn(X, Y, beta, lambd):
    return loss_fn(X, Y, beta) + lambd * regularizer(beta)

def mse(X, Y, beta):
    return (1.0 / X.shape[0]) * loss_fn(X, Y, beta).value

def generate_data(m: int = 1000, n: int = 30, sigma: int = 40):
    """Generates data for regression.

    To experiment with your own data, just replace the contents of this
    function with code that loads your dataset.

    Args
    ----
    m : int
        The number of examples.
    n : int
        The number of features per example.
    sigma : positive float
        The standard deviation of the additive noise.

    Returns
    -------
    X : np.array
        An array of featurized examples, shape (m, n), m the number of
        examples and n the number of features per example.

    Y : np.array
        An array of shape (m,) containing the observed labels for the
        examples.

    beta_star : np.array
        The true parameter. This is the quantity we are trying to
        estimate.
    """
    beta_star = np.random.randn(n)
    # Generate an ill-conditioned data matrix
    X = np.random.randn(m, n)
    U, _, V = np.linalg.svd(X)
    s = np.linspace(30, 1, min(m, n))
    S = np.zeros((m, n))
    S[:min(m, n), :min(m, n)] = np.diag(s)
    X = np.dot(U, np.dot(S, V))
    # Corrupt the observations with additive Gaussian noise
    Y = X.dot(beta_star) + np.random.normal(0, sigma, size=m)
    return X, Y, beta_star

def plot_train_test_errors(train_errors, test_errors, lambd_values):
    plt.plot(lambd_values, train_errors, label="Train error")
    plt.plot(lambd_values, test_errors, label="Test error")
    plt.xscale("log")
    plt.legend(loc="upper left")
    plt.xlabel(r"$\lambda$", fontsize=16)
    plt.title("Mean Squared Error (mSE)")
    plt.show()

def plot_regularization_path(lambd_values, beta_values):
    num_coeffs = len(beta_values[0])
    for i in range(num_coeffs):
        plt.plot(lambd_values, [wi[i] for wi in beta_values])
    plt.xlabel(r"$\lambda$", fontsize=16)
    plt.xscale("log")
    plt.title("Regularization Path")
    plt.show()

if __name__ == "__main__":
    m = 1000
    n = 30
    sigma = 4

    X, Y, beta_star = generate_data(m, n, sigma)
    X_train = X[:800, :]
    Y_train = Y[:800]
    X_test = X[800:, :]
    Y_test = Y[800:]

    beta = cp.Variable(n)
    lambd = cp.Parameter(nonneg=True)
    problem = cp.Problem(
                    cp.Minimize(objective_fn(X_train, Y_train, beta, lambd)))

    lambd_values = np.logspace(-2, 2, 50)
    train_errors = []
    test_errors = []
    beta_values = []
    for v in lambd_values:
        lambd.value = v
        problem.solve()
        train_errors.append(mse(X_train, Y_train, beta))
        test_errors.append(mse(X_test, Y_test, beta))
        beta_values.append(beta.value)

    plot_train_test_errors(train_errors, test_errors, lambd_values)
    plot_regularization_path(lambd_values, beta_values)


'''
Problem 44

Pentagonal numbers are generated by the formula, Pn=n(3n−1)/2. The first ten pentagonal numbers are:

1, 5, 12, 22, 35, 51, 70, 92, 117, 145, ...

It can be seen that P4 + P7 = 22 + 70 = 92 = P8. However, their difference, 70 − 22 = 48, is not pentagonal.

Find the pair of pentagonal numbers, Pj and Pk, for which their sum and difference are pentagonal and D = |Pk − Pj| is minimised; what is the value of D?

'''

import numpy as np

def pentagonal(a: int):

    pent = lambda x: x*(3*x-1)/2
    arr = np.arange(a)
    i = 0
    while i < len(arr):
        arr[i] = pent(i)
        i += 1

    return arr[arr != 0]

def find_pent(a: int):
    arr = pentagonal(a)
    for i in range(0, len(arr)):
        for j in range(0, len(arr)):
            if arr[i] + arr[j] in arr:
                if arr[j] - arr[i] in arr:
                    return arr[np.where(arr == arr[i] + arr[j])], arr[np.where(arr == arr[j] - arr[i])], arr[i], arr[j], arr[j]-arr[i]

def main():

    a = 10000
    print('Pj and Pk are: ', find_pent(a))
    #print(distinct_factors(factors(644),644))

if __name__=='__main__':
    main()


'''
    author: Virag Dosani (@repl.it/@VTech1699)
'''

import sympy
from sympy import *

def readData(f):
    with open(f) as f:
        data = []
        da = f.read().split("\n")
        for line in da:
            data.append(line.split("="))

    return data

def algebraicSimplify(data):
    x,y,z = symbols('x y z')
    simplfy = []
    for i in data:
        expr = eval(i[1])
        smpl = simplify(expr)
        simplfy.append([i[0],expr, smpl])

    return simplfy

def printData(data):
    print("Input Code: ")
    for i in data:
        print(i[0]," = ",i[1])

    print("\nCode after Algebraic Simplification: ")
    for i in data:
        print(i[0]," = ",i[2])

    return

if __name__ == '__main__':
    input = "input.txt"
    data = readData(input)
    output = algebraicSimplify(data)
    print("Algebraic Simplification")
    printData(output)

'''
OUTPUT:
Algebraic Simplification
Input Code:
a   =  sin(x)**2 + cos(x)**2
b   =  (x**3 + x**2 - x - 1)/(x**2 + 2*x + 1)
c   =  gamma(x)/gamma(x - 2)
d   =  (x**2 + y**2 + z**2)/(-x**2 - y**2 - z**2)

Code after Algebraic Simplification:
a   =  1
b   =  x - 1
c   =  (x - 2)*(x - 1)
d   =  -1
'''

Semester 6/SPCC/Code optimization - Algebraic simplification/main.py


# -*- coding: cp1252 -*-
# ------------------------- #
# Description:
# A collection of functions which implement special maths functions in my Masters dissertation.
# ------------------------- #
# Created by: Ashiv Dhondea, RRSG, UCT.
# Date created: 21 July 2016
# Edits: 22 July 2016: added fn_Create_Concatenated_Block_Diag_Matrix, originally from DynamicsFunctions.py
#        28 July 2016: implemented the function invSymQuadForm from the Tracker Component Library.
# ------------------------- #
# Import libraries
import numpy as np
from numpy import linalg
import scipy.linalg
# --------------------------------------------------------------------------- #
def schol(A): # Cholesky decomposition for PSD matrices.
    ## Emulates schol.m of ekfukf toolbox.
    ## Description from schol.m
    ##    %SCHOL  Cholesky factorization for positive semidefinite matrices
    ##% Syntax:
    ##%   [L,def] = schol(A)
    ## % In:
    ##%   A - Symmetric pos.semi.def matrix to be factorized
    ##%
    ##% Out:
    ##%   L   - Lower triangular matrix such that A=L*L' if def>=0.
    ##%   def - Value 1,0,-1 denoting that A was positive definite,
    ##%         positive semidefinite or negative definite, respectively.
    ## % Copyright (C) 2006 Simo Särkkä
    n = np.shape(A)[0];
    L = np.zeros((n,n),dtype=np.float64);
    definite = 1;

    for i in range(0,n):
        for j in range(0,i+1):
            s = A[i,j];
            for k in range (0,j):
                s = s - L[i,k]*L[j,k];
            if j < i :
                if L[j,j] > np.finfo(np.float64).eps:
                    L[i,j] = s/L[j,j];
                else:
                    L[i,j] = 0;
            else:
                if (s < - np.finfo(np.float64).eps ):
                    s = 0;
                    definite = -1;
                elif (s < np.finfo(np.float64).eps):
                    s = 0;
                    definite = min(0,definite);

                L[j,j] = np.sqrt(s);
    # if definite < 0, then negative definite
    # if definite == 0, then positive semidefinite
    # if definite == 1, then positive definite
    return L, definite
# ------------------------------------------------------------------- #
def fn_Create_Concatenated_Block_Diag_Matrix(R,stacklen):
    # fn_Create_Concatenated_Block_Diag_Matrix creates a block diagonal matrix of size (stacklen) x (stacklen)
    # whose diagonal blocks are copies of the matrix R.
##    L = [R];
##    for index in range (0,stacklen):
##        L.append(R);
##        ryn = scipy.linalg.block_diag(*L);
    ryn =np.kron(np.eye(stacklen+1),R); # Edit: 19/07/2016: probably better idea than using a for loop.
    return ryn
# ------------------------------------------------------------------------- #
def invSymQuadForm(x,M):
    """
    %%INVSYMQUADFORM Compute the quadratic form x'*inv(R)*x in a manner that
    %                should be more numerically stable than directly evaluating
    %                the matrix inverse, where R is a symmetric, positive
    %                definite matrix. The parameter M can either be the matrix
    %                R directly (R=M) (the default), or the lower-triangular
    %                square root of R (R=M*M'). The distance  can be computed
    %                for a single matrix and multiple vectors x. If x consists
    %                of vector differences and R is a covariance matrix, then
    %                this can be viewed as a numerically robust method of
    %                computing Mahalanobis distances.
    %
    %INPUTS: x       An mXN matrix of N vectors whose quadratic forms with the
    %                inverse of R are desired.
    %        M       The mXm real, symmetric, positive definite matrix R, or
    %                the square root of R, as specified by the following
    %                parameter.
    %        matType This optional parameter specified the type of matrix that
    %                M is. Possible values are
    %                0 (the default if omitted) M is the matrix R in the form
    %                  x'*inv(R)*x.
    %                1 M is the lower-triangular square root of the matrix R.
    %
    %OUTPUTS: dist   An NX1 vector of the values of x*inv(R)*x for every vector
    %                in the matrix x.
    %
    %As one can find in many textbooks, solvign A*x=b using matrix inversion is
    %generally a bad idea. This relates to the problem at hand, because one can
    %write
    %x'*inv(R)*x=x'inv(C*C')*x
    %           =x'*inv(C)'*inv(C)*x
    %where C is the lower-triangular Cholesky decomposition of R. Next, say
    %that
    %y=inv(C)*x.
    %Then, we are just computing y'*y.
    %What is a stable way to find y? Well, we can rewrite the equation as
    %C*y=x
    %Since C is lower-triangular, we can find x using forward substitution.
    %This should be the same as one of the many ways that Matlab solves the
    %equation C*y=x when one uses the \ operator. One can explicitely tell
    %Matlab that the matrix is lower triangular when using the linsolve
    %function, thus avoiding the need for loops or for Matlab to check the
    %structure of the matrix on its own.
    %
    %August 2014 David F. Crouse, Naval Research Laboratory, Washington D.C.
    %(UNCLASSIFIED) DISTRIBUTION STATEMENT A. Approved for public release.
    """
    ## 28 July 2016. Emulates the invSymQuadForm.m function from the Tracker Component Library. Removed matType to make things simpler.

    # implements x'M x
    C,definiteness = schol(M);
    y = np.linalg.solve(C,x);
    dist = np.dot(y.T,y);
    return dist


"""Generalized Pauli matrices."""
import numpy as np

from toqito.matrices import shift
from toqito.matrices import clock

def gen_pauli(k_1: int, k_2: int, dim: int) -> np.ndarray:
    r"""
    Produce generalized Pauli operator [WikGenPaul]_.

    Generates a :code:`dim`-by-:code:`dim` unitary operator. More specifically,
    it is the operator :math:`X^k_1*Z^k_2`, where :math:`X` and :math:`Z` are
    the "shift" and "clock" operators that naturally generalize the Pauli X and
    Z operators. These matrices span the entire space of
    :code:`dim`-by-:code:`dim` matrices as :code:`k_1` and :code:`k_2` range
    from 0 to :code:`dim-1`, inclusive.

    Note that the generalized Pauli operators are also known by the name of
    "discrete Weyl operators". [WatrousLec6]_

    Examples
    ==========

    The generalized Pauli operator for :code:`k_1 = 1`, :code:`k_2 = 0` and
    :code:`dim = 2` is given as the standard Pauli-X matrix

    .. math::
        G_{1, 0, 2} = \begin{pmatrix}
                         0 & 1 \\
                         1 & 0
                      \end{pmatrix}.

    This can be obtained in :code:`toqito` as follows.

    >>> from toqito.matrices import gen_pauli
    >>> dim = 2
    >>> k_1 = 1
    >>> k_2 = 0
    >>> gen_pauli(k_1, k_2, dim)
    [[0.+0.j, 1.+0.j],
     [1.+0.j, 0.+0.j]])

    The generalized Pauli matrix :code:`k_1 = 1`, :code:`k_2 = 1`, and
    :code:`dim = 2` is given as the standard Pauli-Y matrix

    .. math::
        G_{1, 1, 2} = \begin{pmatrix}
                        0 & -1 \\
                        1 & 0
                      \end{pmatrix}.

    This can be obtained in :code:`toqito` as follows.`

    >>> from toqito.matrices import gen_pauli
    >>> dim = 2
    >>> k_1 = 1
    >>> k_2 = 1
    >>> gen_pauli(k_1, k_2, dim)
    [[ 0.+0.0000000e+00j, -1.+1.2246468e-16j],
     [ 1.+0.0000000e+00j,  0.+0.0000000e+00j]])

    References
    ==========
    .. [WikGenPaul] Wikipedia: Generalizations of Pauli matrices
        https://en.wikipedia.org/wiki/Generalizations_of_Pauli_matrices

    .. [WatrousLec6] Lecture 6: Further remarks on measurements and channels
        https://cs.uwaterloo.ca/~watrous/LectureNotes/CS766.Fall2011/06.pdf

    :param k_1: (a non-negative integer from 0 to :code:`dim-1` inclusive).
    :param k_2: (a non-negative integer from 0 to :code:`dim-1` inclusive).
    :param dim: (a positive integer indicating the dimension).
    :return: A generalized Pauli operator.
    """
    gen_pauli_x = shift(dim)
    gen_pauli_z = clock(dim)

    gen_pauli_w = np.linalg.matrix_power(gen_pauli_x, k_1) @ np.linalg.matrix_power(
        gen_pauli_z, k_2
    )

    return gen_pauli_w


import sys
import scipy.io as sio
from pprint import pprint
import matplotlib.pyplot as plt
import numpy as np

# h(X(i)) = theta_0 + theta_1 * X1(i)

def normalize(x):
   return (x - x.min())/(x.max() - x.min())

# theta = (X.T X)^(-1)(X.T)(Y)
def normal_equation(X, y):
    theta = []
    m = np.size(x)
    bias = np.ones((m, 1))
    X = np.reshape(X, (m, 1))
    X = np.append(bias, X, axis=1)
    theta = np.linalg.inv(X.T.dot(X)).dot(X.T).dot(y)
    return theta

x = np.array([5, 15, 25, 35, 45, 55])
X = normalize(x)
y = np.array([15, 11, 2, 8, 25, 32])

theta = normal_equation(X, y)
print("theta_0", theta[0], "\ntheta_1", theta[1])

y_pred = theta[0] + theta[1] * X
error = y - (y_pred)
print("Error:", error)

plt.figure(figsize=(8, 6))
plt.scatter(X, y, color = "r", marker = "o", s = 30)
plt.title("Predicted Regression Line", fontsize=16)
plt.plot(X, y_pred, color = "b")
plt.legend(['h(x)', '(x, y)'], loc='best')

plt.xlabel('X', fontsize=14)
plt.ylabel('Y', fontsize=14)
plt.show()


import numpy as np
'''
    Exponential kernel to measure the similarity betweeen two points.
'''
class GaussianKernel():
    def __init__(self, sig1=0.01, sig2=0.1):
        self.sig1 = sig1;
        self.sig2 = sig2;

    def dot(self, x1, x2, sig1=None, sig2=None):
        if (sig1 is None):
            sig1 = self.sig1;
        if (sig2 is None):
            sig2 = self.sig2;

        diff = np.linalg.norm(x1 - x2);
        return np.exp(-(diff ** 2) / (2 * sig1 * sig2));


#############################################################
# This is a class that collects statistical distributions.
# This distributions are the essential ones to be remembered.
#############################################################

import numpy as np
import random

from factory import *

class StatisticalDistributions(object):

	def __init__(self):
		super().__init__()

	def Exponential(self, x:float=0, l:float=0, vector:list=None):

		'''
		param x: this is the real number to be processed into an Exponential Distributions
		param l: this is the lambda value of an Exponential Distributions
		param vector: passing this list, these values will be indexes to get corresponding exponential values
		              This value should start from zero and has no upper limit (X belongs to REAL)
		'''

		if vector is None:
			return l * np.exp( -l * x )
		else:
			return [ self.Exponential(x, l, vector=None) for x in vector ]

	def Gaussian(self, x:float=0, mu:float=0, sigma:float=1, vector:list=None):

		'''
		param mu: this is the real mean of our gaussian distribution
		param sigma: this is the variance of our gaussian distribution
		param vector: passing this list, these values will be indexes to get corresponding exponential values
		'''

		if vector is None:
			return (1 / (sigma * (2 * np.pi) ** 0.5 )) * ( np.exp( -((x - mu) / (2 * sigma))**2 ) )
		else:
			return [ self.Gaussian(x=x / 100, mu=mu, sigma=sigma, vector=None) for x in vector ]

	def Poisson(self, x:int=0, l:float=0, vector:list=None):

		'''
		param l: this is the lambda value of a Poisson Distribution
		param vector: passing this list, these values will be indexes to get corresponding exponential values
		              This value should start from zero and has no upper limit (X belongs to REAL)
		'''

		if vector is None:
			return (np.exp(-l) * (l ** x)) / ( self._Factorial( x ) )
		else:
			return [ self.Poisson(x=x, l=l, vector=None) for x in vector ]

	def Binomial(self, n:int, k:int, p:float):

		'''
		param n: number of time the experiment is conducted
		param k: number of time the experiment is a success (k <= n)
		param p: probability a success occurs
		'''

		factory_    = StatisticalIndexes()
		combination = factory_.Combination(n, k)

		return combination * (p ** k) * ( (1 - p) ** (n - k) )


import math
import numpy as np
import scipy.io.wavfile
from scipy import signal
from scipy.ndimage.interpolation import map_coordinates
from imageio import imread
from skimage.color import rgb2gray

GRAYSCALE = 1

def DFT(signal):
    """
    Compute the one-dimensional discrete Fourier Transform
    :param signal: the original signal as array of float64 with shape (N,1)
    :return: DFT of signal as array of complex128 with shape (N,1)
    """
    N = signal.shape[0]
    if N == 0:
        return signal
    # create grid for DFT matrix
    x = np.arange(N)
    DFT_matrix = np.exp((-2 * math.pi * 1j * x.reshape(N, 1) * x) / N)
    return signal.T.dot(DFT_matrix).T

def IDFT(fourier_signal):
    """
    Compute the one-dimensional inverse discrete Fourier Transform
    :param fourier_signal: the fourier signal as array of complex128 with shape (N,1)
    :return: the original signal as array of float64 with shape (N,1)
    """
    N = fourier_signal.shape[0]
    if N == 0:
        return fourier_signal
    # create grid for IDFT matrix
    x = np.arange(N)
    IDFT_matrix = np.exp((2 * math.pi * 1j * x.reshape(N, 1) * x) / N) / N
    return np.real_if_close(fourier_signal.T.dot(IDFT_matrix).T).astype(np.complex128)

def DFT2(image):
    """
    Compute the 2-dimensional discrete Fourier Transform
    :param image: grayscale image of float64 with shape (M,N,1)
    :return: DFT of image as array of complex128 with shape (M,N,1)
    """
    # M, N = image.shape[:2]
    # image = image.astype(np.complex128)
    # for row in range(M):  # DFT over rows
    #     image[row] = DFT(image[row])
    #
    # for col in range(N):  # DFT over cols
    #     image[:, col] = DFT(image[:, col])
    #
    # return image

    # apply DFT matrix on all cols and then DFT matrix again on all rows
    # swapaxes is used to handle case of (M,N,1) where transpose doesn't work as needed
    return DFT(DFT(image).swapaxes(0, 1)).swapaxes(0, 1)

def IDFT2(fourier_image):
    """
    Compute the 2-dimensional inverse discrete Fourier Transform
    :param fourier_image: 2D array of complex128 with shape (M,N,1)
    :return: the original image as array of float64 with shape (M,N,1)
    """
    # M, N = fourier_image.shape[:2]
    # image = fourier_image.astype(np.complex128)
    # for row in range(M):  # DFT over rows
    #     image[row] = IDFT(image[row])
    #
    # for col in range(N):  # DFT over cols
    #     image[:, col] = IDFT(image[:, col])
    #
    # return np.real_if_close(image).astype(np.complex128)

    # apply IDFT matrix on all cols and then IDFT matrix again on all rows
    # swapaxes is used to handle case of (M,N,1) where transpose doesn't work as needed
    return np.real_if_close(IDFT(IDFT(fourier_image).swapaxes(0, 1))).swapaxes(0, 1).astype(np.complex128)

def change_rate(filename, ratio):
    """
    Changes the duration of an audio file by changing the sampling rate
    :param filename: string representing the path to a WAV file
    :param ratio: positive float64 representing the duration change
    """
    rate, data = scipy.io.wavfile.read(filename)
    scipy.io.wavfile.write('change_rate.wav', int(rate * ratio), data)

def change_samples(filename, ratio):
    """
    Changes the duration of an audio file by reducing the number of samples using Fourier
    :param filename: string representing the path to a WAV file
    :param ratio: positive float64 representing the duration change
    :return: 1D ndarray of dtype float64 representing the new sample points
    """
    rate, data = scipy.io.wavfile.read(filename)
    data = data.astype(np.float64)
    # data = data / np.iinfo(np.int16).max  # uncomment if want to save as normalized values for audio to make sense
    data = resize(data, ratio)
    scipy.io.wavfile.write('change_samples.wav', rate, data)
    return data

def resize_spectrogram(data, ratio):
    """
    Speeds up a WAV file, without changing the pitch, using spectrogram scaling
    :param data: 1D ndarray of dtype float64 representing the original sample points,
    :param ratio: positive float64 representing the rate change of the WAV file
    :return: new sample points according to ratio with the same datatype as data
    """
    spectrogram = stft(data)
    new_data = []
    for row in range(spectrogram.shape[0]):
        new_data.append(resize(spectrogram[row], ratio))
    new_data = np.vstack(new_data)
    new_data = istft(new_data)
    return new_data

def resize_vocoder(data, ratio):
    """
    Speeds up a WAV file by phase vocoding its spectrogram
    :param data: 1D ndarray of dtype float64 representing the original sample points,
    :param ratio: positive float64 representing the duration change
    :return: data rescaled according to ratio with the same data type as data
    """
    spectrogram = stft(data)
    new_data = phase_vocoder(spectrogram, ratio)
    return istft(new_data)

def resize(data, ratio):
    """
    Changes the number of samples by the given ratio
    :param data: 1D ndarray of dtype float64 or complex128 representing the original sample points
    :param ratio: positive float64 representing the duration change
    :return: 1D ndarray of the dtype of data representing the new sample points
    """
    if ratio == 1:
        return data
    data_type = data.dtype
    data = DFT(data)
    data = np.fft.fftshift(data)

    delta_samples = abs(data.size - math.floor(data.size / ratio))  # number of samples to add or remove
    if ratio > 1:  # speed up, remove samples
        data = data[math.floor(delta_samples / 2):data.size - math.ceil(delta_samples / 2)]  # clip data
    else:  # slow down, add padding samples
        left_pad = np.zeros(math.floor(delta_samples / 2))
        right_pad = np.zeros(math.ceil(delta_samples / 2))
        data = np.append(left_pad, np.append(data, right_pad))

    data = np.fft.ifftshift(data)
    data = IDFT(data)
    data = data.astype(data_type)
    return data

def conv_der(im):
    """
    Computes the magnitude of image derivatives using convolution
    :param im: grayscale image of type float64
    :return: magnitude of the derivative, with the same dtype and shape
    """
    conv_vec = np.array([[0.5, 0, -0.5]])
    dx = scipy.signal.convolve2d(im, conv_vec, mode='same')
    dy = scipy.signal.convolve2d(im, conv_vec.reshape(3, 1), mode='same')
    return np.sqrt(np.abs(dx) ** 2 + np.abs(dy) ** 2)

# noinspection DuplicatedCode
def fourier_der(im):
    """
    Computes the magnitude of image derivatives using Fourier transform
    :param im: grayscale image of type float64
    :return: magnitude of the derivative, with the same dtype and shape
    """
    rows, cols = im.shape
    fourier_transform = np.fft.fftshift(DFT2(im))

    # get dx by making [-N/2,N/2] coef vec
    x_coef = np.arange(int(-cols / 2), math.ceil(cols / 2)) * ((2 * np.pi * 1j) / cols)
    dx = fourier_transform * x_coef
    dx = IDFT2(np.fft.ifftshift(dx))

    # get dy by making [-M/2, M/2] coef vec
    y_coef = np.arange(int(-rows / 2), math.ceil(rows / 2)) * ((2 * np.pi * 1j) / rows)
    dy = fourier_transform * y_coef[:, None]  # make y_coef col vector
    dy = IDFT2(np.fft.ifftshift(dy))

    return np.sqrt(np.abs(dx) ** 2 + np.abs(dy) ** 2)

#  - - - - - - - - - - added functions from helper and prev ex- - - - - - - - - - - - - -
def read_image(filename, representation):
    """
    Reads an image file and converts it into a given representation
    :param filename: the filename of an image on disk
    :param representation: representation code, either 1 or 2 defining whether the output should be a
    grayscale image (1) or an RGB image (2)
    :return: the image represented by a matrix of type np.float64
    """
    im = imread(filename)
    if representation == GRAYSCALE and im.ndim == 3:
        im = rgb2gray(im)
        return im
    im = im.astype(np.float64)
    im /= 255
    return im

def stft(y, win_length=640, hop_length=160):
    fft_window = signal.windows.hann(win_length, False)

    # Window the time series.
    n_frames = 1 + (len(y) - win_length) // hop_length
    frames = [y[s:s + win_length] for s in np.arange(n_frames) * hop_length]

    stft_matrix = np.fft.fft(fft_window * frames, axis=1)
    return stft_matrix.T

def istft(stft_matrix, win_length=640, hop_length=160):
    n_frames = stft_matrix.shape[1]
    y_rec = np.zeros(win_length + hop_length * (n_frames - 1), dtype=np.float)
    ifft_window_sum = np.zeros_like(y_rec)

    ifft_window = signal.windows.hann(win_length, False)[:, np.newaxis]
    win_sq = ifft_window.squeeze() ** 2

    # invert the block and apply the window function
    ytmp = ifft_window * np.fft.ifft(stft_matrix, axis=0).real

    for frame in range(n_frames):
        frame_start = frame * hop_length
        frame_end = frame_start + win_length
        y_rec[frame_start: frame_end] += ytmp[:, frame]
        ifft_window_sum[frame_start: frame_end] += win_sq

    # Normalize by sum of squared window
    y_rec[ifft_window_sum > 0] /= ifft_window_sum[ifft_window_sum > 0]
    return y_rec

def phase_vocoder(spec, ratio):
    num_timesteps = int(spec.shape[1] / ratio)
    time_steps = np.arange(num_timesteps) * ratio

    # interpolate magnitude
    yy = np.meshgrid(np.arange(time_steps.size), np.arange(spec.shape[0]))[1]
    xx = np.zeros_like(yy)
    coordiantes = [yy, time_steps + xx]
    warped_spec = map_coordinates(np.abs(spec), coordiantes, mode='reflect', order=1).astype(np.complex)

    # phase vocoder
    # Phase accumulator; initialize to the first sample
    spec_angle = np.pad(np.angle(spec), [(0, 0), (0, 1)], mode='constant')
    phase_acc = spec_angle[:, 0]

    for (t, step) in enumerate(np.floor(time_steps).astype(np.int)):
        # Store to output array
        warped_spec[:, t] *= np.exp(1j * phase_acc)

        # Compute phase advance
        dphase = (spec_angle[:, step + 1] - spec_angle[:, step])

        # Wrap to -pi:pi range
        dphase = np.mod(dphase - np.pi, 2 * np.pi) - np.pi

        # Accumulate phase
        phase_acc += dphase

    return warped_spec


import numpy as np
import math
import engine.utils
from engine.utils.constants import *

def get_norm(vector):
    return sum([x**2 for x in vector])**0.5

def normalize(vector, fall_back=None):
    norm = get_norm(vector)
    if norm > 0:
        return np.array(vector) / norm
    elif fall_back is not None:
        return fall_back
    else:
        return np.zeros(len(vector))

def unit_vector(vector):
    """Returns the unit vector of the vector."""
    return vector / np.linalg.norm(vector)

def angle_between(v1, v2):
    """Finds angle between two vectors."""
    v1_u = unit_vector(v1)
    v2_u = unit_vector(v2)
    return np.arccos(np.clip(np.dot(v1_u, v2_u), -1., 1.))

def z_rotation(vector, theta):
    """Rotates a 3-D vector around z-axis"""
    R = np.array([[np.cos(theta), -np.sin(theta), 0], [np.sin(theta), np.cos(theta), 0], [0, 0, 1]])
    return np.dot(R, vector)


# -*- coding: utf-8 -*-
"""
Created on Sun Feb  2 11:47:27 2020

@author: Rapha
"""

import math
#import ctypes
import numpy as np
import OpenGL.GL as gl

class OrbitPolygon():

    def __init__(self, semi_major_axis, semi_minor_axis, focus, num_vertices):

        if(num_vertices < 10):
            num_vertices = 10

        self.__numVertices = num_vertices
        self.__semiMajorAxis = semi_major_axis
        self.__semiMinorAxis = semi_minor_axis
        self.__focus = focus

        self.__vertices = np.empty(self.__numVertices * 4, dtype=np.float32)
        self.__calculateVertices()

    def __calculateVertices(self):

        inc_theta = 2 * math.pi / self.__numVertices
        theta = 0

        vertices_pos = 0

        for i in range(self.__numVertices):

            x, y = self.getPositionAt(theta)

            self.__vertices[vertices_pos] = x
            self.__vertices[vertices_pos + 1] = y
            self.__vertices[vertices_pos + 2] = 0.0
            self.__vertices[vertices_pos + 3] = 1.0

            vertices_pos += 4
            theta += inc_theta

    def getPositionAt(self, angle):

        x = self.__semiMajorAxis * np.sin(angle) + self.__focus
        y = self.__semiMinorAxis * np.cos(angle)

        return (x, y)

    def getVertexPositions(self):

        return self.__vertices