import math

import numpy as np

"""
弗雷歇距离
"""

def calculate_euclid(point_a, point_b):
    """
    Args:
        point_a: a data point of curve_a
        point_b: a data point of curve_b
    Return:
        The Euclid distance between point_a and point_b
    """
    return math.sqrt((point_a - point_b) ** 2)

def calculate_frechet_distance(dp, i, j, curve_a, curve_b):
    """
    Args:
        dp: The distance matrix
        i: The index of curve_a
        j: The index of curve_b
        curve_a: The data sequence of curve_a
        curve_b: The data sequence of curve_b
    Return:
        The frechet distance between curve_a[i] and curve_b[j]
    """
    if dp[i][j] > -1:
        return dp[i][j]
    elif i == 0 and j == 0:
        dp[i][j] = calculate_euclid(curve_a[0], curve_b[0])
    elif i > 0 and j == 0:
        dp[i][j] = max(calculate_frechet_distance(dp, i - 1, 0, curve_a, curve_b),
                       calculate_euclid(curve_a[i], curve_b[0]))
    elif i == 0 and j > 0:
        dp[i][j] = max(calculate_frechet_distance(dp, 0, j - 1, curve_a, curve_b),
                       calculate_euclid(curve_a[0], curve_b[j]))
    elif i > 0 and j > 0:
        dp[i][j] = max(min(calculate_frechet_distance(dp, i - 1, j, curve_a, curve_b),
                           calculate_frechet_distance(dp, i - 1, j - 1, curve_a, curve_b),
                           calculate_frechet_distance(dp, i, j - 1, curve_a, curve_b)),
                       calculate_euclid(curve_a[i], curve_b[j]))
    else:
        dp[i][j] = float("inf")
    return dp[i][j]

def get_similarity(curve_a, curve_b):
    dp = [[-1 for _ in range(len(curve_b))] for _ in range(len(curve_a))]
    similarity = calculate_frechet_distance(dp, len(curve_a) - 1, len(curve_b) - 1, curve_a, curve_b)
    return max(np.array(dp).reshape(-1, 1))[0], similarity

if __name__ == '__main__':
    gs, sim = get_similarity(
        # [1, 2, 3.1, 4, 5.3, 6, 7.4, 8, 9.2],
        [9.2, 8, 7.4, 6.5, 5.3, 4, 3.1, 2, 1.1],
        [9.4, 8.2, 7.6, 6.7, 5.5, 4.2, 3.3, 2.2, 1.3],
        # [1, 2.2, 3, 4.1, 5, 6.1, 7, 8.6, 9]
    )
    print(gs)
    print(sim)


from scipy.constants import golden_ratio as phi

def f(x):
    return x + np.exp(-2 * x) - 1

def golden_ratio_search(f, x1=1, x4=4, num_iter=100, espilon=1e-8):

    x2 = x4 - (x4 - x1) / phi
    x3 = xi + (x4 - x1) / phi
    for i in range(num_iter):
        if f(x2) < f(x3):
            x4, x3 = x3, x2
            x2 = x4 - (x4 - x1) / phi
        else:
            x1, x2 = x2, x3
            x3 = x1 + (x4 - x1) / phi

        if abs(x3 - x2) < epsilon:
            x = (x2 + x3) / 2
            print("x = {} after {} iterations".format(x, i + 1))
            return x

    raise ValueError("FAILURE")


from __future__ import division

import cvxopt
import numpy as np
from pylab import *
import math

# from cvxpy import numpy as my_numpy

from cvxpy import *

# Taken from CVX website http://cvxr.com/cvx/examples/
# Example: Compute and display the Chebyshev center of a 2D polyhedron
# Ported from cvx matlab to cvxpy by Misrab Faizullah-Khan
# Original comments below

# Boyd & Vandenberghe, "Convex Optimization"
# Joelle Skaf - 08/16/05
# (a figure is generated)
#
# The goal is to find the largest Euclidean ball (i.e. its center and
# radius) that lies in a polyhedron described by linear inequalites in this
# fashion: P = { x : a_i'*x <= b_i, i=1,...,m } where x is in R^2

# Create the problem

# variables
radius = Variable(1)
center = Variable(2)

# constraints
a1 = cvxopt.matrix([2,1], (2,1))
a2 = cvxopt.matrix([2,-1], (2,1))
a3 = cvxopt.matrix([-1,2], (2,1))
a4 = cvxopt.matrix([-1,-2], (2,1))

b = cvxopt.matrix(1, (4,1))

constraints = [ a1.T*center + np.linalg.norm(a1, 2)*radius <= b[0],
				a2.T*center + np.linalg.norm(a2, 2)*radius <= b[1],
				a3.T*center + np.linalg.norm(a3, 2)*radius <= b[2],
				a4.T*center + np.linalg.norm(a4, 2)*radius <= b[3] ]

# objective
objective = Maximize(radius)

p = Problem(objective, constraints)
# The optimal objective is returned by p.solve().
result = p.solve()
# The optimal value
print radius.value
print center.value
# Convert to 1D array.
center_val = np.asarray(center.value[:,0])

# Now let's plot it
x = np.linspace(-2, 2, 256,endpoint=True)
theta = np.linspace(0,2*np.pi,100)

# plot the constraints
plot( x, -x*a1[0]/a1[1] + b[0]/a1[1])
plot( x, -x*a2[0]/a2[1] + b[0]/a2[1])
plot( x, -x*a3[0]/a3[1] + b[0]/a3[1])
plot( x, -x*a4[0]/a4[1] + b[0]/a4[1])

# plot the solution
plot( center_val[0] + radius.value*cos(theta), center_val[1] + radius.value*sin(theta) )
plot( center_val[0], center_val[1], 'x', markersize=10 )

# label
title('Chebyshev Centering')
xlabel('x1')
ylabel('x2')

axis([-1, 1, -1, 1])

show()


import numpy as np

def mass_from_radius(r, overdensity, rho_bg):
    return overdensity*rho_bg * 4*np.pi*r**3/3

def radius_from_mass(m, overdensity, rho_bg):
    return (m / (4*np.pi/3) / (overdensity*rho_bg))**(1/3)


import numpy as np

def pca(data_matrix, top_n_features=9999999):
    """the PCA algorithm"""
    mean_values = np.mean(data_matrix, axis=0)
    mean_removed = data_matrix - mean_values
    covariant_matrix = np.cov(mean_removed, rowvar=0)
    eigen_vals, eigen_vects = np.linalg.eig(np.mat(covariant_matrix))
    eigen_val_indices = np.argsort(eigen_vals)
    eigen_val_indices = eigen_val_indices[:-(top_n_features + 1):-1]
    red_eigen_vects = eigen_vects[:, eigen_val_indices]
    low_d_data = mean_removed * red_eigen_vects
    reconstituted_matrix = (low_d_data * red_eigen_vects.T) + mean_values
    return low_d_data, reconstituted_matrix


import numpy as np

print(np.__version__)
# 1.19.0

print(np.pi)
# 3.141592653589793

print(np.radians(180))
# 3.141592653589793

print(type(np.radians(180)))
# <class 'numpy.float64'>

a = np.array([0, 90, 180])
print(type(a))
# <class 'numpy.ndarray'>

print(np.radians(a))
# [0.         1.57079633 3.14159265]

print(type(np.radians(a)))
# <class 'numpy.ndarray'>

l = [0, 90, 180]
print(type(l))
# <class 'list'>

print(np.radians(l))
# [0.         1.57079633 3.14159265]

print(type(np.radians(l)))
# <class 'numpy.ndarray'>

print(np.radians(a))
# [0.         1.57079633 3.14159265]

print(np.round(np.radians(a)))
# [0. 2. 3.]

print(np.round(np.radians(a), 2))
# [0.   1.57 3.14]

print(np.sin(np.radians(30)))
# 0.49999999999999994

print(np.round(np.sin(np.radians(30)), 1))
# 0.5

print(np.sin(np.radians([0, 30, 90])))
# [0.  0.5 1. ]

print(np.sin(np.radians([0, 30, 90]))[1])
# 0.49999999999999994

np.set_printoptions(precision=20)
print(np.sin(np.radians([0, 30, 90])))
# [0.                  0.49999999999999994 1.                 ]

np.set_printoptions(precision=8)  # reset to default

print(np.radians([0, 90, 180]))
# [0.         1.57079633 3.14159265]

print(np.deg2rad([0, 90, 180]))
# [0.         1.57079633 3.14159265]

print(np.degrees([0, np.pi / 2, np.pi]))
# [  0.  90. 180.]

print(np.rad2deg([0, np.pi / 2, np.pi]))
# [  0.  90. 180.]

print(np.sin(np.radians([0, 30, 90])))
# [0.  0.5 1. ]

print(np.degrees(np.arcsin([0, 0.5, 1])))
# [ 0. 30. 90.]

print(np.cos(np.radians([0, 60, 90])))
# [1.000000e+00 5.000000e-01 6.123234e-17]

print(np.round(np.cos(np.radians([0, 60, 90])), 1))
# [1.  0.5 0. ]

print(np.degrees(np.arccos([0, 0.5, 1])))
# [90. 60.  0.]

print(np.tan(np.radians([0, 45, 90])))
# [0.00000000e+00 1.00000000e+00 1.63312394e+16]

print(np.degrees(np.arctan([0, 1, np.inf])))
# [ 0. 45. 90.]

print(np.degrees(np.arctan([-np.inf, -1, 0, 1, np.inf])))
# [-90. -45.   0.  45.  90.]

print(np.degrees(np.arctan2(-1, 1)))
# -45.0

print(np.degrees(np.arctan2(1, -1)))
# 135.0

print(np.degrees(np.arctan2([0, 1, 1, 1, 0],
                            [0, 1, 0, -1, -1])))
# [  0.  45.  90. 135. 180.]

print(np.degrees(np.arctan2([0, -1, -1, -1, 0],
                            [0, 1, 0, -1, -1])))
# [   0.  -45.  -90. -135.  180.]

print(np.degrees(np.arctan2(0, -1)))
# 180.0

print(np.degrees(np.arctan2(-0.0, -1.0)))
# -180.0

print(np.degrees(np.arctan2(-0, -1)))
# 180.0

print(np.degrees(np.arctan2([0.0, -0.0, 0.0, -0.0],
                            [0.0, 0.0, -0.0, -0.0])))
# [   0.   -0.  180. -180.]

print(np.sin(-0.0))
# -0.0

print(np.arcsin(-0.0))
# -0.0

print(np.tan(-0.0))
# -0.0

print(np.arctan(-0.0))
# -0.0


#!/usr/bin/python3
# created by cicek on May 08, 2020 15:46

import matplotlib.pyplot as plt
import numpy as np
from collections import Counter

'''
kaynak:
https://www.python-course.eu/naive_bayes_classifier_introduction.php
'''

# grafik için bakınız: ./figure_in-time-too-late.png

'''
@list "in_time" :
 Hamburg treninin, aktarma treni olan Münih trenini yakalamak için
 zamanında gelmesini temsil eder.

@list "too_late" :
 aktarma trenin kaçırılmış olduğu durumları gösteren
 birkaç haftayı kapsayan verilerdir.

Her listenin içindeki ikili elemanların
 1.ci bileşeni: trenin kaç dakika geç kaldığını gösterir.
 2.ci bileşeni: bunun gerçekleşme süresini ifade eder.
'''

# the tuples consist of (delay time of train1, number of times)
# tuples are (minutes, number of times)

in_time = [(0, 22), (1, 19), (2, 17), (3, 18),
           (4, 16), (5, 15), (6, 9), (7, 7),
           (8, 4), (9, 3), (10, 3), (11, 2)]

too_late = [(6, 6), (7, 9), (8, 12), (9, 17),
            (10, 18), (11, 15), (12, 16), (13, 7),
            (14, 8), (15, 5)]

X, Y = zip(*in_time)
X2, Y2 = zip(*too_late)

bar_width = 0.9
plt.bar(X, Y, bar_width, color="blue", alpha=0.75, label="in time")

bar_width = 0.8
plt.bar(X2, Y2, bar_width, color="red", alpha=0.75, label="too late")

plt.legend(loc='upper right')
plt.show()

'''
Grafik için bakınız: ./figure_in-time-too-late.png

Bu verilere bakarak, Hamburg treninin 5 ve daha az dakika geç kalması
 durumunda, aktarma trenini yakalama olasılığı 1'dir.

12. dakikadan sonra ise aktarma trenine yetişme olasılığı 0'a düşer.

Yani burada koşullu olasılık vardır. Bayes teoremi'ne göre aktarma
 trenine yetişebilme ihtimalini ölçebilmemiz için önce Hamburg treninin
 kaç dakika geç kaldığını bilmemiz gerekir.
'''

in_time_dict = dict(in_time)
too_late_dict = dict(too_late)

# counter = 0
'''
counter değişkeni sadece 1 defa çalışır.
 bu yüzden karmaşıklık -> O(1)
'''
def catch_the_train(min):
    s = in_time_dict.get(min, 0)
    # global counter
    if s == 0:
        return 0
    else:
        # counter += 1
        m = too_late_dict.get(min, 0)
        # print("counter -> ", counter)
        return s / (s + m)

'''
range ne kadar belirlenmiş ise o kadar çalışacak
 bu yüzden karmaşıklık -> O(n)
'''
for minutes in range(0, 13):
    print(minutes, catch_the_train(minutes))

'''
output:

0 1.0
1 1.0
2 1.0
3 1.0
4 1.0
5 1.0
6 0.6
7 0.4375
8 0.25
9 0.15
10 0.14285714285714285
11 0.11764705882352941
12 0

Process finished with exit code 0
'''


# -*- coding: utf-8 -*-
"""
Created on Fri Mar 06 12:44:26 2015

@author: andrew.berkeley
"""

# This script is based on part 3 of Lorena Barba's "12 steps to Navier Stokes" series. It
# introduces the concept of the CFL condition.
#
# In the earlier lessons it was seen that varying the space step either caused numerical
# diffusion (smoothing of wave as derivatives poorly approximated) or instability. As the
# space resolution is increased, the numerical diffusion is decreased, increasing the model
# accuracy but only up to a point, beyond which instability is caused.
#
# The reason is that when the space resolution becomes sufficiently fine relative to the
# time resolution, the distance that the wave travels in 1 time step may be larger than
# a single space step. Stability can therefore be enforced if the timestep is defined
# relative to the space step, ensuring that it is always sufficiently small whatever
# space step is chosen.
#
# This example simply generates several model runs of the 1D linear advection model but
# with the dt term derived as a function of the dx term. The proportionality constant,
# here represented by sigma, is called the Courant Number, and defined in the general case
# as:
#
#   sigma = c*dt/dx < sigma_max
#
# This basically states that there exists a value, sigma_max, that depends on the discritization
# used that will ensure stability. As long as the realised sigma value is less than this value,
# the model will be stable.
#
# This example wraps up the model code from the 1D linear advection lesson into a function
# which can be invoked specifying the space resolution.
#
# Points to note
# --------------
#
# 1. Increasing the space resolution decreases the numerical diffusion - the original top
#    hat function is preserved much better with increasing numbers of nx.
#
# 2. Unlike previous examples, the resolution can be increased much higher than nx = 81
#    without causing instability. This means we can reduce the numerical diffusion with
#    much greater spatial resolution and still have a stable model.
#
# 3. sigma is basically defined as dt/dx in the implementation below, which is not quite the
#    definition above. This is just because c is chosen to be 1. If c is increased to,
#    say, 4, this changes the implied Courant number and in fact returns the model to
#    instability. This requires altering the timestep, in accordance with the definition of
#    the Courant number, which in this implementation is easily done by reducing the sigma
#    value, to say 0.1. So if we have faster advection, we needs a smaller timestep.
#
# 4. If we're only considering a fixed number of timesteps, changing the size of the time
#    step obviously results in a smaller amount of advection in the model run, since a
#    smaller absolute time window is being simulated.
#
# 5. This example illustrates the need for a sufficiently fine spatial resolution so as to
#    minimize numerical diffusion, but given the spatial resolution the time resolution
#    needs to be sufficiently and comparatively fine to ensure stability. Given that numerical
#    diffusion occurs when derivatives are under-estimated due to a coarse grid, it stands
#    to reason that spatial resolution needs to be finest where gradients are expected to vary
#    rapidly across space. This might be the case near a boundary, e.g. near a land boundary
#    or at the sea bed in a coastal hydrodynamic model.
#

import numpy as np                 #numpy is a library for array operations akin to MATLAB
import matplotlib.pyplot as plt    #matplotlib is 2D plotting library

# %% Wrap 1D linear advection model in a function which takes space resolution as argument

def linearconv(nx):
    dx = 2./(nx-1)
    nt = 20    # nt is the number of timesteps we want to calculate
    c = 2

    # Instead of setting the dt term as an absolute, fixed value, let's define it relative to
    # dx. The proportionality constant is sigma, as represented by the Courant Number.
    sigma = 0.5
    dt = sigma*dx

    u = np.ones(nx)        # defining a numpy array which is nx elements long with every value equal to 1.
    u[.5/dx : 1/dx+1] = 2  # setting u = 2 between 0.5 and 1 as per our I.C.s

    un = np.ones(nx) # initializing our placeholder array, un, to hold the values we calculate for the n+1 timestep

    for n in range(nt):  # iterate through time
        un = u.copy()    # copy the existing values of u into un
        for i in range(1,nx):
            u[i] = un[i]-c*dt/dx*(un[i]-un[i-1])

    plt.figure()
    plt.plot(np.linspace(0,2,nx),u)

 # %% Plot some results

linearconv(41)
linearconv(81)
linearconv(101)
linearconv(121)
linearconv(221)


import numpy as np

MIN = 0.00001

def gradient_descent(initial, diff_fn, alpha):
    current = initial
    count = 0
    while count < 1000000:
        next = next_x(current, diff_fn, alpha)
        sum = 0
        for (i, v) in enumerate(current):
            sum += (v - next[i]) ** 2
        distance = sum ** 0.5
        current = next
        count += 1
        if distance < MIN:
            break
    return (current, count)

def next_x(X, diff_fn, alpha):
    next = []
    for (i, v) in enumerate(X):
        next.append(v - alpha * diff_fn(X, i))
    return next

def diff1(X, i):
    A = [
        [9, 12, -6, -3],
        [12, 41, 2, 11],
        [-6, 2, 24, -8],
        [-3, 11, -8, 62]
    ]
    b = [-27, -42, 32, -23]
    sum = 0
    # vertical
    for (j, v) in enumerate(A):
        # if j == i でも2回足されるから大丈夫
        sum += v[i] * X[j] / 2
    # horizontal
    for (j, v) in enumerate(A[i]):
        sum += v * X[j] / 2
    sum += b[i]
    return sum

def diff2(X, i):
    A = [
        [16, 8, 12, -12],
        [8, 29, 16, 9],
        [12, 16, 29, -19],
        [-12, 9, -19, 35]
    ]
    b = [7, 5, -2, 9]
    sum = 0
    # vertical
    for (j, v) in enumerate(A):
        # if j == i でも2回足されるから大丈夫
        sum += v[i] * X[j] / 2
    # horizontal
    for (j, v) in enumerate(A[i]):
        sum += v * X[j] / 2
    sum += b[i]
    return sum

def diff3(X, i):
    if i == 0:
        return 40 * pow(X[0], 3) + 2 * X[0] - 40 * X[0] * X[1] - 2
    elif i == 1:
        return -20 * pow(X[0], 2) + 20 * X[1]
    else:
        print("invalid i == " + i)
        return 0

def f1(X):
    X = np.array(X)
    A = np.array([
        [9, 12, -6, -3],
        [12, 41, 2, 11],
        [-6, 2, 24, -8],
        [-3, 11, -8, 62]
    ])
    b = np.array([-27, -42, 32, -23])
    return np.dot(X.T, np.dot(A, X)) / 2 + np.dot(b.T, X) + 163

def f2(X):
    X = np.array(X)
    A = np.array([
        [16, 8, 12, -12],
        [8, 29, 16, 9],
        [12, 16, 29, -19],
        [-12, 9, -19, 35]
    ])
    b = np.array([7, 5, -2, 9])
    return np.dot(X.T, np.dot(A, X)) / 2 + np.dot(b.T, X) + 5

def f3(X):
    return (X[0] - 1) ** 2 + 10 * (X[0] ** 2 - X[1]) ** 2

if __name__ == '__main__':
    tests = [[0, 0, 0, 0], [-2, -1, 1, 2], [100, 200, 300, 400]]
    print("== 最急降下法-1 ==")
    for test in tests:
        print("initial = ", test)
        (result, count) = gradient_descent(test, diff1, 0.001)
        print("f(x) = ", f1(result))
        print("[x1, x2, x3, x4] = ", result)
        print("count = ", count)
        print()

    print("== 最急降下法-2== ")
    for test in tests:
        print("initial = ", test)
        (result, count) = gradient_descent(test, diff2, 0.001)
        print("f(x) = ", f2(result))
        print("[x1, x2, x3, x4] = ", result)
        print("count = ", count)
        print()

    tests = [[0,0], [-1,1], [10, 20]]
    print("== 最急降下法-3 ==")
    for test in tests:
        print("initial = ", test)
        (result, count) = gradient_descent(test, diff3, 0.001)
        print("f(x) = ", f3(result))
        print("[x1, x2] = ", result)
        print("count = ", count)
        print()


'''
The NumPy module also comes with a number of built-in routines for linear algebra calculations. These can be found in the sub-module linalg.

linalg.det
The linalg.det tool computes the determinant of an array.
print numpy.linalg.det([[1 , 2], [2, 1]])       #Output : -3.0

linalg.eig
The linalg.eig computes the eigenvalues and right eigenvectors of a square array.
vals, vecs = numpy.linalg.eig([[1 , 2], [2, 1]])
print vals                                      #Output : [ 3. -1.]
print vecs                                      #Output : [[ 0.70710678 -0.70710678]
                                                #          [ 0.70710678  0.70710678]]
linalg.inv
The linalg.inv tool computes the (multiplicative) inverse of a matrix.
print numpy.linalg.inv([[1 , 2], [2, 1]])       #Output : [[-0.33333333  0.66666667]
                                                #          [ 0.66666667 -0.33333333]]
Other routines can be found here

Task
You are given a square matrix A with dimensions NXN. Your task is to find the determinant. Note: Round the answer to 2 places after the decimal.

Input Format
The first line contains the integer N.
The next N lines contains the N space separated elements of array A.

Output Format
Print the determinant of A.

Sample Input
2
1.1 1.1
1.1 1.1

Sample Output
0.0

'''

import numpy

a = int(input())
b = []
for i in range(a):
    c = input()
    b.append(list(map(float, c.split())))
d = numpy.linalg.det(b)
print(round(d, 2))


import numpy as np
import matplotlib.pyplot as plt

## Interpolare cu polinoame Lagrange

# Definesc funcția
# def f(x):
#     return np.cos(2 * x) - 2 * np.sin(3 * x)

def f(x):
    return 1 / (1 + 25 * (x ** 2))

# Definesc domeniul pe care fac interpolarea
left = -np.pi
right = +np.pi

# Gradul polinomului de interpolare
N = 20

# Definesc polii de interpolare

use_chebyshev = True

if not use_chebyshev:
    # Polii egal distanțați
    x = np.linspace(left, right, N + 1)
else:
    # Polii Chebyshev
    def chebyshev(k):
        x = np.cos(np.pi * ((N - k) / N))
        return (left + right)/2 + ((right - left)/2) * x

    x = np.array([chebyshev(k) for k in range(N + 1)])

y = f(x)

def compute_ordinary_polynomial():
    # Construiesc matricea Vandermonde pentru sistemul pe care vreau să-l rezolv
    A = np.vander(x, N=N + 1, increasing=True)

    a = np.linalg.solve(A, y)

    def polynomial(x):
        "Evaluează polinomul cu coeficienții `a` în punctul `x`"
        s = 0
        for i in range(N + 1):
            s += a[i] * (x ** i)
        return s

    return polynomial

def compute_lagrange_polynomial():
    def lagrange_coef(k, t):
        coefs = []
        for i in range(N + 1):
            if i != k:
                coefs.append((t - x[i])/(x[k] - x[i]))
        return np.prod(coefs)

    def polynomial(x):
        "Evaluează polinomul obținut cu interpolare Lagrange în punctul `x`"
        s = 0
        for i in range(N + 1):
            s += lagrange_coef(i, x) * y[i]
        return s

    return polynomial

def polynomial_vector(polynomial, v):
    "Evaluează polinomul dat pe vectorul `v`"
    return np.array([polynomial(x) for x in v])

# Afișez funcția
xs = np.linspace(left, right, 200)
plt.plot(xs, f(xs), linestyle='-', label='funcția f')

# Afișez polii interpolării
plt.scatter(x, y, c='red')

# Afișez interpolările

ordinary = compute_ordinary_polynomial()
plt.plot(xs, polynomial_vector(ordinary, xs),
    c='orange',
    linestyle='--',
    label=f'aproximare cu polinom de gradul {N}')

lagrange = compute_lagrange_polynomial()
plt.plot(xs, polynomial_vector(lagrange, xs),
    c='purple',
    linestyle='-.',
    label=f'aproximare cu Lagrange de gradul {N}')

# Afișez o legendă
plt.legend()

# Afișez tot graficul
plt.show()

# Afișăm eroarea aproximării
show_error = True
if show_error:
    y_real = f(xs)

    y_approx = polynomial_vector(ordinary, xs)
    plt.plot(xs, np.abs(y_approx - y_real),
        linestyle='--',
        label=f'eroare pentru N={N}')

    y_approx = polynomial_vector(lagrange, xs)
    plt.plot(xs, np.abs(y_approx - y_real),
        linestyle='-.',
        label=f'eroare pentru Lagrange N={N}')

    plt.legend()
    plt.show()


import time,os
import math
import numpy as np

def profiler(method):
    def wrapper_method(*arg, **kw):
        t = time.time()
        ret = method(*arg, **kw)
        print('Method '  + method.__name__ +' took : ' + "{:2.5f}".format(time.time()-t) + ' sec')
        return ret
    return wrapper_method

@profiler
def part1():

    x = 0
    y = 0

    ang = 0

    for l in open('input.txt', 'r'):
        dr = l[0]
        mag = int(l.strip()[1:])
        if dr == 'F' :
            x += mag * math.cos(math.radians(ang))
            y += mag * math.sin(math.radians(ang))

        elif dr == 'R':
            ang -= mag

        elif dr == 'L':
            ang += mag

        else :
            delta = {'N' : (0,+mag) , 'S' : (0,-mag) , 'E' : (mag,0) ,  'W' : (-mag,0)}

            x += delta[dr][0]
            y += delta[dr][1]

    print('part 1 answer : ' , int(abs(x) +  abs(y)))

@profiler
def part2():

    x = 0
    y = 0

    wx = 10
    wy = 1

    for l in open('input.txt'):
        dr = l[0]
        mag = int(l.strip()[1:])

        if dr == 'F' :
            x += mag * wx
            y += mag * wy

        # source https://stackoverflow.com/a/3162731
        elif dr == 'R':
            nx =   wx * math.cos(math.radians(mag)) + wy * math.sin(math.radians(mag))
            ny =  -wx * math.sin(math.radians(mag)) + wy * math.cos(math.radians(mag))

            wx = round(nx)
            wy = round(ny)

        elif dr == 'L':
            nx =   wx * math.cos(-math.radians(mag)) + wy * math.sin(-math.radians(mag))
            ny =  -wx * math.sin(-math.radians(mag)) + wy * math.cos(-math.radians(mag))

            wx = round(nx)
            wy = round(ny)

        else :
            delta = {'N' : (0,+mag) , 'S' : (0,-mag) , 'E' : (mag,0) ,  'W' : (-mag,0)}

            wx += delta[dr][0]
            wy += delta[dr][1]

    print('part 2 answer : ' , abs(x) +  abs(y))

@profiler
def p2_matrix():

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

    wp  = np.array([10,1])

    for l in open('input.txt'):
        dr = l[0]
        mag = int(l.strip()[1:])

        if dr == 'F' :
            pos += mag * wp

        elif dr == 'R':
            theta = np.radians(mag)
            Rt = np.array([[np.cos(theta) , -np.sin(theta)] , [np.sin(theta) , np.cos(theta)]])
            wp = np.rint(wp.dot(Rt)).astype(int)

        elif dr == 'L':
            theta = np.radians(-mag)
            Rt = np.array([[np.cos(theta) , -np.sin(theta)] , [np.sin(theta) , np.cos(theta)]])

            wp = np.rint(wp.dot(Rt)).astype(int)

        else :
            delta = {'N' : np.array([0,1]) , 'S' : np.array([0,-1]), 'E' : np.array([1,0]) ,  'W' : np.array([-1,0])}

            wp += mag * delta[dr]

    print('part 2 using matrix answer : ' , np.abs(pos[0]) +  np.abs(pos[1]))

@profiler
def p2_complex():

    pos = 0 + 0j

    wp  = 10 + 1j

    for l in open('input.txt'):
        dr = l[0]
        mag = int(l.strip()[1:])

        if dr == 'F' :
            pos += mag * wp

        elif dr == 'R':
           wp *= 1j**(-mag/90)

        elif dr == 'L':
           wp *= 1j**(mag/90)

        else :
            delta = {'N' : 1j , 'S' : -1j, 'E' : 1 ,  'W' : -1}
            wp += mag * delta[dr]

    print('part 2 using complex answer : ' , int(abs(pos.real) +  abs(pos.imag)))

if __name__ == "__main__":

    part1()
    part2()
    p2_complex()
    p2_matrix()


import numpy as np

# activation functions and its derivatives
def tanh(x):
    return np.tanh(x)

def tanh_prime(x):
    return 1-np.tanh(x)**2

def sigmoid(x):
    return 1/(1+np.exp(-x))

def sigmoid_prime(x):
    return sigmoid(x)*(1-sigmoid(x))


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

import numpy as np, cv2 as cv, math
from scipy.special import factorial
from matplotlib import pyplot as plt
from datetime import datetime
from tqdm.notebook import tqdm
from IPython.display import clear_output
from Profiler import *

line = line1 = "*"*60
line2 = "\n" + line + ""
line3 = line2 + "\n"

print( f"Hello... Good morning!" )
print( f"Importing python packages was done." )

@profile
def Rpq(p, q, rho, debug = 0 ) :
    if abs(q) > p :
        print( f"Invalid argument, abs(q = {q}) < p(={p}) is not satisfied")
        return
    pass

    if int(p - abs(q))%2 == 1 :
        print( f"Invalid argument, p({p}) - q({q}) should be an even number.")
        return
    pass

    q = abs( q )

    t = max( (p - q)/2, 0 )
    s = np.arange( 0, t + 1 )

    R_ps = np.power( -1, s )*factorial(p - s)/factorial(s)/factorial( (p + q)/2 - s)/factorial( (p - q)/2 - s )
    rho_power = []

    for p_2s in p - 2*s :
        rho_power.append( np.power(rho, p_2s ) )
    pass

    rho_power = np.array( rho_power )

    R_pq_rho = np.inner( rho_power.T, R_ps )

    #R_sum = np.sum( R_pq_rho )

    if debug :
        print( line2 )
        print( f"p = {p}, q={q}, (p - |q|)/2 = {t}" )
        print( "s = ", s )
        print( "R_ps = ", R_ps )
        print( "rho_power shape = ", rho_power.shape )
        print( "rho_power.T shape = ", rho_power.T.shape )
        print( "rho_power = ", rho_power )
        print( "rho_power.T = ", rho_power.T )
        print( "R_pq_rho = ", R_pq_rho )
        #print( "R_sum = ", R_sum )
        print( line2 )
    pass

    return R_pq_rho
pass

@profile
def Vpq( p, q, x, y, debug = 0 ) :
    rho = np.sqrt( x**2 + y**2 )

    R_pq = Rpq( p, q, rho, debug = 0 )

    V_pq = R_pq

    if q :
        V_pq = R_pq*np.exp( 1j*q*np.arctan2( y, x ) );
    pass

    #print( "rho = ", rho )
    if debug :
        print( f"V_pq({p}, {q}) = ", V_pq )
    pass

    return V_pq
pass

print( "Zernike functions are defined.")

current_time = datetime.now().strftime("%H:%M:%S")
print("Current Time =", current_time)


'''
<title>367 Bozo Sort</title>

<meta name="solved-by"  content="Joe Tacheron">
<meta name="difficulty" content="55%">
<meta name="run time"   content="1s">
<meta name="answer"     content="48271207">

367 Bozo Sort

Problem Description

Bozo sort, not to be confused with the slightly less efficient bogo sort, consists out of checking if the input sequence is sorted and if not swapping randomly two elements. This is repeated until eventually the sequence is sorted.

If we consider all permutations of the first 4 natural numbers as input the expectation value of the number of swaps, averaged over all 4! input sequences is 24.75.
The already sorted sequence takes 0 steps.

In this problem we consider the following variant on bozo sort.
If the sequence is not in order we pick three elements at random and shuffle these three elements randomly.
All 3!=6 permutations of those three elements are equally likely.
The already sorted sequence will take 0 steps.
If we consider all permutations of the first 4 natural numbers as input the expectation value of the number of shuffles, averaged over all 4! input sequences is 27.5.
Consider as input sequences the permutations of the first 11 natural numbers.
Averaged over all 11! input sequences, what is the expected number of shuffles this sorting algorithm will perform?

Give your answer rounded to the nearest integer.

Solution Method

First I'll define what I call a "swap list": a description of a permuted sequence given as a list of 2-swaps, 3-swaps, etc. needed to sort the sequence. E.g., the sequence 53421 has a swap list [2,3], indicating one 2-swap (the 1 & 5) and one 3-swap (the 2, 3, & 4); the swap list [] or [0] means the sequence is correctly sorted.

Note that each permutation is described by exactly one swap list, while each each swap list encompasses multiple permutations. In other words, permutations can be partitioned into "swap-classes." Also, note that after a Bozo swap, the transition probability distribution (from one swap-class to another) is the same for all permutations of the same swap-class.

So with those definitions out of the way, here's my solution method:

1. 	Create a representative permutation for each swap-class

2. 	Calculate its transition probability distribution.

3. 	Solve the "expected number of Bozo swaps" (ENBS) system of equations:

	S(0) = 0
	S(2) = 1 + P(2→2)×S(2) + P(2→2,2)×S(2,2) + ... + P(2→11)×S(11)
	...
	S(11) = 1 + P(11→2)×S(11) + P(11→2,2)×S(2,2) + ... + P(11→11)×S(11)

	where S(x) is the ENBS to go from swap-class [x] to swap-class [], and
	P(x→y) means the probability of transitioning from [x] to [y] in a single Bozo swap.

4. 	Calculate the average ENBS across every permutation. Since the ENBS is the same for every permutation in the same swap-class, you just need to know how large each swap-class is.

	answer = Σ(size(x)*S(x) for each swap-class x) / 11!

Solved in one second in python.

'''

import math
import numpy as np
from collections import Counter

def bozo_swap(seq, i, j, k, type):
	'''Bozo swap 3 elements, 'type' selects how.'''
	I,J,K = ((i,j,k),(j,k,i),(k,i,j),(j,i,k),(i,k,j),(k,j,i))[type]
	seq[i], seq[j], seq[k] = seq[I], seq[J], seq[K]
	return seq

def make_swap_lists(length, _all = None, _parent = []):
	'''Create every possible swap list for a sequence of given length.'''
	if _all is None:
		_all = [tuple()]
	for s in range(2, length+1):
		if len(_parent) > 0 and s < _parent[-1]:
			continue
		if sum(_parent) + s > length:
			break
		swaps = _parent.copy()
		swaps.append(s)
		_all.append(tuple(swaps))
		make_swap_lists(length, _all, swaps)
	return _all

def representative_perm(length, swaps):
	'''Create an arbitrary permutation with the given length and swap list.'''
	seq = list(range(length))
	# an n-swap can be made by n-1 2-swaps
	i = 0
	for s in swaps:
		for j in range(s-1):
			seq[i+j], seq[i+j+1] = seq[i+j+1], seq[i+j]
		i += s
	return seq

def count_perms(length, swaps):
	'''Count the number of permutations with the given swap list.'''
	total = 1
	for s in swaps:
		total *= math.comb(length, s) * math.factorial(s-1) # choose s items & permute them
		length -= s # chosen s items cannot be chosen again
	# divide by repeated swaps
	# e.g. a [2,2] swap list double-counts; [3,3,3] overcounts by a factor of 3!
	for swap,count in Counter(swaps).items():
		total //= math.factorial(count)
	return total

def swap_list(seq):
	'''Identify the swap list from the given permutation.'''
	swaps = []
	for i in range(len(seq)):
		if seq[i] in [-1, i]:
			continue
		s = 0
		while seq[i] != -1:
			seq[i], i = -1, seq[i]
			s += 1
		swaps.append(s)
	swaps.sort()
	return tuple(swaps)

def transition_dist(length, swaps):
	'''
	Calculate the probability distribution for an arbitrary permutation of a given swap-class to transition into another swap-class after undergoing a Bozo swap.
	'''
	seq = representative_perm(length, swaps)
	dist = Counter()
	# count swap-classes visited after a Bozo swap
	for i in range(len(seq)-2):
		for j in range(i+1, len(seq)-1):
			for k in range(j+1, len(seq)):
				for type in range(6):
					swapped = bozo_swap(seq.copy(), i, j, k, type)
					s = swap_list(swapped)
					dist[s] += 1
	# convert to list of probabilities
	total = sum(dist.values())
	dist = [dist[s]/total for s in make_swap_lists(len(seq))]
	return dist

def transition_matrix(length):
	'''Compile transition distributions into a matrix.'''
	swap_lists = make_swap_lists(length)
	mat = []
	for swaps in swap_lists:
		mat.append(transition_dist(length, swaps))
	mat = np.matrix(mat)
	# The first row is special in that the transition probability from a
	# correctly ordered sequence to any other needs to be 0, including [] -> [].
	mat[0, :] = 0
	return mat

def expected_swap_count(length):
	'''Solve the expected swap count system of equations.'''
	A = transition_matrix(length)
	A = A - np.eye(A.shape[0])
	b = -1 * np.ones(A.shape[0])
	b[0] = 0 # 0 swaps needed for the sorted sequence
	expected_swap_count = np.linalg.solve(A, b)
	# average "expected swap count" across all permutations
	class_size = [count_perms(length, swaps) for swaps in make_swap_lists(length)]
	average = expected_swap_count.dot(class_size) / math.factorial(length)
	return average

assert math.isclose(expected_swap_count(4), 27.5)
ans = round(expected_swap_count(11))
print(ans)


import numpy as np
import numpy.linalg as la
import scipy.signal as scisig
import random
from func import *
import time

from sympy import symbols, Matrix, Poly, zeros, eye, Indexed, simplify, IndexedBase, init_printing, pprint
from operator import mul
from functools import reduce

def At(a,m,n):
    return Matrix(m, n, lambda i,j: a[i]**j)

def A(a,m,n):
    return At(a, m-1, n).row_insert(m-1, Matrix(1, n, lambda i,j: 1 if j==n-1 else 0))

def T(a,n):
    return Matrix(Matrix.eye(n).col_insert(n, Matrix(n, 1, lambda i,j: -a[i]**n)))

def Lx(a,n):
    x=symbols('x')
    return Matrix(n, 1, lambda i,j: Poly((reduce(mul, ((x-a[k] if k!=i else 1) for k in range(0,n)), 1)).expand(basic=True), x))

def F(a,n):
    return Matrix(n, 1, lambda i,j: reduce(mul, ((a[i]-a[k] if k!=i else 1) for k in range(0,n)), 1))

def Fdiag(a,n):
    f=F(a,n)
    return Matrix(n, n, lambda i,j: (f[i,0] if i==j else 0))

def FdiagPlus1(a,n):
    f = Fdiag(a,n-1)
    f = f.col_insert(n-1, zeros(n-1,1))
    f = f.row_insert(n-1, Matrix(1,n, lambda i,j: (1 if j==n-1 else 0)))
    return f

def L(a,n):
    lx = Lx(a,n)
    f = F(a, n)
    return Matrix(n, n, lambda i,j: lx[i, 0].nth(j)/f[i]).T

def Bt(a,n):
    return L(a,n)*T(a,n)

def B(a,n):
    return Bt(a,n-1).row_insert(n-1, Matrix(1, n, lambda i,j: 1 if j==n-1 else 0))

FractionsInG=0
FractionsInA=1
FractionsInB=2
FractionsInF=3

def cookToomFilter(a,n,r,fractionsIn=FractionsInG):
    alpha = n+r-1
    f = FdiagPlus1(a,alpha)
    if f[0,0] < 0:
        f[0,:] *= -1
    if fractionsIn == FractionsInG:
        AT = A(a,alpha,n).T
        G = (A(a,alpha,r).T/f).T
        BT = f * B(a,alpha).T
    elif fractionsIn == FractionsInA:
        BT = f * B(a,alpha).T
        G = A(a,alpha,r)
        AT = (A(a,alpha,n)).T/f
    elif fractionsIn == FractionsInB:
        AT = A(a,alpha,n).T
        G = A(a,alpha,r)
        BT = B(a,alpha).T
    else:
        AT = A(a,alpha,n).T
        G = A(a,alpha,r)
        BT = f * B(a,alpha).T
    return (AT,G,BT,f)

def filterVerify(n, r, AT, G, BT):

    alpha = n+r-1

    di = IndexedBase('d')
    gi = IndexedBase('g')
    d = Matrix(alpha, 1, lambda i,j: di[i])
    g = Matrix(r, 1, lambda i,j: gi[i])

    V = BT*d
    U = G*g
    M = U.multiply_elementwise(V)
    Y = simplify(AT*M)

    return Y

def convolutionVerify(n, r, B, G, A):

    di = IndexedBase('d')
    gi = IndexedBase('g')

    d = Matrix(n, 1, lambda i,j: di[i])
    g = Matrix(r, 1, lambda i,j: gi[i])

    V = A*d
    U = G*g
    M = U.multiply_elementwise(V)
    Y = simplify(B*M)

    return Y

def showCookToomFilter(a,n,r,fractionsIn=FractionsInG):

    AT,G,BT,f = cookToomFilter(a,n,r,fractionsIn)

    print("AT = ")
    pprint(AT)
    print("")

    print("G = ")
    pprint(G)
    print("")

    print("BT = ")
    pprint(BT)
    print("")

    if fractionsIn != FractionsInF:
        print("FIR filter: AT*((G*g)(BT*d)) =")
        pprint(filterVerify(n,r,AT,G,BT))
        print("")

    if fractionsIn == FractionsInF:
        print("fractions = ")
        pprint(f)
        print("")

def showCookToomConvolution(a,n,r,fractionsIn=FractionsInG):

    AT,G,BT,f = cookToomFilter(a,n,r,fractionsIn)

    B = BT.transpose()
    A = AT.transpose()

    print("A = ")
    pprint(A)
    print("")

    print("G = ")
    pprint(G)
    print("")

    print("B = ")
    pprint(B)
    print("")

    if fractionsIn != FractionsInF:
        print("Linear Convolution: B*((G*g)(A*d)) =")
        pprint(convolutionVerify(n,r,B,G,A))
        print("")

    if fractionsIn == FractionsInF:
        print("fractions = ")
        pprint(f)
        print("")

# input size = a = m + r -1
# kernel size = r
# output size = r
# number of points = m + r - 2

# function for testing 2d convolution on a variable amount of points
def test_points_variable(points, image, kernel ,image_size, kernel_size):

    A,G,B,f2=  cookToomFilter(tuple(points), image_size, kernel_size)

    #print(B, "\n\n", G, "\n\n", A )

    Atemp = np.array(A, dtype=np.float32)
    Btemp = np.array(B, dtype=np.float32)
    Gtemp = np.array(G, dtype=np.float32)
    #print("Broken: ",B23, "\n\n", G23, "\n\n", A23 )
    B23 = np.asarray(Btemp).T

    G23 = np.asarray(Gtemp)

    A23 = np.asarray(Atemp).T

    #print("Working: ",B23, "\n\n", G23, "\n\n", A23 )

    g = image
    f = kernel

    direct = np.zeros((image_size,image_size))
    for i in range(image_size):
        for j in range(image_size):
            direct[i,j] = np.sum(f * g[i:i+kernel_size,j:j+kernel_size])

    inner = np.dot(G23, np.dot(f, G23.T)) * np.dot(B23.T, np.dot(g, B23))
    Y = np.dot(A23.T, np.dot(inner, A23))
    error = la.norm(Y - direct)/la.norm(direct)
    #print("Error of one Winograd",la.norm(Y - direct)/la.norm(direct))

    return error

def generate_set(image_size,kernel_size, size):
    list= []
    for i in range(size):
        image = np.random.random((image_size,image_size))
        kernel = np.random.random((kernel_size,kernel_size))
        list.append((image,kernel))

    return list

""" from observation that usually you need symmetry between pos / neg numbers"""
# function that tries to balance numbers pos / neg symmetrically from the outgo to not waste time on worse points
# from experimental observations i notice that run time is very similar maybe a bit faster than other random function
# however results are slightly better especially for larger point numbers
# eg 5/7 points, the more points the better performance increase from the symmetric sampling
def sample_floats_symmetric(low, high, k=1):
    if low > 0 or high < 0:
        return None
    result = []
    seen = set()
    var = bool(random.getrandbits(1))
    for i in range(k):
        if var == 0:
            var = 1
        else:
            var = 0

        if var == 0:
            x = random.uniform(0,low)
        else:
            x = random.uniform(0,high)
        while x in seen:
            if var == 0:
                x = random.uniform(0,low)
            else:
                x = random.uniform(0,high)
        seen.add(x)
        result.append(x)
    return result

def sample_floats(low, high, k=1):
    """ Return a k-length list of unique random floats
        in the range of low <= x <= high
    """
    result = []
    seen = set()
    for i in range(k):
        x = random.uniform(low, high)
        while x in seen:
            x = random.uniform(low, high)
        seen.add(x)
        result.append(x)
    return result

# returns the averag error rate for a given set of points for a image set
def test_points_for_image_list(points, imageKernelList, image_size, kernel_size):
    error_rate_sum = 0
    for tuple in imageKernelList:
        image = tuple[0]
        kernel = tuple[1]
        pointsTuple = points

        error_rate_sum += test_points_variable(points,image,kernel,image_size,kernel_size)

    return error_rate_sum/ len(imageKernelList)

def find_error_rate(image_size,kernel_size,number_of_images, number_points_sets,points_per_set,input_size):
    dictionary = {}
    resultList = []
    point_coefficient = 0
    imageKernelList = generate_set(input_size,kernel_size,number_of_images) #  eg generating 4x4 images, 3x3 kernels, 10 images in the set
    for i in range(number_points_sets):
        # loop around this n times generating a new points set each loop to test
        upper = 1
        lower = -1
        pointsSet = sample_floats(lower,upper,image_size+kernel_size-2)
        #pointsSet = sample_floats_symmetric(lower,upper,image_size+kernel_size-2)
        average_error = test_points_for_image_list(pointsSet,imageKernelList,image_size,kernel_size)
        print("average_error :",average_error, "@ points :",pointsSet)
        dictionary[tuple(pointsSet)] = average_error
        point_coefficient += average_error

    # exit loop now we have all average error rates for the points sets we have tested
    sorted_d = sorted((value, key) for (key,value) in dictionary.items())

    point_coefficient = point_coefficient/input_size
    #print("Point coef: ",point_coefficient)
    return sorted_d

def refine_search(high_performance_points, number_of_images, image_size, kernel_size, points_per_set):
    refined = []
    for x in range(len(high_performance_points)):
        input_size = image_size + kernel_size -1
        imageKernelList = generate_set(input_size,kernel_size,number_of_images)
        points = high_performance_points[x][1]

        refined_error = test_points_for_image_list(points, imageKernelList, image_size, kernel_size)
        print("refined_error (average of ",number_of_images," runs) + points : ",(refined_error,points))
        refined.append((refined_error,points))

    return refined

""" one thing i notice with the results is that the best points always have a split on pos/negative numbers, never pos, pos, pos || neg, neg, neg"""

# testing get error rate
image_size = 4
kernel_size = 3
number_of_images = 100
number_points_sets = 1000
points_per_set = 5
input_size = image_size + kernel_size -1 # 4
st = time.time()
result = find_error_rate(image_size,kernel_size,number_of_images, number_points_sets,points_per_set,input_size)
end = time.time()
print("Time elapsed to do initial error rate calculations: ",end-st,"s")

print("Lowest error rate points (100) :")
i=0
for i in range(100):
    print(result[i])

high_performance_points = []
for x in range(100):
    high_performance_points.append(result[x])

number_images_refined = 1000
print("Refining top ",len(high_performance_points), " points with ",number_images_refined," runs each")

refined = refine_search(high_performance_points, number_images_refined, image_size, kernel_size, points_per_set)
refined_sorted = sorted(refined)
for x in refined_sorted:
    print("refined_average_error + points", x)

"""

# testing get error rate
image_size = 4
kernel_size = 3
number_of_images = 100
number_points_sets = 1000
points_per_set = 5
input_size = image_size + kernel_size -1 # 4
st = time.time()
result = find_error_rate(image_size,kernel_size,number_of_images, number_points_sets,points_per_set,input_size)
end = time.time()
print("Time elapsed to do initial error rate calculations: ",end-st,"s")

print("Lowest error rate points (100) :")
i=0
for i in range(100):
    print(result[i])

high_performance_points = []
for x in range(100):
    high_performance_points.append(result[x])

number_images_refined = 2000
print("Refining top ",len(high_performance_points), " points with ",number_images_refined," runs each")

refined = refine_search(high_performance_points, number_images_refined, image_size, kernel_size, points_per_set)
refined_sorted = sorted(refined)
for x in refined_sorted:
    print("refined_average_error + points", x)
"""

"""
# generates unique floating points random list
# possible improvements later to focus only on rational numbers and to try something with reciprocal values

points = sample_floats(0,1,3)
print(points)
"""

"""

# testing average error rate function for a given points set
# also returns a value to access the effectiveness of the point picker function
# it will return the average performance of the points sets picked
image_size = 2
kernel_size = 3
imageKernelList = generate_set(4,3,100) # 4x4 images, 3x3 kernels, 2 tuples total
points = [0.8095133296178773,0.10362463148985646,0.7774840747025125]
average_error = test_points_for_image_list(points,imageKernelList,image_size,kernel_size)
print(average_error)

"""

"""
#
# TESTING VARIOUS INPUT SIZED EG F(2,3)..
#

print("f(2,3)")
image_size = 2
kernel_size = 3
points = [10,20,30]
image = np.random.random((image_size+kernel_size-1,image_size+kernel_size-1))
kernel =  np.random.random((kernel_size,kernel_size))
error = test_points_variable(points,image,kernel,image_size,kernel_size)
print("Error: ",error)

print("f(2,3)")
image_size = 2
kernel_size = 3
points = [0,1,-1]
image = np.random.random((image_size+kernel_size-1,image_size+kernel_size-1))
kernel =  np.random.random((kernel_size,kernel_size))

error = test_points_variable(points,image,kernel,image_size,kernel_size)
print("Error: ",error)

print("f(4,3)")
image_size = 4
kernel_size = 3
points = [-1,-0.5,0,0.5,1] # number of points = (4+3)-2 = 5
image = np.random.random((image_size+kernel_size-1,image_size+kernel_size-1))
kernel =  np.random.random((kernel_size,kernel_size))

error = test_points_variable(points,image,kernel,image_size,kernel_size)
print("Error: ",error)

print("f(6,3)")
image_size = 6
kernel_size = 3
points = [-1.5,-1,-0.5,0,0.5,1,1.5] # number of points = (6+3)-2 = 7 points
image = np.random.random((image_size+kernel_size-1,image_size+kernel_size-1))
kernel =  np.random.random((kernel_size,kernel_size))

error = test_points_variable(points,image,kernel,image_size,kernel_size)
print("Error: ", error)

print("f(5,3)")
image_size = 5
kernel_size = 3
points = [-1.5,-1,-0.5,0,0.5,1] # number of points = (5+3)-2 = 6 points
image = np.random.random((image_size+kernel_size-1,image_size+kernel_size-1))
kernel =  np.random.random((kernel_size,kernel_size))

error = test_points_variable(points,image,kernel,image_size,kernel_size)
print("Error: ", error)

print("f(3,3)")
image_size = 3
kernel_size = 3
points = [-1,-0.5,0,0.5] # number of points = (3+3)-2 = 4 points
image = np.random.random((image_size+kernel_size-1,image_size+kernel_size-1))
kernel =  np.random.random((kernel_size,kernel_size))

error = test_points_variable(points,image,kernel,image_size,kernel_size)
print("Error: ", error)

print("f(2,2)")
image_size = 2
kernel_size = 2
points = [0.5,-0.5] # number of points = (2+2)-2 = 2 points
image = np.random.random((image_size+kernel_size-1,image_size+kernel_size-1))
kernel =  np.random.random((kernel_size,kernel_size))

error = test_points_variable(points,image,kernel,image_size,kernel_size)
print("Error: ", error)

"""


# Linear Algebra Take Home Quiz #3
# Author: Rocco Haro
# University of Alaska, Anchorage
# Oct '17'
# https://github.com/rocco-haro/Linear-Difference-Eq..git

import numpy as np

class StateMtx(object):
    """ Calculates the k+1 values for a linear difference equation
    """
    def __init__(self, *args):
        self.args = args
        # Converts input arr to matrix, which returns a N by 1 matrix.
        # So I take the transpose of this returned matrix to make it a valid
        # matrix for multiplication against the stateMtx
        self.initParameters = np.mat(self.args[0]).T

        # just hard codoing the matrix vals
        prep = [[0.97, 0.05, 0.10], [0.00, 0.90, 0.05], [0.03, 0.05, 0.85]]
        self.stateMtx = np.mat(prep) # converts array to matrix

    def printClass(self):
        print("Initial Parameters: ")
        print(self.initParameters)
        print("\nState Matrix: ")
        print(self.stateMtx)
        print("\n")

    def getNextK(self, currK):
        return self.stateMtx*currK

    def getKthValueAt(self, targetDay):
        currK = self.initParameters
        for x in range(targetDay):
            nextK = self.getNextK(currK)
            currK = nextK
        return currK

if __name__ == "__main__":
    k0 = [295, 55, 150 ]
    m = StateMtx(k0)
    m.printClass()
    res = m.getKthValueAt(30)
    print(res)


# Question 06, Lab 04
# AB Satyaprakash - 180123062

# imports ----------------------------------------------------------------------------
from sympy.abc import t
from sympy import evalf, integrate, sqrt, sin
from math import pi
import numpy as np
import pandas as pd

# functions --------------------------------------------------------------------------

def f(func, x):
    return func.subs(t, x)

def getX(a, b, n):
    h = (b-a)/n
    return np.arange(a, b+h/2, h)

def compositeTrapezoidalRule(func, X, mapf, count):
    sum = 0
    a, b = X[0], X[-1]
    n = X.shape[0]
    h = (b-a)/(n-1)
    cnt = count
    for i in range(X.shape[0]):
        x = X[i]
        if(mapf.get(x) == None):
            mapf[x] = f(func, x)
            val = mapf[x]
            cnt += 1
        else:
            val = mapf[x]
        if(i == 0 or i == n-1):
            sum += val/2
        else:
            sum += val
    return (h*sum), cnt

# program body
# part (a)
func = t/(1+t**2)
a, b = 0, 3
mapf = {}
n = 1
count = 0

H_2, TH_2, errorArray = [], [], []
while(True):
    h = (b-a)/n
    X = getX(a, b, n)
    Th, count = compositeTrapezoidalRule(func, X, mapf, count)
    X = getX(a, b, 2*n)
    Th_2, count = compositeTrapezoidalRule(func, X, mapf, count)
    err = abs(Th-Th_2)/abs(Th_2)
    # append values to tabulate later on
    H_2.append(h/2)
    TH_2.append(Th_2)
    errorArray.append(err)
    # error break condition
    if(err < 10**(-6)):
        break
    n *= 2

print('For part a:')
H_2, TH_2, errorArray = np.array(H_2), np.array(TH_2), np.array(errorArray)
data = np.vstack((H_2, TH_2, errorArray)).T
df = pd.DataFrame(data,
                  columns=['h/2', 'T(h/2)', '|T(h)-T(h/2)|/|T(h/2)|'])
print('\n', df.round(6), '\n')
print('Estimated value of integral is', TH_2[-1])
print('The total number of function evaluations f(x) is', count)
print('\n')

# part (b)
func = 1/(1-t)
a, b = 0, 0.95
mapf = {}
n = 1
count = 0
H_2, TH_2, errorArray = [], [], []
while(True):
    h = (b-a)/n
    X = getX(a, b, n)
    Th, count = compositeTrapezoidalRule(func, X, mapf, count)
    X = getX(a, b, 2*n)
    Th_2, count = compositeTrapezoidalRule(func, X, mapf, count)
    err = abs(Th-Th_2)/abs(Th_2)
    # append values to tabulate later on
    H_2.append(h/2)
    TH_2.append(Th_2)
    errorArray.append(err)
    # error break condition
    if(err < 10**(-6)):
        break
    n *= 2

print('For part b:')
H_2, TH_2, errorArray = np.array(H_2), np.array(TH_2), np.array(errorArray)
data = np.vstack((H_2, TH_2, errorArray)).T
df = pd.DataFrame(data,
                  columns=['h/2', 'T(h/2)', '|T(h)-T(h/2)|/|T(h/2)|'])
print('\n', df.round(6), '\n')
print('Estimated value of integral is', TH_2[-1])
print('The total number of function evaluations f(x) is', count)
print('\n')

# part (c)
M = [0.5, 0.8, 0.95]
for i in range(3):
    m = M[i]
    func = 1/sqrt(1-m*(sin(t)**2))
    a, b = 0, pi/2
    mapf = {}
    n = 1
    count = 0
    H_2, TH_2, errorArray = [], [], []
    while(True):
        h = (b-a)/n
        X = getX(a, b, n)
        Th, count = compositeTrapezoidalRule(func, X, mapf, count)
        X = getX(a, b, 2*n)
        Th_2, count = compositeTrapezoidalRule(func, X, mapf, count)
        err = abs(Th-Th_2)/abs(Th_2)
        # append values to tabulate later on
        H_2.append(h/2)
        TH_2.append(Th_2)
        errorArray.append(err)
        # error break condition
        if(err < 10**(-6)):
            break
        n *= 2

    print('For part c.{} with m = {}:'.format(i+1, m))
    H_2, TH_2, errorArray = np.array(H_2), np.array(TH_2), np.array(errorArray)
    data = np.vstack((H_2, TH_2, errorArray)).T
    df = pd.DataFrame(data,
                      columns=['h/2', 'T(h/2)', '|T(h)-T(h/2)|/|T(h/2)|'])
    print('\n', df, '\n')
    print('Estimated value of integral is', TH_2[-1])
    print('The total number of function evaluations f(x) is', count)
    print('\n')

Semester 6/MA 322 (Scientific Computing Theory)/Labs/Lab 4/Code/q6.py


import numpy as np
from copy import deepcopy
from scipy.stats import multivariate_normal

def sample_mixture(theta0, sigma0, pi0, n):
    d = theta0.shape[1]
    x = np.empty((n,d))

    for i in range(n):
        u = np.random.uniform(size=1)

        this_pi = 0
        for j in range(pi0.size):
            this_pi += pi0[j]

            if u < this_pi:
                x[i,:] = np.random.multivariate_normal(mean=theta0[j,:], cov=sigma0[j,:,:], size=1)
                break

    return x

def density(X, theta, sigma):
    out = np.empty((X.shape[0], theta.shape[0]))

    for i in range(theta.shape[0]):
        out[:,i] = multivariate_normal.pdf(X, mean=theta[i,:], cov=sigma[i,:,:])

    return out

def em(X, theta_start, sigma_start, pi_start, max_iter=5000, eps=1e-8, mix_type="weak", xi=3):

    theta_prev = theta_start
    sigma_prev = sigma_start
    pi_prev    = pi_start

    theta_new = theta_start
    sigma_new = sigma_start
    pi_new    = pi_start

    K = np.size(pi_start)
    n = X.shape[0]
    d = X.shape[1]

    T1 = [None] * K
    T2 = [None] * K
    T3 = [None] * K

    for iiter in range(max_iter):

        density_eval = np.array(density(X, theta_new, sigma_new))
        weights = np.tile(pi_new, (n, 1)) * density_eval#.reshape([-1,1])
        weights /= np.tile(np.sum(weights, 1), (K, 1)).T

        for i in range(K):
            T1[i] = np.sum(weights[:,i])
            T2[i] = np.sum(weights[:,i].reshape([-1,1]) * X, axis=0).reshape([1,d])

        pi_new = [(T1[i] + xi) / (n + K * xi) for i in range(K)]

        for i in range(K):
            theta_new[i,:] = T2[i]/T1[i]

        if mix_type == "weak":
            # Use speedup for variance proposed by McLachlan and Peel (2000).
            for i in range(K):
                T3 = np.dot(X.T, weights[:,i].reshape([-1,1]) * X)
                sigma_new[i,:,:] = (T3 - ((1.0/T1[i] ) * np.outer(T2[i], T2[i]))) / T1[i]

        if iiter > 500 or (iiter > 100 and iiter % 10 == 0):
           if np.linalg.norm(theta_new - theta_prev) + np.linalg.norm(sigma_new - sigma_prev) < eps or iiter > max_iter:
               break

        theta_prev = deepcopy(theta_new)
        sigma_prev = deepcopy(sigma_new)
        pi_prev    = deepcopy(pi_new)

    return (theta_new, sigma_new, pi_new, iiter)


import numpy as np
import time

print('1. 创建大小为 10 的空向量')
a = np.zeros(10)
print(a)

print('2. 查看矩阵占据的内存大小')
print('用元素个数乘以每个元素的大小')
print(f'占据 {a.size * a.itemsize} 字节')

print('3. 创建一个向量，值从 10 到 49')
a = np.arange(10, 50)
print(a)

print('4. 翻转一个向量')
a = a[::-1]
print(a)

print('5. 创建一个 3x3 的矩阵，值从 0 到 8')
a = np.arange(9).reshape(3,3)
print(a)

print('6. 从 [1, 2, 0, 0, 4, 0] 中寻找非零元素索引')
nz = np.nonzero([1, 2, 0, 0, 4, 0])
print(nz)

print('7. 创建 3x3 单位矩阵（对角线元素为 1 的方阵）')
a = np.eye(3)
print(a)

print('8. 创建一个 3x3x3 的随机矩阵')
a = np.random.random((3, 3, 3))
print(a)

print('9. 创建一个 10x10 的矩阵并寻找最大最小值')
a = np.random.random((10, 10))
a_min, a_max = a.min(), a.max()
print('min', a_min, ', max', a_max)

print('10. 创建一个长度为 30 的向量，并求均值')
a = np.random.random(30)
print('mean', a.mean())

print('11. 创建一个边界为 1 其他为 0 的二维矩阵')
a = np.ones((10, 10))
a[1:-1,1:-1] = 0
print(a)

print('12. 为已经存在的矩阵填充 0 的边界')
a = np.ones((5, 5))
print(a)
a = np.pad(a, pad_width=1, mode='constant', constant_values=0)
print(a)

print('13. 给出下列计算的结果')
print('0 * np.nan =', 0 * np.nan)
print('np.nan == np.nan =', np.nan == np.nan)
print('np.inf > np.nan =', np.inf > np.nan)
print('np.nan - np.nan =', np.nan - np.nan)
print('np.nan in set([np.nan]) =', np.nan in set([np.nan]))
print('0.3 == 3 * 0.1 =', 0.3 == 3 * 0.1)

print('14. 创建一个 5x5 的矩阵，对角线下的数值为 1 2 3 4')
a = np.diag(1 + np.arange(4), k=-1)
print(a)

print('15. 创建一个 8x8 矩阵，其中 0 和 1 间隔分布')
a = np.zeros((8, 8), dtype=int)
a[1::2, ::2] = 1
a[::2, 1::2] = 1
print(a)

print('16. 使用 tile 函数创建一个 8x8 矩阵，其中 0 和 1 间隔分布')
a = np.tile(np.array([[0, 1], [1, 0]]), (4, 4))
print(a)

print('17. 假设有一个 (6, 7, 8) 大小的矩阵，那么第 100 个元素的索引是多少')
print(np.unravel_index(100, (6, 7, 8)))

print('18. 归一化一个随机 5x5 矩阵')
a = np.random.random((5, 5))
a = (a - np.mean(a)) / np.std(a)
print(a)

print('19. 点乘一个 5x3 和 3x2 的矩阵')
a = np.dot(np.ones((5, 3)), np.ones((3, 2)))
print(a)

print('20. 给定一个一维数组，不新增空间，把 3~8 之间的数字变成负数')
a = np.arange(10)
a[(3 < a) & (a <= 8)] *= -1
print(a)

print('21. 两个数组求交集')
a1 = np.random.randint(0, 10, 10)
a2 = np.random.randint(0, 10, 10)
print(np.intersect1d(a1, a2))

print('22. 获取 2020 年 6 月的所有日期')
a = np.arange('2020-06', '2020-07', dtype='datetime64[D]')
print(a)

print('23. 用 5 种方法去掉小数部分')
a = np.random.uniform(0, 10, 10)
print('a', a)
print('1:', a - a%1)
print('2:', np.floor(a))
print('3:', np.ceil(a) - 1)
print('4:', a.astype(int))
print('5:', np.trunc(a))

print('24. 创建一个 5x5 的矩阵，每一行都是从 0 到 4')
a = np.zeros((5, 5))
a += np.arange(5)
print(a)

print('25. 创建一个大小为 10，值从 0 到 1 的向量(不包括 0 和 1)')
a = np.linspace(0, 1, 11, endpoint=False)[1:]
print(a)

print('26. 创建一个大小为 10 的随机向量并排序')
a = np.random.random(10)
a.sort()
print(a)

print('27. 如何用比 np.sum 更快的方法对一个小数组求和')
a = np.arange(10)
print('a', a)
start = time.time()
print('add.reduct', np.add.reduce(a))
end = time.time()
print('add.reduce time:', end-start)
start = time.time()
print('np.sum', np.sum(a))
end = time.time()
print('np.sum time:', end - start)

print('28. 比较两个数组是否相等')
a = np.random.randint(0, 10, 10)
b = np.random.randint(0, 10, 10)
print(np.allclose(a, b))
print(np.array_equal(a, b))

print('29. 将一个 10x2 的笛卡尔坐标系的点转成极坐标')
a = np.random.random((10, 2))
x, y = a[:, 0], a[:, 1]
r = np.sqrt(x**2 + y**2)
t = np.arctan2(y, x)
print(r)
print(t)

print('30. 创建一个大小为 10 的随机向量，并将最大的替换成 0')
a = np.random.random(10)
print('before', a)
a[a.argmax()] = 0
print('after', a)

print('31. 不用额外空间将 float 矩阵变成 int 矩阵')
a = np.arange(10, dtype=np.float32)
a = a.astype(np.int32, copy=False)
print(a)

print('32. 在一个 2 维矩阵中随机放 p 个元素')
n, p = 10, 3
a = np.zeros((n, n))
np.put(a, np.random.choice(range(n*n), p, replace=False), 1)
print(a)

print('33. 矩阵的每行减去每行的均值')
a = np.random.randint(0, 10, (5, 10))
print('before', a)
b = a - a.mean(axis=1, keepdims=True)
print('after', b)

print('34. 根据第 i 列给矩阵排序')
a = np.random.randint(0, 10, (3, 3))
print('before', a)
print('after', a[a[:, 1].argsort()])

print('35. 交换矩阵的两行')
a = np.arange(25).reshape(5, 5)
a[[0,1]] = a[[1, 0]]
print(a)

print('36. 如何计算一个数组的滑动窗口')
def moving_averate(a, n=3):
    ret = np.cumsum(a, dtype=float)
    ret[n:] = ret[n:] - ret[:-n]
    return ret[n-1:] / n
a = np.arange(20)
print(moving_averate(a, n=4))

print('37. 如何找到数组中出现次数最多的元素')
a = np.random.randint(0, 10, 50)
print(np.bincount(a).argmax())

print('38. 如何获取数组中最大的 n 个数')
a = np.arange(1000)
np.random.shuffle(a)
n = 5
start = time.time()
print('slow', a[np.argsort(a)[-n:]])
end = time.time()
print('slow time', end - start)
start = time.time()
print('fast', a[np.argpartition(-a, n)[:n]])
end = time.time()
print('fast time', end - start)


import numpy as np
import matplotlib.pyplot as plt

Notes_euler = '''
Euler's method:
    Function Name: Euler

    Inputs: f: defining function ( from y' = f(t,y) )
            a: starting Time
            b: ending Time
            y0: Initial Condition
            h: stepsize
            plot: Binary input
                Default plot = 0: No plotting
                        plot = 1: plots the solution

    Outputs: [t, y] where
        y: solution array
        t: time axis
'''

def Euler(f, a, b, y0, h, plot = 0):
    t = [a]
    y = [y0]
    N = int((b-a)/h)
    for i in range(N):
        y.append(y[-1] + h*f(t[-1], y[-1])) #euler update
        t.append(t[-1] + h) #time update

    if(plot == 1):
        plt.plot(t, y, label = "solution")
        plt.legend()
        plt.show()

    return [t, y]


from numpy import uint64, clip, searchsorted, bincount, maximum, minimum, diff, float64, zeros, equal, where, zeros_like

def cloud_in_cell(x,y, pts_x, pts_y, pts_vals=None, min_val=False, mean_val=True):
    """
    creates a grid of cells with borders x[:] and y[:]
    counts the fraction of particles in each cell, using cloud-in-cell approximation

    x - x borders
    y - y borders
    pts_x - x values of the points
    pts_y - y values of the points
    pts_vals - values for each point (default=None). If set then the grid contains the average value for the cells

    min_val=False - if True then set the minimum number in each cell as 1, (good for log plots)

    mean_val=True - if True then use the mean value for the cell (otherwise use the sum)

    """
    density = zeros((x.size*y.size), dtype=float64)
    if pts_vals is not None:
        values = zeros_like(density)

    # find the surrounding cell borders
    idx_x = searchsorted(x, pts_x)
    idx_x0 = maximum(idx_x-1, 0)
    idx_x1 = minimum(idx_x, x.size-1)

    dx = diff(x)[minimum(idx_x0, x.size-2)]
    frac_x = clip((pts_x - x[idx_x0]) / dx, 0, 1)

    idx_y = searchsorted(y, pts_y)
    idx_y0 = maximum(idx_y-1, 0)
    idx_y1 = minimum(idx_y, y.size-1)

    dy = diff(y)[minimum(idx_y0, y.size-2)]
    frac_y = clip((pts_y - y[idx_y0]) / dy, 0, 1)

    outside_box = equal(idx_x,0) | equal(idx_x,x.size) | equal(idx_y, 0) | equal(idx_y, y.size)

    idx00 = idx_y0 * x.size + idx_x0
    idx01 = idx_y0 * x.size + idx_x1
    idx10 = idx_y1 * x.size + idx_x0
    idx11 = idx_y1 * x.size + idx_x1

    w00 = where(outside_box, 0, (1-frac_y) * (1-frac_x))
    w01 = where(outside_box, 0, (1-frac_y) * frac_x)
    w10 = where(outside_box, 0, frac_y * (1-frac_x))
    w11 = where(outside_box, 0, frac_y * frac_x)

    for idx, weights in zip((idx00,idx01,idx10,idx11), (w00,w01,w10,w11)):
        b = bincount(idx, weights)

        density[:b.size] += b

        if pts_vals is not None:
            b = bincount(idx, weights * pts_vals)
            values[:b.size] += b

    res = density

    if min_val:
        res = maximum(res, 1.0)

    if pts_vals is not None:
        if mean_val:
            res = values / res
        else:
            res = values

        if min_val:
            res = maximum(res, pts_vals.min())
    else:
        # normalise
        res = res / res.sum()

    res = res.reshape((y.size, x.size))

    return res


import numpy as np

def regular_grid(ar_shape, n_points):
    """Find `n_points` regularly spaced along `ar_shape`.

    The returned points (as slices) should be as close to cubically-spaced as
    possible. Essentially, the points are spaced by the Nth root of the input
    array size, where N is the number of dimensions. However, if an array
    dimension cannot fit a full step size, it is "discarded", and the
    computation is done for only the remaining dimensions.

    Parameters
    ----------
    ar_shape : array-like of ints
        The shape of the space embedding the grid. ``len(ar_shape)`` is the
        number of dimensions.
    n_points : int
        The (approximate) number of points to embed in the space.

    Returns
    -------
    slices : list of slice objects
        A slice along each dimension of `ar_shape`, such that the intersection
        of all the slices give the coordinates of regularly spaced points.

    Examples
    --------
    >>> ar = np.zeros((20, 40))
    >>> g = regular_grid(ar.shape, 8)
    >>> g
    [slice(5.0, None, 10.0), slice(5.0, None, 10.0)]
    >>> ar[g] = 1
    >>> ar.sum()
    8.0
    >>> ar = np.zeros((20, 40))
    >>> g = regular_grid(ar.shape, 32)
    >>> g
    [slice(2.0, None, 5.0), slice(2.0, None, 5.0)]
    >>> ar[g] = 1
    >>> ar.sum()
    32.0
    >>> ar = np.zeros((3, 20, 40))
    >>> g = regular_grid(ar.shape, 8)
    >>> g
    [slice(1.0, None, 3.0), slice(5.0, None, 10.0), slice(5.0, None, 10.0)]
    >>> ar[g] = 1
    >>> ar.sum()
    8.0
    """
    ar_shape = np.asanyarray(ar_shape)
    ndim = len(ar_shape)
    unsort_dim_idxs = np.argsort(np.argsort(ar_shape))
    sorted_dims = np.sort(ar_shape)
    space_size = float(np.prod(ar_shape))
    if space_size <= n_points:
        return [slice(None)] * ndim
    stepsizes = (space_size / n_points) ** (1.0 / ndim) * np.ones(ndim)
    if (sorted_dims < stepsizes).any():
        for dim in range(ndim):
            stepsizes[dim] = sorted_dims[dim]
            space_size = float(np.prod(sorted_dims[dim+1:]))
            stepsizes[dim+1:] = ((space_size / n_points) **
                                 (1.0 / (ndim - dim - 1)))
            if (sorted_dims >= stepsizes).all():
                break
    starts = (stepsizes // 2).astype(int)
    stepsizes = np.round(stepsizes).astype(int)
    slices = [slice(start, None, step) for
              start, step in zip(starts, stepsizes)]
    slices = [slices[i] for i in unsort_dim_idxs]
    return slices


#!/usr/bin/env python3
# coding=utf-8

import numpy as np

zero_line = np.zeros((1, 3))
one_column = np.ones((3, 1))
print("zero_line = \n{}\n".format(zero_line))
print("one_column = \n{}\n".format(one_column))

a = np.array([(1, 2, 3), (4, 5, 6)])
b = np.arange(11, 20)
print("a = \n{}\n".format(a))
print("b = \n{}\n".format(b))

b = b.reshape(3, -1)
print("b.reshape(3, -1) = \n{}\n".format(b))

c = np.vstack((a, b, zero_line))
print("c = np.vstack((a,b, zero_line)) = \n{}\n".format(c))

a = a.reshape(3, 2)
print("a.reshape(3, 2) = \n{}\n".format(a))

d = np.hstack((a, b, one_column))
print("d = np.hstack((a,b, one_column)) = \n{}\n".format(d))

# np.vstack((a,b)) # ValueError: dimensions not match

e = np.hsplit(d, 3)  # Split a into 3
print("e = np.hsplit(d, 3) = \n{}\n".format(e))
print("e[1] = \n{}\n".format(e[1]))

# np.hsplit(d, 4) # ValueError: array split does not result in an equal division

f = np.hsplit(d, (1, 3))  # # Split a after the 1st and the 3rd column
print("f = np.hsplit(d, (1, 3)) = \n{}\n".format(f))

g = np.vsplit(d, 3)
print("np.vsplit(d, 3) = \n{}\n".format(g))

# np.vsplit(d, 2) # ValueError: array split does not result in an equal division


#taken from https://machinelearningmastery.com/statistical-hypothesis-tests-in-python-cheat-sheet/
#This section lists statistical tests that you can use to check if two samples are related.

from scipy.stats import pearsonr
def pearson_coeff(data1, data2) -> (float, float):
    """Tests whether two samples have a linear relationship.
    Assumptions
    -----------
    * Observations in each sample are independent and identically distributed (iid).
    * Observations in each sample are normally distributed.
    * Observations in each sample have the same variance.
    Interpretation
    --------------
    * H0: the two samples are independent.
    * H1: there is a dependency between the samples.

    Parameters
    ----------
    data1: (N,) array_like
    data2: (N,) array_like

    Returns
    -------
    corr : float
        Pearson's correlation coefficient
    p : float
        2-tailed p-value
    """
    corr, p = pearsonr(data1, data2)
    return corr, p

from scipy.stats import spearmanr
def spearman_rank(data1, data2):
    """Tests whether two samples have a monotonic relationship.
    Assumptions
    -----------
    * Observations in each sample are independent and identically distributed (iid).
    * Observations in each sample can be ranked.
    Interpretation
    --------------
    * H0: the two samples are independent.
    * H1: there is a dependency between the samples.

    Parameters
    ----------
    data1: (N,) array_like
    data2: (N,) array_like

    Returns
    -------
    corr : float or ndarray (2-D square)
        Spearman correlation matrix or correlation coefficient (if only 2
    variables are given as parameters. Correlation matrix is square with
    length equal to total number of variables (columns or rows) in `a`
    and `b` combined.
    p : float
        The two-sided p-value for a hypothesis test whose null hypothesis is
    that two sets of data are uncorrelated, has same dimension as rho.
    """
    corr, p = spearmanr(data1, data2)
    return corr, p

from scipy.stats import kendalltau
def kendall_rank(data1, data2) -> (float, float):
    """Tests whether two samples have a monotonic relationship.
    Assumptions
    -----------
    * Observations in each sample are independent and identically distributed (iid).
    * Observations in each sample can be ranked.
    Interpretation
    --------------
    * H0: the two samples are independent.
    * H1: there is a dependency between the samples.

    Parameters
    ----------
    data1: (N,) array_like
    data2: (N,) array_like

    Returns
    -------
    corr : float
        The tau statistic.
    p : float
        The two-sided p-value for a hypothesis test whose null hypothesis is an absence of association, tau = 0.
    """
    corr, p = kendalltau(data1, data2)
    return corr, p

from scipy.stats import chi2_contingency
def chi_squared(table):
    """Tests whether two categorical variables are related or independent.
    Assumptions
    -----------
    * Observations used in the calculation of the contingency table are independent.
    * 25 or more examples in each cell of the contingency table.
    Interpretation
    --------------
    * H0: the two samples are independent.
    * H1: there is a dependency between the samples.

    Parameters
    ----------
    table: array_like

    Returns
    -------
    stat : float
        The test statistic.
    p : float
        The p-value of the test
    dof : int
        Degrees of freedom
    expected : ndarray, same shape as observed
        The expected frequencies, based on the marginal sums of the table.
    """
    stat, p, dof, expected = chi2_contingency(table)
    return stat, p, dof, expected


#! /usr/bin/env python

"""
File: find_primes.py
Copyright (c) 2016 Chinmai Raman
License: MIT

Course: PHYS227
Assignment: 1.4
Date: Feb 12, 2016
Email: raman105@mail.chapman.edu
Name: Chinmai Raman
Description: The Sieve of Eratosthenes. Finds all prime numbers less than or equal to a given number N.
"""

import numpy as np

primes = []

def sieve(N):
    """
    Finds all prime numbers less than or equal to a given number N.
    """
    for i in range(2, N + 1):
        primes.append(i)
    p = 2
    while (p < N):
        i = 2
        while((p * i) < (N + 1)):
            if ((p * i in primes) == True):
                del primes[primes.index(p * i)]
            i += 1
        p += 1

def test():
    sieve(10)
    assert([2, 3, 5, 7] == primes), "Failure."


"""Tool for finding the eigenvalues using Jacobi eigenvalue algorithm

Functions:
    calculate_eigenvalues(A, eps) -> ndarray
"""

from numpy import diag, diag_indices, argmax, unravel_index, ones, sign, dot

def calculate_eigenvalues(A, eps):
    """Find the eigenvalues of matrix with a given accuracy.

    Arguments:
    A -- symmetric matrix
    eps -- accuracy
    """

    n = A.shape[0]
    di = diag_indices(n)
    zeroed_diag_A = A.copy()
    zeroed_diag_A[di] = 0
    while True:
        indices = unravel_index(argmax(abs(zeroed_diag_A), axis=None), zeroed_diag_A.shape)
        i, j = min(indices), max(indices)

        T_ij = diag(ones(n))
        d = ((A[i, i] - A[j, j]) ** 2 + 4 * A[i,j] ** 2) ** 0.5
        T_ij[i, i] = (((1 + abs(A[i, i] - A[j, j]) / d)) / 2) ** 0.5
        T_ij[j, j] = T_ij[i, i]
        T_ij[j, i] = sign(A[i, j] * (A[i, i] - A[j, j])) * (((1 - abs(A[i, i] - A[j, j]) / d)) / 2) ** 0.5
        T_ij[i, j] = - T_ij[j, i]

        A = dot(dot(T_ij.T, A), T_ij)

        zeroed_diag_A = A.copy()
        zeroed_diag_A[di] = 0
        if sum(sum(zeroed_diag_A ** 2)) < eps:
            break

    return A[di]

def calculate_eigenvalues_m(A, eps):
    """Find the eigenvalues of matrix with a given accuracy.

    This function just uses formulas without using matrix multiplication

    Arguments:
    A -- symmetric matrix
    eps -- accuracy
    """

    n = A.shape[0]
    di = diag_indices(n)
    zeroed_diag_A = A.copy()
    zeroed_diag_A[di] = 0
    while True:
        indices = unravel_index(argmax(abs(zeroed_diag_A), axis=None), zeroed_diag_A.shape)
        i, j = min(indices), max(indices)

        d = ((A[i, i] - A[j, j]) ** 2 + 4 * A[i,j] ** 2) ** 0.5
        cosfi = (((1 + abs(A[i, i] - A[j, j]) / d)) / 2) ** 0.5
        sinfi = sign(A[i, j] * (A[i, i] - A[j, j])) * (((1 - abs(A[i, i] - A[j, j]) / d)) / 2) ** 0.5

        C = A.copy()
        C[i, i] = (A[i, i] + A[j, j]) / 2 + sign(A[i, i] - A[j, j]) * d / 2
        C[j, j] = (A[i, i] + A[j, j]) / 2 - sign(A[i, i] - A[j, j]) * d / 2
        C[i, j], C[j, i] = 0, 0
        for k in range(n):
            if k == i or k == j:
                continue

            C[i, k] = cosfi * A[k, i] + sinfi * A[k, j]
            C[k, i] = C[i, k]
            C[j, k] = - sinfi * A[k, i] + cosfi * A[k, j]
            C[k, j] = C[j, k]

        A = C
        zeroed_diag_A = A.copy()
        zeroed_diag_A[di] = 0
        if sum(sum(zeroed_diag_A ** 2)) < eps:
            break

    return A[di]


#=============================================================================#
#
#    File:   euler_product_formula.py
#    Author: Jack Morgan
#    Date:   May 2021
#    Description:
#        Calculate zeta by a product of primes
#        Where \zeta(s) = \prod_{p, prime} \frac{1}{1-p^{^{-s}}}
#
#=============================================================================#

from time import time
import numpy as np
from math import floor, sqrt, pi

def sieve_of_eratosthenes(limit):
    possible_primes = np.ones(limit, dtype=bool)
    possible_primes[0:2:1] = False
    for i in range(2, floor(sqrt(limit))):
        possible_primes[i*i:limit:i] = False
    return np.flatnonzero(possible_primes)

def zeta(s):
    limit = 1*10**6
    primes = sieve_of_eratosthenes(limit).astype(float)
    elements = 1/(1-primes**(-s))
    return np.prod(elements)

def test():
    TOL = 1e-3

    test_case_1 = zeta(3.0 + 4.5 * 1j)
    test_case_2 = zeta(1 - 2.5 * 1j)
    test_case_3 = zeta(2 + 0 * 1j)

    # true values taken from wolfram alpha
    test1 = abs(test_case_1.real - 0.89796690) < TOL and abs(test_case_1.imag - 0.023707555) < TOL
    test2 = abs(test_case_2.real - 0.61134995) < TOL and abs(test_case_2.imag - 0.213320800) < TOL
    test3 = abs(test_case_3.real - pi**2/6) < TOL and abs(test_case_3.imag - 0.000000000) < TOL

    print(f'test1: {test1}\ntest2: {test2}\ntest2: {test3}')

if __name__=='__main__':
    start = time()
    test()
    print(f'--- {time()-start} seconds ---')


import numpy as np
from itertools import permutations as perm
import pdb

def compute_present_value(j,I,p):
    """ Computes Present Value (PV) of an ordinary annuity """

    # NOTES:
    # Any ONE of the input arguments can be an array, but the others
    # must be scalar.

    # INPUTS:
    # p -> Periodic payment amount
    # I -> Interest, as annual rate. If 9%, enter 0.09.
    # j -> The number of payments

    # OUTPUT:
    # PV -> The present value of the annuity

    PV = p * ( ( 1 - (1+I)**(-j) )/I )

    return PV

def compute_future_value(j,I,p):
    """ Computes Future Value (FV) or an ordinary annuity """

    # NOTES:
    # Any ONE of the input arguments can be an array, but the others
    # must be scalar.

    # INPUTS:
    # p -> Periodic payment amount
    # I -> Interest, as annual rate. If 9%, enter 0.09.
    # j -> The number of payments

    # OUTPUT:
    # FV -> The future value of the annuity

    FV = p * (((1+I)**j - 1)/I) * (1+I)

    return FV

def compute_monthly_loan_payment(PV,I,j):
    """ Computes the monthly payment on a loan given neccessary inputs """

    # INPUTS:
    # PV -> Present value ($)
    # I -> Interest, as annual rate. If 9%, enter 0.09.
    # j -> Number of payments

    # [10/8/18] PV ordinary annuity only

    p = PV / ( ( 1 - (1+I)**(-j) )/I )

    return p

def compute_n_payments(L0, I, p):
    """ Compute the number of payments required to pay a loan off to a
        balance of zero.
        Any ONE of the input arguments can be an array, but the others
        must be scalar.
    """

    # INPUTS:
    # L0 -> The initial loan amount borrowed.
    # I -> Interest, as annual rate. If 9%, enter 0.09.
    # p -> The periodic payment amount

    # OUTPUT:
    # n -> number of payments, either a single number (if all inputs are
    #      scalars), or an array of n's (if one of the inputs is an array)

    # Eq. for # of payments, n, as a function of payment amount, p
    n = -1*( np.log( 1 - ((L0*I)/p) )  /  np.log(1+I) )
    return n

def compute_total_cost(L, I, p, ndec, ntsteps=0):
    """ This function uses an old-fashioned loop to compute the total cost
        of a loan.
    """

    # INPUTS:
    # L      -> current inital/principal of the loan
    # I      -> interest (presumably annual) of the loan
    # p      -> the payment at each timestep (presumably monthly)
    # nsteps -> optional. compute the total cost only out to this many timesteps,
    #           except if = 0. in this case, compute total cost for entire loan
    #           lifetime (default)
    # If you set ntsteps != 0 then only the total out to nsteps will be
    # computed.

    # OUTPUTS:
    # total  -> the total cost of the loan
    # tsteps -> the total number of timesteps it took to pay off the loan
    # L      -> the running principal of the loan, which is returned in its last
    #           state, representing the amount leftover as a negative

    # NOTES:
    # [1/30/19] All inputs are assumed to be scalar.

    total = 0.0
    tsteps = 0
    leftover = 0.0

    while L > 0:
        leftover = 0.0
        L = np.round( L + np.round(L*I,ndec) - p, ndec )
        total = total + p
        if L < 0: leftover = abs(L)
        tsteps += 1
        if ntsteps == tsteps: break

    total = total - leftover

    return total, tsteps, L

def descending_interest_method(L0, I, p0, a0, ndec):
    """ This function will return the total cost using the method of
        paying off loans in order of descending interest.
    """

    # INPUTS:
    # L0   -> Current inital/principal of the loans [1D array]
    # I    -> Interest (presumably annual) of the loans [1D array]
    # p    -> The payment at each timestep (presumably monthly) [1D array]
    # a0   -> The extra money available each month to put towards max(I) loan (scalar)
    # ndec -> The number of decimal places to round to - presumably 2, cuz money. (scalar)

    # OUTPUTS:
    # (all scalars)
    # grand_total -> The total cost of paying off all loans to a balance of
    #                zero using this method.
    # tsteps      -> The number of timesteps taken to pay off all loans.

    # NOTES:
    #
    # [1/21/19]
    # After each payoff, leftover is applied to next iteration.
    # Payment is kept constant at every iteration, save any leftover from
    # previous iteration. So, even after a loan is paid off, the code
    # continues to use that loans minimum payment to pay off
    # remaining loans.

    # Test if all values of I are equal
    if (I == np.roll(I,1)).sum() == I.shape[0]:
        #print("!!!! WARNING !!!!")
        #print("Descending interest method results not meaningful in case")
        #print("where all interest rates are the same.")
        #print("!!!!!!!!!!!!!!!!")
        pass

    L = np.copy(L0)
    p = np.copy(p0)
    nL = L0.shape[0]
    includeLmask = np.ones(nL,dtype=np.bool)
    payedoff_mask = np.zeros(nL,dtype=np.bool)
    a = np.zeros(nL,dtype=np.float64)
    total = 0.0
    rem  = L0.sum()
    tsteps = 0
    leftover = 0.0

    while rem > 0:
        maxI_indx = I[includeLmask].argmax()
        a[:] = 0
        a[maxI_indx] = a0 + leftover + p0[payedoff_mask].sum()
        leftover = 0.0
        L = np.round( L + np.round(L*I,ndec) - (p + a), ndec )
        payedoff_mask = L <= 0.0
        if payedoff_mask.sum() > 0:
            leftover = np.abs(L[payedoff_mask].sum())
            includeLmask[payedoff_mask] = False
            p[payedoff_mask] = 0.0
            L[payedoff_mask] = 0.0
        total = total + p0.sum() + a0
        rem = L.sum()
        tsteps += 1

    grand_total = total - leftover

    return grand_total, tsteps

def gradient_descent_algo1(L0, I, p0, a, max_iters, x = 0.01):
    """ This algorithm determines the weights and the grand total of
        applying those weights to an amount, a, over the minimum payment
        of an arbitrary number of fixed interest annuities (loans).
        The way the weights are determined is described in the DESCRIPTION
        below.
    """

    # DESCRIPTION:
    # This algorithm repeatedly checks which loan's grand total cost is
    # reduced the most by applying the same amount over the minimum fixed
    # payment (a) to each loan. Let's call this loan the "winner."
    # At the end of each iteration, the winner's payment amount is increased
    # by x (fraction of 1, input, defined below). The next iteration begins.
    # Iterations continue until 100% of "a" (input, defined below) is allocated.
    # The winner will sometimes change as the payments change, as the code
    # iterates. At the end of iterations, you're left with an array that
    # contains the "optimal" fractions (called weights in output) of "a"
    # to apply to each of the loans.
    # [5/17/20] Like "descending_interest_method" function...
    # Payment is kept constant at every iteration, save any leftover from
    # previous iteration. So, even after a loan is paid off, the code
    # continues to use that loan's minimum payment to pay off
    # remaining loans.

    # INPUTS:
    # L0        -> The initial principal loan amount [numpy 1D array]
    # I         -> The interest [numpy 1D array]
    # p0        -> The minimum payment amounts [numpy 1D array]
    # a         -> extra amount over the minimum payments willing to be paid [scalar]
    # max_iters -> maximum iterations to try allocating a [scalar]
    # x         -> fraction by which to increment weights [scalar]

    # OUTPUTS:
    # w -> the weights optimizing the allocation of a to each loan [numpy 1D array]
    # n -> the resultant number of payments made for each loan [numpy 1D array]
    # grand_total_paid -> the resultant grand total paid [scalar]

    p = np.copy(p0)
    nL = L0.shape[0]
    w = np.zeros(nL)
    delta = np.zeros(nL)
    j = 0
    wrem = 1.0 # represents the remainding % of 'a' to allocate

    while (wrem > 0.0):
        delta_last = 0.0
        isave = None
        for i in range(len(L0)):
            n0 = compute_n_payments(L0[i], I[i], p[i])
            t0 = n0 * p[i]
            pmod = p[i] + x*a
            n1 = compute_n_payments(L0[i], I[i], pmod)
            t1 = n1 * pmod
            delta[i] = t0 - t1 # diff in totals b4 & after modification
            if delta[i] > delta_last:
                isave = i
            delta_last = delta[i]
        if isave is None:
            pdb.set_trace()
        else:
            wrem = wrem - x
        w[isave] = w[isave] + x
        p[isave] = p[isave] + x*a
        if j > max_iters:
            print('Max iterations reached...')
            pdb.set_trace()
            break
        j += 1

    paid = []
    n = []
    for i in range(len(L0)):
        nt = compute_n_payments(L0[i], I[i], p0[i]+w[i]*a)
        paid.append(p[i] * nt)
        n.append(nt)
    grand_total_paid = sum(paid)
    return w, np.asarray(n), grand_total_paid

def optimize_algo1(L0, I, p, a0, max_iters, ndec, x = 0.01):
    """ Uses  gradient_descent_algo1 to compute total loan cost """

    # DESCRIPTION:
    # The "gradient_descent_algo1" function defined above is run using
    # the input loans. This yields a somewhat optimized array of weights
    # and # of timesteps (n). These are the timesteps required to payoff
    # each of the input loans. The total cost is computed by paying all
    # loans to n.min() timesteps. Unless every element of n is the same,
    # you'll have a new set of loans to optimize paying off, each time you
    # you reach the next highest n. Therefore "gradient_descent_algo1"
    # is called iteratively in a 'while' loop until the principal
    # of all remaining loans goes to zero.

    # INPUTS:
    # L0        -> The initial principal loan amount [numpy 1D array]
    # I         -> The interest [numpy 1D array]
    # p0        -> The minimum payment amounts [numpy 1D array]
    # a         -> extra amount over the minimum payments willing to be paid [scalar]
    # max_iters -> maximum iterations to try allocating a [scalar]
    # ndec      -> number of decimal places to round to when computing total cost [scalar]
    # x         -> fraction by which gradient_descent_algo1 should increment weights [scalar]

    # OUTPUTS:
    # tot_all     -> total amount paid [scalar]
    # tot_ts      -> total number of timesteps taken to payoff all loans [scalar]
    # all_w       -> list of numpy arrays containing the "best" weights for each iteration
    # all_ntsteps -> list of timesteps taken at each iteration

    # NOTES:
    #
    # [1/21/19]
    # Leftover gets applied on next iteration.
    # [4/19/20]
    # Patch put in for condition where >= 2 loans are zero and rest are neg.

    nL = L0.shape[0]
    L = np.copy(L0)
    mask = np.ones(nL,dtype=np.bool)
    tot_all = 0.0
    tot_ts = 0
    a = a0
    leftover = 0.0
    all_w = []      # all weights, for all iterations
    all_ntsteps = []# all num of tsteps, for all iterations

    while (mask.sum() > 1):

        nloans_toploop = mask.sum()
        L = L[mask]
        I = I[mask]
        p = p[mask]
        #print("This many L remain: ",L.shape)

        # IF the remainder to be paid on all loans is less than leftover,
        # or the minimum payments, then quit. But be sure to increase
        # loans by 1 ts worth of I before making this comparison.
        L_nts = np.round( L + np.round(L*I,ndec), ndec)
        if (L_nts.sum() < a) | ((L_nts >= p).sum() == 0):
            # First subtract the minimum payments from the remaining loan
            # amounts. The results will likely be negative.
            payment_step = L_nts - p
            # If min payments weren't enough, then apply extra amount 'a'
            after_payment = payment_step.sum()
            if after_payment > 0:
                leftover = a - after_payment
            else: # <= 0
                leftover = a + np.fabs(after_payment)
            L = L*0.0
            mask = np.zeros(L.shape, dtype=np.bool)
            break # done with this while loop

        w, n, grand_total_paid = gradient_descent_algo1(L, I,
                                 p, a, max_iters, x)

        n = np.ceil(n) # round up to nearest int
        ntsteps = n.min()
        all_ntsteps.append(ntsteps)
        tot_ts = tot_ts + ntsteps
        all_w.append(w)

        for i in range(0,nloans_toploop):
            tot, ts, Lout = compute_total_cost(L[i], I[i], p[i]+a*w[i], ndec, ntsteps)
            tot_all = tot_all + tot
            L[i] = Lout

        mask = L >= 0
        # Put a patch in here so that if every value in L is <= 0, mask.sum() == 0.
        # This will terminate while loop once it gets back to top.
        if (L > 0).sum() == 0:
            mask = np.zeros(mask.shape,dtype=np.bool)
        invmask = L < 0
        leftover = np.abs(L[invmask]).sum()
        a = p[invmask].sum() + a0 + leftover # keeps total payment amount constant

    # Compute cost of paying off rest of the remaining loan, if applicable
    if mask.sum() > 0:
        L = L[mask]
        I = I[mask]
        p = p[mask]
        tot, ts, Lout = compute_total_cost(L[0], I[0], p[0]+a, ndec)
        tot_all = tot_all + tot + Lout # Lout should be neg here
        tot_ts = tot_ts + ts
        all_ntsteps.append(ts)
        all_w.append(1.0)
    else:
        # At least some of the loans must've been paid off in same number
        # of times steps.
        tot_all = tot_all - leftover

    return tot_all, tot_ts, all_w, all_ntsteps

def partition_permutations(number):
    """ Compute the permutations of the partitions of the input number.
    """
    answer = set()
    answer.add((number, ))
    for x in range(1, number):
        for y in partition(number - x):
            answer.add((x, ) + y)
    return answer

def partition(number):
    """ Compute the of the partitions of the input number.
    """
    answer = set()
    answer.add((number, ))
    for x in range(1, number):
        for y in partition(number - x):
            answer.add(tuple(sorted((x, ) + y)))
    return answer

def gradient_descent_algo2(L0, I, p, a, n, m):
    """ This algorithm finds the m lowest costs produced by running weights
        created using combinatorics and permutations to test the full range
        of possibilities.
    """

    # DESCRIPTION:
    # This algorithm

    # INPUTS:
    # L0        -> The initial principal loan amount [numpy 1D array]
    # I         -> The interest [numpy 1D array]
    # p         -> The minimum payment amounts [numpy 1D array]
    # a         -> extra amount over the minimum payments willing to be paid [scalar]
    # n         -> defines the resolution of the combinatorics
    # m         -> the m lowest total cost combinations will be returned

    # OUTPUTS:
    # w                -> the weights resulting in the m lowest total costs [numpy array]
    # n_pay            -> the resultant number of payments made for each
    #                     loan, corrresponding to w [numpy array]
    # grand_total_paid -> the resultant grand totals paid, corresponding to w [numpy array]

    # Google: partitions, compositions, number theory, combinatorics
    #
    # NOTES:
    # [5/25/20]
    # Code abandoned indefinitely because it takes a long time to run.

    nL = L0.shape[0]

    partitions = list(partition(n))
    # pared_partitions will be a list of tuples. Each tuple will be a set
    # of no more than nL numbers that add up to n. This set should include
    # every possible combo of nL (or less) numbers that sums to n.
    pared_partitions = list(filter(lambda x: len(x) <= nL, partitions))
    samp_arr = np.zeros(nL,dtype=np.float64)

    m_lowest_costs = []
    n_of_low_costs = []
    tup_of_low_costs = []

    for pt in pared_partitions:
        samp_arr[:] = 0.0
        partlen = len(pt)
        samp_arr[:partlen] = np.array(pt)
        all_vals_eq = (samp_arr[1:] == samp_arr[:-1]).sum() == (nL-1)
        # At least get rid of the repeats in cases where every value is equal
        if all_vals_eq:
            permu_tup_list = [tuple(samp_arr)]
        else:
            permu_tup_list = list(perm(samp_arr))
        for tup in permu_tup_list:
            i = 0
            tot_cost = 0.0
            n_pay_list = []
            for val in tup:
                w = float(val)/float(n)
                pmod = p[i]+w*a
                n_pay = compute_n_payments(L0[i], I[i], pmod)
                total = n_pay * pmod
                tot_cost = tot_cost + total
                n_pay_list.append(n_pay)
                i += 1
            # Keep m lowest values in m_lowest_costs list.
            if len(m_lowest_costs) >= m:
                list_max = max(m_lowest_costs)
                if tot_cost < list_max:
                    max_indx = m_lowest_costs.index(list_max)
                    m_lowest_costs[max_indx] = tot_cost
                    n_of_low_costs[max_indx] = n_pay_list
                    tup_of_low_costs[max_indx] = tup
                    #print(tot_cost)
                    #print(m_lowest_costs)
                    #print(n_of_low_costs)
                    #print(tup_of_low_costs)
                    #pdb.set_trace()
            else:
                m_lowest_costs.append(tot_cost)
                n_of_low_costs.append(n_pay_list)
                tup_of_low_costs.append(tup)
            if i < nL:
                pdb.set_trace()

    w = np.array(tup_of_low_costs,dtype=np.float64)
    w = w / float(n)
    n_pay = np.array(n_of_low_costs)
    grand_total_paid = np.array(m_lowest_costs)
    return w, n_pay, grand_total_paid


"""

This is is a part of the DeepLearning.AI TensorFlow Developer Professional Certificate offered on Coursera.

All copyrights belong to them. I am sharing this work here to showcase the projects I have worked on
Course: Introduction to TensorFlow for Artificial Intelligence, Machine Learning, and Deep Learning

Week 4: Sequences and Prediction

Aim: Artificially generating Time series

"""

import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow import keras

# Define a plot series
def plot_series(time, series, format="-", start=0, end=None, label=None):
    plt.plot(time[start:end], series[start:end], format, label=label)
    plt.xlabel("Time")
    plt.ylabel("Value")
    if label:
        plt.legend(fontsize=14)
    plt.grid(True)

# Trend and seasonality
def trend(time, slope=0):
    return slope * time

# Upwards Trend

time = np.arange(4 * 365 + 1)
baseline = 10
series = trend(time, 0.1)

plt.figure(figsize=(10, 6))
plot_series(time, series)
plt.show()

# Series with a seasonal pattern

def seasonal_pattern(season_time):
    return np.where(season_time<0.4, np.cos(season_time*2*np.pi), 1/np.exp(3*season_time))

def seasonality(time, period, amplitude=1, phase=0):
    season_time=((time+phase) % period)/period
    return amplitude*seasonal_pattern(season_time)

baseline = 10
amplitude = 40
series = seasonality(time, period=365, amplitude=amplitude)

plt.figure(figsize=(10, 6))
plot_series(time, series)
plt.show()

# Time series with both trend and seasonality

slope = 0.05
series = baseline + trend(time, slope) + seasonality(time, period=365, amplitude=amplitude)

plt.figure(figsize=(10, 6))
plot_series(time, series)
plt.show()

# Noise: White noise

def white_noise(time, noise_level=1, seed=None):
    rnd = np.random.RandomState(seed)
    return rnd.randn(len(time)) * noise_level

noise_level = 1
noise = white_noise(time, noise_level, seed=42)

plt.figure(figsize=(10, 6))
plot_series(time, noise)
plt.show()

# Add noise to the time series
series += noise

plt.figure(figsize=(10, 6))
plot_series(time, series)
plt.show()

#All right, this looks realistic enough for now. Let's try to forecast it. We will split it into two periods: the training period and the validation period (in many cases, you would also want to have a test period). The split will be at time step 1000.
split_time = 1000
time_train = time[:split_time]
x_train = series[:split_time]
time_valid = time[split_time:]
x_valid = series[split_time:]

# Autocorrelation: Steps and seasonality like

def autocorrelation(time, amplitude, seed=None):
    rnd = np.random.RandomState(seed)
    φ1 = 0.5
    φ2 = -0.1
    ar = rnd.randn(len(time) + 50)
    ar[:50] = 100
    for step in range(50, len(time) + 50):
        ar[step] += φ1 * ar[step - 50]
        ar[step] += φ2 * ar[step - 33]
    return ar[50:] * amplitude

def autocorrelation(time, amplitude, seed=None):
    rnd = np.random.RandomState(seed)
    φ = 0.8
    ar = rnd.randn(len(time) + 1)
    for step in range(1, len(time) + 1):
        ar[step] += φ * ar[step - 1]
    return ar[1:] * amplitude

series = autocorrelation(time, 10, seed=42) + trend(time, 2)
plot_series(time[:200], series[:200])
plt.show()

series = autocorrelation(time, 10, seed=42) + seasonality(time, period=50, amplitude=150) + trend(time, 2)
plot_series(time[:200], series[:200])
plt.show()

series = autocorrelation(time, 10, seed=42) + seasonality(time, period=50, amplitude=150) + trend(time, 2)
series2 = autocorrelation(time, 5, seed=42) + seasonality(time, period=50, amplitude=2) + trend(time, -1) + 550
series[200:] = series2[200:]
#series += noise(time, 30)
plot_series(time[:300], series[:300])
plt.show()

# Impulses
def impulses(time, num_impulses, amplitude=1, seed=None):
    rnd = np.random.RandomState(seed)
    impulse_indices = rnd.randint(len(time), size=10)
    series = np.zeros(len(time))
    for index in impulse_indices:
        series[index] += rnd.rand() * amplitude
    return series

series = impulses(time, 10, seed=42)
plot_series(time, series)
plt.show()

# Another autocorrelation + impukses

def autocorrelation(source, φs):
    ar = source.copy()
    max_lag = len(φs)
    for step, value in enumerate(source):
        for lag, φ in φs.items():
            if step - lag > 0:
              ar[step] += φ * ar[step - lag]
    return ar

signal = impulses(time, 10, seed=42)
series = autocorrelation(signal, {1: 0.99})
plot_series(time, series)
plt.plot(time, signal, "k-")
plt.show()

signal = impulses(time, 10, seed=42)
series = autocorrelation(signal, {1: 0.70, 50: 0.2})
plot_series(time, series)
plt.plot(time, signal, "k-")
plt.show()

# Plot the difference
series_diff1 = series[1:] - series[:-1]
plot_series(time[1:], series_diff1)

# Rest of the course contains Pandas stuff: https://colab.research.google.com/github/lmoroney/dlaicourse/blob/master/TensorFlow%20In%20Practice/Course%204%20-%20S%2BP/S%2BP_Week_1_Lesson_2.ipynb#scrollTo=5_lx-AlWhT5v