import math
import numpy as np
from scipy import signal
from skimage import color as col
from scipy import ndimage as ndi

def G_carrier_real(t, freq, phase):
    '''
    Real part of the carrier.
    '''
    topass = 2 * math.pi * freq * t + phase
    out = np.cos(topass)
    return out

def G_carrier_imag(t, freq, phase):
    '''
    Imaginary part of the carrier.
    '''
    topass = 2 * math.pi * freq * t + phase
    out = np.sin(topass)
    return out

def G_envelope(t, amp, sdev):
    '''
    The impact of the filter is controlled by a Gaussian function.
    '''
    out = amp * np.exp( (-(t/sdev)**2) )
    return out

def G_fil_real(t, paras):
    '''
    Custom-built filter response (real part).
    Assumes that t is an array of temporal inputs.
    '''
    carrier = G_carrier_real(t=t, freq=paras["freq"], phase=paras["phase"])
    envelope = G_envelope(t=t, amp=paras["amp"], sdev=paras["sdev"])
    out = carrier * envelope
    return out

def G_fil_imag(t, paras):
    '''
    Custom-built filter response (imaginary part).
    Assumes that t is an array of temporal inputs.
    '''
    carrier = G_carrier_imag(t=t, freq=paras["freq"], phase=paras["phase"])
    envelope = G_envelope(t=t, amp=paras["amp"], sdev=paras["sdev"])
    out = carrier * envelope
    return out

def my_signal(t):

    highfreq = 0.25 * np.sin((2*math.pi*para_HIGHFREQ*t))
    lowfreq = 2 * np.sin((2*math.pi*para_LOWFREQ*t))

    cond = (np.abs(t) <= 5)
    signal = highfreq + lowfreq
    out = np.select([cond], [signal])

    return out

## Frequency response of the Gabor filter is obtained analytically as the (complex) Fourier transform.
def G_ft_real(f, amp, sdev, freq, phase):
    '''
    Real part of the complex Gabor filter's frequency response.
    '''
    topass = (f - freq) * sdev
    env = G_envelope(t=topass, amp=1, sdev=1)
    out = math.cos(phase) * amp * sdev * env
    return out

def G_ft_imag(f, amp, sdev, freq, phase):
    '''
    Imaginary part of the complex Gabor filter's frequency response.
    '''
    topass = (f - freq) * sdev
    env = G_envelope(t=topass, amp=1, sdev=1)
    out = math.sin(phase) * amp * sdev * env
    return out

def G_fr_real(f, paras):
    '''
    Frequency response for our custom-built filter (real part).
    Assumes that f is an array of frequency settings.
    '''
    out = G_ft_real(f=f, amp=paras["amp"], sdev=paras["sdev"], freq=paras["freq"], phase=paras["phase"])
    return out

def G_fr_imag(f, paras):
    '''
    Frequency response for our custom-built filter (real part).
    Assumes that f is an array of frequency settings.
    '''
    out = G_ft_imag(f=f, amp=paras["amp"], sdev=paras["sdev"], freq=paras["freq"], phase=paras["phase"])
    return out

def G2_carrier_real(x, y, freqx, freqy, phase):
    '''
    Real part of the 2-D Gabor carrier.
    '''
    topass = 2 * math.pi * (freqx*x + freqy*y) + phase
    out = np.cos(topass)
    return out

def G2_carrier_imag(x, y, freqx, freqy, phase):
    '''
    Imaginary part of the 2-D Gabor carrier.
    '''
    topass = 2 * math.pi * (freqx*x + freqy*y) + phase
    out = np.sin(topass)
    return out

def G2_envelope(x, y, amp, sdev):
    '''
    Gaussian envelope for a 2-D Gabor filter.
    We assume that it is circular (same decrease in x/y directions).
    '''
    out = amp * np.exp(-(x**2+y**2)/(sdev**2))
    return out

def G2_fil_real(x, y, paras):
    '''
    Custom-built filter response (real part).
    '''
    # Spatial frequency in polar coordinates.
    u = paras["freqs"] * math.cos(paras["dir"])
    v = paras["freqs"] * math.sin(paras["dir"])
    # Computations.
    carrier = G2_carrier_real(x=x, y=y, freqx=u, freqy=v, phase=paras["phase"])
    envelope = G2_envelope(x=x, y=y, amp=paras["amp"], sdev=paras["sdev"])
    out = carrier * envelope
    return out

def G2_fil_imag(x, y, paras):
    '''
    Custom-built filter response (imaginary part).
    '''
    # Spatial frequency in polar coordinates.
    u = paras["freqs"] * math.cos(paras["dir"])
    v = paras["freqs"] * math.sin(paras["dir"])
    # Computations.
    carrier = G2_carrier_imag(x=x, y=y, freqx=u, freqy=v, phase=paras["phase"])
    envelope = G2_envelope(x=x, y=y, amp=paras["amp"], sdev=paras["sdev"])
    out = carrier * envelope
    return out

def fil_kernel(paras, n_stds=3):
    '''
    Complex values of 2D Gabor filter, for use in convolution.
    When applied to images, this is typically called
    The linear size of the filter is determined as a multiple
    of the standard deviation of the Gaussian envelope, and the
    values passed to the filter are symmetric about zero.

    USAGE: pass the parameters only; the size of the grid of
    response values generated depends on these.
    '''

    pixnum = 2*math.ceil(n_stds*paras["sdev"])

    y0 = pixnum/2
    x0 = pixnum/2
    y, x = np.mgrid[-y0:(y0+1), -x0:(x0+1)]

    # Spatial frequency in polar coordinates.
    u = paras["freqs"] * math.cos(paras["dir"])
    v = paras["freqs"] * math.sin(paras["dir"])
    # Computations.
    envelope = G2_envelope(x=x, y=y,
                           amp=paras["amp"]/(2*math.pi*paras["sdev"]**2),
                           sdev=paras["sdev"])
    out = {"real": None, "imag": None}
    out["real"] = envelope * G2_carrier_real(x=x, y=y, freqx=u, freqy=v, phase=paras["phase"])
    out["imag"] = envelope * G2_carrier_imag(x=x, y=y, freqx=u, freqy=v, phase=paras["phase"])

    return out

def patch_stats(image, grid_w, grid_h):
    '''
    A simple function which takes an image, divides it
    into a (grid_y x grid_x) grid of patches, and iterates
    over the patches, computing per-patch statistics.

    In the special case of grid_x=grid_y=1, stats are for the whole image.
    '''

    pix_h = image.shape[0] # number of pixels
    pix_w = image.shape[1]

    gridsize = grid_w*grid_h

    dh = math.floor(pix_h/grid_h) # typical (non-edge) patch sizes
    dw = math.floor(pix_w/grid_w)

    meanvec = np.zeros(gridsize, dtype=np.float32) # initialize vectors to hold the stats
    medvec = np.zeros(gridsize, dtype=np.float32)
    maxvec = np.zeros(gridsize, dtype=np.float32)
    minvec = np.zeros(gridsize, dtype=np.float32)

    # Loop over the patches, starting at the top-left, and doing one grid row at a time.
    idx = 0
    for i in range(grid_h):

        start_h = i * dh
        if (i+1 == grid_h):
            stop_h = pix_h
        else:
            stop_h = start_h + dh

        for j in range(grid_w):

            start_w = j * dw
            if (j+1 == grid_w):
                stop_w = pix_w
            else:
                stop_w = start_w + dw

            patch = image[start_h:stop_h, start_w:stop_w]
            meanvec[idx] = np.mean(patch) # patch mean
            medvec[idx] = np.median(patch) # patch median
            maxvec[idx] = np.max(patch) # patch maximum
            minvec[idx] = np.min(patch) # patch minimum
            idx += 1

    return {"mean": meanvec, "med": medvec, "max": maxvec, "min": minvec}

def nonlin(u):
    '''
    A non-linear function to pass per-patch magnitude statistics through.
    '''

    return np.log(1+u)

def nonlin(u):
    '''
    A non-linear function to pass per-patch magnitude statistics through.
    '''

    return np.log(1+u)

def G2_getfeatures(ims, fil_paras, gridshape, mode="reflect", cval=0):
    '''
    A routine which takes an array of images with 4 coords.
    Dim 1 and 2: pixel position.
    Dim 3: RGB channel index.
    Dim 4: Time index.
    '''

    num_ims = ims.shape[3]
    num_feats = gridshape[0] * gridshape[1]

    out = np.zeros(num_ims*num_feats, dtype=np.float32).reshape((num_ims,num_feats))

    # Generate the kernel prior to loop over images.
    fil_values = fil_kernel(paras=fil_paras, n_stds=2)

    # Iterate over images.
    for i in range(num_ims):

        featvec = np.arange(0, dtype=np.float32)

        # Slice -> XYZ -> CIE Lab -> Take only Luminance channel.
        im = col.xyz2lab(col.rgb2xyz(ims[:,:,:,i]))[:,:,0]

        # Convolution.
        fil_response_real = ndi.convolve(input=im,
                                         weights=fil_values["real"],
                                         mode=mode, cval=cval)
        fil_response_imag = ndi.convolve(input=im,
                                         weights=fil_values["imag"],
                                         mode=mode, cval=cval)
        fil_response_magnitude = np.sqrt((fil_response_real**2 + fil_response_imag**2))

        # Per-patch statistics.
        imstats = patch_stats(image=fil_response_magnitude, grid_h=gridshape[0], grid_w=gridshape[1])

        # Pass per-patch statistics through non-linearity to compute final feature vector.
        imfeats = nonlin(imstats["mean"])

        # Store the feature vector for this image.
        out[i,:] = imfeats

    # Output is the array of feature vectors, one feature vector for each image.
    return out


import numpy as np
import copy as cp
import math

def get_distance(dictionary, city1, city2):
    """
    Calculate distance between 2 nodes
    :param dictionary: Dictionary. Dict with node names as keys, coordinates as values
    :param city1: int. Name of city
    :param city2: int. Name of city
    :return: float. Distance from one node to another.
    """
    x = dictionary[city1][0] - dictionary[city2][0]
    y = dictionary[city1][1] - dictionary[city2][1]
    return math.sqrt(x ** 2 + y ** 2)

# calculate the total distance
def total_distance(tour, dictionary):
    """
    Calculate total distance of path
    :param tour: list. Current path
    :param dictionary: Dictionary. Dict with node names as keys, coordinates as value
    :return: float. Current length of path
    """
    distance = 0
    for i in range(len(tour) - 1):
        distance += get_distance(dictionary, tour[i], tour[i + 1])

    return distance

def swap(tour):
    """
    Exchange two coordinates and get a candidate solution solution
    :param tour: list. List with current path
    :return: list. List with new path
    """
    c1, c2 = np.random.randint(1, len(tour) - 1, size=2)
    # Swap coordinates, make deepcopy of list
    stour = cp.deepcopy(tour)
    stour[c1], stour[c2] = stour[c2], stour[c1]
    return stour

# inserting
def insert(tour):
    """
    Randomly insert node in random place of path
    :param tour: list. List with current path
    :return: list. List with new path
    """
    randindex = np.random.randint(1, len(tour) - 2)
    randcity = np.random.randint(2, len(tour) - 1)
    instour = cp.deepcopy(tour)
    instour.remove(randcity)
    instour.insert(randindex, randcity)
    return instour

# invert
def invert(tour):
    """
    Invert part of path
    :param tour: list. List with current path
    :return: list. List with new path
    """
    c1, c2 = np.random.randint(1, len(tour) - 1, size=2)
    if c2 < c1:
        a = c2
        b = c1
    else:
        a = c1
        b = c2

    itour = cp.deepcopy(tour)
    itour = itour[:a] + itour[b:a - 1:-1] + itour[b + 1:]
    return itour

def calc_nn(nodes):
    """
    Calculate Euclidean distance to all other nodes in path
    :param nodes:  Dictionary. Dict with node names as keys, coordinates as values
    :return: Dictionary. Dict containing coords and an individual dict for each node,
    containing distances to other neighbor sorted in ascending order.
    """
    for node in range(1, len(nodes) + 1):
        t_dict = dict()
        tour = [i for i in nodes.keys()]
        tour.remove(node)

        for j in tour:
            t_dict[j] = get_distance(nodes, node, j)

        nodes[node].append(sorted(t_dict.items(), key=lambda x: x[1]))

    return nodes


import numpy as np
import math
def hashValue(data, planes):

    signature = 0
    for plane in planes:
        signature = signature << 1
        val = np.dot(plane,data)
        if val >=0:
            signature = signature | 1

    return signature

def onesCount(n):
    count = 0
    if n == 0:
        return 0
    else:
        while n:
            count += 1
            n = n & (n-1)
    return count

def angleCalc(a,b):
    prod = np.dot(a,b)
    magA = sum(a**2) ** 0.5
    magB = sum(b**2) ** 0.5
    cos = prod/magA/magB
    theta = math.acos(cos)

    return theta

dim = 3
bits = 3
run = 1
averageSimilarity = 0

for i in range(run):

    point1 = np.random.randn(dim)
    point2 = np.random.randn(dim)
    print("point1: ",point1)
    print("point2: ",point2)

    #point3 = np.array([1,1,1])

    planes = np.random.randn(bits, dim)

    sig1 = hashValue(point1, planes)
    sig2 = hashValue(point2, planes)
    print("sig1: ", sig1)
    #sig3 = hashValue(point3, planes)

    angle = angleCalc(point1,point2)
    probSameAngle = 1 - (angle/math.pi)

    probSameHash = 1 - (onesCount(sig1^sig2)/bits)

    error = (probSameAngle-probSameHash)/probSameAngle

    print("Angle: ", probSameAngle, "Hash: ", probSameHash, "Error: ", error, "%")


import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
import sys

training_data = []
classes = []

def readData(filenameX,filenameY): # Read data from filenames
	global training_data
	global classes
	training_data = pd.read_fwf(filenameX,header=None,usecols=[0,1])
	classes = pd.read_csv(filenameY,header=None,usecols=[0])

readData(sys.argv[1],sys.argv[2])

examples_count = training_data.shape[0]
dimensions = training_data.shape[1]
training_data = np.array(training_data.values,dtype='f')
training_data = np.reshape(training_data,[examples_count,dimensions])

labels = []

for i in range(examples_count): # Create a label vector
	if classes.values[i] == 'Alaska':
		labels.append(0)
	elif classes.values[i] == 'Canada':
		labels.append(1)

labels = np.reshape(np.array(labels,dtype='f'),[examples_count,1])
labels_comp = (labels+1)%2

covariance = np.zeros([dimensions,dimensions])
phi = sum(labels)/float(examples_count) #Bernoulli variable for y

mean_0 = np.matmul(np.transpose(training_data),labels_comp)/sum(labels_comp) #Mean for class 0
mean_1 = np.matmul(np.transpose(training_data),labels)/sum(labels) # Mean for class 1

for i in range(examples_count):
	add = np.matmul(np.subtract(np.reshape(training_data[i],[2,1]),mean_0*labels_comp[i]+mean_1*labels[i]),np.transpose(np.subtract(np.reshape(training_data[i],[2,1]),mean_0*labels_comp[i]+mean_1*labels[i])))
	covariance = np.add(covariance,add)
covariance = covariance/examples_count # Covariance matrix

intercept = np.matmul(np.transpose(mean_1),np.matmul(np.linalg.inv(covariance),mean_1))
intercept = intercept - np.matmul(np.transpose(mean_0),np.matmul(np.linalg.inv(covariance),mean_0))
intercept = (intercept - 2*np.log(phi/(1-phi))).flatten() # Compute the intercept term in linear equation

linear = (2*np.matmul(np.linalg.inv(covariance),np.subtract(mean_0,mean_1))).flatten()

zeros = {}
ones = {}
zeros["x1"] = []
zeros["x2"] = []
ones["x1"] = []
ones["x2"] = []

for i in range(examples_count): # Separate data into classes
	if labels[i] == 1:
		ones["x1"].append(training_data[i][0])
		ones["x2"].append(training_data[i][1])
	elif labels[i] == 0:
		zeros["x1"].append(training_data[i][0])
		zeros["x2"].append(training_data[i][1])

min1 = min([min(zeros["x1"]),min(ones["x1"])])
max1 = max([max(zeros["x1"]),max(ones["x1"])])

x1 = np.linspace(min1,max1,100)
x2 = -1*(intercept+linear[0]*x1)/linear[1]

plt.scatter(zeros["x1"],zeros["x2"],c='r',marker='o',label='Alaska') # Plot data
plt.scatter(ones["x1"],ones["x2"],c='b',marker='^',label='Canada')
plt.xlabel('Fresh Water Diameter')
plt.ylabel('Marine Water Diameter')
plt.legend(loc='upper right')
plt.title('Data')
plt.show()

if int(sys.argv[3]) == 0:
	print("Mean_0",mean_0)
	print("Mean_1",mean_1)
	print("Covariance",covariance)
	plt.scatter(zeros["x1"],zeros["x2"],c='r',marker='o',label='Alaska')
	plt.scatter(ones["x1"],ones["x2"],c='b',marker='^',label='Canada')
	plt.plot(x1,x2,c='g',label='Linear Boundary') # Plot linear boundary
	plt.xlabel('Fresh Water Diameter')
	plt.ylabel('Marine Water Diameter')
	plt.legend(loc='upper right')
	plt.title('Linear Seperator')
	plt.show()

covariance_0 = np.zeros([dimensions,dimensions]) # Different covariance matrix
covariance_1 = np.zeros([dimensions,dimensions])

for i in range(examples_count):
	add = np.matmul(np.subtract(np.reshape(training_data[i],[2,1]),mean_0*labels_comp[i]+mean_1*labels[i]),np.transpose(np.subtract(np.reshape(training_data[i],[2,1]),mean_0*labels_comp[i]+mean_1*labels[i])))
	covariance_0 = np.add(covariance_0,add*labels_comp[i])
	covariance_1 = np.add(covariance_1,add*labels[i])

covariance_0 = covariance_0/sum(labels_comp)
covariance_1 = covariance_1/sum(labels)

intercept = np.matmul(np.transpose(mean_1),np.matmul(np.linalg.inv(covariance_1),mean_1))
intercept = intercept - np.matmul(np.transpose(mean_0),np.matmul(np.linalg.inv(covariance_0),mean_0))
intercept = (intercept - 2*np.log(phi/(1-phi))).flatten() # Constant term in quadratic equation

linear = np.matmul(np.linalg.inv(covariance_0),mean_0)
linear = (2*np.subtract(linear,np.matmul(np.linalg.inv(covariance_1),mean_1))).flatten() # Linear terms

quadratic = np.subtract(np.linalg.inv(covariance_1),np.linalg.inv(covariance_0)) # Quadratic terms

z1 = np.copy(x1)

def solveforZ2(z1): # Solve for z2 given z1 using the quadratic equation
	c = intercept+quadratic[0][0]*z1*z1+linear[0]*z1
	b = linear[1]+2*quadratic[0][1]*z1
	a = quadratic[1][1]
	return((-b+np.sqrt(b*b-4*a*c))/(2*a))

z2 = solveforZ2(z1)

if int(sys.argv[3]) == 1:
	print("Mean_0",mean_0)
	print("Mean_1",mean_1)
	print("Covariance_0",covariance_0)
	print("Covariance_1",covariance_1)
	plt.scatter(zeros["x1"],zeros["x2"],c='r',marker='o',label='Alaska')
	plt.scatter(ones["x1"],ones["x2"],c='b',marker='^',label='Canada')
	plt.plot(x1,x2,c='g',label='Linear Boundary')
	plt.plot(z1,z2,c='y',label='Quadratic Boundary') # Plot the quadratic boundary
	plt.xlabel('Fresh Water Diameter')
	plt.ylabel('Marine Water Diameter')
	plt.legend(loc='upper right')
	plt.title('Quadratic Seperator')
	plt.show()


#!/usr/bin/env python3

import numpy as np
import operator as op
from functools import reduce

#question 18 triangle sum

#By starting at the top of the triangle below and
#moving to adjacent numbers on the row below, the
#maximum total from top to bottom is 23.
#3
#7 4
#2 4 6
#8 5 9 3

#That is, 3 + 7 + 4 + 9 = 23.
#Find the maximum total from top to bottom of the triangle below:

grid='''75
95 64
17 47 82
18 35 87 10
20 04 82 47 65
19 01 23 75 03 34
88 02 77 73 07 63 67
99 65 04 28 06 16 70 92
41 41 26 56 83 40 80 70 33
41 48 72 33 47 32 37 16 94 29
53 71 44 65 25 43 91 52 97 51 14
70 11 33 28 77 73 17 78 39 68 17 57
91 71 52 38 17 14 91 43 58 50 27 29 48
63 66 04 68 89 53 67 30 73 16 69 87 40 31
04 62 98 27 23 09 70 98 73 93 38 53 60 04 23
'''

# idea. start from end and check at each step the max value
# then check sums of possible routes, keep max
# didn't work :-(

# convert string to matrix
lines = grid.splitlines()

# create matrix to hold data
grid_n = np.zeros(shape=(len(lines),len(lines)))

for i in range(len(lines)):
  num_str = lines[i]
  nums = np.asarray(num_str.split(' '))
  grid_n[i,:len(nums)] = nums

#print(grid_n)

def walk_sum(grid,row,col):
  if row==0: return grid[0,0]
  if (row>0):
    if(col>0):
      temp1=grid[row-1,col]
      temp2=grid[row-1,col-1]
      if temp1>temp2:
        return(grid[row,col]+walk_sum(grid,row-1,col))
      return(grid[row,col]+walk_sum(grid,row-1,col-1))
    return(grid[row,col]+walk_sum(grid,row-1,col))

count = 0
#print(grid_n.shape)
#for i in range(grid_n.shape[1]):
#  temp = walk_sum(grid_n,grid_n.shape[0]-1,i)
#  if temp > count: count = temp

#print(count)

# idea 2 - optimal substructure!
# start from top, and calculate max sum for each element
# at the bottom, take the max

def max_sum(grid):
  grid_sum = np.zeros(shape=(grid.shape[0],grid.shape[1]))

  for row in range(0,grid.shape[0]):
    for col in range(0,row+1):
      #print(row,col, grid[row,col])
      grid_sum[row,col] = grid[row,col]
      if row==0:
        if col==0: grid_sum[0,0] = grid[0,0]
        else: grid_sum[row,col] += grid_sum[row-1,col]
        #print("row 0,", row,grid[row-1,col],grid_sum[row,col])
      elif col==row:
        grid_sum[row,col] += grid_sum[row-1,col-1]
        #print("col=row,", row,grid[row-1,col-1],grid_sum[row,col])
      else:
        grid_sum[row,col] += max([grid_sum[row-1,col-1],grid_sum[row-1,col]])
  return(grid_sum)

#temp = max_sum(grid_n)
#print(temp,temp[-1,:],max(temp[-1,:]))

# 1074 YAY

#question 17
# count the number of letters in the numbers 1 to 1000
# format
# one hundred and thirty one

digits_str = ['one','two','three','four','five','six','seven','eight','nine']
teens_str = ['ten','eleven','twelve','thirteen','fourteen','fifteen','sixteen','seventeen','eighteen','nineteen']
twenty_plus_str = ['twenty','thirty','fourty','fifty','sixty','seventy','eighty','ninety']

digits = [len(x) for x in digits_str]
teens = [len(x) for x in teens_str]
tens =  [len(x) for x in twenty_plus_str]
hundred = 7
hundred_and = 10
# X hundred *(1+(3+ninty nine others))
one_thou = len('onethousand')

total_sum = 0

# digits and teens
total_sum+= sum(digits) + sum(teens)

# tens are in the form twenty, thrity, then
#  twenty-one two...nine, thirty-one two... nine,
# = twenty appears 9 times more
# but the number one appears 8 times more for each twenty,
# so 8*9

total_sum += 9*(sum(tens) + 8*sum(digits))
sumto99 = total_sum

# hundreds are of the form
# one hundred, two, three... nine
# one hundred AND one... ninety nine... nine hundred AND one... ninety nine
# -> hundred appears nine times more digits one time
# -> sumto99 appears 9 times more + hundred_and *90 (less 9 bc not including 100,200,300)

total_sum += 9*(hundred + sum(digits))
total_sum += 9*sumto99 + 90*hundred_and

# one thousand once
total_sum += one_thou

print(total_sum)

#sum_to_99 = sum(digits) + sum(teens) + sum(tens) + sum(tens)*sum(digits)

#total_num_sum = sum_to_99+ sum(hundreds[0]*digits)+sum([x*sum_to_99 for x in hundreds])*sum(digits)

#print([x*sum_to_99 for x in hundreds if x>7])
#print(sum_to_99,total_num_sum)


# In[1]:

import pymc as pm
import numpy as np

# Model
# -----
# Consider the following true generative model:
#
# $$ x_{true} \sim \textrm{Normal}(2,1) $$
# $$ y_{true} \sim \textrm{Normal}(\textrm{exp}(x_{true}),1)$$
# $$ z_{data} \sim \textrm{Normal}(x_{true} + y_{true},0.75)$$
#
# Where $x_{true}$ is a scalar, $y_{true}$ is a vector of length 2, and $z_{data}$ is a $2\times 20$ matrix.
#
# We can simulate this using Numpy:

# In[2]:

ndims = 2
nobs = 20

xtrue = np.random.normal(scale=2., size=1)
ytrue = np.random.normal(loc=np.exp(xtrue), scale=1, size=(ndims, 1))
zdata = np.random.normal(loc=xtrue + ytrue, scale=.75, size=(ndims, nobs))

# `zdata` is observed but `xtrue` and `ytrue` are not. Thus x and y are unknown, and we want to come up with posterior distributions for them.

# Build Model
# -----------
#
# Now we want to do inference assuming the following model:
#
# $$ x \sim \textrm{Normal}(0,1) $$
# $$ y \sim \textrm{Normal}(\textrm{exp}(x),2)$$
# $$ z \sim \textrm{Normal}(x + y,0.75)$$
#
# The aim here is to get posteriors over $x$ and $y$ given the data we have about $z$ (`zdata`).
#
# We create a new `Model` objects, and do operations within its context. The `with` lets PyMC know this model is the current model of interest.
#
# We construct new random variables with the constructor for its prior distribution such as `Normal` while within a model context (inside the `with`). When you make a random variable it is automatically added to the model. The constructor returns a Theano variable.
#
# Using the constructor may specify the name of the random variable, the parameters of a random variable's prior distribution, as well as the shape of the random variable. We can specify that a random variable is observed by specifying the data that was observed.

# In[3]:

with pm.Model() as model:
    x = pm.Normal('x', mu=0., sd=1)
    y = pm.Normal('y', mu=pm.exp(x), sd=2., shape=(ndims, 1)) # here, shape is telling us it's a vector rather than a scalar.
    z = pm.Normal('z', mu=x + y, sd=.75, observed=zdata) # shape is inferred from zdata

# A parenthetical note on the parameters for the normal. Variance is encoded as `tau`, indicating precision, which is simply inverse variance (so $\tau=\sigma^{-2}$ ). This is used because the gamma function is the conjugate prior for precision, and must be inverted to get variance. Encoding in terms of precision saves the inversion step in cases where variance is actually modeled using gamma as a prior.

# Fit Model
# ---------
# We need a starting point for our sampling. The `find_MAP` function finds the maximum a posteriori point (MAP), which is often a good choice for starting point. `find_MAP` uses an optimization algorithm (`scipy.optimize.fmin_l_bfgs_b`, or [BFGS](http://en.wikipedia.org/wiki/BFGS_method), by default) to find the local maximum of the log posterior.
#
# Note that this `with` construction is used again. Functions like `find_MAP` and `HamiltonianMC` need to have a model in their context. `with` activates the context of a particular model within its block.

# In[4]:

with model:
    start = pm.find_MAP()

# Points in parameter space are represented by dictionaries with parameter names as they keys and the value of the parameters as the values.

# In[5]:

print("MAP found:")
print("x:", start['x'])
print("y:", start['y'])

print("Compare with true values:")
print("ytrue", ytrue)
print("xtrue", xtrue)

# Out[5]:

#     MAP found:
#     x: 2.08679716543
#     y: [[ 7.93662462]
#      [ 9.10291792]]
#     Compare with true values:
#     ytrue [[ 8.09247902]
#      [ 9.01023203]]
#     xtrue [ 2.03525495]
#

# We will use NUTS to sample from the posterior as implemented by the `NUTS` step method class.

# In[6]:

with model:
    step = pm.NUTS()

# The `sample` function takes a number of steps to sample, a step method, a starting point. It returns a trace object which contains our samples.

# In[7]:

with model:
    trace = pm.sample(3000, step, start)

# Out[7]:

# To use more than one sampler, pass a list of step methods to `sample`.
#
# The trace object can be indexed by the variables in the model, returning an array with the first index being the sample index
# and the other indexes the shape of the parameter. Thus for this example:

# In[8]:

trace[y].shape

# Out[8]:

#     (3000, 2, 1)

# `traceplot` is a summary plotting function for a trace.

# In[9]:

pm.traceplot(trace);

# Out[9]:

# image file:

# ## PyMC Internals
#
# ### Model
#
# The `Model` class has very simple internals: just a list of unobserved variables (`Model.vars`) and a list of factors which go into computing the posterior density (`Model.factors`) (see model.py for more).
#
# A Python "`with model:`" block has `model` as the current model. Many functions, like `find_MAP` and `sample`, must be in such a block to work correctly by default. They look in the current context for a model to use. You may also explicitly specify the model for them to use. This allows us to treat the current model as an implicit parameter to these functions.
#
# ### Distribution Classes
#
# `Normal` and other distributions are actually `Distribution` subclasses. The constructors have different behavior depending on whether they are called with a name argument or not (string argument in 1st slot). This allows PyMC to have intuitive model specification syntax and still distinguish between random variables and distributions.
#
# When a `Distribution` constructor is called:
#
# * Without a name argument, it simply constructs a distribution object and returns it. It won't construct a random variable. This object has properties like `logp` (density function) and `expectation`.
# * With a name argument, it constructs a random variable using the distrubtion object as the prior distribution and inserts this random variable into the current model. Then the constructor returns the random variable.
#

# In[10]:

help(model)

# Out[10]:

#     Help on Model in module pymc.model object:
#
#     class Model(Context)
#      |  Base class for encapsulation of the variables and
#      |  likelihood factors of a model.
#      |
#      |  Method resolution order:
#      |      Model
#      |      Context
#      |      __builtin__.object
#      |
#      |  Methods defined here:
#      |
#      |  AddNamed(model, var)
#      |
#      |  Data(model, data, dist)
#      |
#      |  TransformedVar(model, name, dist, trans)
#      |
#      |  Var(model, name, dist)
#      |
#      |  __getitem__(self, key)
#      |
#      |  __init__(self)
#      |
#      |  d2logpc(model, vars=None)
#      |      Compiled log probability density hessian function
#      |
#      |  dlogpc(model, vars=None)
#      |      Compiled log probability density gradient function
#      |
#      |  ----------------------------------------------------------------------
#      |  Data descriptors defined here:
#      |
#      |  cont_vars
#      |      All the continuous variables in the model
#      |
#      |  logp
#      |      log-probability of the model
#      |
#      |      Parameters
#      |      ----------
#      |
#      |      model : Model
#      |
#      |      Returns
#      |      -------
#      |
#      |      logp : Theano scalar
#      |
#      |  logpc
#      |      Compiled log probability density function
#      |
#      |  test_point
#      |      Test point used to check that the model doesn't generate errors
#      |
#      |  ----------------------------------------------------------------------
#      |  Data and other attributes defined here:
#      |
#      |  contexts = []
#      |
#      |  ----------------------------------------------------------------------
#      |  Methods inherited from Context:
#      |
#      |  __enter__(self)
#      |
#      |  __exit__(self, typ, value, traceback)
#      |
#      |  ----------------------------------------------------------------------
#      |  Class methods inherited from Context:
#      |
#      |  get_context(cls) from __builtin__.type
#      |
#      |  get_contexts(cls) from __builtin__.type
#      |
#      |  ----------------------------------------------------------------------
#      |  Data descriptors inherited from Context:
#      |
#      |  __dict__
#      |      dictionary for instance variables (if defined)
#      |
#      |  __weakref__
#      |      list of weak references to the object (if defined)
#
#

# In[10]:


import numpy as np

def ModifyMatrix(A):
    newA = A
    if(A.shape[1]%2 == 1):
        aux = np.zeros((A.shape[0],A.shape[1]+1))
        aux = np.insert(A,A.shape[1],0,axis=1)
        newA = aux
    if(newA.shape[0]%2 == 1):
        aux = np.zeros((newA.shape[0]+1,newA.shape[1]))
        aux = np.insert(newA,newA.shape[0],0,axis=0)
        newA = aux
    return newA

def FastMultiply(oldA,oldB):
    rows = oldA.shape[0]
    columns = oldB.shape[1]
    if(rows <= 2 or columns <=2 or oldA.shape[1] <= 2 or oldB.shape[0] <= 2):
        return np.dot(oldA,oldB)

    A = ModifyMatrix(oldA)
    B = ModifyMatrix(oldB)
    N1 = A.shape[0]
    N2 = A.shape[1]
    N3 = B.shape[0]
    N4 = B.shape[1]

    a = A[0:N1//2,0:N2//2]
    b = A[0:N1//2,N2//2:N2//2+N2//2]
    c = A[N1//2:N1//2+N1//2,0:N2//2]
    d = A[N1//2:N1//2+N1//2,N2//2:N2//2+N2//2]

    e = B[0:N3//2,0:N4//2]
    f = B[0:N3//2,N4//2:N4//2+N4//2]
    g = B[N3//2:N3//2+N3//2,0:N4//2]
    h = B[N3//2:N3//2+N3//2,N4//2:N4//2+N4//2]

    p1 = FastMultiply(a,(f-h))
    p3 = FastMultiply((c+d),e)
    p2 = FastMultiply((a+b),h)
    p4 = FastMultiply(d,(g-e))
    p5 = FastMultiply((a+d),(e+h))
    p6 = FastMultiply((b-d),(g+h))
    p7 = FastMultiply((a-c),(e+f))

    C = np.zeros((rows,columns))

    c11 = p5 + p4 - p2 + p6
    c12 = p1 + p2
    c21 = p3 + p4
    c22 = p1 + p5 - p3 - p7

    for i in range(0,N1//2):
        for j in range(0,N4//2):
            C[i][j] = c11[i][j]
            if(j + N4//2 < columns):
                C[i][j+N4//2] = c12[i][j]
            if(i + N1//2 < rows):
                C[i+N1//2][j] = c21[i][j]
                if(j + N4//2 < columns):
                    C[i+N1//2][j+N4//2] = c22[i][j]
    return C

MC601-Metodología de la Investigación/strassen.py


# Starting with the number 1 and moving to the right in a clockwise direction a 5 by 5 spiral is formed as follows:

# 21 22 23 24 25
# 20  7  8  9 10
# 19  6  1  2 11
# 18  5  4  3 12
# 17 16 15 14 13

# It can be verified that the sum of the numbers on the diagonals is 101.

# What is the sum of the numbers on the diagonals in a 1001 by 1001 spiral formed in the same way?

import numpy as np
np.set_printoptions(linewidth=240)

size = 1001
z = np.empty((size, size), dtype=np.int64)

count = 1
row = size//2
col = size//2
loc = 0
z[row, col] = 1
while True:
	loc += 1
	for i in range(loc):
		count += 1
		if col+i+1 == size:break
		z[row, col+i+1] = count
	col += loc
	if col == size:
		break
	for i in range(loc):
		count += 1
		z[row+i+1, col] = count
	row += loc

	loc += 1
	for i in range(loc):
		count += 1
		z[row, col-i-1] = count
	col -= loc
	for i in range(loc):
		count += 1
		z[row-i-1, col] = count
	row -= loc
print(np.sum(np.diagonal(z)) + np.sum(np.diagonal(np.rot90(z))) - 1)


import torch
import torch.nn as nn
import numpy as np
import scipy.io as scio
import os
import matplotlib.pyplot as plt

os.environ['CUDA_VISIBLE_DEVICES'] = '0'
torch.manual_seed(1)
np.random.seed(1)

lapl_op = [[[[    0,   0, -1/12,   0,     0],
             [    0,   0,   4/3,   0,     0],
             [-1/12, 4/3,    -5, 4/3, -1/12],
             [    0,   0,   4/3,   0,     0],
             [    0,   0, -1/12,   0,     0]]]]

# ============ define relevant functions =============
# https://github.com/benmaier/reaction-diffusion/blob/master/gray_scott.ipynb

def apply_laplacian(mat, dx = 0.01):
    # dx is inversely proportional to N
    """This function applies a discretized Laplacian
    in periodic boundary conditions to a matrix
    For more information see
    https://en.wikipedia.org/wiki/Discrete_Laplace_operator#Implementation_via_operator_discretization
    """

    # the cell appears 4 times in the formula to compute
    # the total difference
    neigh_mat = -5*mat.copy()

    # Each direct neighbor on the lattice is counted in
    # the discrete difference formula
    neighbors = [
                    ( 4/3,  (-1, 0) ),
                    ( 4/3,  ( 0,-1) ),
                    ( 4/3,  ( 0, 1) ),
                    ( 4/3,  ( 1, 0) ),
                    (-1/12,  (-2, 0)),
                    (-1/12,  (0, -2)),
                    (-1/12,  (0, 2)),
                    (-1/12,  (2, 0)),
                ]

    # shift matrix according to demanded neighbors
    # and add to this cell with corresponding weight
    for weight, neigh in neighbors:
        neigh_mat += weight * np.roll(mat, neigh, (0,1))

    return neigh_mat/dx**2

# Define the update formula for chemicals A and B
def update(A, B, DA, DB, f, k, delta_t):
    """Apply the Gray-Scott update formula"""

    # compute the diffusion part of the update
    diff_A = DA * apply_laplacian(A)
    diff_B = DB * apply_laplacian(B)

    # Apply chemical reaction
    reaction = A*B**2
    diff_A -= reaction
    diff_B += reaction

    # Apply birth/death
    diff_A += f * (1-A)
    diff_B -= (k+f) * B

    A += diff_A * delta_t
    B += diff_B * delta_t

    return A, B

def GetEachTerm(A, B, DA, DB, f, k, delta_t, dx):
    lap_A = DA * apply_laplacian(A,dx)
    lap_B = DB * apply_laplacian(B,dx)
    # Apply chemical reaction
    reaction = A * B ** 2

    # Apply birth/death, linear term
    lin_A = f * (1 - A)
    lin_B = (k + f) * B
    return lap_A, lap_B, reaction, lin_A, lin_B

def update_rk4(A0, B0, DA, DB, f, k, delta_t, dx):
    """Update with Runge-kutta-4 method
       See https://en.wikipedia.org/wiki/Runge%E2%80%93Kutta_methods
    """
    ############# Stage 1 ##############
    # compute the diffusion part of the update
    diff_A = DA * apply_laplacian(A0, dx)
    diff_B = DB * apply_laplacian(B0, dx)

    # Apply chemical reaction
    reaction = A0 * B0 ** 2
    diff_A -= reaction
    diff_B += reaction

    # Apply birth/death
    diff_A += f * (1 - A0)
    diff_B -= (k + f) * B0

    K1_a = diff_A
    K1_b = diff_B

    ############# Stage 1 ##############
    A1 = A0 +  K1_a * delta_t/2.0
    B1 = B0 +  K1_b * delta_t/2.0

    diff_A = DA * apply_laplacian(A1, dx)
    diff_B = DB * apply_laplacian(B1, dx)

    # Apply chemical reaction
    reaction = A1 * B1 ** 2
    diff_A -= reaction
    diff_B += reaction

    # Apply birth/death
    diff_A += f * (1 - A1)
    diff_B -= (k + f) * B1

    K2_a = diff_A
    K2_b = diff_B

    ############# Stage 2 ##############

    A2 = A0 + K2_a * delta_t/2.0
    B2 = B0 + K2_b * delta_t/2.0

    diff_A = DA * apply_laplacian(A2, dx)
    diff_B = DB * apply_laplacian(B2, dx)

    # Apply chemical reaction
    reaction = A2 * B2 ** 2
    diff_A -= reaction
    diff_B += reaction

    # Apply birth/death
    diff_A += f * (1 - A2)
    diff_B -= (k + f) * B2

    K3_a = diff_A
    K3_b = diff_B

    ############# Stage 3 ##############
    A3 = A0 + K3_a * delta_t
    B3 = B0 + K3_b * delta_t

    diff_A = DA * apply_laplacian(A3, dx)
    diff_B = DB * apply_laplacian(B3, dx)

    # Apply chemical reaction
    reaction = A3 * B3 ** 2
    diff_A -= reaction
    diff_B += reaction

    # Apply birth/death
    diff_A += f * (1 - A3)
    diff_B -= (k + f) * B3

    K4_a = diff_A
    K4_b = diff_B

    # Final solution
    A = A0 + delta_t*(K1_a+2*K2_a+2*K3_a+K4_a)/6.0
    B = B0 + delta_t*(K1_b+2*K2_b+2*K3_b+K4_b)/6.0

    return A, B

def get_initial_A_and_B(N, random_influence = 0.2):
    """get the initial chemical concentrations"""
    # get initial homogeneous concentrations
    A = (1-random_influence) * np.ones((N,N))
    B = np.zeros((N,N))

    # put some noise on there
    A += random_influence * np.random.random((N,N))
    B += random_influence * np.random.random((N,N))

    # initial disturbance
    N1, N2, N3 = N//4-4, N//2, 3*N//4
    r = int(N/10.0)

    # initial disturbance 1
    A[N1-r:N1+r, N1-r:N1+r] = 0.50
    B[N1-r:N1+r, N1-r:N1+r] = 0.25

#    # initial disturbance 2
#    A[N1-r:N1+r, N3-r:N3+r] = 0.50
#    B[N1-r:N1+r, N3-r:N3+r] = 0.25
#
#    # initial disturbance 3
#    A[N3-r:N3+r, N3-r:N3+r] = 0.50
#    B[N3-r:N3+r, N3-r:N3+r] = 0.25
#
#    # initial disturbance 4
#    A[N3-r:N3+r, N1-r:N1+r] = 0.50
#    B[N3-r:N3+r, N1-r:N1+r] = 0.25

    # initial disturbance 5
    A[N2-r:N2+r, N2-r:N2+r] = 0.50
    B[N2-r:N2+r, N2-r:N2+r] = 0.25
#
#    # initial disturbance 6
#    A[N2-r:N2+r, N3-r:N3+r] = 0.50
#    B[N2-r:N2+r, N3-r:N3+r] = 0.25

    return A, B

def postProcess(output, N, xmin, xmax, ymin, ymax, num, batch, save_path):
    ''' num: Number of time step
    '''
    x = np.linspace(xmin, xmax, N+1)[:-1]
    y = np.linspace(ymin, ymax, N+1)[:-1]
    x_star, y_star = np.meshgrid(x, y)
    u_pred = output[num, 0, :, :]

    # v_star = true[num+25, 1, 1:-1, 1:-1]
    v_pred = output[num, 1, :, :]

    fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(6, 3))
    fig.subplots_adjust(hspace=0.3, wspace=0.3)

    cf = ax[0].scatter(x_star, y_star, c=u_pred, alpha=0.95, edgecolors='none', cmap='hot', marker='s', s=2)
    ax[0].axis('square')
    ax[0].set_xlim([xmin, xmax])
    ax[0].set_ylim([ymin, ymax])
    cf.cmap.set_under('black')
    cf.cmap.set_over('white')
    ax[0].set_title('u-FDM')
    fig.colorbar(cf, ax=ax[0], extend='both')

    cf = ax[1].scatter(x_star, y_star, c=v_pred, alpha=0.95, edgecolors='none', cmap='hot', marker='s', s=2) #
    ax[1].axis('square')
    ax[1].set_xlim([xmin, xmax])
    ax[1].set_ylim([ymin, ymax])
    cf.cmap.set_under('black')
    cf.cmap.set_over('white')
    ax[1].set_title('v-FDM')
    fig.colorbar(cf, ax=ax[1], extend='both')

    # plt.draw()
    plt.savefig(save_path + '/uv_[b=%d][t=%d].png'%(batch, num))
    plt.close('all')

if __name__ == '__main__':
    #################### generate data #####################
    # =========== define model parameters ==========
    # dt should be 1/2 of dx

    # Diffusion coefficients
    DA = 0.16 #2*10**-5
    DB = 0.08 #DA/4

    # define birth/death rates
    f = 0.06 #1/25
    k = 0.062 #3/50

    # grid size
    N = 256 # 128

    # update in time
    delta_t = 1.0 #1.0/2
    # spatial step
    dx = 1.0 #1.0 / N

    # intialize the chemical concentrations, random_incluence=0
    A, B = get_initial_A_and_B(N, random_influence = 0.0)
    A_record = A.copy()[None,...]
    B_record = B.copy()[None,...]

    N_simulation_steps = 15000
    for step in range(N_simulation_steps):
        # Runge-kutta scheme
        #A, B = update(A, B, DA, DB, f, k, delta_t)
        A, B = update_rk4(A, B, DA, DB, f, k, delta_t, dx)

        if step%5 ==0:
            print(step)
            A_record = np.concatenate((A_record, A[None,...]), axis=0)
            B_record = np.concatenate((B_record, B[None,...]), axis=0)

    UV = np.concatenate((A_record[None,...], B_record[None,...]), axis=0)
    save_path = './2DGS_IC1_2x3001x256x256.mat'
    scio.savemat(save_path, {'uv': UV})

    # Plot the result
    output = np.transpose(UV, [1, 0, 2, 3])
    fig_save_path = './2DGS/'
    for i in range(21):
        postProcess(output, N, 0, N*dx, 0, N*dx, num=150*i, batch=1,save_path=fig_save_path)

    plt.figure()
    plt.plot(UV[0, :, 50, 50], alpha=0.6, label='rk4, dt=1.0')
    plt.legend()
    # plt.show()
    plt.savefig(fig_save_path + '/fig[x=50,y=50].png')


import numpy as np
import matplotlib.pyplot as plt
import scipy.spatial.distance
import bats

# first, generate a circle
n = 100
X = np.random.normal(size=(n,2))
X = X / np.linalg.norm(X, axis=1).reshape(-1,1)
X = X + np.random.normal(size=(n,2), scale = 0.1 )
fig = plt.scatter(X[:,0], X[:,1])
fig.axes.set_aspect('equal')
plt.savefig('figures/RipsEx_data.png')

# use bats to compute distances internally
data = bats.DataSet(bats.Matrix(X)) # put into a bats.DataSet
dist = bats.Euclidean()  # distance we would like to use
F = bats.RipsFiltration(data, dist, np.inf, 2) # generate a RipsFiltration

R = bats.reduce(F, bats.F2()) # reduce with F2 coefficients
ps = []
for d in range(R.maxdim()):
    ps.extend(R.persistence_pairs(d))

fig, ax = bats.persistence_diagram(ps, tmax = 2.0)
plt.savefig('figures/RipsEx_pd_euc.png')

# use pairwise distance matrix

# method 1: use bats to get pairwise distances
data = bats.DataSet(bats.Matrix(X)) # put into a bats.DataSet
dist = bats.Euclidean()  # distance we would like to use
pdist = dist(data, data) # returns a bats.Matrix of pairwise distances

# method 2: use scipy to get pairwise distances
pdist_sp = scipy.spatial.distance.squareform(scipy.spatial.distance.pdist(X, 'euclidean'))
pdist = bats.Matrix(pdist_sp)

F = bats.RipsFiltration(pdist, np.inf, 2) # generate a filtraiton on pariwise distances
R = bats.reduce(F, bats.F2()) # reduce with F2 coefficients
ps = []
for d in range(R.maxdim()):
    ps.extend(R.persistence_pairs(d))

fig, ax = bats.persistence_diagram(ps, tmax = 2.0)
plt.savefig('figures/RipsEx_pd.png')

# greedy subsampling

# inds is sequence of indices selected by greedy landmarking
# dists[k] is hausdorff distance from X[inds[:k]] to X
inds, dists = bats.greedy_landmarks_hausdorff(pdist, 0)

k = 40 # we'll landmark 40 points
# print('hausdorff distance is {}'.format(dists[k]))
fig = plt.figure()
ret = plt.scatter(X[inds[:k],0], X[inds[:k],1])
fig.suptitle('hausdorff distance: {:.3f}'.format(dists[k]))
ret.axes.set_aspect('equal')
plt.savefig('figures/RipsEx_data_landmark.png')

# use scipy to get pairwise distances
pdist_sp = scipy.spatial.distance.squareform(scipy.spatial.distance.pdist(X[:k], 'euclidean'))
pdist = bats.Matrix(pdist_sp)

F = bats.RipsFiltration(pdist, np.inf, 2) # generate a filtraiton on pariwise distances
R = bats.reduce(F, bats.F2()) # reduce with F2 coefficients
ps = []
for d in range(R.maxdim()):
    ps.extend(R.persistence_pairs(d))

fig, ax = bats.persistence_diagram(ps, tmax = 2.0)
plt.savefig('figures/RipsEx_pd_landmark.png')

import plotly
import plotly.graph_objects as go

# use bats to get pairwise distances
data = bats.DataSet(bats.Matrix(X)) # put into a bats.DataSet
dist = bats.L1Dist()  # distance we would like to use
pdist = dist(data, data) # returns a bats.Matrix of pairwise distances

pdist_np = np.array(pdist) # numpy array of pairwise distances

F = bats.RipsFiltration(pdist, np.inf, 2) # generate a filtraiton on pariwise distances
R = bats.reduce(F, bats.F2()) # reduce with F2 coefficients

# get longest H1 pair
ps1 = R.persistence_pairs(1)
lens = [p.death() - p.birth() for p in ps1] # find longest length pair
ind = np.argmax(lens)
pair = ps1[ind]

def plot_representative_2D(X, F, R, pair, D, thresh=None, **kwargs):
    """
    Plot H1 represnetative on 2D scatter plot

    plot representative
    X: 2-dimensional locations of points
    F: bats FilteredSimplicialComplex
    R: bats ReducedFilteredChainComplex
    pair: bats PersistencePair
    D: N x N distance matrix
    thresh: threshold parameter
    kwargs: passed onto figure layout
    """
    if thresh is None:
        thresh = pair.birth()

    fig = go.Figure()
    fig.add_trace(go.Scatter(
        x=X[:,0], y=X[:,1],
        mode='markers',
    ))
    edge_x = []
    edge_y = []
    N = X.shape[0]
    for i in range(N):
        for j in range(N):
            if D[i, j] <= thresh:
                edge_x.extend([X[i,0], X[j,0], None])
                edge_y.extend([X[i,1], X[j,1], None])

    fig.add_trace(go.Scatter(
        x=edge_x, y=edge_y,
        line=dict(width=0.5, color='#888'),
        hoverinfo='none',
        mode='lines')
     )

    edge_x = []
    edge_y = []
    r = R.representative(pair)
    nzind = r.nzinds()
    cpx = F.complex()
    for k in nzind:
        [i, j] = cpx.get_simplex(1, k)
        if D[i, j] <= thresh:
            edge_x.extend([X[i,0], X[j,0], None])
            edge_y.extend([X[i,1], X[j,1], None])
    fig.add_trace(go.Scatter(
        x=edge_x, y=edge_y,
        line=dict(width=2, color='red'),
        hoverinfo='none',
        mode='lines')
     )
    fig.update_layout(**kwargs)
    return fig

fig = plot_representative_2D(X, F, R, pair, pdist_np, width=800, height=800)
fig.write_image('figures/H1_rep.png')


from scipy.stats import pearsonr
from math import sqrt
from math import isnan

# Получить список фильмов, оцененных обоими
def similar_films(critics_dict, person1, person2):
    # создаем пустой список фильмов
    sim_film = []

    # для всех фильмов у person1...
    for film in critics_dict[person1]:
        # если такой фильм оценивался person 2...
        if film in critics_dict[person2]:
            # добавляем этот фильм в список sim_film
            sim_film.append(film)

    return sim_film

#Подсчета сходства двух критиков на основе расстояния Евклида
def sim_distance(critics_dict, person1, person2):
    # используем написанную функцию для получения фильмов, оцененных обоими критиками
    sim_films = similar_films(critics_dict, person1, person2)

    # если нет ни одной общей оценки, то есть длина sim_film равна 0 - вернуть 0
    if len(sim_films) == 0:
        return 0

    # для каждого фильма из sim_film посчитаем евклидово расстояние и просуммируем все полученные значения
    sum_of_euclead_dist = 0

    for film in sim_films:
        sum_of_euclead_dist = sum_of_euclead_dist + pow(critics_dict[person1][film] - critics_dict[person2][film], 2)

    # считаем похожесть
    score = 1 / (1 + sum_of_euclead_dist)

    return score

# Возвращает коэффициент корреляции Пирсона между person1 и person2
def sim_pearson(critics_dict, person1, person2):
    # получить список фильмов, оцененных обоими критиками
    sim_films = similar_films(critics_dict, person1, person2)

    # если нет ни одной общей оценки, вернуть 0
    if len(sim_films) < 2:
        return 0

    # получим оценки критиков для фильмов из пересечения
    scores1 = []
    scores2 = []
    for film in sim_films:
        scores1.append(critics_dict[person1][film])
        scores2.append(critics_dict[person2][film])

    # посчитаем коэффициент корреляции
    # берем [0] от результата, так как функция pearsonr() возвращает еще значение p-value
    r = pearsonr(scores1, scores2)[0]

    # если вдруг было деление на ноль и функция вернула nan, то присваиваем ноль
    if isnan(r):
        r = 0
    return r

# возвращает топ-n наиболее похожих на person человек из critics_dict
def topMatches(critics_dict, person, n=5, similarity=sim_pearson):
    # инициализируем лист оценок похожести
    scores = []

    # для каждого человека в словаре критиков
    for other in critics_dict:

        # если этот человек не тот, для которого мы подбираем соответствия
        if other != person:
            # добавить в scores похожесть, вычисленную с помощью функции, записанной в similarity, и имя человека
            scores.append((similarity(critics_dict, person, other), other))

    # отсортировать список по убыванию оценок
    scores.sort(reverse=True)  # аргумент reverse=True отвечает за убывающий порядок

    # возвращаем первые n человек
    return scores[0:n]

# Получить рекомендации для заданного человека, пользуясь взвешенным средним
# оценок, данных всеми остальными пользователями
def getRecommendations(prefs, person, similarity=sim_pearson):

    # инициализируем словари
    totals = {}
    simSums = {}

    # для каждого пользователя
    for other in prefs:

    # сравнивать person с person не нужно
        if other == person:
            continue
        # считаем похожесть
        sim = similarity(prefs, person, other)

        # игнорировать нулевые и отрицательные значения похожести
        if sim <= 0:
            continue

        # для каждого фильма в оцененных other
        for item in prefs[other]:

            # оценивать только фильмы, которые я еще не смотрел
            if item not in prefs[person] or prefs[person][item] == 0:

                # коэффициент подобия * Оценка
                totals.setdefault(item,0)
                totals[item] = totals[item] + prefs[other][item]*sim

                # cумма коэффициентов подобия
                simSums.setdefault(item,0)
                simSums[item] = simSums[item] + sim
    # создать нормированный список
    rankings = [(total/simSums[item], item) for item, total in totals.items( )]

    # вернуть отсортированный список
    rankings.sort(reverse=True)
    return rankings

# обращает матрицу предпочтений, чтобы строки соответствовали образцам
def transformPrefs(critics_dict):

    # инициилизируем новый словарь с фильмами и их оценками
    result = {}

    # для каждого критика в словаре
    for person in critics_dict:

        # для каждого оцененного им фильма
        for item in critics_dict[person]:

            # добавляем такой объект в словарь
            result.setdefault(item, {})
            # меняеи местами человека и предмет
            result[item][person] = critics_dict[person][item]
    return result

# Создать словарь, содержащий для каждого образца те образцы, которые больше всего похожи на него.
def calculateSimilarItems(prefs, n=10):

    result = {}
    # обращаем матрицу предпочтений, чтобы строки соответствовали образцам
    itemPrefs = transformPrefs(prefs)

    c = 0
    for item in itemPrefs:
        # проверка состояния для больших наборов данных
        c = c + 1
        if c % 100 == 0: print("%d / %d" % (c,len(itemPrefs)))

        # найти образцы, максимально похожие на данный
        scores = topMatches(itemPrefs, item, n=n, similarity=sim_distance)
        result[item] = scores
    return result

# Получить рекомендации для заданного человека, пользуясь взвешенным средним
# оценок, похожих на оцененные пользователем объектов

def getRecommendedItems(prefs, itemMatch, user):

    # инициализируем словари
    userRatings = prefs[user]
    scores = {}
    totalSim = {}

    # цикл по образцам, оцененным данным пользователем (хранятся в userRatings)
    for (item, rating) in userRatings.items():

        # цикл по образцам, похожим на данный (хранятся в словаре itemMatch)
        for (similarity, item2) in itemMatch[item]:
            # пропускаем объект, если пользователь уже оценивал данный образец
            if item2 in userRatings:
                continue
            # взвешенная суммы оценок, умноженных на коэффициент подобия
            scores.setdefault(item2, 0)
            scores[item2] = scores[item2] + similarity * rating
            # сумма всех коэффициентов подобия
            totalSim.setdefault(item2, 0)
            totalSim[item2] = totalSim[item2] + similarity
            if totalSim[item2] == 0:
                totalSim[item2] = 0.0000001  # чтобы избежать деления на ноль
    # делим каждую итоговую оценку на взвешенную сумму, чтобы вычислить среднее
    rankings = [(score / totalSim[item], item) for item, score in scores.items()]

    # возвращает список rankings, отсортированный по убыванию
    rankings.sort(reverse=True)
    return rankings

# Функция для загрузки данных MovieLens
def loadMovieLens():
    # получить названия фильмов
    movies = {}
    for line in open('u.item', encoding = "ISO-8859-1"):
        (id, title) = line.split('|')[0:2]
        movies[id] = title
    # загрузить данные
    prefs = {}
    for line in open('u.data'):
        (user, movieid, rating, ts) = line.split('\t')
        prefs.setdefault(user, {})
        prefs[user][movies[movieid]] = float(rating)
    return prefs


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

from scipy.integrate import quad
import numpy as np
import bigfloat
import argparse

class Stikers():
    """Calculate the amount paid for n stikers while exchanging with f friends
    n/f[*+]integrate_0^inf(1-(1-(1+x/2!+x**2)/e^x))dx
    Based on the video:
        The Math (and money) of Soccer Stickers - Numberphile
        (https://youtu.be/aKPkQCys86c)
    and:
       Soccer Stickers (extra math bit) - Numberphile
        (https://youtu.be/iMSitUaoS3I)"""

    def __init__(self, n, f, spp, valueperunit, valuetohigher, highersign, lastbuy):
        self.n = n
        self.f = f+1 #to be semanticly correct, one represents you, 2 is you and a friend
        self.spp = spp  # stickers per pack
        self.valueperunit = valueperunit
        self.valuetohigher = valuetohigher
        self.highersign = highersign
        self.lastbuy = lastbuy

    def lasum(self, x):
        return 1 + sum(bigfloat.pow(x, f) / bigfloat.factorial(f) for f in range(1, self.f))

    def calc_ExpectedToBuy(self, stop=0):
        return sum((self.n * 1.) / dec for dec in range(stop + 1, self.n + 1)[::-1])

    def integrand(self, x):
        return 1.0 - (1.0 - self.lasum(x)
                      / bigfloat.exp(x, bigfloat.precision(100)))**self.n

    def calc(self):
        try:
            a = quad(self.integrand, 0, np.inf)
            b = self.n / self.f * 1.
            title = "You and {} friend{}".format(self.f-1, "s" if self.f > 2 else "")
            print "\n=={}==".format("Alone" if self.f == 1 else title)
            print "Stickers you buy:\t{:.2f}\n" \
                  "         you pay:\t{}{:.2f}".format((b * a[0]), self.highersign,
                                                       ((b * a[0]) *
                                                        self.valueperunit)
                                                       / self.valuetohigher)
        except ZeroDivisionError:
            print("Cannot devide by zero.")

    def extrainfo(self):
        try:
            expected = self.calc_ExpectedToBuy()
            expectedWithBuy = self.calc_ExpectedToBuy(self.lastbuy)
            print "\n==Information=="
            print "To complete the album alone is expected " \
                  "you to buy {:.2f} stickers ({:.2f} packs)" \
                  ", paying {}{:.2f} for it.".format(expected, expected / self.spp,
                                                   self.highersign,
                                                   (expected *
                                                    self.valueperunit)
                                                   / self.valuetohigher)
            print "You will need to pay " \
                  "{}{:.2f} for {:.2f} stickers if you plan to buy the last " \
                  "{}{} directly " \
                  "from the company.".format(self.highersign,
                                             (expectedWithBuy *
                                              self.valueperunit)
                                             / self.valuetohigher,
                                             expectedWithBuy,
                                             self.lastbuy if self.lastbuy > 1 else "\b",
                                             " stickers" if self.lastbuy > 1 else " sticker")
            print "N=n*(log(n)+Euler constant) = " \
                  "{}".format(self.n * bigfloat.log(self.n) +
                              bigfloat.const_euler())
            print "---------------"
        except ZeroDivisionError:
            print("Cannot devide by zero.")

if __name__ == '__main__':
    print("=Stickers=")
    parser = argparse.ArgumentParser()
    parser.add_argument(
        '-n', type=int, help='Number of stickers', required=True)
    parser.add_argument(
        '-f', type=int, help='Number of friends', required=True)
    parser.add_argument('-p', '--spp', type=int,
                        dest='spp', default=5, help='Number of stickers per pack')
    parser.add_argument('-u', '--unit', type=int,
                        dest='valueperunit', default=16, help='Value of a single unit, like penny')
    parser.add_argument('-b', '--basecurrency', default=100, type=int,
                        dest='valuetohigher', help='Ex: from pence to pounds')
    parser.add_argument('-s', '--sign', default='£', dest='highersign',
                        help='Currency sign')
    parser.add_argument('-l', '--lastbuy', type=int, default=50, dest='lastbuy',
                        help='How many stickers you can buy directly from the company')

    args = parser.parse_args()
    s = Stikers(**vars(args))
    s.extrainfo()
    s.calc()


import scipy.linalg as la
import numpy as np

class LQR(object):
    def __init__(self) -> None:
        pass

    def solve_DARE(self, A, B, Q, R):
        """
        solve a discrete time_Algebraic Riccati equation (DARE)
        """
        X = Q
        maxiter = 150
        eps = 0.01

        for i in range(maxiter):
            Xn = A.T @ X @ A - A.T @ X @ B @ \
                la.inv(R + B.T @ X @ B) @ B.T @ X @ A + Q
            if (abs(Xn - X)).max() < eps:
                break
            X = Xn

        return Xn

    def dlqr(self, A, B, Q, R):
        """Solve the discrete time lqr controller.
        x[k+1] = A x[k] + B u[k]
        cost = sum x[k].T*Q*x[k] + u[k].T*R*u[k]
        # ref Bertsekas, p.151
        """

        # first, try to solve the ricatti equation
        X = self.solve_DARE(A, B, Q, R)

        # compute the LQR gain
        K = la.inv(B.T @ X @ B + R) @ (B.T @ X @ A)

        eigVals, eigVecs = la.eig(A - B @ K)

        return K, X, eigVals


from sympy import symbols, sympify, factor, pprint, init_printing, plot
from sympy.core import SympifyError

init_printing( order = 'rev-lex' )

def graph_system( expr1, expr2 ):

	y = symbols( 'y' )

	expr1_sln = solve( expr1, y )
	expr2_sln = solve( expr2, y )

	p = plot( expr1_sln[ 0 ], expr2_sln[ 0 ], legend = True, show = False )

	p[ 0 ].line_color = 'b'
	p[ 1 ].line_color = 'r'

	sln = solve( ( expr1, expr2 ), symbols( 'x, y' ) )

	pprint( sln )

	p.show()

if __name__== '__main__':

	try:
		expr1 = input( 'Enter an expression in terms of x and y: ' )
		expr2 = input( 'Enter an expression in terms of x and y: ' )

		expr1 = sympify( expr1 )
		expr2 = sympify( expr2 )
	except SympifyError:
		print( 'Invalid Input' )
	else:
		graph_system( expr1, expr2 )


import numpy as np

def generate_gaussian_heatmap(shape, xy, sigma):
    """

    Args:
        shape: (height, width)

    """
    x, y = xy
    grid_x = np.tile(np.arange(shape[1]), (shape[0], 1))
    grid_y = np.tile(np.arange(shape[0]), (shape[1], 1)).transpose()
    grid_distance = (grid_x - x) ** 2 + (grid_y - y) ** 2
    gaussian_heatmap = np.exp(-0.5 * grid_distance / sigma**2)
    return gaussian_heatmap

def generate_heatmaps(img, poses, sigma=7.0):
    """

    Args:
        img (`~numpy.ndarray`):
             shape of img is ``(height, width, channel)``
        poses (`~numpy.ndarray`):
             shape of poses is ``(number of instances, num_joint, 3)``
        sigma (`~float`):
              sigma of gaussian

    """
    _, num_joint, _ = poses.shape
    height, width, _ = img.shape
    heatmaps = np.zeros((0, height, width))
    sum_heatmap = np.zeros((height, width))
    for joint_index in range(num_joint):
        heatmap = np.zeros((height, width))
        for pose in poses:
            if pose[joint_index, 2] > 0:
                jointmap = generate_gaussian_heatmap(
                    (height, width), pose[joint_index][:2], sigma)
                heatmap[jointmap > heatmap] = jointmap[jointmap > heatmap]
                sum_heatmap[jointmap >
                            sum_heatmap] = jointmap[jointmap > sum_heatmap]
        heatmaps = np.vstack((heatmaps, heatmap.reshape((1, height, width))))
    bg_heatmap = 1 - sum_heatmap  # background channel
    heatmaps = np.vstack((heatmaps, bg_heatmap[None]))
    return heatmaps.astype('f')


#################################################################
# Name:     SolveLinear.py                                      #
# Authors:  Michael Battaglia                                   #                                        #
# Function: Program contains routines for solving linear        #
#           systems of equations.                               #
#################################################################

#essential imports
import numpy as np
from time import process_time
import matplotlib.pyplot as plt

#function: Implement Gaussian Elimination
def GaussElim(A_in,v_in):
    #np.copy A and v to temporary variables using np.copy command
    A = np.copy(A_in)
    v = np.copy(v_in)
    N = len(v)

    #for each mth element in diagonal
    for m in range(N):
        # Divide by the diagonal element
        div = A[m,m]
        A[m,:] /= div
        v[m] /= div

        # Now subtract from the lower rows
        for i in range(m+1,N):
            mult = A[i,m]
            A[i,:] -= mult*A[m,:]
            v[i] -= mult*v[m]

    # Backsubstitution
    #create an array of the same type as the input array
    x = np.empty(N,dtype=v.dtype)
    for m in range(N-1,-1,-1):
        x[m] = v[m]
        for i in range(m+1,N):
            x[m] -= A[m,i]*x[i]
    return x

#function: Implement Partial Pivot
def PartialPivot(A_in,v_in):
    #np.copy A and v to temporary variables using np.copy command
    A = np.copy(A_in)
    v = np.copy(v_in)
    N = len(v)

    #for each mth element in diagonal
    for m in range(N):
        # Partial pivot on mth column from A[m,m]
        col = A[:,m]
        row = m + np.argmax(np.square(col[m:]))
        # Swap row m with optimal row
        A[m,:], A[row,:] = np.copy(A[row,:]), np.copy(A[m,:])
        v[m], v[row] = v[row], v[m]

        # Divide by the diagonal element
        div = A[m,m]
        A[m,:] /= div
        v[m] /= div

        # Now subtract from the lower rows
        for i in range(m+1,N):
            mult = A[i,m]
            A[i,:] -= mult*A[m,:]
            v[i] -= mult*v[m]

    # Backsubstitution
    #create an array of the same type as the input array
    x = np.empty(N,dtype=v.dtype)
    for m in range(N-1,-1,-1):
        x[m] = v[m]
        for i in range(m+1,N):
            x[m] -= A[m,i]*x[i]
    return x

#function: Implement LU Decomposition
def LUdecomp(A_in):
    #Length of vector inputs
    N = A.shape[1]
    #A will eventually become U
    U = np.copy(A_in)
    #initiate L array
    L = np.zeros(U.shape)
    #initiate list to record swaps
    swaps = []

    #for each mth element in diagonal
    for m in range(N):
        #record state in L array
        L[:,m][m:] = U[:,m][m:]

        # Partial pivot on mth column from A[m,m]
        col = U[:,m]
        row = m + np.argmax(np.square(col[m:]))
        swaps.append(row)
        # Swap row m with optimal row
        U[m,:], U[row,:] = np.copy(U[row,:]), np.copy(U[m,:])
        L[m,:], L[row,:] = np.copy(L[row,:]), np.copy(L[m,:])

        # Divide by the diagonal element
        div = U[m,m]
        U[m,:] /= div

        # Now subtract from the lower rows
        for i in range(m+1,N):
            mult = U[i,m]
            U[i,:] -= mult*U[m,:]
    #Conlusion of LU decomposition
    return L, U, swaps

#function: Apply permutation operation on vector
def permute(op, v_in):
    #input vector to be permuted
    v = np.copy(v_in)
    N = len(v)
    #permute elements of v according to operator
    for m in range(N):
        v[m], v[op[m]] = v[op[m]], v[m]
    return v

#function: Implement LU Backsubstitution
def LUbacksub(L_in, U_in, v_in):
    #input vector (permuted accordingly)
    v = np.copy(v_in)
    N = len(v)
    #LU decomposition matrices
    L = np.copy(L_in)
    U = np.copy(U_in)

    # Backsubstitution for Ly = v
    #create an array of the same type as the input array
    y = np.empty(N,dtype=v.dtype)
    for m in range(N):
        y[m] = v[m]/L[m,m]
        for i in range(m):
            y[m] -= L[m,i]*y[i]/L[m,m]

    # Backsubstitution for Ux = y
    #create an array of the same type as the input array
    x = np.empty(N,dtype=y.dtype)
    for m in range(N-1,-1,-1):
        x[m] = y[m]/U[m,m]
        for i in range(m+1,N):
            x[m] -= U[m,i]*x[i]/U[m,m]
    return x

#function: main
if __name__ == '__main__':
    #simple diagnostic of linear equation solver
    A = np.array([[ 2, 1, 4, 1],
                  [ 3, 4,-1,-1],
                  [ 1,-4, 1, 5],
                  [ 2,-2, 1, 3]], float)
    v = np.array([-4, 3, 9, 7], float)
    print("matrix A")
    print(A)
    print("vector v")
    print(v)
    print("Solve Ax = v")
    #solve using gaussian elimination
    x_gaus = GaussElim(A,v)
    print("By gaussian elimination")
    print("x =",x_gaus)
    #solve using partial pivot
    x_ppiv = PartialPivot(A,v)
    print("By gaussian elimination with partial pivot")
    print("x =",x_ppiv)
    #solve using LU decomposition
    L, U, op = LUdecomp(A)
    x_LUdc = LUbacksub(L,U,permute(op,v))
    print("By LU decomposition with partial pivot")
    print("x =",x_LUdc)
    #check diagnostic success
    x_sol = np.array([2, -1, -2, 1], float)
    print("In reality")
    print("x =",x_sol)

    #advanced diagnostic of linear equation solver
    #trial N, sizes of matrices
    N_trials = np.arange(10,1000,10)
    #error as a function of N
    e_gaus = np.zeros(len(N_trials))
    e_ppiv = np.zeros(len(N_trials))
    e_LUdc = np.zeros(len(N_trials))
    #timing as a function of N
    t_gaus = np.zeros(len(N_trials))
    t_ppiv = np.zeros(len(N_trials))
    t_LUdc = np.zeros(len(N_trials))

    print("Solving "+str(len(N_trials))+" linear systems")
    #for each trial size N
    for i, N in enumerate(N_trials):
        #generate a random matrix
        A = np.random.rand(N,N)
        #perform QR factorization
        Q, R = np.linalg.qr(A)
        #random orthogonal (nonsingular) matrix
        A = Q
        #generate a random vector
        v = np.random.rand(N)

        print("Solving "+str(i+1)+"/"+str(len(N_trials)))
        #solve using gaussian elimination
        start = process_time()
        x_gaus = GaussElim(A,v)
        end = process_time()
        e_gaus[i] = np.mean(abs(v - np.dot(A,x_gaus)))
        t_gaus[i] = end - start
        #solve using partial pivot
        start = process_time()
        x_ppiv = PartialPivot(A,v)
        end = process_time()
        e_ppiv[i] = np.mean(abs(v - np.dot(A,x_ppiv)))
        t_ppiv[i] = end - start
        #solve using LU decomposition
        start = process_time()
        L, U, op = LUdecomp(A)
        x_LUdc = LUbacksub(L,U,permute(op,v))
        end = process_time()
        e_LUdc[i] = np.mean(abs(v - np.dot(A,x_LUdc)))
        t_LUdc[i] = end - start

    #plot dependence of error on N
    plt.plot(N_trials,e_gaus,label="gaus")
    plt.plot(N_trials,e_ppiv,label="ppiv")
    plt.plot(N_trials,e_LUdc,label="LUdc")
    plt.title("Error vs Matrix size")
    plt.ylabel("error")
    plt.xlabel("N")
    plt.yscale('log')
    plt.xscale('log')
    plt.legend()
    plt.show()

    #plot dependence of timing on N
    plt.plot(N_trials,t_gaus,label="gaus")
    plt.plot(N_trials,t_ppiv,label="ppiv")
    plt.plot(N_trials,t_LUdc,label="LUdc")
    plt.title("Execution time vs Matrix size")
    plt.ylabel("exec time [s]")
    plt.xlabel("N")
    plt.yscale('log')
    plt.xscale('log')
    plt.legend()
    plt.show()


import numpy as np
from matplotlib import pyplot as plt

class PCA:
    def fit(self, X):

        '''
        fits sorted eigenvalues and eigenvectors to class attributes. same goes for variance and explained variance.
        '''

        n_samples = X.shape[0]
        # We center the data and compute the sample covariance matrix.
        X -= np.mean(X, axis=0)
        self.cov_matrix_ = np.dot(X.T, X) / (n_samples-1)
        #test = np.cov(X)

        #Negative values are ignored with eigh
        (self.eigvalues_, self.components_) = np.linalg.eigh(self.cov_matrix_)

        idx = self.eigvalues_.argsort()[::-1]
        self.eigvalues_ = self.eigvalues_[idx]
        self.components_ = self.components_[idx]
        self.variance_ = np.sum(self.eigvalues_)
        self.explained_variance_ = self.eigvalues_ / self.variance_

    def transform(self, X):
        #project data onto eigenvectors
        print(self.components_.shape, X.shape)
        self.projected_ = X @ self.components_.T
        return self.projected_

pca = PCA()

# Generate some dummy data
subsample = np.random.randn(69,2)*0.1
subsample[:,0] = subsample[:,0]*8
subsample[:,1] = subsample[:,0] + subsample[:,1]

pca.fit(subsample)

plt.scatter(subsample[:,0], subsample[:,1], edgecolor='none', alpha=0.5)
plt.quiver(pca.components_[0,0]*2, pca.components_[0,1]*2,
       angles='xy', scale_units='xy', scale=1, width=0.006)
plt.quiver(pca.components_[1,0]*2, pca.components_[1,1]*2,
       angles='xy', scale_units='xy', scale=1, width=0.006)
plt.gca().set_aspect('equal')
plt.show()


import numpy as np

def euclidianDist(data, centers):
    '''Compute the euclidian distance of each data point to each center'''
    return np.sqrt(np.sum((data - centers[:, np.newaxis]) ** 2, axis=2)).T

def manhattanDist(data, centers):
    '''Compute the Manhattan distance of each data point to each center'''
    return np.sum(np.abs(data - centers[:, np.newaxis]), axis=2).T


""" Orbital Mechanics for Engineering Students Example 1.18
Question:
Solve
    xDDot + 2*zeta*omegaN*xDot + omegaN**2*x = F0/m*sin(omega*t)
numerically using the RK method.
Parameter values:
    - m = 1 kg
    - omegaN = 1 rad/s
    - zeta = 0.03
    - F0 = 1 N
    - omega = 0.4 rad/s
Initial conditions:
    x = xDot = 0
Written by: J.X.J. Bannwarth
"""
import numpy as np
import matplotlib.pyplot as plt
from orbitutils.solvers import rk14
import collections

# Exact response - answer from Example 1.17
def CartSystemExactResponse(t, m=1.0, omegaN=1.0, zeta=0.03, F0=1.0,
                            omega=0.4):
    x = np.exp(-0.03 * t) * (
        0.03399 * np.cos(0.9995 * t) - 0.4750 * np.sin(0.9995 * t)) + (
            1.190 * np.sin(0.4 * t) - 0.03399 * np.cos(0.4 * t))
    return x

# Equation for the derivatives of the states
def Rates(t, y, m=1.0, omegaN=1.0, zeta=0.03, F0=1.0, omega=0.4):
    f = np.zeros((2, ))
    f[0] = y[1]
    f[1] = ((F0 / m) * np.sin(omega * t) -
            (omegaN**2) * y[0] - 2 * zeta * omegaN * y[1])
    return f

# Plotting functions
def PlotResponse(t, y, tExact=None, xExact=None):
    nStates = y.shape[1]
    _, axs = plt.subplots(nStates)
    units = ["m", "m/s"]
    for idx, ax in enumerate(axs):
        ax.plot(t, y[:, idx], label="RK")
        if (idx == 0) and xExact is not None:
            ax.plot(tExact, xExact, label="Exact")
            ax.legend()
        ax.grid()
        ax.set_ylabel(f"$y_{idx+1}$ ({units[idx]})")
    axs[-1].set_xlabel("Time (s)")
    plt.show()

def PlotFig123(tOut, xOut, sets, tExact, xExact):
    # Note that for some reason the textbook normalises both axes - we replicate
    # this here for consistency
    _, axs = plt.subplots(5, figsize=(8,12))

    # First plot: exact answer
    axs[0].plot(tExact / tExact[-1], xExact / np.max(xExact), color="black")
    axs[0].set_title("Exact")

    # Other plots: sort by method
    for m, ax in enumerate(axs[1:]):
        rk = m + 1
        colors = ["black", "red"]
        for n, set in enumerate(sets):
            if set.order == rk:
                ax.plot(
                    tOut[n] / tOut[n][-1],
                    xOut[n] / np.max(np.abs(xOut[n])),
                    label=f"h = {set.h}",
                    color=colors[0])
                colors.pop(0)
        ax.set_title(f"RK{rk}")
        ax.legend()
        ax.set_ylim(bottom=-1.,top=1.)
    plt.tight_layout()
    plt.show()

if __name__ == "__main__":
    # Title
    print("Orbital Mechanics for Engineering Students Example 1.18")

    # (1) Solve individual example
    # Parameters
    y0 = np.array([0, 0])
    tMax = 110.0
    h = 1.0
    order = 4

    # Solve equation numerically
    ys, ts = rk14(Rates, y0, tMax, h, order)

    # Get exact answer
    tExact = np.linspace(0.0, tMax, 5000)
    xExact = CartSystemExactResponse(tExact)

    # Plot results
    PlotResponse(ts, ys, tExact, xExact)

    # (2) Run through different order and h combinations to reproduce Figure 1.23
    TestSet = collections.namedtuple("TestSet", "order h")
    sets = [
        TestSet(1, 0.01), TestSet(1, 0.1), TestSet(2, 0.1), TestSet(2, 0.5),
        TestSet(3, 0.5), TestSet(3, 1.0), TestSet(4, 1.0), TestSet(4, 2.0)
    ]

    xOut = []
    tOut = []
    for set in sets:
        yOut, tOutTmp = rk14(Rates, y0, tMax, set.h, set.order)
        xOut.append(yOut[:, 0])
        tOut.append(tOutTmp)

    PlotFig123(tOut, xOut, sets, tExact, xExact)


# -*- coding: utf-8 -*-
"""
Created on Mon Oct 26 10:44:49 2020

@author: Larisa
"""

import sistemeEcuatiiLiniare as sel # si. ecuații liniare
import numpy as np

# Testăm metSubDesc
# Test 1
A = np.array([[1, 2, 3],
              [4, 5, 6]])
b = np.array([1, 4, 5])
tol = 10 ** (-16)

x = sel.metSubDesc(A, b, tol)
print(f'Test 1: {x}\n')

# Test 2
A = np.array([[1, 2, 3],
              [4, 5, 6],
              [7, 8, 9]])
b = np.array([[1], [4], [5]])
tol = 10 ** (-16)

x = sel.metSubDesc(A, b, tol)
print(f'Test 2: {x}\n')

# Test 3
A = np.array([[0, 2, 3],
              [0, 5, 6],
              [0, 0, 9]])
b = np.array([1, 4, 5])
tol = 10 ** (-16)

x = sel.metSubDesc(A, b, tol)
print(f'Test 3: {x}\n')

# Test 4
A = np.array([[1, 2, 3],
              [0, 5, 6],
              [0, 0, 8]])
b = np.array([1, 4, 5])
tol = 10 ** (-16)

x = sel.metSubDesc(A, b, tol)
print(f'Test 4: \n{x}')

# Verificare
print("Verificare 1:")
print(A@x)
print("Verificare 2:")
print(np.dot(A, x))
print('\n')

# Exercițiul 3.b)
A = np.array([[0, 1, 1],
              [2, 1, 5],
              [4, 2, 1]])
b = np.array([[3], [5], [1]])
x = sel.GaussFP(A, b, tol)
print(f'Soluția Sistemului 1 - GaussFP: \n{x}')
x = sel.GaussPP(A, b, tol)
print(f'Soluția Sistemului 1 - GaussPP: \n{x}')
print(f'Verificare (Ax - b = 0): \n{A@x-b}\n')

A = np.array([[0, 1, -2],
              [1, -1, 1],
              [1, 0, -1]])
b = np.array([[4], [6], [2]])
x = sel.GaussFP(A, b, tol)
print(f'Soluția Sistemului 2 - GaussFP: \n{x}')
x = sel.GaussPP(A, b, tol)
print(f'Soluția Sistemului 2 - GaussPP: \n{x}')
print(f'Determinantul Matricei A din Sistemul 2: {np.linalg.det(A)}\n') # determinantul matricei

# Exercițiul 3.c)
eps = 10 ** -20
tol1 = 10 ** -30
tol2 = 10 ** -16
A = np.array([[eps, 1],
              [1, 1]])
b = np.array([[1], [2]])
x = sel.GaussFP(A, b, tol1)
print(f'Soluție 1: \n{x}')
x = sel.GaussFP(A, b, tol2)
print(f'Soluție 2: \n{x}')

Calcul Numeric (CN)/Laborator/Laborator 3, 4/lab3.py


import numpy as np

def mean_square_error(y_true: np.ndarray, y_pred: np.ndarray) -> float:
    """
    Calculate MSE loss

    Parameters
    ----------
    y_true: ndarray of shape (n_samples, )
        True response values
    y_pred: ndarray of shape (n_samples, )
        Predicted response values

    Returns
    -------
    MSE of given predictions
    """
    return np.power((y_true-y_pred), 2).mean()

def misclassification_error(y_true: np.ndarray, y_pred: np.ndarray, normalize: bool = True) -> float:
    """
    Calculate misclassification loss

    Parameters
    ----------
    y_true: ndarray of shape (n_samples, )
        True response values
    y_pred: ndarray of shape (n_samples, )
        Predicted response values
    normalize: bool, default = True
        Normalize by number of samples or not

    Returns
    -------
    Misclassification of given predictions
    """

    eq = np.sum(y_true != y_pred).__float__()
    if normalize:
        return eq/y_true.size
    else:
        return eq

def accuracy(y_true: np.ndarray, y_pred: np.ndarray) -> float:
    """
    Calculate accuracy of given predictions

    Parameters
    ----------
    y_true: ndarray of shape (n_samples, )
        True response values
    y_pred: ndarray of shape (n_samples, )
        Predicted response values

    Returns
    -------
    Accuracy of given predictions
    """
    # total true labels of 1
    P = np.sum(y_true == 1)
    # total true labels of -1
    N = np.sum(y_true == -1)
    # True Positive: we predict a label of 1, and the true label is 1.
    TP = np.sum(np.logical_and(y_pred == 1, y_true == 1))
    # True Negative: we predict a label of -1, and the true label is -1.
    TN = np.sum(np.logical_and(y_pred == -1, y_true == -1))

    return (TP+TN)/(P+N)

def cross_entropy(y_true: np.ndarray, y_pred: np.ndarray) -> float:
    """
    Calculate the cross entropy of given predictions

    Parameters
    ----------
    y_true: ndarray of shape (n_samples, )
        True response values
    y_pred: ndarray of shape (n_samples, )
        Predicted response values

    Returns
    -------
    Cross entropy of given predictions
    """
    raise NotImplementedError()


# -*- coding: utf-8 -*-
"""
Created on Mon Sep 23 10:02:57 2019

@author: hamil
"""

import numpy as np
import matplotlib.pyplot as plt
import numpy.random as rand
from numpy import histogram as hist

#define a function to calculate the integral using mean value
def meanvalue_Int(N, initial,final):
    #Generate the sampling values for x
    #for n in range(N):
    x = rand.uniform(initial,final, N)
        #calculate f(x) / y values for the function
    #y = np.square(x)
    y = x**2
        #calculate the sum and mean of the y_array
    sum_function = np.sum(y)
    mean_value = (final - initial) / N
        #calculate the result
    f_result = mean_value*sum_function

    return f_result, x, y

#test function
test, x_values, y_values = meanvalue_Int(10000, 0,10)
answer = test
print("This is the approx value:", answer)
plt.plot(x_values, y_values, '.')
plt.show()
#Given how we approached the function, the graph of the x and y values generated
#makes sense and it is what is expected. When running the function several times, the output
#value varies a quite a bit more than I thought it would.The output value ranges from
#315 to 341 whereas the analytical value is 333.333.. in the several times I ran the code.

#When increasing the sampling size to 10000, it actually brought the overall output to a more
#acceptable value. It actually decreases the error. The value ranges from 329 to 339 the several
#times I ran the code.

#Now run the simulation 100 times and calculate the mean and median values for the simulation
answer_array = []
for n in range(100):
    trial, x_array, y_array = meanvalue_Int(1000, 0,10)
    answer_array.append(trial)

mean_num = np.mean(answer_array)
median_num = np.median(answer_array)
st_div = np.std(answer_array)
print("Mean value of 100 runs:", mean_num)
print("Median value of 100 runs:", median_num)
print("Error in 100 trials:", st_div)
x_array = np.arange(0,100,1)
plt.hist(answer_array)
plt.plot()

#The mean and median values are relatively close to the analytical value where
# It is difficult to say which, mean or median, is better at estimating the actual value
#of the integral. There is still the spread in values with just using 1000 samples.

#When comparing my calculated error with equation 5.80 from the book,
st_div2 = 1/(np.sqrt(1000))*100
st_div3 = 1/ np.sqrt(1000)
print("book version for error:",st_div2)
print("error for large N:", st_div3)
#The two different values for the standard deviation are quite quite different
#than my calculated value for the standard deviation


# -*- coding: utf-8 -*-
"""
Created on Sun Jun 24 22:24:01 2018

@author: santhosh

-mcmm-

simple examples showing the use of monte carlo methods:
1. to generate samples from p(y), given p(y|x) and p(x)
2. to generate samples from a "posterior", p(x|y), given "likelihood", p(y|x)
   and "prior" p(x)
"""
import numpy as np
import matplotlib.pyplot as plt

def findx(c,r):
    """
    Returns the smallest index of the element(s) in c that is nearest to r

    Keyword arguements:
    c: cumulative sum of a probability function
    r: a value between 0.0 and 1.0
    """
    idxs = np.zeros(len(r), dtype=int)
    for i in range(len(r)):
        idxs[i] = int(np.argmin(c < r[i]))

    return idxs

def sample(labels, p, N):
    """
    samples random variables according to a given probability mass function

    Keyword arguments:
    labels: the values that the random variables can take (e.g. [0,1] or ['H','T'])
    p: probability mass function (p[i] >= 0 and p.sum() == 1.0)
    N: The number of samples

    Returns an array of random variables
    """
    c = p.cumsum()
    r = np.random.uniform(0,1,N)
    idxs = findx(c,r)
    x = labels[idxs]
    return x

if __name__ == '__main__':
    y_labels = np.array([0,1])
    x_labels = np.array([0,1])

    """
       p_x = p(x)
       p(x == 0) = 0.6 and p(x == 1) = 0.4
    """

    p_x = np.array([0.6,0.4])

    """
       p_y_x = p(y|x)
       p(y|x = 0) = 0.1, y == 0
                  = 0.9, y == 1

       p(y|x = 1) = 0.7, y == 0
                  = 0.3, y == 1
    """

    p_y_x = np.array([[0.1,0.9],[0.7,0.3]])

    """
    Task 1: We wish to determine p(y)
            By the "law of total probability", we have
            p(y == 0) = 0.1 * 0.6 + 0.7 * 0.4 = 0.34
            p(y == 1) = (1 - 0.34) = 0.66

            For doing this the "Monte Carlo" way, we first
            simulate a bunch of 'x's according to p(x)
            and for each x, we simulate a bunch of 'y's according
            to p(y|x). Finally, a histogram should give us the
            desired answer
    """
    x = sample(x_labels, p_x, 10000)
    y = np.array([])
    for i in range(len(x)):
        q = p_y_x[x[i],]
        y = np.append(y, sample(y_labels, q, 10))

    cnt_y = np.array([(y == 0).sum(), (y == 1).sum()])
    p_y = cnt_y/len(y)
    print('p(y) is: [%1.2f, %1.2f]'%(p_y[0], p_y[1]))

    """
    Task 2: Sample from posterior. We wish to determine
            p(x|y); say, p(x == 0 | y = 0), p(x == 1| y = 0)
            This is easily done by applying Bayes' theorem
            p(x|y) = p(y|x) p(x) / sum_x (p(y|x) p(x))
            e.g.:
            p(x == 0| y = 0) = p(y == 0| x = 0) p(x == 0)
                              ----------------------------
              (p(y == 0| x = 0) p(x == 0) + p(y == 0| x = 1) p(x == 1))

              = 0.1 * 0.6 / (0.1 * 0.6 + 0.7 * 0.4) = 0.06 / 0.34 = 0.176

             and,
             p(x == 1| y = 0) = (1 - 0.176) = 0.823

             similarly,
             p(x == 0| y = 1) = 0.9 * 0.6/(0.9 * 0.6 + 0.3 * 0.4) = 0.818
             and
             p(x == 1| y = 1) = (1 - 0.818) = 0.182

             ---------

             To do this the "Monte Carlo" way, we begin by sampling
             x from p(x). Then, for each x, we generate a y based on
             p(y|x).
             Now, choose only those x:s from which y = 1 was generated and
             discard the rest. The new set of x's (the resampled batch)
             would have the distribution p(x|y=1)
    """
    x = sample(x_labels, p_x, 10000)
    x_new = np.array([])
    for i in range(len(x)):
        q = p_y_x[x[i],]
        y = sample(y_labels, q, 1)
        if y == 0:
            x_new = np.append(x_new, x[i])

    #done! now, check the distribution
    cnt_x_new = np.array([(x_new == 0).sum(), (x_new == 1).sum()])
    p_x_y0 = cnt_x_new / len(x_new)
    print('p(x|y=0) is: [%1.2f, %1.2f]'%(p_x_y0[0], p_x_y0[1]))
    print('INFO: len(x) = %d, len(x_new) = %d'%(len(x), len(x_new)))


from sympy import symbols, solve, diff, pprint, integrate
# The 2000 census in a particular area gives us an age distribution that is approximately given (in millions) by the function

multiplier = 1000000

x = symbols( 'x' )
F = 39 + 2.11*x - 0.81*x**2

# where x varies from 0 to 9 decades.
# The population of a given age group can be found by integrating this function over the interval for that age group.

# Find the integral over the interval
a, b = 0, 9
population = round( integrate( F, ( x, a, b ) ) )

# b
print( 'The total number of people in this area aged 0 to 90 was about {0} million in 2000.'.format( population ) )

# Find the number of people alive in 2000 that were born in the seventies, that is, those in the range of 4 to 5 decades in 2000
a, b = 4, 5
round( integrate( F, ( x, a, b ) ) )


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

# # Método de bisección
# J.J
# ---
# Dada una función continua $f$ en un intervalo cerrado $[a,b]$, si $f(a)f(b) < 0$ se puede asegurar que existe un valor $f(p) = 0$ para algún $p \in [a,b]$.
#
# El método consiste en escoger un valor $x_{1}$ en el centro del intervalo: $x_{1} = \frac{1}{2}(a+b)$. Si $f(a)f(x_{1}) < 0$ la raíz se encuentra ahora en el intervalo $[a,x_{1}]$ y se procede con otra iteración ahora en el punto $x_{2} =  \frac{1}{2}(a+x_{1})$. En el caso de que el signo opuesto haya sido con $f(b)$, $f(x_{1})f(b) < 0$, la raíz se encuentra en el intervalo $[x_{1},b]$ y la siguiente iteración es entonces $x_{2} = \frac{1}{2}(x_{1}+b)$.
#
# Siguiendo estos pasos, el método eventualmente disminuye la longitud del intervalo convergiendo a la raíz de la función.

# Ejemplo: $f(x) = 2x^{3} - 9x^{2} + 7x + 6$

# In[1]:

import numpy as np
import matplotlib.pyplot as plt

# In[2]:

def f(x):
    f = 2*x**3 - 9*x**2 + 7*x + 6
    return f

# In[3]:

X = np.linspace(-1, 4, 100)
plt.plot(X,f(X), label = 'f(x)')
plt.legend()
plt.show()

# Hay raices en los intervalos $[-1,0]$, $[1.5, 2.5]$ y $[2.5, 3.5$].

# In[4]:

e = 0.0001 #error

# In[5]:

def Biseccion(a, b, func = f):
    x = (1/2)*(a+b)
    while f(x) > e:
        if f(a)*f(x) < 0:
            Biseccion(a,x)
        elif f(x)*f(b) < 0:
            Biseccion(x,b)
    return x

# In[6]:

sol1 = Biseccion(-1,0)
sol2 = Biseccion(1.5,2.5)
sol3 = Biseccion(2.5,3.5)
print(sol1, sol2, sol3)

# In[7]:

X = np.linspace(-1, 4, 100)
plt.plot(X,f(X), label = 'f(x)')
plt.plot(sol1, f(sol1), 'ro')
plt.plot(sol2, f(sol2), 'ro')
plt.plot(sol3, f(sol3), 'ro')
plt.legend()
plt.show()


from dataclasses import dataclass
from enum import Enum
from typing import List, Callable

import numpy as np

def _line_search(func: Callable, g0: np.ndarray, x0: np.ndarray, s: np.ndarray) -> np.ndarray:
    """Line-search in direction s, starting from x0, with the function func to minimize and g0 the gradient at x0."""
    f0 = func(x0)

    a = 1
    c = 1e-4
    rho = 0.5
    max_iter = 100

    for _ in range(max_iter):
        if func(x0 + a * s) > f0 + c * a * np.dot(g0, s):
            a *= rho
        else:
            return a * s

    return a * s

@dataclass
class Result:
    """Value x that minimizes the function, and path from initial guess to minimum."""
    x: np.ndarray
    path: List[np.ndarray]

class UpdateRule(Enum):
    """Update types that determine how Hessian approximation is computed."""
    DIAGONAL = 1
    SCALED_IDENTITY = 2
    IDENTITY = 3

def optimize(func: Callable,
             grad: Callable,
             x0: np.ndarray,
             update_rule: UpdateRule = UpdateRule.DIAGONAL,
             grad_zero_tol: float = 1e-5,
             eps: float = np.finfo(np.float).eps,
             min_curv: float = 0.01,
             max_curv: float = 1000,
             max_iter: int = 1000,
             verbose: bool = False) -> Result:
    """
    Minimize function value starting with an initial guess.

    Args:
        func: Function to be minimized.
        grad: Gradient of function to be minimized.
        x0: Start value (initial guess).
        update_rule: UpdateRule.DIAGONAL, UpdateRule.SCALED_IDENTITY, or UpdateRule.IDENTITY.
        grad_zero_tol: If gradient norm is below this value, the algorithm terminates.
        eps: Small value that it added to denominator to avoid division by 0.
        min_curv: Hessian values are clipped to [min_curv, max_curv].
        max_curv: Hessian values are clipped to [min_curv, max_curv].
        max_iter: Maximum number of iterations.
        verbose: Print internal state of algorithm.

    Returns:
        An object of type Result containing the value x that minimizes the function and path towards the minimum.
    """
    x = np.asarray(x0)
    path = [x]

    # apply initial step along steepest descent direction
    D = np.ones_like(x)
    g0 = grad(x)
    s = -g0
    s = _line_search(func, g0, x, s)
    x = x + s
    path.append(x)
    g1 = grad(x)

    # iterate
    for iter_ctr in range(max_iter):
        if np.linalg.norm(g1) < grad_zero_tol * max(np.linalg.norm(x), 1):
            break

        if update_rule == UpdateRule.DIAGONAL:
            y = g1 - g0
            b = s.T @ y
            a = (s ** 2).T @ D
            U = ((b - a) / (np.sum(s ** 4) + eps)) * (s ** 2)
            D = D + U

        elif update_rule == UpdateRule.SCALED_IDENTITY:
            y = g1 - g0
            D = (s.T @ y) / (s.T @ s + eps)

        elif update_rule == UpdateRule.IDENTITY:
            D = 1

        # a very crude way to ensure that diag(D) is pos. definite and s is a descent direction
        D = np.clip(D, min_curv, max_curv)

        s = -g1 / D
        s = _line_search(func, g1, x, s)
        x = x + s
        path.append(x)
        g0 = g1
        g1 = grad(x)

        if verbose:
            print(f'i={iter_ctr} | x={x} | s={s} | g1={g1} | D={D}')

    return Result(x, path)


import numpy as np
import torch

def get_fourier_freq(t_max):
    """Returns the frequencies for a vector of length t_max"""
    n_cos = np.ceil((t_max + 1) / 2)  # number of cosine terms (positive freqs)
    n_sin = np.floor((t_max - 1) / 2)  # number of sine terms (negative freqs)
    w_cos = np.arange(0, n_cos)  # cosine freqs
    w_sin = np.arange(-n_sin, 0)  # sine freqs
    w_vec = np.concatenate((w_cos, w_sin), axis=0)
    frequencies = 2 * np.pi / t_max * w_vec

    return frequencies

def real_fft(x, n=None):
    """Performs a real discrete 1D Fourier transform of the columns of x"""

    single_vec = False
    if len(x.shape) == 1:
        single_vec = True
        x = x[:, None]

    if n is None:
        n = x.shape[0]

    x_fft = torch.fft.fft(x, n, dim=0) / np.sqrt(n / 2)
    x_fft[0, :] = x_fft[0, :] / np.sqrt(2)

    if np.mod(n, 2) == 0:
        imx = int(np.ceil((n - 1) / 2))
        x_fft[imx, :] = x_fft[imx, :] / np.sqrt(2)

    x_hat = x_fft.real
    isin = int(np.ceil((n + 1) / 2))
    x_hat[isin:, :] = -x_fft[isin:, :].imag

    if single_vec:
        x_hat = x_hat[:, 0]

    return x_hat

def real_ifft(x_hat, n=None):
    """Performs an inverse of a real discrete 1D Fourier transform of the columns of x"""

    single_vec = False
    if len(x_hat.shape) == 1:
        single_vec = True
        x_hat = x_hat[:, None]

    if n is None:
        n = x_hat.shape[0]

    nxh = x_hat.shape[0]
    n_cos = int(np.ceil((nxh + 1) / 2))
    n_sin = int(np.floor((nxh - 1) / 2))

    x_hat[0, :] = x_hat[0, :] * np.sqrt(2)

    if np.mod(nxh, 2) == 0:
        x_hat[n_cos-1, :] = x_hat[n_cos-1, :] * np.sqrt(2)

    xfft = torch.zeros_like(x_hat, dtype=torch.complex128, device=x_hat.device)
    xfft[:] = x_hat[:]
    xfft[n_cos:, :] = torch.flip(x_hat[1:n_sin+1, :], dims=[0])
    xfft[1:n_sin+1, :] = xfft[1:n_sin+1, :] + 1j * torch.flip(x_hat[n_cos:, :], dims=[0])
    xfft[n_cos:] = xfft[n_cos:, :] - 1j * x_hat[n_cos:, :]

    x = torch.fft.ifft(xfft, dim=0).real * np.sqrt(nxh / 2)
    x = x[:n, :]

    if single_vec:
        x = x[:, 0]

    return x

def get_fourier_basis(n_ind):
    """Returns an orthonormal real Fourier basis for a vector of length n_ind"""

    n_cos = (np.ceil((n_ind + 1) / 2))
    n_sin = (np.floor((n_ind - 1) / 2))

    cos_freq = 2 * np.pi / n_ind * np.arange(n_cos)
    sin_freq = 2 * np.pi / n_ind * np.arange(-n_sin, 0)
    frequency_vec = np.concatenate((cos_freq, sin_freq), axis=0)  # frequency vector

    x = np.arange(n_ind)
    cos_basis = np.cos(cos_freq[:, None] * x[None, :]) / np.sqrt(n_ind / 2)
    sin_basis = np.sin(sin_freq[:, None] * x[None, :]) / np.sqrt(n_ind / 2)
    fourier_basis = np.concatenate((cos_basis, sin_basis), axis=0)

    fourier_basis[0, :] = fourier_basis[0, :] / np.sqrt(2)

    if np.mod(n_ind, 2) == 0:
        fourier_basis[int(n_cos-1), :] = fourier_basis[int(n_cos-1), :] / np.sqrt(2)

    return fourier_basis, frequency_vec