# -*- encoding: utf-8 -*-

import warnings
import numpy as np
from math import radians

from ..config import R
from ..api import OSOptions
from ..exceptions import TypeWarning

if OSOptions.is_sklearn_available:
	from sklearn.metrics.pairwise import haversine_distances # Using SKLearn for Calculating Haversine

def EuclideanDistance(startPoint : np.ndarray, targetPoint : np.ndarray) -> float:
	'''Calculates Eculidean Distance b/w Two Points in n-Dimensional Space

	: param startPoint  : Starting Point (P)
	: param targetPoint : Target Point, or Point-Q
	'''
	if (type(startPoint) != np.ndarray) or (type(targetPoint) != np.ndarray):
		warnings.warn(f'Expects np.ndarray got {type(startPoint)} and {type(targetPoint)}, Attempting a Fix.', TypeWarning)
		startPoint  = np.array(startPoint)
		targetPoint = np.array(targetPoint)

	return np.sqrt(np.sum((startPoint - targetPoint) ** 2))

def ManhattanDistance(startPoint : np.ndarray, targetPoint : np.ndarray) -> float:
	'''Calculates Manhattan Distance b/w Two Points in n-Dimensional Space

	: param startPoint  : Starting Point (P)
	: param targetPoint : Target Point, or Point-Q
	'''
	if (type(startPoint) != np.ndarray) or (type(targetPoint) != np.ndarray):
		warnings.warn(f'Expects np.ndarray got {type(startPoint)} and {type(targetPoint)}, Attempting a Fix.', TypeWarning)
		startPoint  = np.array(startPoint)
		targetPoint = np.array(targetPoint)

	return sum([abs(i) for i in (startPoint - targetPoint)])

def HaversineDistance(startPoint : list, targetPoint : list, **kwargs) -> float:
	'''Calculate Haversine Distance

	: param startPoint  : Starting Point (P). Format [longitude, latitude]
	: param targetPoint : Target Point, or Point-Q. Format [longitude, latitude]

	Keyword Arguments
	-----------------
		: param input_unit  : (str) Unit of the Latitude and Longitude (in deg or rad). Default 'deg'
		: param output_unit : (str) Output unit (in rad, m, km). Default 'km'
	'''
	input_unit  = kwargs.get('input_unit', 'deg')
	output_unit = kwargs.get('output_unit', 'km')

	# Check argument(s)
	if input_unit not in ['deg', 'rad']:
		raise ValueError(f'{input_unit} is invalid.')

	if output_unit not in ['m', 'km', 'rad']:
		raise ValueError(f'{output_unit} is invalid.')

	if input_unit == 'deg':
		startPoint_rad  = [radians(i) for i in startPoint]
		targetPoint_rad = [radians(i) for i in targetPoint]

	if OSOptions.is_sklearn_available:
		dist = haversine_distances([startPoint_rad, targetPoint_rad])
		dist = dist[0][1] # since this returns a np.ndarray or 2x2
	else:
		dist = _haversine_wo_sklearn_(*startPoint, *targetPoint)

	if output_unit == 'rad':
		return dist
	elif output_unit == 'm':
		return dist * R

	return dist * R / 1000

def _haversine_wo_sklearn_(lon1 : int or float, lat1 : int or float, lon2 : int or float, lat2 : int or float):
	'''Calculate Haversine, if scikit-learn Package is unavailable'''
	dlat = lat2 - lat1 # delta Latitude
	dlon = lon2 - lon1 # delta Longitude

	a = np.sin(dlat/2.0)**2 + np.cos(lat1) * np.cos(lat2) * np.sin(dlon/2.0)**2
	c = 2 * np.arcsin(np.sqrt(a)) # this is in Radian, no need to Change

	return c


import numpy as np
import matplotlib.pyplot as plt

def normal_pdf(x, mu, sigma):
    pdf = 1/(sigma * np.sqrt(2*np.pi)) * np.exp(-(x-mu)**2 / (2*sigma**2))
    return pdf

def unifrom_pdf(x, a, b):
    pdf = np.piecewise(x, [(x>=a) & (x<=b), (x<a) & (x>b)], [1/(b-a), 0])
    return pdf

# Random sampling of a normal distribution
my_mu, my_sigma = 0, 0.1 # mean and standard deviation
sn = np.random.default_rng().normal(loc=my_mu, scale=my_sigma, size=10000)
fig = plt.figure()
ax1 = fig.add_subplot(2, 1, 1)
ax1.hist(sn, density=True, bins='auto', edgecolor='k', color='#c7ddf4', label='Random Sampling of the Normal Distribution')
my_xn = np.linspace(my_mu - 4 * my_sigma, my_mu + 4 * my_sigma, 1000)
my_yn = normal_pdf(x=my_xn, mu=my_mu, sigma=my_sigma)
ax1.plot(my_xn, my_yn,linewidth=2, linestyle='--', color='#ff464a', label='Target Normal Probability Density Function')
ax1.set_ylim(0.0, 7.0)
ax1.set_xlabel('x')
ax1.set_ylabel('Prob. Density')
ax1.legend()

# Random sampling of a uniform distribution
my_a, my_b = -1, 1 # lower and upper bound of the uniform distribution
su = np.random.default_rng().uniform(low=my_a, high=my_b, size=10000)
ax2 = fig.add_subplot(2, 1, 2)
ax2.hist(su, density=True, bins='auto', edgecolor='k', color='#c7ddf4', label='Random Sampling of the Uniform Distribution')
my_xu = np.linspace(-2, 2, 1000)
my_yu = unifrom_pdf(x=my_xu, a=my_a, b=my_b)
ax2.plot(my_xu, my_yu, linewidth=2, linestyle='--', color='#ff464a', label='Target Uniform Probability Density Function')
ax2.set_ylim(0, 1)
ax2.set_xlabel('x')
ax2.set_ylabel('Prob. Density')
ax2.legend()

fig.tight_layout()


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

one = np.ones(15)
noise = np.random.normal(size=20)
x = np.linspace(-3, 3, 20)
x = np.random.permutation(x)

# training error and testing error

testX = x[15:]
X = np.vstack([x[:15],one])

#print("{}".format( noise ))
#print("{}".format( x ))
#print("{}".format( one ))
#print("{}".format( X ))
#print("{}".format( X.shape ))
#print("{}".format( testX ))

y = np.add(x[:15] * 2, noise[:15])
#print("{}".format( y ))

testY = np.add(x[15:] * 2, noise[15:])
#print("{}".format( testY ))

Wlin = np.linalg.inv(X.dot(X.T)).dot(X).dot(y.T)
#print("{}".format( Wlin ))

c = x[:15] * Wlin[0] + Wlin[1]
d = x[15:] * Wlin[0] + Wlin[1]
trainError = math.pow( np.square(np.subtract( c, y )).mean(), 1/2 )
print("training error : {}".format( trainError ))
testError = math.pow( np.square(np.subtract( d, testY )).mean(), 1/2 )
print("testing errors : {}".format( testError ))

a = np.linspace(-3, 3, 100)
b = a * Wlin[0] + Wlin[1]

plt.scatter(x[:15], y, color = 'blue')
plt.scatter(x[15:], testY, color = 'yellow')
plt.plot(a, b, color = 'red')
plt.ylim(-8, 8)
plt.title('Fitting plots for training error and testing error')
plt.xlabel("trainingPoint : blue , testingPoint : yellow")
plt.show()

# cross-validation errors ( leave-one-out )

sum = 0

for i in range(15):

	tmpX = np.delete(x[:15], i)
	validX = x[i]
	X = np.vstack([tmpX, one[:14]])

	#print("{}".format( tmpX ))
	#print("{}".format( validX ))
	#print("{}".format( X ))

	tmpY = np.add(tmpX * 2, np.delete(noise[:15], i) )
	#print("{}".format( tmpY ))

	validY = np.add(validX * 2, noise[i])
	#print("{}".format( validY ))

	Wlin = np.linalg.inv(X.dot(X.T)).dot(X).dot(tmpY.T)
	#print("{}".format( Wlin ))

	d = validX * Wlin[0] + Wlin[1]
	crossError = np.abs(np.subtract( d, validY ))
	#print("{}".format( crossError ))
	sum += crossError

	a = np.linspace(-3, 3, 100)
	b = a * Wlin[0] + Wlin[1]
	plt.plot(a, b, color = 'red')

print("cross-validation errors for leave-one-out : {}".format( sum/15 ))

plt.scatter(x[:15], y, color = 'blue')
plt.ylim(-8, 8)
plt.title('Fitting plots for cross-validation errors ( leave-one-out )')
plt.xlabel("trainingPoint + validationPoint : blue")
plt.show()

# cross-validation errors ( five-fold )

sum = 0

for i in range(0, 15, 3):

	tmpX = np.delete(x[:15], [i, i+1, i+2])
	validX = x[i:i+3]
	X = np.vstack([tmpX, one[:12]])

	#print("{}".format( tmpX ))
	#print("{}".format( validX ))
	#print("{}".format( X ))

	tmpY = np.add(tmpX * 2, np.delete(noise[:15], [i, i+1, i+2]) )
	#print("{}".format( tmpY ))

	validY = np.add(validX * 2, noise[i:i+3])
	#print("{}".format( validY ))

	Wlin = np.linalg.inv(X.dot(X.T)).dot(X).dot(tmpY.T)
	#print("{}".format( Wlin ))

	d = validX * Wlin[0] + Wlin[1]
	crossError = math.pow( np.square(np.subtract( d, validY )).mean(), 1/2 )
	#print("{}".format( crossError ))
	sum += crossError

	a = np.linspace(-3, 3, 100)
	b = a * Wlin[0] + Wlin[1]
	plt.plot(a, b, color = 'red')

print("cross-validation errors for five-fold : {}".format( sum/5 ))

plt.scatter(x[:15], y, color = 'blue')
plt.ylim(-8, 8)
plt.title('Fitting plots for cross-validation errors ( five-fold )')
plt.xlabel("trainingPoint + validationPoint : blue")
plt.show()


import scipy

# ==================================
# === Error function definitions ===
# ==================================

def gradient(x, x_min, x_max):
    """
    Gradient scaling function. The gradient is computed
    to result in +/-1 scales at x_max and x_min correspondingly.

    Parameters
    ----------
    x: ndarray
        An input array, for which to compute the scalings.
    x_min: float
        A point, that corresponds to -1 output value.
    x_max: float
        A point, that corresponds to +1 output value.

    Returns
    -------
    ndarray:
        An array of scales, ranging [-1;1] in [x_min; x_max] range.
    """

    res = (2*x - (x_min + x_max)) / (x_max - x_min)

    return res

def step(x, break_points):
    """
    Step-like scaling function.

    Parameters
    ----------
    x: ndarray
        An input array, for which to compute the scalings.
    break_points: tuple
        A list of the break points. Each entry should be a tuple of
        (break_position, break_width).

    Returns
    -------
    ndarray
        Array of computed scales in the [-1; 1] range.
    """

    # Applying the first break point
    break_point = break_points[0]
    break_x = break_point[0]
    break_width = break_point[1]

    res = scipy.tanh((x - break_x) / break_width)
    sign = 1

    # If there are more break points given, applying them as well
    for break_point in break_points[1:]:
        # First recalling the previous break point position
        break_x_old = break_x

        # New break point data
        break_x = break_point[0]
        break_width = break_point[1]

        # Will fill only points above the transition position
        trans_x = (break_x + break_x_old) / 2.0
        above_trans_x = scipy.where(x >= trans_x)

        # Flip the sign - above the transition position function behaviour is reversed
        sign *= -1
        res[above_trans_x] = sign * scipy.tanh((x[above_trans_x] - break_x) / break_width)

    return res


'''
Angular radius of query circle = r
Radius of Earth = R (kilometers)
Circle that is formed by all points which have distance d (kilometers) from M the query circle
Then, r = d/R
#Computing the Minimum and Maximum Latitude
   Let, M=(lat, lon)
  lat_min = lat - r rad
  lat_max = lat + r rad

# Computing the Minimum and Maximum Longitude
	latT = arcsin(sin(lat)/cos(r))
	lon_min = lonT1 = lon - delta(lon)
	lon_max = lonT2 = lon + delta(lon)
	delta(lon) = arccos( ( cos(r) - sin(latT) * sin(lat) ) / ( cos(latT) * cos(lat) ) ) = arcsin(sin(r)/cos(lat))
# Poles and the 180th Meridian
	#TODO
# Optimized SQL Query
	SELECT * FROM Places WHERE
    (Lat => lat_min AND Lat <= lat_max) AND (Lon >= lon_min AND Lon <= lon_max)
	AND
    acos(sin(lat) * sin(Lat) + cos(lat) * cos(Lat) * cos(Lon - (lon))) <= r;
***********************************************************************************************
Source : http://janmatuschek.de/LatitudeLongitudeBoundingCoordinates#SQLQueries
**********************************************************************************************
'''

import math
import numpy as np
RADIUS_EARTH = 6371.0

class Geolocation:

	def __init__(self,lat,lon,distance):

		self.lat = lat  # in radians
		self.lon = lon # in radians
		self.distance = float(distance) # in kilometers

	def angular_radius(self):

		return float(self.distance/RADIUS_EARTH)

	def min_max_latitudes(self):

		lat_min = self.lat - self.angular_radius()
		lat_max = self.lat + self.angular_radius()

		return (lat_min,lat_max)

	def min_max_longitudes(self):

		r = self.angular_radius()

		latT = np.arcsin(math.sin(self.lat)/math.cos(r))

		delta_lon = np.arccos( ( math.cos(r) - math.sin(latT) * math.sin(self.lat) ) / ( math.cos(latT) * math.cos(self.lat) ) )

		lon_min =  self.lon - delta_lon
		lon_max =  self.lon + delta_lon

		return (lon_min,lon_max)


import numpy as np

def l2_distance(a, b):
    """Computes the Euclidean distance matrix between a and b.
    """

    if a.shape[0] != b.shape[0]:
        raise ValueError("A and B should be of same dimensionality")

    aa = np.sum(a**2, axis=0)
    bb = np.sum(b**2, axis=0)
    ab = np.dot(a.T, b)

    return np.sqrt(aa[:, np.newaxis] + bb[np.newaxis, :] - 2*ab)


import numpy as np
import matplotlib.pyplot as plt

# %matplotlib inline

a_np_list = np.array([0,1,2,3,4])

a_np_list.size:5
a_np_list.ndim:1
a_np_list.shape: (5,)

b = np.array([3.1,11.02,6.2,213.2,5.2])
print(type(b)) # :numpy.ndarray
b.dtype:np.dtype("float64")

c = np.array([20,1,2,3,4])
c:np.array([20,1,2,3,4])
c[0]=100
c:np.array([100,1,2,3,4])
c[4]=0
c:np.array([100,1,2,3,4])
d=c[1:4]
d:np.array([1,2,3])
c[3:5]=300,400
c:np.array([0,1,2,300,400])

u=[1,0]
v=[0,1]
z=[]
for n, m in zip(u,v):
    z.append(n+m)

u=np.array([1,0])
v=np.array([0,1])
z=u+v
z:np.array([1,1])

u=[1,0]
v=[0,1]
z=[]
for n, m in zip(u,v):
    z.append(n-m)

u=np.array([1,0])
v=np.array([0,1])
z=u-v
z:np.array([1,1])

y=np.array([1,2])
z=2*y
z:np.array([2,4])

y=[1,2]
z=2*y
for n in y:
    z.append(2*n)

u=np.array([1,2])
v=np.array([3,2])
z=u*v
z:np.array([3,4])

u=[1,2]
v=[3,2]
z=[]
for n, m in zip(u,v):
    z.append(n*m)

u=np.array([1,2])
v=np.array([3,1])
result=np.dot(u,v)
result:5

u=np.array([1,2,3,-1])
z=u+1
z:np.array([2,3,4,0])

a=np.array([1,-1,1,-1])
mean_a=a.mean
mean_a=0.0

a=np.array([1,4])
z=a*(1-1+1-1)
z=0

b=np.array([1,-2,3,4,5])
max_b=b.max()
max_b:5

np.pi
x=np.array([0,np.pi/2,np.pi])
y=np.sin(x)
y:np.array([0,1,1.2e-16])

np.linspace(-2,2,num=5)
np.linspace(-2,2,num=9)

x=np.linspace(0,2*np.pi,100)
y=np.sin(x)
plt.plot(x,y)
plt.show()


import math
from decimal import *
from scipy import poly1d
import scipy.special
import numpy as np
from fractions import Fraction as F
import time
"""
CardinalityFC(n,k)

inputs:
n: number of variables
k: the value of the threshold

CNF clauses are special cases of cardinality constraints

return:
the n*1 vector of Fourier coefficients of Card(x1,x2,...,xn)>=k, in other words, x1+x2+...+xk<=n-2k. Ordered by degree, thus ret[0] is the constant.
"""
def binom(n,k):
    if k==0: return 1
    ans = F(1)
    for i in range(n):
        ans = ans * (i+1)
        if i < k:
            ans = ans/(i+1)
        else:
            ans = ans/(n-i)
    return ans

def verify_paserval(x):
    ans = 0
    for i in range(len(x)):
        ans += x[i]*x[i]*binom(len(x)-1,i)
    return ans

def CardinalityFC(n,k):
#    print('n='+repr(n)+' k='+repr(k))
    startall = time.time()
    if n==1 and k==1:
        return [0,1]
    d = np.zeros(n + 1)
    if k==0:
        d[0]=-1
        return d
    templist = ([-1 for i in range(n-k)] + [1 for i in range(k-1)])
    templist = np.array(templist,dtype=object)
#    print('before poly1d takes '+repr(time.time()-startall))

    start = time.time()
    temppoly = poly1d(templist,True) * (1-2*((k-1)%2))   # complexity: O(n(logn)^2)
#    print('poly1d takes '+repr(time.time()-start))
    temparray = temppoly.c[::-1]
    start = time.time()
    temparray = [F(temparray[i],(2**(n-1))) for i in range(len(temparray))]
#    print('temparray/2^n-1 takes '+repr(time.time()-start))
    start = time.time()
    binom_nk = binom(n-1,k-1)
    binom_ni_inv = [1 for i in range(n)]  # iteratively computing binom(n-1,i)
    for i in range(0,n-1):
        binom_ni_inv[i+1] = binom_ni_inv[i] * (i+1) / (n-i-1)
    binocoef = [binom_nk * binom_ni_inv[i] for i in range(n)]  #complexity: O(nk)
    #binocoef = [binom_nk / binom(n-1,i) for i in range(n)]  #complexity: O(nk)
 #   print('binom takes '+repr(time.time()-start))
    temparray = [temparray[i] * binocoef[i] for i in range(len(temparray))]
    for j in range(n):
        d[j+1] = F(temparray[j])
    start = time.time()
    templist3 = []
    templist3.append(binom(n,k))
    for i in range(k,n):
        templist3.append(templist3[i-k] * (n-i) / (i+1) )
  #  print('m1 uses '+repr(time.time()- start))
    #templist3 = [F(binom(n,i)) for i in range(k,n+1)]
    start = time.time()
    d[0] = F(1 - F(int(sum(templist3)),(2 ** (n - 1))))
  #  print('m2 uses '+repr(time.time()- start))
  #  print('CardFC takes '+repr(time.time()-startall))
    return d

# the Fourier Coefficients of Not-all-equal constraints
def NAEFC(n):
    d = np.zeros(n + 1)
    d[0] = (F(1,2))**(n-2)-1
    for i in range(2,n+1,2):
        d[i] = (F(1/2))**(n-2)
    return d

#total = 0
#for i in range(0,101):
#    total = abs(coef[i])*binom(100,i)
#    print(repr(total)+','),


import numpy as np

def _assert_even_mesh(x_nodes):
    x_nodes_forward = np.roll(x_nodes, -1)
    dxs = x_nodes_forward - x_nodes
    if not np.isclose(0, np.max(np.abs(1 - dxs[:-1] / dxs[0]))):
        raise Exception('Central differences can only be used on even meshes')

def fd_point(x_nodes, f_nodes, i):
    dx = x_nodes[i + 1] - x_nodes[i]
    dy = f_nodes[i + 1] - f_nodes[i]
    return dy / dx

def bd_point(x_nodes, f_nodes, i):
    dx = x_nodes[i] - x_nodes[i - 1]
    df = f_nodes[i] - f_nodes[i - 1]
    return df / dx

def three_points_midpoint(x_nodes, f_nodes, i):
    _assert_even_mesh(x_nodes[i-1:i+2])
    dx = x_nodes[i] - x_nodes[i - 1]
    return (f_nodes[i+1] - f_nodes[i-1]) / (2*dx)

def three_points_left_endpoint(x_nodes, f_nodes, i):
    _assert_even_mesh(x_nodes[i:i+2])
    dx = x_nodes[i + 1] - x_nodes[i]
    return (-3*f_nodes[i] + 4*f_nodes[i + 1] - f_nodes[i + 2]) / (2*dx)

def three_points_right_endpoint(x_nodes, f_nodes, i):
    _assert_even_mesh(x_nodes[i-2:i+1])
    dx = x_nodes[i] - x_nodes[i - 1]
    return (f_nodes[i - 2] - 4*f_nodes[i - 1] + 3*f_nodes[i]) / (2*dx)

def cd(x_nodes, f_nodes):
    df = np.zeros_like(f_nodes)
    df[0] = three_points_left_endpoint(x_nodes, f_nodes, 0)
    df[1:-1] = np.array([three_points_midpoint(x_nodes, f_nodes, i) for i in range(1, len(f_nodes) - 1)])
    df[-1] = three_points_right_endpoint(x_nodes, f_nodes, len(f_nodes) - 1)
    return df

def fd(x_nodes, f_nodes):
    df = np.zeros_like(f_nodes)
    df[0:-1] = np.array([fd_point(x_nodes, f_nodes, i) for i in range(0, len(f_nodes) - 1)])
    df[-1] = df[-2]
    return df


# -*- coding: utf-8 -*-
"""
Created on Tue Aug 31 22:04:01 2021

@author: Julien

https://en.wikipedia.org/wiki/Sierpi%C5%84ski_triangle
"""

#  _____                            _
# |_   _|                          | |
#   | |  _ __ ___  _ __   ___  _ __| |_ ___
#   | | | '_ ` _ \| '_ \ / _ \| '__| __/ __|
#  _| |_| | | | | | |_) | (_) | |  | |_\__ \
# |_____|_| |_| |_| .__/ \___/|_|   \__|___/
#                 | |
#                 |_|

import cmath
import numpy as np

#   _____                _              _
#  / ____|              | |            | |
# | |     ___  _ __  ___| |_ __ _ _ __ | |_ ___
# | |    / _ \| '_ \/ __| __/ _` | '_ \| __/ __|
# | |___| (_) | | | \__ \ || (_| | | | | |_\__ \
#  \_____\___/|_| |_|___/\__\__,_|_| |_|\__|___/

X_LIMITS = (0, 1)
Y_LIMITS = (-0.1, np.sin(np.pi/3)+0.1)

#  ______                _   _
# |  ____|              | | (_)
# | |__ _   _ _ __   ___| |_ _  ___  _ __  ___
# |  __| | | | '_ \ / __| __| |/ _ \| '_ \/ __|
# | |  | |_| | | | | (__| |_| | (_) | | | \__ \
# |_|   \__,_|_| |_|\___|\__|_|\___/|_| |_|___/

def compute_new(z1, z2, up):
    '''
    uses complex numbers to compute the next points to add for each edge.

    Parameters
    ----------
    z1 : complex
        first vertice.
    z2 : complex
        second vertice.
    up : boolean
        wether or not add the point in a clockwise or counterclockwise fashion.

    Returns
    -------
    a, b : complex, complex
        the new point(s) generated from the given edge.
    up : boolean
        returning "up" value for computing purposes
    '''
    v = (z2-z1)/2 #v is the 'vector' from z1 to the middle of the segment [z1, z2]

    #rotating the vectors by 60 degrees clokwise or counterclockwise depending on the up value
    #and on the starting point
    if up :
        a = z1 + v*1j**(2/3)
        b = z2 + v*1j**(4/3)
    else :
        a = z1 - v*1j**(4/3)
        b = z2 - v*1j**(2/3)

    up = not(up) #flipping the up value

    return a, b, up

def partial_sierpinski_triangle(T):
    '''
    Generates the next generation of the fractal curve algorithm. For each pair of consecutive points of T
    that make a vertice, we add between those two points the new point(s) generated by the "compute_new"
    method.

    Parameters
    ----------
    T : list[complex]
        the current state of the algorithm.

    Returns
    -------
    res : list[complex]
        The next step of the algorithm.
    '''

    up = len(T)%10<3
    res = []
    for i in range(len(T)-1):
        res.append(T[i])
        a, b, up = compute_new(T[i], T[i+1], up)
        res.append(a)
        res.append(b)
    res.append(T[-1])
    return res

def sierpinski_triangle(n):
    '''
    Recursive method to build the final fractal curve of degree n.

    Parameters
    ----------
    n : int
        The degree of the fractal curve to build.

    Returns
    -------
    list[complex]
        The list of complex numbers that represent the n-th degree fractal curve.
    '''

    if n==0 :
        return [complex(0,0), complex(1,0)]
    else :
        return partial_sierpinski_triangle(sierpinski_triangle(n-1))

def pts_num(n):
    '''
    Optional function that returns the amount of points in the n-th generation of the algorithm.

    Parameters
    ----------
    n : int
        The degree of the fractal curve.

    Returns
    -------
    int
        The number of vertices needed to build the n-th degree fractal curve.
    '''

    n0 = 2
    return (3**n)*(n0-1)+1

#  _            _
# | |          | |
# | |_ ___  ___| |_ ___
# | __/ _ \/ __| __/ __|
# | ||  __/\__ \ |_\__ \
#  \__\___||___/\__|___/

if __name__ == '__main__' :
    import matplotlib.pyplot as plt

    N = 12
    T = sierpinski_triangle(N)
    Tr = [el.real for el in T]
    Ti = [el.imag for el in T]
    plt.plot(Tr, Ti, lw=np.exp(-N/10))
    plt.axis('equal')
    plt.show()


import numpy as np
from sklearn.linear_model import LinearRegression

n = 120
beta = np.array([0.5, 2.5, -1.44])
p = np.shape(beta)[0]

mu = 0
sigma = 0.098765
seed = 4321
rng = np.random.default_rng(seed)
epsilon = rng.normal(mu, sigma, n)

x1 = np.ones((n,1))
x2 = rng.random((n,1))
x3 = np.repeat([1,2,3,4,5], n/5)[:,np.newaxis]
x = np.concatenate((x1,x2,x3), axis=1)
y = x @ beta + epsilon

reg = LinearRegression().fit(x, y)
true_beta_hat = np.append(reg.intercept_, reg.coef_[1:p])

def compute_gamma_val(regressor, regressand):
    return (regressor.T @ regressand) / (regressor.T @ regressor)

def compute_gamma_and_residuals(x):
    n,p = np.shape(x)
    gamma = np.diag(np.ones(p))
    Z = np.empty((n,p))
    Z[:,0] = np.ones(n)
    for j_idx in range(1,p):
        for k_idx in range(p):
            if k_idx < j_idx:
                gamma[k_idx, j_idx] = compute_gamma_val(Z[:, k_idx], x[:, j_idx])
            else:
                pass
    Z[:,j_idx] = x[:,j_idx] - sum([gamma[i_idx,j_idx] * Z[:,i_idx] for i_idx in range(j_idx)])
    return (gamma, Z)

def compute_D(residuals):
    _,p = np.shape(residuals)
    norms = np.array([np.linalg.norm(residuals[:,col_idx]) for col_idx in range(p)])
    D = np.diag(norms)
    return D

def gram_schmidt(x,y):
    gamma, Z = compute_gamma_and_residuals(x)
    D = compute_D(Z)
    Q = Z @ np.linalg.inv(D)
    R = D @ gamma
    beta_hat = np.linalg.solve(R, Q.T @ y)
    return beta_hat

print("Beta hat from sklearn: ", true_beta_hat)
print("Beta hat from our G-S procedure:", gram_schmidt(x,y))


import numpy as np

class Loss(object):
    def __call__(self, X, *args, **kwargs):
        return self.forward(X, *args, **kwargs)

    def forward(self, Y_true, Y_pred, *args, **kwargs):
        pass

    def backward(self, Y_true, Y_pred, *args, **kwargs):
        """
        Computes the gradient of the loss with respect to Y_pred
        """
        pass

class MSE(Loss):
    def forward(self, Y_true, Y_pred):
        J = np.mean(np.square(Y_pred - Y_true))
        return J

    def backward(self, Y_true, Y_pred):
        size = Y_true.size

        dJ_dY_pred = 2 * (Y_pred - Y_true) / size
        return dJ_dY_pred

class BinaryCrossentropy(Loss):
    def forward(self, Y_true, Y_pred, epsilon=1e-8):
        Y_pred = np.clip(Y_pred, epsilon, 1 - epsilon)

        J = -np.mean(Y_true * np.log(Y_pred) + (1 - Y_true) * np.log(1 - Y_pred))
        return J

    def backward(self, Y_true, Y_pred, epsilon=1e-8):
        Y_pred = np.clip(Y_pred, epsilon, 1 - epsilon)
        size = Y_true.size

        dJ_dY_pred = ((1 - Y_true) / (1 - Y_pred) - Y_true / Y_pred) / size
        return dJ_dY_pred

class CategoricalCrossentropy(Loss):
    """
    Expects Y_true to be one-hot encoded and Y_pred to be normalized probabilities (e.g.
    the output of a Softmax layer)
    """
    def forward(self, Y_true, Y_pred, epsilon=1e-8):
        Y_pred = np.clip(Y_pred, epsilon, 1 - epsilon)
        m = Y_true.shape[0]

        J =  -np.sum(Y_true * np.log(Y_pred)) / m
        return J

    def backward(self, Y_true, Y_pred, epsilon=1e-8):
        Y_pred = np.clip(Y_pred, epsilon, 1 - epsilon)
        m = Y_true.shape[0]

        dJ_dY_pred = -(Y_true / Y_pred) / m
        return dJ_dY_pred


from Plot_Tools import Basic_Plot as BP
import numpy as np
import sys

class Gradient_Descent_Solver_with_Numpy:
    def __init__(self, X, Y, LR,
                 ci=1000, tol=1e-12,
                 max_cnt=1e9, rnd=6):

        if type(X) is not np.ndarray:
            X = np.asarray(X)
        if type(Y) is not np.ndarray:
            Y = np.asarray(Y)

        self.X = X
        self.Y = Y
        self.LR = LR
        self.ci = ci
        self.tol = tol
        self.max_cnt = max_cnt
        self.rnd = rnd

        self.num_records = len(X)
        self.num_dims = len(X[0])

        self.Yp = np.zeros((self.num_records, 1))
        self.delta = np.zeros((self.num_records, 1))
        self.randomize_weights()

        self.cnt_list = []
        self.cost_list = []

    def set_weights(self, ws):
        if type(ws) is not np.ndarray:
            ws = np.asarray(ws)

        self.ws = ws

    def set_labels(self, Y):
        if Y is not np.ndarray:
            Y = np.asarray(Y)

        self.Y = Y

    def randomize_weights(self):
        self.ws = np.random.rand(self.num_dims, 1)

    def model(self, X):
        if X is not np.ndarray:
            X = np.asarray(X)
        self.Yp = np.matmul(X, self.ws)
        return self.Yp

    def train(self):
        cost_delta = 1.0
        cost_last = 1.0

        self.count = 0
        self.cost_list = []
        self.cnt_list = []

        while cost_delta > self.tol and self.__iterations_below_max__():
            self.model(self.X)
            self.__update_weights__()

            self.cost_now = self.__cost__()
            cost_delta = abs(cost_last - self.cost_now)
            cost_last = self.cost_now

            self.__record_values__()

    def __update_weights__(self):
        self.delta = self.Yp - self.Y
        dcdw = np.sum(self.X * self.delta, axis=0).reshape((self.num_dims, 1))
        self.ws = self.ws - self.LR * dcdw

    def __cost__(self):
        return np.sum(np.square(self.delta)) ** 0.5

    def __record_values__(self):
        self.count += 1

        if self.count % self.ci == 0:
            self.cost_list.append(self.cost_now)
            self.cnt_list.append(self.count)

    def __iterations_below_max__(self):
        if self.count < self.max_cnt:
            return True
        else:
            print("Exceeded Max Iterations")
            return False

    def report_results(self):
        ws = np.around(self.ws, decimals=6)
        print(f'Solved Weights: {ws}')
        print(f'Iteration Steps to Solution: {self.count}')

    def plot_solution_convergence(self):
        BP(self.cnt_list, self.cost_list,
           t='Cost vs. Solution Steps',
           x_t='Solution Steps', y_t='Cost')

    def plot_predictions(self, X, Y, col_of_X=1):
        if type(X) is not np.ndarray:
            X = np.asarray(X)
        Xsp = self.X[:, col_of_X].tolist()
        Xtp = X[:, col_of_X].tolist()

        BP(Xtp, self.Yp, xp=Xsp, yp=self.Y,
           t='Model Predictions vs. Inputs',
           x_t='Inputs',
           y_t='Predictions and Original Output')


# modelling a linear relationship between "chirps of a cricket" and ground temperature.

# Let's import tensorFlow and python dependencies
import numpy as np
import tensorflow as tf
import pandas as pd
import matplotlib.pyplot as plt

# Download and Explore the Data
df = pd.read_csv("PierceCricketData.csv")
print(df.head())

# Plot the Data Points
x_data, y_data = (df["Chirps"].values,df["Temp"].values)

# plots the data points
plt.plot(x_data, y_data, 'ro')
# label the axis
plt.xlabel("# Chirps per 15 sec")
plt.ylabel("Temp in Farenhiet")

# Create a Data Flow Graph using TensorFlow
X = tf.placeholder(tf.float32, shape=(x_data.size))
Y = tf.placeholder(tf.float32, shape=(y_data.size))

m = tf.Variable(3.0)
c = tf.Variable(2.0)

# Construindo o Modelo
Ypred = tf.add(tf.multiply(X, m), c)

# Create and Run a Session to Visualize the Predicted Line
init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)

pred = sess.run(Ypred, feed_dict={X:x_data})

#plot initial prediction against datapoints
plt.plot(x_data, pred)
plt.plot(x_data, y_data, 'ro')
# label the axis
plt.xlabel("# Chirps per 15 sec")
plt.ylabel("Temp in Farenhiet")

# normalization factor
nf = 1e-1

# seting up the loss function
loss = tf.reduce_mean(tf.squared_difference(Ypred*nf,Y*nf))

# Define an Optimization Graph to Minimize the Loss and Training the Model

optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01)
#optimizer = tf.train.AdagradOptimizer(0.01 )

# pass the loss function that optimizer should optimize on.
train = optimizer.minimize(loss)

# Initialize all the vairiables again
sess.run(tf.global_variables_initializer())

# Run session to train and predict the values of 'm' and 'c'
convergenceTolerance = 0.0001
previous_m = np.inf
previous_c = np.inf

steps = {}
steps['m'] = []
steps['c'] = []

losses=[]

for k in range(100000):
    # run a session to train , get m and c values with loss function
    _, _m, _c, _l = sess.run([train, m, c, loss], feed_dict={X: x_data, Y: y_data})

    steps['m'].append(_m)
    steps['c'].append(_c)
    losses.append(_l)
    if (np.abs(previous_m - _m) <= convergenceTolerance) or (np.abs(previous_c - _c) <= convergenceTolerance):
        print("Finished by Convergence Criterion")
        print("k = {}".format(k))
        print("Loss = {}".format(_l))
        print("m = {}".format(_m))
        print("c = {}".format(_c))
        break
    previous_m = _m,
    previous_c = _c,

sess.close()

plt.plot(losses[:])
plt.show()


## 3. Statistical significance ##

import numpy as np
import matplotlib.pyplot as plt
mean_group_a = np.mean(weight_lost_a)
mean_group_b = np.mean(weight_lost_b)
print(mean_group_a)
print(mean_group_b)
plt.hist(weight_lost_a)
plt.show()
plt.hist(weight_lost_b)
plt.show()

## 4. Test statistic ##

mean_difference = mean_group_b - mean_group_a
print(mean_difference)

## 5. Permutation test ##

import numpy as np
mean_difference = 2.52
print(all_values)
mean_differences = []
for i in range(1000):
    group_a, group_b = [],[]
    for loop in all_values:
        tmp = np.random.rand()
        if tmp >= 0.5: group_a.append(loop)
        else: group_b.append(loop)
    iteration_mean_difference = numpy.mean(group_b) - numpy.mean(group_a)
    mean_differences.append(iteration_mean_difference)
plt.hist(mean_differences)
plt.show()

## 7. Dictionary representation of a distribution ##

sampling_distribution = {}
for loop in mean_differences:
    if sampling_distribution.get(loop,False):
        val =sampling_distribution.get(loop)
        sampling_distribution[loop] = val+1
    else:
        sampling_distribution[loop] = 1

## 8. P value ##

frequencies = []
for lopp in sampling_distribution:
    if lopp >= 2.52: frequencies.append(lopp)
p_value = sum(frequencies) / 1000

Probability Statistics Intermediate/Significance Testing-174.py


# -*- coding: utf-8 -*-
"""
Created on Sat Nov 27 20:58:50 2021

@author: Panayiota Damianou
"""
import numpy as np
import sympy
import matplotlib.pyplot as plt
from scipy import integrate

# Legendre polynomial
def leg(n, x):
    if(n == 0):
        return 1 # P0 = 1
    elif(n == 1):
        return x # P1 = x
    else:
        return (((2 * n)-1)*x * leg(n-1, x)-(n-1)*leg(n-2, x))/float(n)

def hbasis(i,x):
#  Evaluates the function Ni(y) at x
#  In function L(y) i-2 is the degree of the legendre polynomial

    if i==1:
        Ni=0.5 *(1-x)
    elif i==2:
        Ni=0.5 *(1+x);
    else:
        Ni=(np.sqrt(1/(4*i-6)))*(leg(i-1,x)-leg(i-3,x))
    return Ni

def dhbasis(i,x):
#   Evaluates the derrivative of the function Ni(x) at x
#   In function L(y) i-2 is the degree of the legendre polynomial

    if i==1:
        dNi=-0.5
    elif i==2:
        dNi=0.5
    else:
        dNi=leg(i-2,x)*np.sqrt((2*i-3)/2)
    return dNi

def stifness_matrix(p):
#evaluates the elemental stifness matrix of size (p+1)x(p+1)
    K=np.zeros((p+1,p+1))
    K[0,0]=K[1,1]=0.5
    K[0,1]=K[1,0]=-0.5
    if p>=1:
        for i in range(2,p+1):
            K[i,i]=1
    return K

def mass_matrix(p):
# Evaluates the elemental mass matrix of size (p+1)x(p+1)
    G=np.zeros((p+1,p+1))
    G[0,0]=G[1,1]=2/3
    G[0,1]=G[1,0]=1/3
    if p>=1:
        G[0,2]=G[1,2]=G[2,0]=G[2,1]=-1/np.sqrt(6)
    for i in range(2,p+1):
        G[i,i]=2/(((2*i)-1)*((2*i)-5))
    if p>=3:
        G[0,3]=G[3,0]=1/3*np.sqrt(10)
        G[1,3]=G[3,1]=-1/3*np.sqrt(10)
        for i in range(2,p+1):
            if i+2<=p+1:
               G[i,i+2]=G[i+2,i]=(-1)/(((2*i)-1)*np.sqrt(((2*i)-3)*((2*i)+1)))
    return G

def load_vector(p,f,l,c):
#Evaluates the element load vector
    b=np.zeros((1,p+1))
    for i in range(p+2):
        g = lambda x : f(x,l,c) * hbasis(i,x)
        b[i]= integrate.quad(g,-1,1)
    return b

def fem_solution(p,f,x,l,c):
# Evaluates the finite element method solution and the coifficients that generate the solution
    A=stifness_matrix(p)+mass_matrix(p)
    b=load_vector(p,f,l,c)
    a=A.solve(b)
    ufe_list=[a[i]*[hbasis(i,point) for point in x] for i in range(1,p+2)]
    ufe=sum([sum(l) for l in ufe_list])
    return a, ufe

def energy_norm(p,f,x,l,c):
# Evaluates the energy norm of the finite element method solution and it finds
# the percentage of the error
    a=fem_solution(p,f,x,l,c)
    enorm= np.dot(a, load_vector(p,f,l,c))
    err=1
    return enorm,err

# IMPORTANT NOTE (IF ERROR): [f(x) for x in x_list]

def main():
    f = lambda x,l,c : (((1+x)**(l-2))*2*l*(1+x))-((1-x)*l*(l-1))+(c*(1-x)*(1+x)**2)
    l=[8.7,4.4,2.9,1.2]
    for k in l:
        c=1
        p=[2,3,4,5,6,7,8]
        error=[]
        x = np.linspace(-1,1,1001)
        for j in p:
            error.append(energy_norm(j,f,x,k,c)[1])
        fig, axes = plt.subplots(1,3, figsize=(12,3))

        axes[k].loglog(x,error)

        axes[k].xlabel("error")
        axes[k].ylabel("N")

    l=[1.1]
    c=2
    p=4
    x = np.linspace(-1,1,1001)
    u_Ex = lambda x : (1-x)*((1+x)**l)
    u_Fe = fem_solution(p,f,x)[1]
    fig , axes =plt.subplots()
    axes[0].plot(x,u_Fe, color="red")
    axes[0].plot(x,u_Ex, color="blue")
    axes[0].set_xlabel("x")
    axes[0].set_ylabel("y")
    axes[0].legend("Γραφική Παραστάση των u_(FE) και u_(EX)")

    axes[1].plot(x,abs(u_Ex-u_Fe),color='black')
    axes[1].set_xlabel("x")
    axes[1].set_ylabel("y")
    axes[1].legend("Γραφική Παραστάση του σφάλματος |u_(FE)-u_(EX)|")

if __name__ == "__main__":
    main()


from numpy import array, zeros

'''
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Taking user input ~~~~~~~~~~~~~~~~~~~~~~~~~~~
while True:
    try:
        N = int(input("Enter the Number of Equations: "))
        if N > 0: break
        else: print("Number of Equations must be greater than 0")
    except ValueError:
        print("Enter a Natural Number")

col, colB = [], []
for i in range(N):
    row = []
    for j in range(N):
        while True:
            try:
                coeff = float(input("Enter Coefficient "+str(j+1)+\
                                    " of Equation "+str(i+1)+": "))
            except ValueError:
                print("Enter a Real Number")
                continue
            else: break
        row.append(coeff)
    col.append(row)

    while True:
        try:
            const = float(input("Enter the Constant of Equation "+str(i+1)+": "))
        except ValueError:
                print("Enter a Real Number")
                continue
        else: break
    colB.append(const)

A = array(col, float)
B = array(colB, float)
print()
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'''
# System of Equations
'''
A = array([[ 3, -.1, -.2],
           [.1,   7, -.3],
           [.3, -.2,  10]], float)
B = array([7.85, -19.3, 71.4], float)
'''
A = array([[2, 7, -1,  3, 1],
           [2, 3,  4,  1, 7],
           [6, 2, -3, 2, -1],
           [2, 1,  2, -1, 2],
           [3, 4,  1, -2, 1]], float)
B = array([5, 7, 2, 3, 4], float)
'''
A = array([[0, 7, -1,  3, 1],
           [2, 3,  4,  1, 7],
           [6, 2,  0, 2, -1],
           [2, 1,  2,  0, 2],
           [3, 4,  1, -2, 1]], float)
B = array([5, 7, 2, 3, 4], float)
'''

N = len(B)

# Coincident or Parallel Equations  # a1 x + b1 y = c1       a2 x + b2 y = c2
for i in range(N-1):
    ratioA = A[i,:] / A[i+1,:]      # A[0,col]/A[1,col]        [a1/a2, b1/b2]
    ratioB = B[i] / B[i+1]          # B[0] / B[1]               c1/c2

    if all(ratioA == ratioA[0]):    # a1/a2 = b1/b2             Conditions of
        if ratioB == ratioA[0]:     # a1/a2 = b1/b2  = c1/c2    coincident
            print("Two or More Equations are Coincident")
        else:                       # a1/a2 = b1/b2 != c1/c2    parallel
            print("Two or More Equations are Parallel")
        raise SystemExit            # else: a1/a2 != b1/b2      intersect

# Elimination:                      # k = 1 to n-1
for k in range(N-1):                # k = 0 to N-2  # Index starts from 0

    # Partial Pivoting
    if abs(A[k,k]) < 1e-12:         # 0 in the main diagonal
        for i in range(k+1, N):
            if abs(A[i,k]) > abs(A[k,k]):
                A[[k,i]] = A[[i,k]] # A[k,:], A[k+1,:] = A[k+1,:], A[k,:]
                B[[k,i]] = K[[i,k]] # B[k],   B[k+1]   = B[k+1],   B[k]
                break

    for i in range(k+1, N):         # i = k+1 to N-1
        if A[i,k] == 0: continue    # fctr = A[k,k] / 0 will be inf

        fctr = A[k,k] / A[i,k]

        for j in range(k, N):       # j = k to N-1
            A[i,j] = A[k,j] - fctr * A[i,j]

        B[i] = B[k] - fctr * B[i]

# Back Substitution:
X = zeros(N, float)
X[N-1] = B[N-1] / A[N-1][N-1]

for i in range(N-2, -1, -1):        # i = N-2 to 0

    sumAX = 0
    for j in range(i+1, N):         # j = i+1 to N-1
        sumAX += A[i,j] * X[j]

    X[i] = (B[i] - sumAX) / A[i,i]  # X[i]=(B[i]-sum(A[i,i+1:]*X[i+1:]))/A[i,i]

print("The Solution of the System:")
for i in range(N):
    print('X[', i+1, '] = ', round(X[i], 6), sep='')

'''
  1  2 3 4  b
1│3 -2 5 0│ 2
2│4  5 8 1│ 4
3│1  1 2 1│ 5
4│2  7 6 5│ 7               Elimination:

  1  2   3 4    b
1│3 -2   5 0  │ 2           Fixing row 1, multiply from row 2 to 4, col 1 to b
2│3 3.75 6 .75│ 3                      k              i = k+1 to n, j = k to n
3│3  3   2 3  │15    a[1,1]/a[row,1] a[1,1]/a[2,1] a[1,1]/a[3,1] a[1,1]/a[4,1]
4│3 10.5 6 7.5│10.5           fctr = a[k,k]/a[i,k]

  1  2     3  4     b
1│3 -2     5  0  │  2       From fixed row 1, substract row 2 to 4, col 1 to b
2│0 -5.75 -1 -.75│ -1                      k          i = k+1 to n, j = k to n
3│0 -5    -1 -3  │-13       a[i,j] = a[k,j] - fctr * a[i,j]
4│0 -12.5 -4 -7.5│-8.5      b[i]   = b[k]   - fctr * b[i]

│3 -2     5      0  │ 2     Fixing row 2, multiply from row 3 to 4, col 2 to b
│0 -5.75 -1     -.75│-1                k              i = k+1 to n, j = k to n
│0 -5.75 -1.15 -3.45│-14.95 a[2,2]/a[row,2] a[2,2]/a[3,2] a[2,2]/a[4,2]
│0 -5.75 -1.84 -3.45│-3.91  a[k,k]/a[i,k]

│3 -2     5   0  │ 2        From fixed row 2, substract row 3 to 4, col 2 to b
│0 -5.75 -1  -.75│-1                       k          i = k+1 to n, j = k to n
│0  0    .15  2.7│-14.9     a[i,j] = a[k,j] - fctr * a[i,j]
│0  0    .84  2.7│-3.91     b[i]   = b[k]   - fctr * b[i]

│3 -2     5   0  │ 2        Fixing row 3, multiply from row 4 to 4, col 3 to b
│0 -5.75 -1  -.75│-1                   k              i = k+1 to n, j = k to n
│0  0    .15  2.7│-13.95    a[3,3]/a[row,2] a[3,3]/a[4,3]
│0  0    .15 .482│.51964    a[k,k]/a[i,k]

│3 -2     5   0  │ 2        From fixed row 3, substract row 4 to 4, col 3 to b
│0 -5.75 -1  -.75│-1                       k          i = k+1 to n, j = k to n
│0  0    .15  2.7│13.95     a[i,j] = a[k,j] - fctr * a[i,j]
│0  0     0  2.22│13.43     b[i]   = b[k]   - fctr * b[i]

fctr = a[k,k] / a[i,k]
a[i,j] = a[k,j] - fctr * a[i,j]
b[i]   = b[k]   - fctr * b[i]
where fixed row k = 1 to n-1, row below fixed row i = k+1 to n, j = k to n

Back Substitution :                            AX = B
a[1,1]x[1] + a[1,2]x[2] + a[1,3]x[3] + a[1,4]x[4] = b[1]
             a[2,2]x[2] + a[2,3]x[3] + a[2,4]x[4] = b[2]
                          a[3,3]x[3] + a[3,4]x[4] = b[3]
                                       a[4,4]x[4] = b[4]
x[4] =  b[4] / a[4,4] = 6.056
x[n] =  b[n] / a[n,n]

x[3] = (b[3]                             - a[3,4]*x[4]) / a[3,3] = -16
x[2] = (b[2]               - a[2,3]*x[3] - a[2,4]*x[4]) / a[2,2] = 2.167
x[1] = (b[1] - a[1,2]*x[2] - a[1,3]*x[3] - a[1,4]*x[4]) / a[1,1] = 28.78
x[i] = (b[i] - a[i,j]*x[j] - a[i,j]*x[j] - a[i,j]*x[j]) / a[i,i]

x[i] = (b[i] - ∑(a[i,j]*x[j])) / a[i,i]
where i = n-1 to 1, j = i+1 to n

'''

5. Systems of Linear Equations/2. Gauss Elimination Method.py


import numpy as np, os, sys

import matplotlib as mpl #patch-wise similarities, droi images

import matplotlib.pyplot as plt

def datagen1d(eps,num):

  x=np.random.uniform(low=0,high=1,size=(num,1))
  y=np.cos(3*2*np.pi*x)+eps*np.random.normal(size=(num,1))

  print(x.shape,',y.shape',y.shape)

  #this creates features with an appended 1 for use as a term modeling the bias
  x=np.concatenate((x, np.ones_like(x)),axis=1)

  #x.shape=(numdata,dims) dims=2 here
  #y.shape=(numdata)
  #print(x.shape,y.shape)

  return x,y

def rndsplit(x,y,numtr):

  inds=np.arange(y.size)
  np.random.shuffle(inds)

  xtr=x[inds[0:numtr],:]
  ytr=y[inds[0:numtr]]

  xv=x[inds[numtr:],:]
  yv=y[inds[numtr:]]

  return xtr,ytr,xv,yv

def gendata(numtrain,numtest):

  xtr,ytr = datagen1d(eps=0.3,num= int( numtrain) ) #numtotal
  xv,yv = datagen1d(eps=0.3,num= numtest ) #fix 3k test samples
  #xtr,ytr,xv,yv = rndsplit(x,y,numtr=int( 0.7*numtotal))
  return xtr,ytr,xv,yv

def linreg_train(xtr,ytr,C):

  print(ytr.shape)

  #your code here

  return w

def linreg_apply(xv,w):
  return np.dot(xv,w)

def mse(ypred,ytrue):
  e=np.mean( (ypred-ytrue)**2 )
  return e

def run1(xtr,ytr,xv,yv,C):

  w=linreg_train(xtr,ytr,C=C) # 0.1

  ypred=linreg_apply(xv,w)

  print('xtr',xtr.shape,'w',w.shape,xv.shape,yv.shape,ypred.shape)

  e=mse(ypred,yv)

  plt.figure(1)
  plt.plot(xv[:,0],ypred,'b-')
  plt.plot(xv[:,0],yv,'g.')
  #plt.show()

  #print('mse',e)

  return e

def rbffeats(x,protos,gamma):

  #implement your code here

  #what should be here:
  #x.shape = (numsamples,1)  bcs dims=1 for this example
  # protos.shape=(numprotos,1)
  pass

def run2(xtr,ytr,xv,yv,C):

  randinds=np.random.choice(np.arange(xtr.shape[0]),size=50,replace=False)
  protos=xtr[randinds,:]

  gamma=0.05

  traindata_nobiasdim=xtr[:,0].reshape((xtr.shape[0],1))
  protos_nobiasdim=protos[:,0].reshape((protos.shape[0],1))

  fttr=rbffeats(traindata_nobiasdim,protos_nobiasdim,gamma)

  w=linreg_train(fttr,ytr,C=C) # 0.1

  print(w.shape)

  valdata_nobiasdim=xv[:,0].reshape((xv.shape[0],1))

  ftv=rbffeats(valdata_nobiasdim,protos_nobiasdim,gamma)

  ypred=linreg_apply(ftv,w)

  print('xtr',xtr.shape,'w',w.shape,xv.shape,yv.shape,ypred.shape)

  e=mse(ypred,yv)

  plt.figure(2)
  plt.plot(xv[:,0],yv,'go')
  plt.plot(xv[:,0],ypred,'bx')

  #print('mse',e)

  return e

if __name__=='__main__':

  xtr,ytr,xv,yv=gendata(1600,5000)

  plt.figure(3)
  plt.plot(xv[:,0],yv,'go')
  plt.show()

  e=run1(xtr,ytr,xv,yv,1e-3)
  print('mse',e)

  e=run2(xtr,ytr,xv,yv,1e-3)
  print('mse',e)

  plt.show()


import numpy as np
import matplotlib.pyplot as plt
from values_bsm import *

"""
1. f : The function whose roots we want to find
2. a : lower bound on the root
3. b : upper bound on the root
4. e : permissible error in the value of root obtained
5. N_max : maximum number of iterations after which function stops.
"""

"""Function 1"""

# for range 1 : {-3,0}, error = 0.0001
root = 0
f1_N_R1 = []
f1_Roots_R1 = []

for i in range(1,11):
    #print()
    #print("N_Max = {}".format(i))
    root = bisection(func_num = 1,a = -3,b = 0,e = 0.001,N_Max = i)
    f1_N_R1.append(i)
    f1_Roots_R1.append(root)

# for range 2 : {-5,5}, error = 0.0001
root = 0
f1_N_R2 = []
f1_Roots_R2 = []

for i in range(1,11):
    #print()
    #print("N_Max = {}".format(i))
    root = bisection(func_num = 1, a = -5,b = 5,e = 0.001,N_Max = i)
    f1_N_R2.append(i)
    f1_Roots_R2.append(root)

#print(f1_N_R2)
#print(f1_Roots_R2)
#print()

"""Function 2"""

# for range 1 : {-3,0}, error = 0.0001
root = 0
f2_N_R1 = []
f2_Roots_R1 = []

for i in range(1,11):
    #print()
    #print("N_Max = {}".format(i))
    root = bisection(func_num = 2, a = -3,b = 0,e = 0.001,N_Max = i)
    f2_N_R1.append(i)
    f2_Roots_R1.append(root)

# for range 2 : {-5,5}, error = 0.0001
root = 0
f2_N_R2 = []
f2_Roots_R2 = []

for i in range(1,11):
    #print()
    #print("N_Max = {}".format(i))
    root = bisection(func_num = 2, a = -5,b = 5,e = 0.001,N_Max = i)
    f2_N_R2.append(i)
    f2_Roots_R2.append(root)

func = np.vectorize(f)
f1_value_at_roots_R1 = func(1,f1_Roots_R1)
f1_value_at_roots_R2 = func(1,f1_Roots_R2)
f2_value_at_roots_R1 = func(2,f2_Roots_R1)
f2_value_at_roots_R2 = func(2,f2_Roots_R2)

"""
# change the data and use the following code
# to plot and save for both the Functions
plt.plot(f2_N_R1,f2_value_at_roots_R1,'.-',label = 'initial Limit : {-3,0}')
plt.plot(f2_N_R2,f2_value_at_roots_R2,'.-',label = 'initial Limit : {-5,5}')
plt.xlabel('N',fontsize = 10)
plt.ylabel('Function Value at Root',fontsize = 10)
plt.xticks(fontsize = 8)
plt.yticks(fontsize = 8)
plt.title('Bisection Method - Function2 - permissible error : 0.0001')
plt.grid(linestyle = '--')
plt.legend()
plt.savefig('bsm-f2.png')
plt.show()
"""

# Plotting Absolute Values
f1_value_at_roots_R1_ABS =  [abs(ele) for ele in f1_value_at_roots_R1]
f1_value_at_roots_R2_ABS =  [abs(ele) for ele in f1_value_at_roots_R2]
f2_value_at_roots_R1_ABS =  [abs(ele) for ele in f2_value_at_roots_R1]
f2_value_at_roots_R2_ABS =  [abs(ele) for ele in f2_value_at_roots_R2]

# change the data and use the following code
# to plot and save for both the Functions ABSOLUTE VALUES
plt.plot(f2_N_R1,f2_value_at_roots_R1_ABS,'.-',label = 'initial Limit : {-3,0}')
plt.plot(f2_N_R2,f2_value_at_roots_R2_ABS,'.-',label = 'initial Limit : {-5,5}')
plt.xlabel('N',fontsize = 10)
plt.ylabel('Function Value at Root',fontsize = 10)
plt.xticks(fontsize = 8)
plt.yticks(fontsize = 8)
plt.title('Bisection Method - Function2 Absolute Value - permissible error : 0.001')
plt.grid(linestyle = '--')
plt.legend()
plt.savefig('bsm-f2-abs.png')
plt.show()

AS2101_Labwork/3.Trials-References/Task 3/Submission Codes/Bisection Method/bsm_plot/bsm_plot.py


import numpy.linalg
from Point import *
#from bresenham import bresenham

def random_partition(n,n_data):
	"""return n random rows of data (and also the other len(data)-n rows)"""
	all_idxs = numpy.arange( n_data )
	numpy.random.shuffle(all_idxs)
	idxs1 = all_idxs[:n]
	idxs2 = all_idxs[n:]
	return idxs1.tolist(), idxs2.tolist()

def leastSquares(points,  weighted=False):
	x_d = []
	y_d = []
	for point in points:
		(x, y) = point.toTuple()
		if weighted:
			w = point.w
			x_d += [x]*w
			y_d += [y]*w
		else:
			x_d.append(x)
			y_d.append(y)

	n=len(x_d)

	B=numpy.array(y_d)
	A=numpy.array(([[x_d[j], 1] for j in range(n)]))
	X=numpy.linalg.lstsq(A,B)[0]
	a=X[0]; b=X[1]
	#print("Line is: y=",a,"x+",b)

	Xplot = []
	Yplot = []

	x = min(x_d)
	y = a*x + b
	Xplot.append(x)
	Yplot.append(y)

	x = max(x_d)
	y = a*x + b
	Xplot.append(x)
	Yplot.append(y)
	return (Xplot, Yplot, a, b)

def calcResidue(a, b, xi, yi):
	yi_ = a*xi + b
	return (yi - yi_)

def calcTotalError(points, a, b):
	totalError = 0

	n = len(points)
	for point in points:
		(xi, yi) = point.toTuple()
		ei = calcResidue(a, b, xi, yi)
		totalError += ei*ei

	return totalError

class LinearLeastSquaresModel:
	"""linear system solved using linear least squares

	This class serves as an example that fulfills the model interface
	needed by the ransac() function.

	"""
	#def __init__(self):
	def fit(self, data, weighted=False):
		return leastSquares(data, weighted=weighted)
	def get_error( self, data, model):
		(_, _, a, b) = model
		totalError = calcTotalError(data, a, b)
		n = len(data)
		err_per_point = totalError/n
		return err_per_point

def getDataByIndex(data, idxs):
	returnData = []

	if type(idxs) != list:
		print("idxs = ", idxs)
		print("type(idxs) = ", type(idxs))
		returnData = [data[idxs]]
	else:
		for i in idxs:
			returnData.append(data[i])
		#if type(returnData) != type(list):
		#   returnData = [returnData]
	return returnData

def ransac(data,model,n,k,t,d,debug=False,return_all=False, weighted=False, random=True):
	"""fit model parameters to data using the RANSAC algorithm

This implementation written from pseudocode found at
http://en.wikipedia.org/w/index.php?title=RANSAC&oldid=116358182

{{{
Given:
	data - a set of observed data points
	model - a model that can be fitted to data points
	n - the minimum number of data values required to fit the model
	k - the maximum number of iterations allowed in the algorithm
	t - a threshold value for determining when a data point fits a model
	d - the number of close data values required to assert that a model fits well to data
Return:
	bestfit - model parameters which best fit the data (or nil if no good model is found)
iterations = 0
bestfit = nil
besterr = something really large
while iterations < k {
	maybeinliers = n randomly selected values from data
	maybemodel = model parameters fitted to maybeinliers
	alsoinliers = empty set
	for every point in data not in maybeinliers {
		if point fits maybemodel with an error smaller than t
			 add point to alsoinliers
	}
	if the number of elements in alsoinliers is > d {
		% this implies that we may have found a good model
		% now test how good it is
		bettermodel = model parameters fitted to all points in maybeinliers and alsoinliers
		thiserr = a measure of how well model fits these points
		if thiserr < besterr {
			bestfit = bettermodel
			besterr = thiserr
		}
	}
	increment iterations
}
return bestfit
}}}
"""
	alsoinliers = []
	iterations = 0
	bestfit = None
	besterr = numpy.inf
	best_inlier_idxs = None
	while iterations < k:
		maybeinliers = data
		test_points = data
		maybe_idxs, test_idxs = random_partition(n,len(data))
		if random:
			# print("maybe_idxs = ", maybe_idxs)
			# print("test_idxs = ", test_idxs)
			maybeinliers = getDataByIndex(data, maybe_idxs)
			test_points = getDataByIndex(data, test_idxs)
		#print("maybeinliers = ", maybeinliers)

		maybemodel = model.fit(maybeinliers, weighted=weighted)
		test_err = model.get_error( test_points, maybemodel)
		#print("test_err = ", test_err)
		#print("len test_idxs = ", len(test_idxs))
		also_idxs = test_idxs[test_err < t] # select indices of rows with accepted points
		#print("also_idxs = ", also_idxs)

		#print("also_idxs = ", also_idxs)
		#alsoinliers = getDataByIndex(data, also_idxs)
		alsoinliers = []
		test_err = 0
		for point in test_points:
			(_, _, a, b) = maybemodel
			(xi, yi) = point.toTuple()
			ei = abs(calcResidue(a, b, xi, yi))
			#ei = ei*ei
			test_err += ei
			if ei < t:
				alsoinliers.append(point)

		#if alsoinliers != list:
		#	alsoinliers = [alsoinliers]

		if debug:
			print ('test_err.min()',test_err.min())
			print ('test_err.max()',test_err.max())
			print ('numpy.mean(test_err)',numpy.mean(test_err))
			#print("alsoinliers = ", alsoinliers)
			print ('iteration %d:len(alsoinliers) = %d'%(iterations,len(alsoinliers)))
		if len(alsoinliers) > d:
			betterdata = maybeinliers + alsoinliers  #numpy.concatenate( (maybeinliers, alsoinliers) )
			bettermodel = model.fit(betterdata)
			better_errs = model.get_error( betterdata, bettermodel)

			thiserr = 0
			for point in betterdata:
				(_, _, a, b) = bettermodel
				(xi, yi) = point.toTuple()
				ei = abs(calcResidue(a, b, xi, yi))
				#ei = ei*ei
				thiserr += ei
				#if ei < t:
				#	alsoinliers.append(point)

			thiserr = numpy.mean(thiserr)#( better_errs )
			if thiserr < besterr:
				bestfit = bettermodel
				besterr = thiserr
				#best_inlier_idxs = numpy.concatenate( (maybe_idxs, also_idxs) )
				#print("best_inlier_idxs = ", best_inlier_idxs)
		iterations+=1
	if bestfit is None:
		print("did not meet fit acceptance criteria")
		return None
		#raise ValueError("did not meet fit acceptance criteria")

	alsoinliers = sorted(alsoinliers, key=lambda p: p.x)
	# print("alsoinliers: ", alsoinliers)

	return alsoinliers

	if return_all:
		return bestfit#, {'inliers':best_inlier_idxs}
	else:
		return bestfit


#
# undamped pendulum motion, in small-angle approximation
#
# ODE system:   1) dtheta/dt = omega
#               2) domega/dt = -Omega0^2 * theta
#
#     where Omega0^2 = g / L
#
# solved numerically via Euler's method
#
#
# by Denes Molnar for PHYS 580, Fall 2020

import math
import numpy as np

import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt

# compute evolution of theta and omega from given initconds
def computeTrajectory(initconds, Omega0, dt, nsteps):
   (th0, om0) = initconds

   # allocate storage - store both theta and omega
   theta = np.zeros(nsteps + 1)
   omega = np.zeros(nsteps + 1)
   theta[0] = th0
   omega[0] = om0

   # solve using Euler method
   for i in range(nsteps):
      theta[i + 1] = theta[i] + omega[i] * dt
      omega[i + 1] = omega[i] - Omega0**2 * theta[i] * dt

   # return results
   return theta, omega

# get parameters
#

# physical parameters and initial conditions (units are in [] brackets)
L    = float( input('length of pendulum [m]: ') )
th0  = float( input('initial angle wrt to vertical [rad]: ') )
om0  = float( input('initial angular velocity [rad/s]: ') )
#
g    = 9.8      # gravitational acceleration [m/s^2] (hardcoded)

Omega0 = math.sqrt(g / L)

# computational parameters: dt[s], nsteps
dt = float( input('dt [s]: ') )
nsteps = int( input('number of time steps to calculate: ') )

# compute evolution
#

theta, omega = computeTrajectory( (th0, om0), Omega0, dt, nsteps)
# time values, for plotting
timevals = np.array( [ dt * i  for i in range(nsteps + 1) ] )
# calculate energy at each timestep
#energy = [ omega[i]**2 + Omega0*Omega0 * theta[i]**2   for i in range(nsteps + 1) ]
energy = omega**2 + Omega0**2 * theta**2  # use numpy by-element array operations

# plots
#

fontsize = 30   # fontsize, need to be tuned to screen resolution
linewidth = 3

# create 3 plots under each other
fig, axArray = plt.subplots(3, sharex = True)

(ax0, ax1, ax2) = axArray

# theta
ax0.plot(timevals, theta, linewidth = linewidth, label = "theta")

titleString  = "dt=" + str(dt) + ", L=" + str(L)
titleString += ", omega0=" + str(om0) + ", theta0=" + str(th0)

ax0.set_title(titleString, fontsize = fontsize)
ax0.tick_params(labelsize = fontsize)
ax0.legend(fontsize = fontsize)

# omega
ax1.plot(timevals, omega, linewidth = linewidth, label = "omega")

ax1.tick_params(labelsize = fontsize)
ax1.legend(fontsize = fontsize)

# energy
ax2.plot(timevals, energy, linewidth = linewidth, label = "energy")

ax2.tick_params(labelsize = fontsize)
ax2.legend(fontsize = fontsize)
ax2.set_xlabel("t[s]", fontsize = fontsize)

# show plot
plt.show()

#EOF


import numpy as np

def get_epipole(F):
    '''
    Epipole is the eigenvector associated with smallest eigenvalue of F
    '''
    evalue, evector = np.linalg.eig(F)  # normalized evector
    index = np.argmin(evalue)
    epipole = evector[:, index]
    return epipole

def get_rotation_axis(d):
    # d_i to make image plane parallel
    # intersection line
    axis = np.array([-d[1], d[0], 0])
    return axis

def get_angle(epipole, axis):
    return np.arctan(epipole[2] / (axis[1] * epipole[0] - axis[0] * epipole[1]))

def get_plane_rotation_matrix(axis, angle):
    cos_angle = np.cos(angle)
    sin_angle = np.sin(angle)
    dx = axis[0]
    dx2 = dx ** 2
    dy = axis[1]
    dy2 = dy ** 2
    return np.array([[dx2 + (1 - dx2) * cos_angle, dx * dy * (1 - cos_angle), dy * sin_angle],
                     [dx * dy * (1 - cos_angle), dy2 + (1 - dy2) * cos_angle, -dx * sin_angle],
                     [-dy * sin_angle, dx * sin_angle, cos_angle]])

def get_scanline_rotation_matrix(angle):
    cos = np.cos(angle)
    sin = np.sin(angle)
    return np.array([[cos, -sin, 0],
                     [sin, cos, 0],
                     [0, 0, 1]])

def compute_prewarp(F, all=False):
    # apply a 3D rotation to make the image planes parall
    epipole1 = get_epipole(F)
    epipole2 = get_epipole(F.T)
    # step 1: rotate image plane
    axis1 = get_rotation_axis(epipole1)
    theta1 = get_angle(epipole1, axis1)
    R1_plane = get_plane_rotation_matrix(axis1, theta1)

    conjugate = F @ axis1
    axis2 = get_rotation_axis(conjugate)
    theta2 = get_angle(epipole2, axis2)
    R2_plane = get_plane_rotation_matrix(axis2, theta2)

    # step2: align scanline
    new_epipole1 = R1_plane @ epipole1
    new_epipole2 = R2_plane @ epipole2

    phi1 = -np.arctan(new_epipole1[1] / new_epipole1[0])
    phi2 = -np.arctan(new_epipole2[1] / new_epipole2[0])
    R1_scanline = get_scanline_rotation_matrix(phi1)
    R2_scanline = get_scanline_rotation_matrix(phi2)

    # step 3: get homography
    H1 = R1_scanline @ R1_plane
    H2 = R2_scanline @ R2_plane
    H1 /= H1[-1,-1]
    H2 /= H2[-1,-1]
    if all:
        return H1, H2, R1_plane, R2_plane, R1_scanline, R2_scanline
    return H1, H2


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

import numpy as np

base_data = (np.random.random((5, 5)) - 0.5) * 100
print("base_data = \n{}\n".format(base_data))

print("np.amin(base_data) = {}".format(np.amin(base_data)))
print("np.amax(base_data) = {}".format(np.amax(base_data)))
print("np.average(base_data) = {}".format(np.average(base_data)))
print("np.sum(base_data) = {}".format(np.sum(base_data)))
print("np.sin(base_data) = \n{}".format(np.sin(base_data)))

# a = np.array([[1, 2, 3], [4, 5, 6]])
a = np.arange(1, 7).reshape(2, 3)

b = np.array([[2, 1, 0], [1, 1, 1]])

c = a.transpose()

print('\na = %s type=%s\n' % (a, type(a)))
print('b = %s\n' % b)

print('a + 1 = %s' % (a + 1))
print('a * 2 = %s' % (a * 2))
print('a * b = %s' % (a * b))
print('a + b = %s' % (a + b))
print('np.multiply(a, b) = %s' % (np.multiply(a, b)))
print('np.multiply(np.mat(a), np.mat(b)) = %s' % (np.multiply(np.mat(a), np.mat(b))))
print('np.mat(a) * np.mat(b) = %s' % (np.mat(a) * np.mat(c)))

print('c = %s' % c)
print('a.T = %s' % a.T)
print('dot(a, c) = %s\n' % np.dot(a, c))
print('a @ c = %s' % (a @ c))

print('sum=%s min=%s max=%s mean=%s var=%s std=%s\n' % (a.sum(), a.min(), a.max(), a.mean(), a.var(), a.std()))

print('np.hstack = %s\n' % np.hstack((a, b)))
print('np.vstack = %s\n' % np.vstack((a, b)))
print('np.hsplit(a,3) = %s\n' % np.hsplit(a, 3))
print('np.vsplit(a,2) = %s\n' % np.vsplit(a, 2))

print('np.diag(a) = %s' % np.diag(a))
print('diag(diag(a)) = %s' % np.diag(np.diag(a)))

x = np.arange(10)

print('x = %s\n' % x)
print('x[2] = %s\n' % x[2])
print('x[-2] = %s\n' % x[-2])
print('x[2:5] = %s\n' % x[2: 5])
print('x[::-1] = %s\n' % x[::-1])
print('x[::-2] = %s\n' % x[::-2])

indexs = [2, 3, 4]
print('x[indexs]=%s' % (x * 2)[indexs])
x[::2] = [-1, -2, -3, -4, -5]
print('x after::2 = %s\n' % x)
print('x[::1] = %s\n' % x[::2])

print('a[1] = %s\n' % a[1])
print('a[1, :] = %s\n' % a[1, :])
print('a[:, 1] = %s\n' % a[:, 1])
print('a[1:3, 1:3] = %s\n' % a[1:3, 1:3])

x = np.arange(12).reshape(3, 4)
x = x > 4
print('x = %s\n' % x)
print('bools.any = %s\n' % x.any())
print('bools.all = %s\n' % x.all())

print('type(a) = %s' % type(a))
print('type((np.mat(a)) = %s' % type(np.mat(a)))

print('\na = %s\n' % a)
print("a's ndim {}".format(a.ndim))
print("a's shape {}".format(a.shape))
print("a's size {}".format(a.size))
print("a's dtype {}".format(a.dtype))
print("a's itemsize {}".format(a.itemsize))

# 生成均值为loc，标准差为scale的正态分布矩阵，numpy中很多方法如果size不写则返回一个值
y = np.random.normal(10, 1, (2, 5))
print('\ny = %s\n' % y)
print('y.mean=%s y.std=%s' % (y.mean(), y.std()))

D = np.mat("1 2 3; 2 3 1; 3 1 2")
print('D.det = %s' % np.linalg.det(D))
# print('A.det = %s' % np.linalg.det(np.mat(a))) #LinAlgError: Last 2 dimensions of the array must be square
print('\nD = %s type = %s' % (D, type(D)))
D_I = D.I
print('D.I = %s type = %s' % (D_I, type(D_I)))
print('D * D.I = %s' % np.dot(D, D_I))
print('D.I * D = %s' % np.dot(D_I, D))

print('\neye(3) = %s' % (np.eye(3)))

M = np.array([1, 2, 3])
N = np.array([1, 0, 2])
print('\nM = %s    N = %s type(M)=%s' % (M, N, type(M)))

DOT_MN = np.dot(M, N)
print('dot(M,N)=%s  type=%s' % (DOT_MN, type(DOT_MN)))

DOT_MAT_MN = np.dot(np.mat(M), np.mat(N).T)
print('dot(mat(M), mat(N).T)=%s  type=%s' % (DOT_MAT_MN, type(DOT_MAT_MN)))

np_array = np.array([np.inf, -np.inf, np.nan, -128, 128])
print('np.nan_to_num=%s' % np.nan_to_num(np_array))


# -*- coding: utf-8 -*-
"""
Interpolaciones para explicar el fenomeno de Runge

"""
from __future__ import division, print_function
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import lagrange
import sympy as sym

plt.rcParams["axes.spines.right"] = False
plt.rcParams["axes.spines.top"] = False
plt.rcParams["mathtext.fontset"] = "cm"

def base_lagrange(x_datos, var, cont):
    """Crea un polinomio base de Lagrange para los datos x"""
    prod = sym.prod((var - x_datos[i])/(x_datos[cont] - x_datos[i])
                    for i in range(len(x_datos)) if i != cont)

    return sym.lambdify(var, sym.simplify(prod), "numpy")

runge = lambda x: 1/(1 + 25*x**2)
x = np.linspace(-1, 1, 201)
y = runge(x)
x_inter = np.linspace(-1, 1, 11)
y_inter = runge(x_inter)
x_cheb = -np.cos(np.linspace(0, np.pi, 11))
y_cheb = runge(x_cheb)
gris = "#969696"

plt.close("all")

#%% Funcion de Runge
plt.figure(figsize=(4, 3))
plt.plot(x, y)
plt.plot(x_inter, y_inter, "ok")
plt.xlabel("$y$", fontsize=14)
plt.ylabel("$y$", fontsize=14)
plt.savefig("runge_fun.pdf", bbox_inches="tight", transparent=True)

#%% Interpolacion equidistante
plt.figure(figsize=(5, 2.5))
plt.subplot(122)
plt.title("Funciones base")
for cont_base in range(11):
    if cont_base == 5:
        color = None
        zorder = 4
        linewidth = 2
    else:
        color = gris
        zorder = 3
        linewidth = 1
    base = base_lagrange(x_inter, sym.symbols("x"), cont_base)
    y_base = base(x)
    plt.plot(x, y_base, color=color, zorder=zorder, linewidth=linewidth)
    plt.xlabel("$x$", fontsize=14)
    plt.ylabel("$y$", fontsize=14)

plt.subplot(121)
plt.title("Interpolación")
plt.plot(x, y)
inter = lagrange(x_inter, y_inter)
plt.plot(x, inter(x), linestyle="dashed")
plt.plot(x_inter, y_inter, "ok")
plt.xlabel("$x$", fontsize=14)
plt.ylabel("$y$", fontsize=14)
plt.tight_layout()
plt.savefig("runge_equi.pdf", bbox_inches="tight", transparent=True)

#%% Interpolacion con nodes de Chebyshev
plt.figure(figsize=(5, 2.5))
plt.subplot(122)
plt.title("Funciones base")
for cont_base in range(11):
    if cont_base == 5:
        color = None
        zorder = 4
        linewidth = 2
    else:
        color = gris
        zorder = 3
        linewidth = 1
    base = base_lagrange(x_cheb, sym.symbols("x"), cont_base)
    y_base = base(x)
    plt.plot(x, y_base, color=color, zorder=zorder, linewidth=linewidth)
    plt.xlabel("$x$", fontsize=14)
    plt.ylabel("$y$", fontsize=14)

plt.subplot(121)
plt.title("Interpolación")
plt.plot(x, y)
inter = lagrange(x_cheb, y_cheb)
plt.plot(x, inter(x), linestyle="dashed")
plt.plot(x_cheb, y_cheb, "ok")
plt.xlabel("$x$", fontsize=14)
plt.ylabel("$y$", fontsize=14)
plt.tight_layout()
plt.savefig("runge_cheb.pdf", bbox_inches="tight", transparent=True)

#%% Interpolacion con nodes de Chebyshev
plt.figure(figsize=(5, 2.5))
titles = ["Puntos equidistantes", "Puntos no equidistantes"]
x_sample = [x_inter, x_cheb]
for cont in range(2):
    ax = plt.subplot(1, 2, cont + 1)
    plt.title(titles[cont])
    for cont_base in [0, 5]:
        if cont_base == 0:
            linestyle = None
        else:
            linestyle = "dashed"
        base = base_lagrange(x_sample[cont], sym.symbols("x"), cont_base)
        y_base = base(x)
        plt.plot(x, y_base, linestyle=linestyle)

    plt.xlabel("$x$", fontsize=14)
    plt.ylim(-1.5, 7)
    if cont == 0:
        plt.ylabel("$y$", fontsize=14)

plt.legend(["Punto izquierda", "Punto central"])
ax.yaxis.set_ticks([])
ax.spines["left"].set_color("none")
plt.tight_layout()
plt.savefig("runge_comparacion.pdf", bbox_inches="tight", transparent=True)

#plt.show()


import numpy.random as rnd
import matplotlib.pyplot as plt
import numpy as np
import time as tm

# the function walk1D consists of N steps in the positive/negative direction based on a randomly generated value
def walk1D(steps):
    path = np.zeros(steps, dtype=int)

    for i in range(steps-1):
        if rnd.randint(2):
            path[i+1] = path[i] + 1
        else:
            path[i+1] = path[i] - 1

    return path[-1]

def walk1Dgooder(steps):
    direction = np.array([-1,1])

    path =  np.cumsum(direction[rnd.randint(2, size=steps)], axis=0)
    return path[-1]

def walk1Dstats(steps, numWalkers):
    xf = np.zeros(numWalkers)

    for i in range(numWalkers):
        xf[i] = walk1D(steps)

    displacement = np.mean(xf)
    stddev = np.std(xf)
    distance = np.mean(np.abs(xf))

    print("Average displacement: %f metres"  %(displacement))
    print("Standard deviation:   %f metres"  %(stddev))
    print("Average distance:     %f metres"  %(distance))

    return xf

# look at an individual random walk
numSteps = 100000

walk = walk1D(numSteps)
walk = walk1Dgooder(numSteps)

plotTheWalk = 0

# plot the walk:  plot position vs. step number (represented by index, below)
if plotTheWalk:
    index = np.arange(numSteps)
    plt.plot(index, walk)
    plt.plot(index, walk1Dgooder(numSteps), 'g')
    plt.plot(index, walk1Dgooder(numSteps), 'r')
    plt.plot(index, walk1Dgooder(numSteps), 'b')

# look at the statistics for many random walks
# vall the random walk function for each walker, and save into the final_pos array
numWalkers = 1000
numSteps = 1000

# the stats that we are interested in relates to final positions of the walks assuming that all walks start at the same initial position (x=0)
x_final = walk1Dstats(numSteps,numWalkers)

# for walk1Dstats, with function returning x_final:
plotStats = 1
if plotStats:
    r=100
    plt.hist(x_final, range=(-r,r), bins=r)

    plt.title('2-D Random Walk')
    plt.xlabel('Displacement')
    plt.ylabel('Number of Walkers')

# look at the relationship between final_x number of steps
plotRelationship = 0
if plotRelationship:
    num = np.array([10,50,100,500,1000,5000,10**4])
    Dist = np.zeros(num.size)

    for i in range(num.size):
        Dist[i]=walk1Dstats(num[i],numWalkers)

    plt.plot(num,Dist,'ok')

plt.savefig('distribution1D.png')


# Demo of perceptron algorithm in 2d
# Code is modified from various sources, including
#https://medium.com/@thomascountz/19-line-line-by-line-python-perceptron-b6f113b161f3
#http://stamfordresearch.com/scikit-learn-perceptron/
#https://glowingpython.blogspot.com/2011/10/perceptron.html
#https://stackoverflow.com/questions/31292393/how-do-you-draw-a-line-using-the-weight-vector-in-a-linear-perceptron?rq=1
#https://medium.com/@thomascountz/calculate-the-decision-boundary-of-a-single-perceptron-visualizing-linear-separability-c4d77099ef38

import numpy as np
import matplotlib.pyplot as plt

figdir = "../figures"
def save_fig(fname):
    if figdir: plt.savefig(os.path.join(figdir, fname))

np.random.seed(0)

class Perceptron(object):

    def __init__(self, no_of_inputs, max_iter=10, learning_rate=1):
        self.max_iter = max_iter
        self.learning_rate = learning_rate
        self.weights = np.zeros(no_of_inputs + 1)
        self.weights_hist = []

    def predict_single(self, inputs):
        summation = np.dot(inputs, self.weights[1:]) + self.weights[0]
        if summation > 0:
          output = 1
        else:
          output = 0
        return output

    def predict(self, X):
        n = X.shape[0]
        yhat = np.zeros(n)
        for i in range(n):
            yhat[i] = self.predict_single(X[i,:])
        return yhat

    def fit(self, training_inputs, labels):
        for epoch in range(self.max_iter):
            for inputs, label in zip(training_inputs, labels):
                prediction = self.predict_single(inputs)
                self.weights[1:] += self.learning_rate * (label - prediction) * inputs
                self.weights[0] += self.learning_rate * (label - prediction)
                self.weights_hist.append(np.copy(self.weights))

def generateData(n):
     # Generates a 2D linearly separable dataset with 2n samples.
     # Red and blue clusters in top left and bottom right quadrant
     xb = (np.random.rand(n)*2-1)/2-0.5
     yb = (np.random.rand(n)*2-1)/2+0.5
     xr = (np.random.rand(n)*2-1)/2+0.5
     yr = (np.random.rand(n)*2-1)/2-0.5
     XB = np.stack([xb,yb], axis=1)
     XR = np.stack([xr,yr], axis=1)
     X = np.concatenate([XB, XR])
     y = np.concatenate([np.zeros(n, dtype=np.int), np.ones(n, dtype=np.int)])
     return X, y

X, y = generateData(10)

def plot_dboundary_contourf(net):
    x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    h = 0.02
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
                         np.arange(y_min, y_max, h))

    fig, ax = plt.subplots()
    Z = net.predict(np.c_[xx.ravel(), yy.ravel()])

    Z = Z.reshape(xx.shape)
    ax.contourf(xx, yy, Z) #, cmap=plt.cm.Paired)
    ax.axis('off')
    colormap = np.array(['r', 'k'])
    ax.scatter(X[:, 0], X[:, 1], c=colormap[y])
    #ax.axis('square')

def plot_dboundary(weights, offset):
    w1 = weights[0]
    w2 = weights[1]
    b = offset
    slope = -w1/w2
    intercept = -b/w2
    xx = np.linspace(-1, 1, 10)
    yy = xx*slope + intercept
    plt.figure()
    colormap = np.array(['r', 'k'])
    plt.scatter(X[:,0], X[:,1], c=colormap[y], s=40)
    plt.plot(xx, yy, 'k-')

ninputs = 2
net = Perceptron(ninputs)
net.fit(X, y)

w = net.weights[1:]
b = net.weights[0]
plot_dboundary(w, b)
plot_dboundary_contourf(net)

'''
H = net.weights_hist
niter = len(H)
snapshots = [int(t) for t in np.linspace(0, 20, 5)]
#snapshots = [15,  20, 25]
for t in snapshots:
    w = H[t][1:]
    b = H[t][0]
    plot_dboundary(w, b)
    plt.title('iter {}'.format(t))
'''

# sklearn version
from sklearn.linear_model import perceptron
net_sklearn = perceptron.Perceptron()
net_sklearn.fit(X, y)
w = net_sklearn.coef_[0]
offset = net_sklearn.intercept_[0]

plot_dboundary(w, b)
plot_dboundary_contourf(net)


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Apr 23 20:58:05 2020

@author: tyrion
"""
import numpy as np

def sphere(n_grid = 30, equalize_areas = True):
    '''
    Create vectors to conveniently plot a sphere

    Arguments
    ----------
    n_grid  : int, optional, default 30
        Number of grid points for the sphere for both
        longitude and lattitude
    equalize_areas : bool, optional, default True
        If True, enforces that surface patches are of same area

    Returns
    ----------
    x : ndarray
    y : ndarray
    z : ndarray
    '''

    if equalize_areas == True:
        u = np.arccos(np.linspace(-1, 1, n_grid))

    else:
        u = np.linspace(0, np.pi, n_grid)

    v = np.linspace(0, 2 * np.pi, n_grid)

    x = np.outer(np.sin(u), np.sin(v))
    y = np.outer(np.sin(u), np.cos(v))
    z = np.outer(np.cos(u), np.ones_like(v))

    return x, y, z

def evaluate_on_sphere(func, n_grid = 30, equalize_areas = True):
    '''
    Evaluate a function over a sphere

    Arguments
    ----------
    func : callable
        Must be of the form ndarray (n, 3)-> (n, )
    n_grid  : int, optional, default 30
        Number of grid points for the sphere for both
        longitude and lattitude
    equalize_areas : bool, optional, default True
        If True, enforces that surface patches are of same area

    Returns
    ----------
    f : ndarray (n, )
    '''
    if equalize_areas == True:
        u = np.arccos(np.linspace(-1, 1, n_grid))

    else:
        u = np.linspace(0, np.pi, n_grid)
    v = np.linspace(0, 2 * np.pi, n_grid)

    u_grid, v_grid = np.meshgrid(u, v)

    vertex_vecs = np.array([np.sin(v_grid)*np.sin(u_grid), \
                        np.cos(v_grid)*np.sin(u_grid),np.cos(u_grid)])

    vertex_vecs = vertex_vecs.reshape(3,n_grid*n_grid).T

    #calculate function values for the sphere faces

    f_faces = func(vertex_vecs).reshape(n_grid,n_grid).T

    return f_faces

def spherical_hist(vectors, n_grid = 100):
    '''
    Basic spherical histogram

    Arguments
    ----------
    vectors : ndarray (n, 3)
        Vectors to calculate histogram of
    n_grid  : int, optional, default 100
        number of grid points for the sphere for both
        longitude and lattitude

    Returns
    ----------
    hist : ndarray
    x : ndarray
    y : ndarray
    z : ndarray
    '''

    #u = np.linspace(0, np.pi , n_grid, endpoint = True)
    u = np.flip(np.arccos(np.linspace(-1,1,n_grid, endpoint = True)))
    v = np.linspace(-np.pi, np.pi, n_grid, endpoint = True)

    phis = np.arctan2(vectors[:,1], vectors[:,0])
    #print(np.rad2deg(phis))
    thetas = np.arccos(vectors[:,2])

    hist,thetabins, phibins = np.histogram2d(thetas, phis, bins = [u,v])

    #u_surface = np.linspace(0, np.pi , n_grid -1, endpoint = True)
    u_surface = np.flip(np.arccos(np.linspace(-1,1,n_grid -1, endpoint = True)))
    v_surface = np.linspace(-np.pi, np.pi, n_grid -1, endpoint = True)
    #print(u_centers)
    x = np.outer(np.sin(u_surface), np.cos(v_surface))
    y = np.outer(np.sin(u_surface), np.sin(v_surface))
    z = np.outer(np.cos(u_surface), np.ones_like(v_surface))

    return hist, x, y, z


import functools
import math
import numpy as np
from typing import Union

from scipy import special

numberTypes = (float, np.float64, np.float32, int, np.int32, np.int64)
onesDict = {}

def ones(value: np.ndarray):
    if value.shape not in onesDict.keys():
        newOnes = np.ones(value.shape)
        onesDict[value.shape] = newOnes
        return newOnes
    return onesDict[value.shape]

def Sigmoid(value: Union[np.ndarray, float]) -> Union[np.ndarray, float]:
    if isinstance(value, numberTypes):
        if value > 230:
            return 1
        elif value < -230:
            return 0
        return 1 / (1 + math.exp(-value))
    elif isinstance(value, (np.ndarray,)):
        return ones(value) / (ones(value) + np.exp(-value))
    else:
        assert value is None

def SigmoidDerivative(value: Union[np.ndarray, float]):
    if isinstance(value, numberTypes):
        return value * (1 - value)
    elif isinstance(value, np.ndarray):
        return value * (ones(value) - value)
    else:
        assert value is None

def Softplus(value: Union[np.ndarray, float]):
    if isinstance(value, numberTypes):
        return math.log(1 + math.exp(value))
    elif isinstance(value, np.ndarray):
        return np.log(ones(value) + np.exp(value))
    else:
        assert value is None

def SoftplusDerivative(value: Union[np.ndarray, float]):
    if isinstance(value, numberTypes):
        return value / (1 + value)
    elif isinstance(value, np.ndarray):
        return value / (ones(value) + value)
    else:
        assert value is None

def Softmax(value: np.ndarray):
    if isinstance(value, np.ndarray):
        exp = np.exp(value - (ones(value) * np.max(value)))
        return exp / np.sum(exp)
    else:
        assert value is None

def SoftmaxDerivative(value: np.ndarray):
    if isinstance(value, np.ndarray):
        return value * (ones(value) - value)
        # if need derivative of same value, because every element changes the other elements value by definition
    else:
        assert value is None

def Relu(value: Union[np.ndarray, float]):
    if isinstance(value, numberTypes):
        return max(0, value)
    elif isinstance(value, np.ndarray):
        return np.maximum(ones(value)*0, value)
    else:
        assert value is None

def ReluDerivative(value: Union[np.ndarray, float]):
    if isinstance(value, numberTypes):
        if value > 0:
            return 1
        else:
            return 0
    elif isinstance(value, np.ndarray):
        return np.where(value > 0, 1, 0)
    else:
        assert value is None

# Todo maybe add leaky-relu = max(value,0) + x*min(value,0)


# --------------
# Code starts here

import numpy as np

# Code starts here

# Adjacency matrix
adj_mat = np.array([[0,0,0,0,0,0,1/3,0],
                   [1/2,0,1/2,1/3,0,0,0,0],
                   [1/2,0,0,0,0,0,0,0],
                   [0,1,0,0,0,0,0,0],
                  [0,0,1/2,1/3,0,0,1/3,0],
                   [0,0,0,1/3,1/3,0,0,1/2],
                   [0,0,0,0,1/3,0,0,1/2],
                   [0,0,0,0,1/3,1,1/3,0]])

# Compute eigenvalues and eigencevectrs
eigenvalues, eigenvectors=np.linalg.eig(adj_mat)

# Eigen vector corresponding to 1
abs(eigenvectors[:,0])
eigen_1 = abs(eigenvectors[:,0])/np.linalg.norm(eigenvectors[:,0],1)

# most important page
page =  np.where(np.max(eigen_1) == eigen_1)[0][0] + 1

# Code ends here
print(page)

# --------------
# Code starts here

# Initialize stationary vector I
init_I = np.array([1,0,0,0,0,0,0,0])

# Perform iterations for power method
for _ in range(10):
    init_I = np.dot(adj_mat,init_I)
    init_I = init_I/np.linalg.norm(init_I,1)

print(init_I)
power_page = np.where(np.max(init_I) == init_I)[0][0]+1
print(power_page)
# Code ends here

# --------------
new_adj_mat = np.array([[0,0,0,0,0,0,0,0],
                   [1/2,0,1/2,1/3,0,0,0,0],
                  [1/2,0,0,0,0,0,0,0],
                   [0,1,0,0,0,0,0,0],
                   [0,0,1/2,1/3,0,0,1/2,0],
                   [0,0,0,1/3,1/3,0,0,1/2],
                   [0,0,0,0,1/3,0,0,1/2],
                   [0,0,0,0,1/3,1,1/2,0]])

new_init_I = np.array([1,0,0,0,0,0,0,0])
for _ in range(10):
    new_init_I  = np.dot(new_adj_mat, new_init_I)
    new_init_I /= np.linalg.norm(new_init_I,1)

print(new_init_I)

# --------------
# Alpha value
alpha = 0.85

# Code starts here

new_adj_mat = np.array([[0,0,0,0,0,0,0,0],
                   [1/2,0,1/2,1/3,0,0,0,0],
                  [1/2,0,0,0,0,0,0,0],
                   [0,1,0,0,0,0,0,0],
                   [0,0,1/2,1/3,0,0,1/2,0],
                   [0,0,0,1/3,1/3,0,0,1/2],
                   [0,0,0,0,1/3,0,0,1/2],
                   [0,0,0,0,1/3,1,1/2,0]])

# Modified adjancency matrix
G = (alpha*new_adj_mat) + (1-alpha)*(1 / len(new_adj_mat))*np.ones(new_adj_mat.shape)

# Initialize stationary vector I
final_init_I = np.array([1,0,0,0,0,0,0,0])

# Perform iterations for power method
for _ in range(1000):
    final_init_I = np.dot(new_adj_mat, final_init_I)
    final_init_I /= np.linalg.norm(final_init_I, 1)

print(final_init_I)
# Code ends here


#Import stuff
import numpy as np
import matplotlib.pyplot as pp

a = 0.5 #A parameter defined in the problem statement

def initial_guess(x): #A function defining our initial guess
    return 1 + 2*x

def phi(x):  #A function defined in the problem statement
    return 20*np.pi*x*x*x

def phiprime(x):  #The derivative of the above function
    return 60*np.pi*x*x

def phidoubleprime(x):  #The derivative of the derivative of the two-above function
    return 120*np.pi*x

def f(x):  #u''(x) = f(x), which is this f
    return -20. + a*phidoubleprime(x)*np.cos(phi(x)) - a*(phiprime(x))**2 *np.sin(phi(x))

def true_solution(x):  #The analytic solution
    return 1 + 12*x - 10*x*x + a*np.sin(phi(x))

n_grid = 255 #Number of interior grid points
h = 1./float(n_grid+1) #size of grid spacing
u_0 = 1. #Left boundary condition
u_1 = 3. #Right boundary condition
epsilon = 1e-3  #The error tolerance I want to be within the true value before saying I've converged
number_of_iterations_per_level = 100 #Number of iterations per multigrid level

#In what follows, we are trying to solve the matrix equation A u = b

A_diagonal = -2./(h*h)  #The diagonol elements of my A matrix
A_offdiagonal = 1./(h*h) #The off-diagonol elements of my tridiagonol A matrix

u = np.zeros(n_grid) #the solution vector

x_binedges = [] #This list will store the x-values of the edges of the cells
for i in range(n_grid):
    x_binedges.append(float(i+1)/float(n_grid+1))

#initialize the solution vector:
for i in range(n_grid):
    u[i] = initial_guess(x_binedges[i])

b = np.zeros(n_grid)  #The RHS vector

for i in range(n_grid): #Initialize this
    b[i] = f(x_binedges[i])

b[0] = b[0] - u_0/(h*h) #Add LHS boundary condition to b
b[n_grid-1] = b[n_grid-1] - u_1/(h*h) #Add RHS boundary condition to b

#Next two lines is to plot the true solution
x_toplot = np.linspace(0,1,200)
pp.plot(x_toplot,true_solution(x_toplot),label="True Solution",lw=4)

#Begin the iteration
for k255 in range(number_of_iterations_per_level):
  for i in range(len(u)):
    if i==0:  #If we're on the first row, only add A[i][i+1]
        summation = A_offdiagonal * u[i+1]
    elif i== (len(u)-1): #If we're on the last row, only add A[i][i-1]
        summation = A_offdiagonal * u[i-1]
    else:  #Only need to add two elements together
        summation = A_offdiagonal * u[i+1] + A_offdiagonal * u[i-1]
    u[i] = 1./A_diagonal * (b[i] - summation) #Update u based on Jacobi algorithm

#Calculate the residual
A = np.zeros( (n_grid,n_grid) )
for i in range(len(A)):
    for j in range(len(A[i])):
        if i == j:
            A[i][j] = A_diagonal
        elif j == (i-1) or j == (i+1):
            A[i][j] = A_offdiagonal
tau255 = np.add(b, -1.*np.dot(A,u) )

#############################################
#Now to iterate on the residual in the multigrid fashion
#First restrict to 2 times smaller than original grid
tau127 = np.zeros( (n_grid+1)/2 -1)  #The coarser tau
for i in range(len(tau127)):
    tau127[i] = tau255[i*2 + 1]  #Copy values at corresponding cell edges to coarsen

#b127 = np.zeros( (ngrid+1)/2)
#for i in range(len(b127)):
#    b127[i] = b[i*2]
A_diagonal = A_diagonal/4.
A_offdiagonal = A_offdiagonal/4.

error127 = np.zeros( (n_grid+1)/2 -1) #the error vector, set initally to zero

for k127 in range(number_of_iterations_per_level): #Solve A * error127 = tau127
  for i in range(len(error127)):
    if i==0:  #If we're on the first row, only add A[i][i+1]
        summation = A_offdiagonal * error127[i+1]
    elif i== (len(error127)-1): #If we're on the last row, only add A[i][i-1]
        summation = A_offdiagonal * error127[i-1]
    else:  #Only need to add two elements together
        summation = A_offdiagonal * error127[i+1] + A_offdiagonal * error127[i-1]
    error127[i] = 1./A_diagonal * (tau127[i] - summation) #Update error127 based on Jacobi algorithm

A127 = np.zeros( ((n_grid+1)/2-1,(n_grid+1)/2-1) ) #Make a matrix A for the tau prime calculation
for i in range(len(A127)):
    for j in range(len(A127[i])):
        if i == j:
            A127[i][j] = A_diagonal
        elif j == (i-1) or j == (i+1):
            A127[i][j] = A_offdiagonal

tau127_prime = np.add(tau127, -1.*np.dot(A127,error127)) #Calculate a tau prime to communicate to
                                                           #Next restriction

#############################################
#Now restrict to 4 times smaller than original grid
tau63 = np.zeros( (n_grid+1)/4 -1)  #The coarser tau
for i in range(len(tau63)):
    tau63[i] = tau127_prime[i*2 + 1]  #Copy values at corresponding cell edges to coarsen

A_diagonal = A_diagonal/4.
A_offdiagonal = A_offdiagonal/4.

error63 = np.zeros( (n_grid+1)/4 -1) #the error vector, set initally to zero

for k63 in range(number_of_iterations_per_level): #Solve A * error63 = tau63
  for i in range(len(error63)):
    if i==0:  #If we're on the first row, only add A[i][i+1]
        summation = A_offdiagonal * error63[i+1]
    elif i== (len(error63)-1): #If we're on the last row, only add A[i][i-1]
        summation = A_offdiagonal * error63[i-1]
    else:  #Only need to add two elements together
        summation = A_offdiagonal * error63[i+1] + A_offdiagonal * error63[i-1]
    error63[i] = 1./A_diagonal * (tau63[i] - summation) #Update error63 based on Jacobi algorithm

A63 = np.zeros( ((n_grid+1)/4 -1,(n_grid+1)/4 -1) ) #Make a matrix A for the tau prime calculation
for i in range(len(A63)):
    for j in range(len(A63[i])):
        if i == j:
            A63[i][j] = A_diagonal
        elif j == (i-1) or j == (i+1):
            A63[i][j] = A_offdiagonal

tau63_prime = np.add(tau63, -1.*np.dot(A63,error63)) #Calculate a tau prime to communicate to
                                                           #Next restriction

#############################################
#Now restrict to 8 times smaller than original grid
tau31 = np.zeros( (n_grid+1)/8 -1)  #The coarser tau
for i in range(len(tau31)):
    tau31[i] = tau63_prime[i*2 + 1]  #Copy values at corresponding cell edges to coarsen

A_diagonal = A_diagonal/4.
A_offdiagonal = A_offdiagonal/4.

error31 = np.zeros( (n_grid+1)/8 -1) #the error vector, set initally to zero

for k31 in range(number_of_iterations_per_level): #Solve A * error31 = tau31
  for i in range(len(error31)):
    if i==0:  #If we're on the first row, only add A[i][i+1]
        summation = A_offdiagonal * error31[i+1]
    elif i== (len(error31)-1): #If we're on the last row, only add A[i][i-1]
        summation = A_offdiagonal * error31[i-1]
    else:  #Only need to add two elements together
        summation = A_offdiagonal * error31[i+1] + A_offdiagonal * error31[i-1]
    error31[i] = 1./A_diagonal * (tau31[i] - summation) #Update error31 based on Jacobi algorithm

A31 = np.zeros( ((n_grid+1)/8 -1,(n_grid+1)/8 -1) ) #Make a matrix A for the tau prime calculation
for i in range(len(A31)):
    for j in range(len(A31[i])):
        if i == j:
            A31[i][j] = A_diagonal
        elif j == (i-1) or j == (i+1):
            A31[i][j] = A_offdiagonal

tau31_prime = np.add(tau31, -1.*np.dot(A31,error31)) #Calculate a tau prime to communicate to
                                                           #Next restriction

#############################################
#Now restrict to 16 times smaller than original grid
tau15 = np.zeros( (n_grid+1)/16 -1)  #The coarser tau
for i in range(len(tau15)):
    tau15[i] = tau31_prime[i*2 + 1]  #Copy values at corresponding cell edges to coarsen

A_diagonal = A_diagonal/4.
A_offdiagonal = A_offdiagonal/4.

error15 = np.zeros( (n_grid+1)/16 -1) #the error vector, set initally to zero

for k15 in range(number_of_iterations_per_level): #Solve A * error15 = tau15
  for i in range(len(error15)):
    if i==0:  #If we're on the first row, only add A[i][i+1]
        summation = A_offdiagonal * error15[i+1]
    elif i== (len(error15)-1): #If we're on the last row, only add A[i][i-1]
        summation = A_offdiagonal * error15[i-1]
    else:  #Only need to add two elements together
        summation = A_offdiagonal * error15[i+1] + A_offdiagonal * error15[i-1]
    error15[i] = 1./A_diagonal * (tau15[i] - summation) #Update error15 based on Jacobi algorithm

A15 = np.zeros( ((n_grid+1)/16 -1,(n_grid+1)/16 -1) ) #Make a matrix A for the tau prime calculation
for i in range(len(A15)):
    for j in range(len(A15[i])):
        if i == j:
            A15[i][j] = A_diagonal
        elif j == (i-1) or j == (i+1):
            A15[i][j] = A_offdiagonal

tau15_prime = np.add(tau15, -1.*np.dot(A15,error15)) #Calculate a tau prime to communicate to
                                                           #Next restriction

#############################################
#Now restrict to 32 times smaller than original grid
tau7 = np.zeros( (n_grid+1)/32 -1)  #The coarser tau
for i in range(len(tau7)):
    tau7[i] = tau15_prime[i*2 + 1]  #Copy values at corresponding cell edges to coarsen

A_diagonal = A_diagonal/4.
A_offdiagonal = A_offdiagonal/4.

error7 = np.zeros( (n_grid+1)/32 -1) #the error vector, set initally to zero

for k7 in range(number_of_iterations_per_level): #Solve A * error7 = tau7
  for i in range(len(error7)):
    if i==0:  #If we're on the first row, only add A[i][i+1]
        summation = A_offdiagonal * error7[i+1]
    elif i== (len(error7)-1): #If we're on the last row, only add A[i][i-1]
        summation = A_offdiagonal * error7[i-1]
    else:  #Only need to add two elements together
        summation = A_offdiagonal * error7[i+1] + A_offdiagonal * error7[i-1]
    error7[i] = 1./A_diagonal * (tau7[i] - summation) #Update error7 based on Jacobi algorithm

A7 = np.zeros( ((n_grid+1)/32 -1,(n_grid+1)/32 -1) ) #Make a matrix A for the tau prime calculation
for i in range(len(A7)):
    for j in range(len(A7[i])):
        if i == j:
            A7[i][j] = A_diagonal
        elif j == (i-1) or j == (i+1):
            A7[i][j] = A_offdiagonal

tau7_prime = np.add(tau7, -1.*np.dot(A7,error7)) #Calculate a tau prime to communicate to
                                                           #Next restriction

#############################################
#Now restrict to 64 times smaller than original grid
tau3 = np.zeros( (n_grid+1)/64 -1)  #The coarser tau
for i in range(len(tau3)):
    tau3[i] = tau7_prime[i*2 + 1]  #Copy values at corresponding cell edges to coarsen

A_diagonal = A_diagonal/4.
A_offdiagonal = A_offdiagonal/4.

error3 = np.zeros( (n_grid+1)/64 -1) #the error vector, set initally to zero

for k3 in range(number_of_iterations_per_level): #Solve A * error3 = tau3
  for i in range(len(error3)):
    if i==0:  #If we're on the first row, only add A[i][i+1]
        summation = A_offdiagonal * error3[i+1]
    elif i== (len(error3)-1): #If we're on the last row, only add A[i][i-1]
        summation = A_offdiagonal * error3[i-1]
    else:  #Only need to add two elements together
        summation = A_offdiagonal * error3[i+1] + A_offdiagonal * error3[i-1]
    error3[i] = 1./A_diagonal * (tau3[i] - summation) #Update error3 based on Jacobi algorithm

A3 = np.zeros( ((n_grid+1)/64 -1,(n_grid+1)/64 -1) ) #Make a matrix A for the tau prime calculation
for i in range(len(A3)):
    for j in range(len(A3[i])):
        if i == j:
            A3[i][j] = A_diagonal
        elif j == (i-1) or j == (i+1):
            A3[i][j] = A_offdiagonal

tau3_prime = np.add(tau3, -1.*np.dot(A3,error3)) #Calculate a tau prime to communicate to
                                                           #Next restriction

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

#### Now work our way back "up" the V

####################################
###Moving from 3 grids to 7 grids

error3_prolongated = np.zeros( (n_grid+1)/32 -1)  #Create the prolongated error vector for moving to finer grid
for i in range(len(error3_prolongated)):  #Do simple interpolation to prolongate the error vector
    if i == 0:
        error3_prolongated[0] = .5*(u_0 + error3[0])
    elif i == (len(error3_prolongated) -1):
        error3_prolongated[-1] = .5*(u_1 + error3[-1])
    elif i%2 == 1:
        error3_prolongated[i] = error3[(i-1)/2]
    else:
        error3_prolongated[i] = .5*(error3[i/2] + error3[i/2-1])

A_diagonal = A_diagonal*4.
A_offdiagonal = A_offdiagonal*4.

error7 = np.add(error7 , error3_prolongated)  #Update error7 based on error3_prolongated vector

for k7 in range(number_of_iterations_per_level): #Solve A * error7 = tau7
  for i in range(len(error7)):
    if i==0:  #If we're on the first row, only add A[i][i+1]
        summation = A_offdiagonal * error7[i+1]
    elif i== (len(error7)-1): #If we're on the last row, only add A[i][i-1]
        summation = A_offdiagonal * error7[i-1]
    else:  #Only need to add two elements together
        summation = A_offdiagonal * error7[i+1] + A_offdiagonal * error7[i-1]
    error7[i] = 1./A_diagonal * (tau7[i] - summation) #Update error7 based on Jacobi algorithm

####################################
###Moving from 7 grids to 15 grids

error7_prolongated = np.zeros( (n_grid+1)/16 -1)  #Create the prolongated error vector for moving to finer grid
for i in range(len(error7_prolongated)):  #Do simple interpolation to prolongate the error vector
    if i == 0:
        error7_prolongated[0] = .5*(u_0 + error7[0])
    elif i == (len(error7_prolongated) -1):
        error7_prolongated[-1] = .5*(u_1 + error7[-1])
    elif i%2 == 1:
        error7_prolongated[i] = error7[(i-1)/2]
    else:
        error7_prolongated[i] = .5*(error7[i/2] + error7[i/2-1])

A_diagonal = A_diagonal*4.
A_offdiagonal = A_offdiagonal*4.

error15 = np.add(error15 , error7_prolongated)  #Update error15 based on error7_prolongated vector

for k15 in range(number_of_iterations_per_level): #Solve A * error15 = tau15
  for i in range(len(error15)):
    if i==0:  #If we're on the first row, only add A[i][i+1]
        summation = A_offdiagonal * error15[i+1]
    elif i== (len(error15)-1): #If we're on the last row, only add A[i][i-1]
        summation = A_offdiagonal * error15[i-1]
    else:  #Only need to add two elements together
        summation = A_offdiagonal * error15[i+1] + A_offdiagonal * error15[i-1]
    error15[i] = 1./A_diagonal * (tau15[i] - summation) #Update error15 based on Jacobi algorithm

####################################
###Moving from 15 grids to 31 grids

error15_prolongated = np.zeros( (n_grid+1)/8 -1)  #Create the prolongated error vector for moving to finer grid
for i in range(len(error15_prolongated)):  #Do simple interpolation to prolongate the error vector
    if i == 0:
        error15_prolongated[0] = .5*(u_0 + error15[0])
    elif i == (len(error15_prolongated) -1):
        error15_prolongated[-1] = .5*(u_1 + error15[-1])
    elif i%2 == 1:
        error15_prolongated[i] = error15[(i-1)/2]
    else:
        error15_prolongated[i] = .5*(error15[i/2] + error15[i/2-1])

A_diagonal = A_diagonal*4.
A_offdiagonal = A_offdiagonal*4.

error31 = np.add(error31 , error15_prolongated)  #Update error31 based on error15_prolongated vector

for k31 in range(number_of_iterations_per_level): #Solve A * error31 = tau31
  for i in range(len(error31)):
    if i==0:  #If we're on the first row, only add A[i][i+1]
        summation = A_offdiagonal * error31[i+1]
    elif i== (len(error31)-1): #If we're on the last row, only add A[i][i-1]
        summation = A_offdiagonal * error31[i-1]
    else:  #Only need to add two elements together
        summation = A_offdiagonal * error31[i+1] + A_offdiagonal * error31[i-1]
    error31[i] = 1./A_diagonal * (tau31[i] - summation) #Update error31 based on Jacobi algorithm

####################################
###Moving from 31 grids to 63 grids

error31_prolongated = np.zeros( (n_grid+1)/4 -1)  #Create the prolongated error vector for moving to finer grid
for i in range(len(error31_prolongated)):  #Do simple interpolation to prolongate the error vector
    if i == 0:
        error31_prolongated[0] = .5*(u_0 + error31[0])
    elif i == (len(error31_prolongated) -1):
        error31_prolongated[-1] = .5*(u_1 + error31[-1])
    elif i%2 == 1:
        error31_prolongated[i] = error31[(i-1)/2]
    else:
        error31_prolongated[i] = .5*(error31[i/2] + error31[i/2-1])

A_diagonal = A_diagonal*4.
A_offdiagonal = A_offdiagonal*4.

error63 = np.add(error63 , error31_prolongated)  #Update error63 based on error31_prolongated vector

for k63 in range(number_of_iterations_per_level): #Solve A * error63 = tau63
  for i in range(len(error63)):
    if i==0:  #If we're on the first row, only add A[i][i+1]
        summation = A_offdiagonal * error63[i+1]
    elif i== (len(error63)-1): #If we're on the last row, only add A[i][i-1]
        summation = A_offdiagonal * error63[i-1]
    else:  #Only need to add two elements together
        summation = A_offdiagonal * error63[i+1] + A_offdiagonal * error63[i-1]
    error63[i] = 1./A_diagonal * (tau63[i] - summation) #Update error63 based on Jacobi algorithm

####################################
###Moving from 63 grids to 127 grids

error63_prolongated = np.zeros( (n_grid+1)/2 -1)  #Create the prolongated error vector for moving to finer grid
for i in range(len(error63_prolongated)):  #Do simple interpolation to prolongate the error vector
    if i == 0:
        error63_prolongated[0] = .5*(u_0 + error63[0])
    elif i == (len(error63_prolongated) -1):
        error63_prolongated[-1] = .5*(u_1 + error63[-1])
    elif i%2 == 1:
        error63_prolongated[i] = error63[(i-1)/2]
    else:
        error63_prolongated[i] = .5*(error63[i/2] + error63[i/2-1])

A_diagonal = A_diagonal*4.
A_offdiagonal = A_offdiagonal*4.

error127 = np.add(error127 , error63_prolongated)  #Update error127 based on error63_prolongated vector

for k127 in range(number_of_iterations_per_level): #Solve A * error127 = tau127
  for i in range(len(error127)):
    if i==0:  #If we're on the first row, only add A[i][i+1]
        summation = A_offdiagonal * error127[i+1]
    elif i== (len(error127)-1): #If we're on the last row, only add A[i][i-1]
        summation = A_offdiagonal * error127[i-1]
    else:  #Only need to add two elements together
        summation = A_offdiagonal * error127[i+1] + A_offdiagonal * error127[i-1]
    error127[i] = 1./A_diagonal * (tau127[i] - summation) #Update error127 based on Jacobi algorithm

####################################
###Moving from 127 grids to 255 grids

error127_prolongated = np.zeros( (n_grid+1) -1)  #Create the prolongated error vector for moving to finer grid
for i in range(len(error127_prolongated)):  #Do simple interpolation to prolongate the error vector
    if i == 0:
        error127_prolongated[0] = .5*(u_0 + error127[0])
    elif i == (len(error127_prolongated) -1):
        error127_prolongated[-1] = .5*(u_1 + error127[-1])
    elif i%2 == 1:
        error127_prolongated[i] = error127[(i-1)/2]
    else:
        error127_prolongated[i] = .5*(error127[i/2] + error127[i/2-1])

A_diagonal = A_diagonal*4.
A_offdiagonal = A_offdiagonal*4.

print A_diagonal*h*h
print A_offdiagonal*h*h

####Whew! Enough already!  Now for the final solution

u = np.add(u, error127_prolongated)  #Update u based on error255_prolongated vector

for k in range(number_of_iterations_per_level): #Solve A * u = b
  for i in range(len(u)):
    if i==0:  #If we're on the first row, only add A[i][i+1]
        summation = A_offdiagonal * u[i+1]
    elif i== (len(u)-1): #If we're on the last row, only add A[i][i-1]
        summation = A_offdiagonal * u[i-1]
    else:  #Only need to add two elements together
        summation = A_offdiagonal * u[i+1] + A_offdiagonal * u[i-1]
    u[i] = 1./A_diagonal * (b[i] - summation) #Update u based on Jacobi algorithm

#Plot the final solution and save the figure
pp.plot(x_binedges,u,label='final iteration', lw=2.5)
pp.xlabel("x")
pp.ylabel("u(x)")
pp.legend(loc='best')
pp.xlim(0,1)
#pp.ylim(0,5.5)
pp.savefig('problem1b.pdf')