# 将阈值转换为偏置实现AND、OR、NAND
# 在阈值的式子为 w1 * x1 + w2 * x2 <= theta 为0， w1 * x1 + w2 * x2 > theta 为1
# 令theta = -b, 通过移项可得到如下式子
# w1 * x1 + w2 * x2 + b <= 0 为 0， w1 * x1 + w2 * x2 + b > 0 为 1
# w1, w2是控制输入信号的重要性参数， 而bias偏置的值决定了激活神经元的难易程度
import numpy as np

def AND(x1, x2): # 使用numpy完成权重和的计算
    x = np.array([x1, x2])
    w = np.array([0.5, 0.5])
    b = -0.7
    res = np.sum(w * x) + b
    if res <= 0:
        return 0
    else:
        return 1

def OR(x1, x2):
    x = np.array([x1, x2])
    w = np.array([0.5, 0.5])
    b = -0.3
    res = np.sum(w * x) + b
    if res <= 0:
        return 0
    else:
        return 1

def NAND(x1, x2):
    x = np.array([x1, x2])
    w = np.array([-0.5, -0.5])
    b = 0.7
    res = np.sum(w * x) + b
    if res <= 0:
        return 0
    else:
        return 1

print('Test AND:')
print(AND(0, 0))
print(AND(0, 1))
print(AND(1, 0))
print(AND(1, 1))

print('Test OR:')
print(OR(0, 0))
print(OR(0, 1))
print(OR(1, 0))
print(OR(1, 1))

print('Test NAND:')
print(NAND(0, 0))
print(NAND(0, 1))
print(NAND(1, 0))
print(NAND(1, 1))


import copy
import heapq
import numpy as np
from typing import Iterable, Tuple

def normalize(vector):
    """
    Returns the normalized vector (same dtype) using the L2 norm (||v||2) for a vector, v.
    """
    return vector / np.sqrt(np.dot(vector, np.conjugate(vector)))

def cosine_similarity(vector1, vector2):
    """
    Returns the cosine of the angle between vector1 and vector 2
    """
    vector1 = np.array(vector1)
    vector2 = np.array(vector2)

    norm1 = np.sqrt(np.dot(vector1, np.conjugate(vector1)))
    norm2 = np.sqrt(np.dot(vector2, np.conjugate(vector2)))
    return np.real(np.dot(vector1, np.conjugate(vector2)) / (norm1*norm2))

def bind(vec1, vec2, field=np.float):
    if np.issubdtype(field, np.complex128):
        return circular_convolution_complex(vec1, vec2)
    else:
        return circular_convolution(vec1, vec2)

def circular_convolution(vec1, vec2):
    """
    Returns a vector that is the circular convolution of vec1 and vec2.

    Implementation is one of several suggestions from
    https://stackoverflow.com/questions/35474078/python-1d-array-circular-convolution

    vec1 and vec2: array-like, e.g., list of floats
    """
    return np.real(np.fft.ifft(np.fft.fft(vec1) * np.fft.fft(vec2)))

def exact_circular_correlation(vec1, vec2):
    """
    Returns a vector that is the circular correlation of vec1 and vec2.
    vec1 and vec2: array-like, e.g., list of floats
    """
    return np.real(np.fft.ifft(np.fft.fft(vec1) / np.fft.fft(vec2)))

def circular_convolution_complex(vec1, vec2):
    """
    Returns a vector from complex vectors by adding their respective phase angles
    """
    return vec1 * vec2

def circular_correlation_complex(vec1, vec2):
    """
    Returns a vector from complex vectors by subtracting their respective phase angles
    """
    return vec1 / vec2

def create_dense_random_vector(dimension: int, seed=None, field=np.float):
    if seed:
        np.random.seed(seed)

    if np.issubdtype(field, np.complex128):
        real_part = np.random.uniform(low=-1, high=1, size=dimension)
        complex_part = np.random.uniform(low=-1, high=1, size=dimension)
        return real_part + 1j * complex_part

    return np.random.uniform(low=-1, high=1, size=dimension)

def get_k_neighbors_from_pairs(object_vectors: Iterable[Tuple[object, Iterable[float]]],
                               query_vector: Iterable[float], k: int) -> Iterable[Tuple[object, float]]:
    """
    Gets the nearest k vectors and their cosine similarity scores.
    :param object_vectors: Iterable of (object, vector), e.g., from calling .items on a dict[word:vector]
    :param query_vector: Vector for matching
    :param k: Number of neighbors to find
    :return: Iterable of (object, score) pairs.
    """
    object_scores = iter(map(lambda word_vec: (word_vec[0], cosine_similarity(word_vec[1], query_vector)),
                             object_vectors))
    k_best = heapq.nlargest(k, object_scores, key=lambda x: x[1])
    return k_best


# A Sphere, brough to you by PharaohCola13

import mpl_toolkits.mplot3d.axes3d as p3
import matplotlib.pyplot as plt
from matplotlib import *
from numpy import *
from mpl_toolkits.mplot3d.art3d import *
from matplotlib.animation import *

name = "Sine Surface"

def shape(fig, alpha, color, edge_c, edge_w, grid, sides, edges, multi_pi, radius):

	# Definition of x
	def x_(u,v):
		    x = a * sin(u)
		    return x

	# Definition of y
	def y_(u,v):
		    y = a * sin(v)
		    return y

	# Definition of z
	def z_(u,v):
		    z  = a * sin(u + v)
		    return z

	a = radius #
	# Values of the angles
	s = sides
	u = linspace(0, 2 * pi, edges)
	v = linspace(0, 2 * pi, s + 1)

	u, v = meshgrid(u, v)

	# Symbolic Representation
	x = x_(u,v)
	y = y_(u,v)
	z = z_(u,v)

	# Figure Properties
	ax = p3.Axes3D(fig)
	ax.set_facecolor('black') # Figure background turns black

	# Axis Properties
	plt.axis(grid) # Turns off the axis grid
	plt.axis('equal')

	# Axis Limits
	#ax.set_xlim(-1,1)
	#ax.set_ylim(-1,1)
	#ax.set_zlim(-1,1)

	# Surface Plot
	sine = ax.plot_surface(x, y, z)

	sine.set_alpha(alpha) # Transparency of figure
	sine.set_edgecolor(edge_c) # Edge color of the lines on the figure
	sine.set_linewidth(edge_w) # Line width of the edges
	sine.set_facecolor(color) # General color of the figure


# -*- coding: utf-8 -*-
"""
Interpolazione numerica in base di Lagrange

Pn = sommatoria di j = 0 -> yj * Lj
"""
import numpy as np
import math

"""
    np.poly() -> dato un vettore es [2,3] restituisce (x-2)(x-3)

"""
def polinomioLagrange(xnodi, i):
    n = xnodi.size
    xzeri = []
    if i == 0:
        xzeri = xnodi[1:n]
    else:
        xzeri = np.append(xnodi[:i], xnodi[i + 1:n])
    num = np.poly(xzeri)
    den = np.polyval(num, xnodi[i])
    return num/den

"""
    Devo restituire le varie approssimazioni
    mi costruisco L = [L0(x0), ..., L0(xn)]
                      [...,     ...       ]
                      [Ln(x0), ..., Ln(xn)]

    Avendo poi y della forma y = [y0, ..., yn]
    facendo y[:] * L[ : , k] ottengo Pn(k)

"""
def interpolL(x, y, xx):
    """"
        %funzione che determina in un insieme di punti il valore del polinomio
        %interpolante ottenuto dalla formula di Lagrange.
        % DATI INPUT
        %  x  vettore con i nodi dell'interpolazione
        %  f  vettore con i valori dei nodi
        %  xx vettore con i punti in cui si vuole calcolare il polinomio
        % DATI OUTPUT
        %  y vettore contenente i valori assunti dal polinomio interpolante
        %
     """
    m = x.size
    n = xx.size
    somma = 0
    L = np.zeros((m,n))
    for i in range(m):
        L[i, :] = np.polyval(polinomioLagrange(x, i), xx)
    return np.dot(y,L)

"""
    Nodi di Chebishev
"""
def zeri_Cheb(a,b,n):
    t1=(a+b)/2
    t2=(b-a)/2
    x=np.zeros((n+1,))
    for k in range(n+1):
        x[k]=t1+t2*np.cos(((2*k+1)/(2*(n+1))*math.pi))
    return x


import numpy as np
import matplotlib.pyplot as plt

'''
 Utility functions
'''
def calc_phi2d(fx, fy, dx=1, dy=1):
	'''
	Calculate phi by integrating dphi = fx*dx + fy*dy, phi[0,0] = 0 (<---> [fx, fy] = grad(phi))
	'''
	ny,nx=fx.shape
	phi=np.zeros((ny,nx))
	for jx in range(1,nx):
		phi[0,jx]=phi[0,jx-1]+fx[0,jx]*dx
	for jy in range(1,ny):
		phi[jy,:]=phi[jy-1,:]+fy[jy,:]*dy
	return phi

def calc_psi2d(fx, fy, dx=1, dy=1):#solenoidal flows
	'''
	Calcualte psi by integrating dpsi = -fy*dx + fx*dy, psi[0,0]=0.
	Notes:
		1. (fx=dpsi/dy,fy=-dpsi/dx) is called Hamiltonian gradient of psi, and	contours of psi give vector field (fx, fy);
		2. div(f)=0
	'''
	#FIXME: avoid loop for higher performance
	ny,nx=fx.shape
	psi=np.zeros((ny,nx))
	for jx in range(1,nx):
		#psi[0,jx]=psi[0,jx-1]-0.5*(fy[0,jx-1]+fy[0,jx])*dx
		psi[0,jx]=psi[0,jx-1]-fy[0,jx]*dx
	for jy in range(1,ny):
		#psi[jy,:]=psi[jy-1,:]+0.5*(fx[jy-1,:]+fx[jy,:])*dy
		psi[jy,:]=psi[jy-1,:]+fx[jy,:]*dy
	# since f = rot(A) gives extra restraints on f (e.g., div(f)=0)
	# it makes sense that information provided by fy[1:nx,:] is useless here
	return psi

def calc_psi2d_Kai(fx,fy, dx=1, dy=1):
	ny,nx=fx.shape
	psi=np.zeros((ny,nx))
	for i in range(1,ny):
	    psi[i,0] = psi[i-1,0] + dy * ( fx[i,0] + fx[i-1,0] )/2.
	for j in range(1,nx):
	    psi[:,j] = psi[:,j-1] - dx * ( fy[:,j-1] + fy[:,j] )/2.
	return psi


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

# https://www.jianshu.com/p/550c90dfffa0
# 如果两个数组的后缘维度（即从末尾开始算起的维度）的轴长度相等或其中一方的长度为1，则认为他们是广播兼容的，广播会在缺失和(或)长度为1的维度上进行。

import numpy as np

arr = np.arange(12).reshape(4, 3)
print(arr)  # (4, 3)
print(f'arr.ndim={arr.ndim}\n')

mean0 = arr.mean(0)  # (3,)
print(f'mean0={mean0}')

print(f'arr-mean0={arr - mean0}\n')

mean1 = arr.mean(1)  # (4,)
mean1 = mean1.reshape((4, 1))
print(f'mean1={mean1}')

print(f'arr-mean1={arr - mean1}')


# Converted from fdstencil.ipynb and then edited

# coding: utf-8

# # Computing finite difference stencils
#
# [AMath 585, Winter Quarter 2020](http://staff.washington.edu/rjl/classes/am585w2020/) at the University of Washington. Developed by R.J. LeVeque and distributed under the [BSD license](https://github.com/rjleveque/amath585w2020/blob/master/LICENSE).  You are free to modify and use as you please, with attribution.
#
# These notebooks are all [available on Github](https://github.com/rjleveque/amath585w2020/).

# This notebook illustrates how finite difference coefficients can be determined for approximating the $k$th derivative of a function at some point $\bar x$, based on function values at $n$ nearby points, with $n > k$.  If the points have uniform spacing $\Delta x$, then the order of accuracy will be ${\cal O}\left((\Delta x)^{n-k}\right)$ in general, but the points need not be equally spaced.
#
# The general idea is to determine the interpolating polynomial of degree $n-1$ and compute the $k$th derivative of this at the specified point.
#

# In[2]:

from pylab import *
from scipy.special import factorial

# ## Vandermonde matrix solution
#
# The simplest way to determine the coefficients is to derive an $n \times n$ linear system of equations involving a Vandermonde matrix, as described in Section 1.5 of [[FDM-book]](http://faculty.washington.edu/rjl/fdmbook/), and implemented there as a simple Matlab code (also available as `fdcoeffV.m` from the book website).  Here is the Python equivalent:

# In[3]:

def fdcoeffV(k,xbar,x):
    x = array(x)  # in case a list or tuple passed in, convert to numpy array
    n = len(x)
    if k >=n:
        raise ValueError('*** len(x) must be larger than k')

    A = ones((n,n))
    xrow = x - xbar  # displacement vector

    for i in range(1,n):
        A[i,:] = (xrow**i) / factorial(i)

    condA = cond(A)  # condition number
    if condA > 1e8:
        print("Warning: condition number of Vandermonde matrix is approximately %.1e" % condA)

    b = zeros(x.shape)
    b[k] = 1.

    c = solve(A,b)

    return c

# ## Printing the stencil
#
# If you want to compute the weights in general, we can choose $\Delta x = 1$ and then no
# te that any second-derivative difference approximation will have $\Delta x^2$ in the denominator.  In the function below this is made more general, to work for any order $k$ derivative, but assuming equally spaced points.

# In[5]:

def fdstencil(k, jbar, stencil_points):
    """
    Compute and print the finite difference stencil for an order k derivative
    using at least k+1 equally spaced points.
    The stencil_points are thus assumed to be integers (indices of stencil points)
    as is jbar, the index at which the approximation is to be used.

    For example, the standard second order stencil for u''(x_0)
    can be printed via
        fdstencil(2, 0, [-1,0,1])
    """
    assert type(jbar) is int, '*** jbar should be an integer'
    stencil_pts = array(stencil_points)
    assert stencil_pts.dtype == int, '*** stencil_points should be integers'

    c = fdcoeffV(k, jbar, stencil_pts)

    print("Stencil for approximation to order %s derivative at U_{%s} is:"           % (str(k), str(jbar)))
    coeffs = ['%s / h^2' % str(cj) for cj in c]
    s = '['
    for j in range(len(c)):
        subj = str(stencil_pts[j])
        cj = c[j]
        sj = '%g U_{%s}' % (abs(cj), stencil_pts[j])
        if cj >= 0:
            s = s + ' + ' + sj
        else:
            s = s + ' - ' + sj
    s = s + '] / h^%i' % k
    print(s)
    return c

# ## Fornberg's method
#

# A more stable approach was introduced by B. Fornberg in
# - Generation of Finite Difference Formulas on Arbitrarily Spaced Grids, *Mathematics of Computation* 51(1988) pp. 699-706, [doi:10.1090/S0025-5718-1988-0935077-0](https://doi.org/10.1090/S0025-5718-1988-0935077-0)
#
# and explained in these classroom notes,
# - Calculation of weights in finite difference formulas, *SIAM Review* 40 (1998), pp. 685-691, [doi:10.1137/S0036144596322507](https://doi.org/10.1137/S0036144596322507)
#
# This approach is implemented in Matlab in the file `fdcoeffF.m` available from the website [[FDM-book]](http://faculty.washington.edu/rjl/fdmbook/).
#
# A Python version is below.  This is based directly on the Fortran code in the SIAM Review paper listed above (which uses 0-based indexing, like Python, whereas the Matlab code is 1-based).
#
# **Note:** Forberg's algorithm can be used to simultaneously compute the coefficients for derivatives of order `0, 1, ..., m` where `m <= n-1`. This gives a coefficient matrix `C(1:n,1:m)` whose k'th column gives the coefficients for the k'th derivative.
#
# In the version below we set `m=k` and only compute the coefficients for derivatives of order up to order k, and then return only the k'th column of the resulting `C` matrix (converted to a row vector).   This routine is then compatible with ` fdcoeffV` defined above.    If the optional argument `fullC` is `True` then the full matrix of coefficients for derivatives of all orders up to `n-1` will be returned.

# In[10]:

def fdcoeffF(k, xbar, x, fullC=False):
    n = len(x) - 1
    if k > n:
        raise ValueError('*** len(x) must be larger than k')

    m = k  # for consistency with Fornberg's notation
    c1 = 1.
    c4 = x[0] - xbar
    C = zeros((n+1,m+1))
    C[0,0] = 1.
    for i in range(1,n+1):
        mn = min(i,m)
        c2 = 1.
        c5 = c4
        c4 = x[i] - xbar
        for j in range(i):
            c3 = x[i] - x[j]
            c2 = c2*c3
            if j==i-1:
                for s in range(mn,0,-1):
                    C[i,s] = c1*(s*C[i-1,s-1] - c5*C[i-1,s])/c2
                C[i,0] = -c1*c5*C[i-1,0]/c2
            for s in range(mn,0,-1):
                C[j,s] = (c4*C[j,s] - s*C[j,s-1])/c3
            C[j,0] = c4*C[j,0]/c3
        c1 = c2

    if fullC:
        return C
    else:
        c = C[:,-1] # last column of C
        return c


# Copyright (C) 2021-2022 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
"""Numpy optimizers test functions module."""
from typing import Dict

import numpy as np

def rosenbrock_func(point: Dict[str, np.ndarray]) -> float:
    """
    Calculate Rosenbrock function.

    More details: https://en.wikipedia.org/wiki/Rosenbrock_function
    """
    return (1 - point['x'])**2 + 100 * (point['y'] - point['x']**2)**2

def _get_rosenbrock_grads(point: Dict[str, np.ndarray]) -> Dict[str, np.ndarray]:
    """Calculate gradients for Rosenbrock function."""
    grads = {'x': np.array([0]), 'y': np.array([0])}
    grads['x'] = -2 * (1 - point['x']) - 400 * point['x'] * (point['y'] - point['x']**2)
    grads['y'] = grads['y'] + 200 * (point['y'] - point['x']**2)
    return grads

def mc_cormick_func(point: Dict[str, np.ndarray]) -> float:
    """
    Calculate McCormick function.

    More details: https://en.wikipedia.org/wiki/Test_functions_for_optimization
    """
    return (np.sin(point['x'] + point['y'])
            + (point['x'] - point['y'])**2
            - 1.5 * point['x'] + 2.5 * point['y'] + 1)

def _get_mc_cormick_grads(point: Dict[str, np.ndarray]) -> Dict[str, np.ndarray]:
    """Calculate gradients for McCormick function."""
    grads = {'x': np.array([0]), 'y': np.array([0])}
    grads['x'] = np.cos(point['x'] + point['y']) + 2 * (point['x'] - point['y']) - 1.5
    grads['y'] = np.cos(point['x'] + point['y']) - 2 * (point['x'] - point['y']) + 2.5
    return grads

rosenbrock_func.get_grads = _get_rosenbrock_grads
rosenbrock_func.true_answer = {'x': np.array([1.0]), 'y': np.array([1.0])}

mc_cormick_func.get_grads = _get_mc_cormick_grads
mc_cormick_func.true_answer = {'x': np.array([-0.54719]), 'y': np.array([-1.54719])}

__all__ = [
    'rosenbrock_func',
    'mc_cormick_func',
]


import numpy

def linear(yin, xin, xout, method='linear'):
    """
    Interpolate the curve defined by (xin, yin) at points xout. The array
    xin must be monotonically increasing. The output has the same data type as
    the input yin.

    :param yin: y values of input curve
    :param xin: x values of input curve
    :param xout: x values of output interpolated curve
    :param method: interpolation method ('linear' | 'nearest')

    @:rtype: numpy array with interpolated curve
    """
    lenxin = len(xin)

    i1 = numpy.searchsorted(xin, xout)
    i1[i1 == 0] = 1
    i1[i1 == lenxin] = lenxin - 1

    x0 = xin[i1 - 1]
    x1 = xin[i1]
    y0 = yin[i1 - 1]
    y1 = yin[i1]

    if method == 'linear':
        return (xout - x0) / (x1 - x0) * (y1 - y0) + y0
    elif method == 'nearest':
        return numpy.where(numpy.abs(xout - x0) < numpy.abs(xout - x1), y0, y1)
    else:
        raise ValueError('Invalid interpolation method: %s' % method)  # Python code here

import numpy as np
from math import sqrt

def cubic_splines(x0, x, y):
    """
    Interpolate a 1-D function using cubic splines.
      x0 : a float or an 1d-array
      x : (N,) array_like
          A 1-D array of real/complex values.
      y : (N,) array_like
          A 1-D array of real values. The length of y along the
          interpolation axis must be equal to the length of x.

    Implement a trick to generate at first step the cholesky matrice L of
    the tridiagonal matrice A (thus L is a bidiagonal matrice that
    can be solved in two distinct loops).

    additional ref: www.math.uh.edu/~jingqiu/math4364/spline.pdf
    """
    x = np.asfarray(x)
    y = np.asfarray(y)

    # remove non finite values
    # indexes = np.isfinite(x)
    # x = x[indexes]
    # y = y[indexes]

    # check if sorted
    if np.any(np.diff(x) < 0):
        indexes = np.argsort(x)
        x = x[indexes]
        y = y[indexes]

    size = len(x)

    xdiff = np.diff(x)
    ydiff = np.diff(y)

    # allocate buffer matrices
    Li = np.empty(size)
    Li_1 = np.empty(size - 1)
    z = np.empty(size)

    # fill diagonals Li and Li-1 and solve [L][y] = [B]
    Li[0] = sqrt(2 * xdiff[0])
    Li_1[0] = 0.0
    B0 = 0.0  # natural boundary
    z[0] = B0 / Li[0]

    for i in range(1, size - 1, 1):
        Li_1[i] = xdiff[i - 1] / Li[i - 1]
        Li[i] = sqrt(2 * (xdiff[i - 1] + xdiff[i]) - Li_1[i - 1] * Li_1[i - 1])
        Bi = 6 * (ydiff[i] / xdiff[i] - ydiff[i - 1] / xdiff[i - 1])
        z[i] = (Bi - Li_1[i - 1] * z[i - 1]) / Li[i]

    i = size - 1
    Li_1[i - 1] = xdiff[-1] / Li[i - 1]
    Li[i] = sqrt(2 * xdiff[-1] - Li_1[i - 1] * Li_1[i - 1])
    Bi = 0.0  # natural boundary
    z[i] = (Bi - Li_1[i - 1] * z[i - 1]) / Li[i]

    # solve [L.T][x] = [y]
    i = size - 1
    z[i] = z[i] / Li[i]
    for i in range(size - 2, -1, -1):
        z[i] = (z[i] - Li_1[i - 1] * z[i + 1]) / Li[i]

    # find index
    index = x.searchsorted(x0)
    np.clip(index, 1, size - 1, index)

    xi1, xi0 = x[index], x[index - 1]
    yi1, yi0 = y[index], y[index - 1]
    zi1, zi0 = z[index], z[index - 1]
    hi1 = xi1 - xi0

    # calculate cubic
    f0 = zi0 / (6 * hi1) * (xi1 - x0) ** 3 + \
         zi1 / (6 * hi1) * (x0 - xi0) ** 3 + \
         (yi1 / hi1 - zi1 * hi1 / 6) * (x0 - xi0) + \
         (yi0 / hi1 - zi0 * hi1 / 6) * (xi1 - x0)
    return f0


"""
Methods for computing pure Nash equilibria of a normal form game.
(For now, only brute force method is supported)

"""

import numpy as np

def pure_nash_brute(g, tol=None):
    """
    Find all pure Nash equilibria of a normal form game by brute force.

    Parameters
    ----------
    g : NormalFormGame
    tol : scalar(float), optional(default=None)
        Tolerance level used in determining best responses. If None,
        default to the value of the `tol` attribute of `g`.

    Returns
    -------
    NEs : list(tuple(int))
        List of tuples of Nash equilibrium pure actions.
        If no pure Nash equilibrium is found, return empty list.

    Examples
    --------
    Consider the "Prisoners' Dilemma" game:

    >>> PD_bimatrix = [[(1, 1), (-2, 3)],
    ...                [(3, -2), (0, 0)]]
    >>> g_PD = NormalFormGame(PD_bimatrix)
    >>> pure_nash_brute(g_PD)
    [(1, 1)]

    If we consider the "Matching Pennies" game, which has no pure nash
    equilibirum:

    >>> MP_bimatrix = [[(1, -1), (-1, 1)],
    ...                [(-1, 1), (1, -1)]]
    >>> g_MP = NormalFormGame(MP_bimatrix)
    >>> pure_nash_brute(g_MP)
    []

    """
    return list(pure_nash_brute_gen(g, tol=tol))

def pure_nash_brute_gen(g, tol=None):
    """
    Generator version of `pure_nash_brute`.

    Parameters
    ----------
    g : NormalFormGame
    tol : scalar(float), optional(default=None)
        Tolerance level used in determining best responses. If None,
        default to the value of the `tol` attribute of `g`.

    Yields
    ------
    out : tuple(int)
        Tuple of Nash equilibrium pure actions.

    """
    for a in np.ndindex(*g.nums_actions):
        if g.is_nash(a, tol=tol):
            yield a


import numpy as np

def softmax(predictions):
    '''
    Computes probabilities from scores

    Arguments:
      predictions, np array, shape is either (N) or (batch_size, N) -
        classifier output

    Returns:
      probs, np array of the same shape as predictions -
        probability for every class, 0..1
    '''
    def softmax_row(v):
      exps = np.exp(v - v.max())
      r = exps / exps.sum()
      r[r == 0.0] = np.finfo(float).eps
      return r

    if len(predictions.shape) == 1:
      return softmax_row(predictions)
    else:
      return np.apply_along_axis(softmax_row, 1, predictions)

def cross_entropy_loss(probs, target_index):
    '''
    Computes cross-entropy loss

    Arguments:
      probs, np array, shape is either (N) or (batch_size, N) -
        probabilities for every class
      target_index: np array of int, shape is (1) or (batch_size) -
        index of the true class for given sample(s)

    Returns:
      loss: single value
    '''
    if len(probs.shape) == 1:
      return -np.log(probs[target_index])
    else:
      return np.negative(np.log(probs[np.arange(probs.shape[0]), target_index])).mean()

def l2_regularization(W, reg_strength):
    """
    Computes L2 regularization loss on weights and its gradient

    Arguments:
      W, np array - weights
      reg_strength - float value

    Returns:
      loss, single value - l2 regularization loss
      gradient, np.array same shape as W - gradient of weight by l2 loss
    """
    loss = reg_strength * (W ** 2).sum()
    grad = 2 * reg_strength * W
    return loss, grad

def softmax_with_cross_entropy(predictions, target_index):
    """
    Computes softmax and cross-entropy loss for model predictions,
    including the gradient

    Arguments:
      predictions, np array, shape is either (N) or (batch_size, N) -
        classifier output
      target_index: np array of int, shape is (1) or (batch_size) -
        index of the true class for given sample(s)

    Returns:
      loss, single value - cross-entropy loss
      dprediction, np array same shape as predictions - gradient of predictions by loss value
    """
    probs = softmax(predictions)
    loss = cross_entropy_loss(probs, target_index)
    dprediction = probs.copy()

    if len(predictions.shape) == 1:
      dprediction[target_index] -= 1
    else:
      dprediction[np.arange(dprediction.shape[0]), target_index] -= 1
      dprediction /= dprediction.shape[0]

    return loss, dprediction

class Param:
    """
    Trainable parameter of the model
    Captures both parameter value and the gradient
    """

    def __init__(self, value):
        self.value = value
        self.grad = np.zeros_like(value)

class ReLULayer:
    def __init__(self):
        self.X = None

    def forward(self, X):
        Z = np.vectorize(lambda x: x if x > 0 else 0, [X.dtype])(X)
        self.X = X.copy()
        return Z

    def backward(self, d_out):
        """
        Backward pass

        Arguments:
        d_out, np array (batch_size, num_features) - gradient
           of loss function with respect to output

        Returns:
        d_result: np array (batch_size, num_features) - gradient
          with respect to input
        """
        return np.where(self.X > 0, d_out, 0.0)

    def params(self):
        # ReLU Doesn't have any parameters
        return {}

class FullyConnectedLayer:
    def __init__(self, n_input, n_output):
        self.W = Param(0.001 * np.random.randn(n_input, n_output) * np.sqrt(2 / n_output))
        self.B = Param(0.001 * np.random.randn(1, n_output) * np.sqrt(2 / n_output))
        self.X = None

    def forward(self, X):
        self.X = X.copy()
        return self.X @ self.W.value + self.B.value

    def backward(self, d_out):
        """
        Backward pass
        Computes gradient with respect to input and
        accumulates gradients within self.W and self.B

        Arguments:
        d_out, np array (batch_size, n_output) - gradient
           of loss function with respect to output

        Returns:
        d_result: np array (batch_size, n_input) - gradient
          with respect to input
        """
        self.W.grad += self.X.T @ d_out
        self.B.grad += d_out.sum(axis=0)[np.newaxis, :]
        return d_out @ self.W.value.T

    def params(self):
        return {'W': self.W, 'B': self.B}


import matplotlib.pyplot as plt
import numpy as np

learning_rate=0.1
def unit_step(v):
	if v >= 0:
		return 1
	else:
		return 0

def perceptron(x, w,b):
    v = np.dot(w, x)+b
    y =unit_step(v)
    return y

def NOR_percep(x,w,b):
    result = perceptron(x, w,b)
    return result

#random initialize

w = [0.2,0.1]
b=0.1
x=[[1.0,1.0],[1.0,0.0],[0.0,1.0],[0.0,0.0]]
y=[0.0,0.0,0.0,1.0]

for i in range(0,3):
    for j in range (0,len(x)):#one iteration
        #it is given from gradient descent formula
        w[0] = w[0] + learning_rate*np.multiply(x[j][0],(y[j]-NOR_percep(x[j],w,b)))
        w[1] = w[1] + learning_rate*np.multiply(x[j][1],(y[j]-NOR_percep(x[j],w,b)))
        b=b+ learning_rate*(y[j]- NOR_percep(x[j],w,b))

print(" w after 3 epochs is : ",w)

example1 = np.array([1, 1])
example2 = np.array([1, 0])
example3 = np.array([0, 1])
example4 = np.array([0, 0])

print("1 NOR 1: ",NOR_percep(example1,w,b))
print("1 NOR 0: ",NOR_percep(example2,w,b))
print("0 NOR 1: ",NOR_percep(example3,w,b))
print("0 NOR 0: ",NOR_percep(example4,w,b))


import numpy as np

'''
Problem 2.2
'''

def euler_inertial(n, u0, f, Dt):
    """

    Parameters
    ----------
    n : int
        Number of time steps
    u0
    f
    Dt

    Returns
    -------
    x, y, u, v : np.ndarray

    """
    # Initial conditions
    u = [u0]
    v = [0.]
    x = [0.]
    y = [0.]

    # Proceed
    for ii in range(n):
        # Velocity
        u += [u[-1] + f*Dt*v[-1]]
        v += [v[-1] - f*Dt*u[-2]] # Note we had already updated u!
        # Position
        x += [x[-1] + Dt*u[-1]]
        y += [y[-1] + Dt*v[-1]]
    # Return
    return np.array(x), np.array(y), np.array(u), np.array(v)

def semi_implicit_inertial(n, u0, f, Dt):
    """

    Parameters
    ----------
    n : int
        Number of time steps
    u0
    f
    Dt

    Returns
    -------
    u, v : np.ndarray

    """
    # Initial conditions
    u = [u0]
    v = [0.]

    # Proceed
    for ii in range(n):
        # Velocity
        u += [ (f*Dt*v[-1] + u[-1]*(1-0.25*(f*Dt*f*Dt)))/(1+0.25*f*Dt*f*Dt) ]
        v += [(-f * Dt * u[-1] + v[-1] * (1 + 0.25 * (f * Dt * f * Dt))) / (1 - 0.25 * f * Dt * f * Dt)]
    # Return
    return np.array(u), np.array(v)


import numpy as np

import matplotlib.pyplot as plt
#%matplotlib inline

#import matplotlib.image as img
#import PIL.Image as Image
from PIL import Image
import math
import cmath

import time

import csv

from numpy import binary_repr

from fractions import gcd

class DCT(object):
    """
    This class DCT implements all the procedures for transforming a given 2D digital image
    into its corresponding frequency-domain image (Forward DCT Transform)
    """

    def __init__():
        pass

    @classmethod
    def __computeSinglePoint2DCT(self, imge, u, v, N):
        """
        A private method that computes a single value of the 2D-DCT from a given image.

        Parameters
        ----------
        imge : ndarray
            The input image.

        u : ndarray
            The index in x-dimension.

        v : ndarray
            The index in y-dimension.

        N : int
            Size of the image.

        Returns
        -------
        result : float
            The computed single value of the DCT.
        """
        result = 0

        for x in xrange(N):
            for y in xrange(N):
                result += imge[x, y] * math.cos(((2*x + 1)*u*math.pi)/(2*N)) * math.cos(((2*y + 1)*v*math.pi)/(2*N))

        #Add the tau value to the result
        if (u==0) and (v==0):
            result = result/N
        elif (u==0) or (v==0):
            result = (math.sqrt(2.0)*result)/N
        else:
            result = (2.0*result)/N

        return result

    @classmethod
    def __computeSinglePointInverse2DCT(self, imge, x, y, N):
        """
        A private method that computes a single value of the 2D-DCT from a given image.

        Parameters
        ----------
        imge : ndarray
            The input image.

        u : ndarray
            The index in x-dimension.

        v : ndarray
            The index in y-dimension.

        N : int
            Size of the image.

        Returns
        -------
        result : float
            The computed single value of the DCT.
        """
        result = 0

        for u in xrange(N):
            for v in xrange(N):
                if (u==0) and (v==0):
                    tau = 1.0/N
                elif (u==0) or (v==0):
                    tau = math.sqrt(2.0)/N
                else:
                    tau = 2.0/N
                result += tau * imge[u, v] * math.cos(((2*x + 1)*u*math.pi)/(2*N)) * math.cos(((2*y + 1)*v*math.pi)/(2*N))

        return result

    @classmethod
    def computeForward2DDCT(self, imge):
        """
        Computes/generates the 2D DCT of an input image in spatial domain.

        Parameters
        ----------
        imge : ndarray
            The input image to be transformed.

        Returns
        -------
        final2DDFT : ndarray
            The transformed image.
        """

        # Assuming a square image
        N = imge.shape[0]
        final2DDCT = np.zeros([N, N], dtype=float)
        for u in xrange(N):
            for v in xrange(N):
                #Compute the DCT value for each cells/points in the resulting transformed image.
                final2DDCT[u, v] = DCT.__computeSinglePoint2DCT(imge, u, v, N)
        return final2DDCT

    @classmethod
    def computeInverse2DDCT(self, imge):
        """
        Computes/generates the 2D DCT of an input image in spatial domain.

        Parameters
        ----------
        imge : ndarray
            The input image to be transformed.

        Returns
        -------
        final2DDFT : ndarray
            The transformed image.
        """

        # Assuming a square image
        N = imge.shape[0]
        finalInverse2DDCT = np.zeros([N, N], dtype=float)
        for x in xrange(N):
            for y in xrange(N):
                #Compute the DCT value for each cells/points in the resulting transformed image.
                finalInverse2DDCT[x, y] = DCT.__computeSinglePointInverse2DCT(imge, x, y, N)
        return finalInverse2DDCT

    @classmethod
    def normalize2DDCTByLog(self, dctImge):
        """
        Computes the log transformation of the transformed DCT image to make the range
        of the DCT values b/n 0 to 255

        Parameters
        ----------
        dctImge : ndarray
            The input DCT transformed image.

        Returns
        -------
        dctNormImge : ndarray
            The normalized version of the transformed image.
        """

        #Normalize the DCT values of a transformed image:
        dctImge = np.absolute(dctImge)
        dctNormImge = (255/ math.log10(255)) * np.log10(1 + (255/(np.max(dctImge))*dctImge))

        return dctNormImge


import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from Regression import Regression
from sklearn.model_selection import train_test_split

class L1_Regularizer(object):
    ''' L1 Regularizer for Lasso Regression '''

    def __init__(self, alpha: float) -> None:
        self.alpha = alpha

    def __call__(self, w):
        return self.alpha * np.sum(np.abs(w))

    def grad(self, w):
        return self.alpha * np.sign(w)

class LassoRegression(Regression):
    '''
    Lasso regression model (Inherited from Regression class)
    - L1 Regularizartion used
    '''

    def __init__(self, alpha: float) -> None:
        '''
        Initialize the model

        :param alpha: L1 regularization parameter
        '''

        super().__init__()
        self.regularization = L1_Regularizer(alpha)

    def fit(self, X: np.array, y: np.array, epochs: int = 100, learning_rate: int = 0.0001) -> None:
        '''
        Fit the model to the training data X using Gradient Descent and L1 regularization

        :param X: training data
        :param y: target data
        :param epochs: number of epochs (Iterations) in case gradient decent is used (Default: 100)
        :param learning_rate: learning rate for gradient decent (Deafult: 0.0001)
        :return: None
        '''

        self.epochs = epochs
        self.learning_rate = learning_rate

        # Initialize weight
        self.initialize_weight(n_features = len(X))

        n_features = len(X)

        for _ in range(self.epochs):

            # Make prediction
            y_pred = np.dot(X, self.weight) + self.bias

            # Calculate error
            error = y - y_pred

            # Calculate gradient descent
            D_m = -(2 / n_features) * sum(X * (error))     # Partial Derivative for M (Weights)
            D_c = -(2 / n_features) * sum(error)           # Partial Derivative for C (Bias)

            # Apply L1 regularization (Lasso)
            D_m += self.regularization.grad(self.weight)

            # Update weights and bias
            self.weight -= (learning_rate * D_m)
            self.bias -= (learning_rate * D_c)

if __name__ == '__main__':

    # Load the dataset
    df = pd.read_csv('data/scatter.csv')

    # Preparing the data
    X = np.array(df.iloc[:, 0])
    y = np.array(df.iloc[:, 1])
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3)

    # Define model
    regressor = LassoRegression(alpha = 0.01)
    regressor.fit(X_train, y_train, epochs = 1000, learning_rate = 0.0001)

    # Make predictions
    y_pred = regressor.predict(X_test)

    # Plotting the results
    plt.figure(figsize = (5, 3))
    plt.scatter(X_train, y_train , color = 'green')
    plt.scatter(X_test, y_test , color = 'blue')
    plt.plot(X_test , y_pred , color = 'r' , lw = 1)
    plt.show()


import numpy             as np
import matplotlib.pyplot as plt

from mpl_toolkits.mplot3d import Axes3D

from hilbert import decode

num_dims = 3

def draw_curve(ax, num_bits):

  # The maximum Hilbert integer.
  max_h = 2**(num_bits*num_dims)

  # Generate a sequence of Hilbert integers.
  hilberts = np.arange(max_h)

  # Compute the 2-dimensional locations.
  locs = decode(hilberts, num_dims, num_bits)

  # Draw
  ax.plot(locs[:,0], locs[:,1], locs[:,2], '.-')
  # ax.set_aspect('equal')
  ax.set_title('%d bits per dimension' % (num_bits))
  ax.set_xlabel('dim 1')
  ax.set_ylabel('dim 2')
  ax.set_zlabel('dim 3')

fig = plt.figure(figsize=(16,4))
for ii, num_bits in enumerate([1, 2, 3, 4]):
  ax = fig.add_subplot(1,4,ii+1, projection='3d')
  draw_curve(ax, num_bits)
plt.savefig('example_3d.png', bbox_inches='tight')


#!/usr/bin/python
import math
import numpy as np
from sys import argv as argv
from unittest import main, TestCase

class TestDay25(TestCase):
    def test_nrow_calculation(self):
        assert(calculate_matrix_size(6) == 3)
        assert(calculate_matrix_size(21) == 6)

    def test_generate_triangular_matrix(self):
        tiny_table = np.zeros((2,2))
        tiny_table[0][0] = 1
        tiny_table[1][0] = 2
        tiny_table[0][1] = 3
        tiny_table[0][0] = 0
        assert(tiny_table[i][j] == generate_triangular_matrix(3)[i][j] for i in (0,1) for j in (0,1))

    def test_solve_problem(self):
        assert(solve_problem(1,1) == 20151125)
        assert(solve_problem(4,4) == 9380097)

def calculate_matrix_size(n_entries):
    # total entries = 1/2 * (n^2 + n)
    # 6 = 1/2 * (9 + 3) -- looks good!
    # n^2 / 2 + n / 2 - nentries = 0
    a = 0.5
    b = 0.5
    c = -n_entries
    nrows = (-b + math.sqrt(b**2 - 4*a*c)) / (2*a)
    return int(nrows)

def generate_triangular_matrix(n_entries):
    ncols = nrows = calculate_matrix_size(n_entries)
    table = np.zeros((nrows, ncols))
    (row, col) = (0,0)
    for i in range(1, n_entries+1):
        table[row][col] = i
        if row == 0:
            row = col+1
            col = 0
        else:
            row -= 1
            col += 1
    return table

def generate_number_to_index_map(n_entries):
    triangular_matrix = generate_triangular_matrix(n_entries)
    result = {}
    for row in triangular_matrix:
        for col in triangular_matrix[row]:
            if triangular_matrix[row][col] != 0:
                n = triangular_matrix[row][col]
                result[n] = (row,col)
    return result

def generate_code_table(n_entries):
    ncols = nrows = calculate_matrix_size(n_entries)
    codes = np.zeros((n_entries+2,))
    codes[1] = 20151125
    for i in range(2, n_entries+2):
        codes[i] = codes[i-1] * 252533 % 33554393
    return codes

def solve_problem(row, col):
    nrows = row+col
    n_entries = int(1/2 * (nrows**2 + nrows))
    codes = generate_code_table(n_entries)
    triangular_table = generate_triangular_matrix(n_entries)
    code_number = triangular_table[row-1][col-1]
    return codes[code_number]

if __name__ == '__main__':
    if len(argv) == 1:
        main()
    elif len(argv) == 3:
        row = int(argv[1])
        col = int(argv[2])
        print(solve_problem(row, col))
    else:
        print('Usage: `{} [row col]`'.format(argv[0]))

    goal = (2947, 3029)


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

def euler_forward(f,t0,x0,b,h):
    n= int((b-t0)/h)
    t_arr=[t0]
    X_arr=[x0]
    for i in range(n):
        xn = x0 + h * f(t0,x0)
        x0 = xn
        t0 = t0+h
        X_arr.append(x0)
        t_arr.append(t0)
    return t_arr,X_arr

def RK2(f,t0,x0,b,h):
    n= int((b-t0)/h)
    t_arr=[t0];X_arr=[x0]
    for i in range(n):
        k1=h*f(t0,x0)
        k2=h*f(t0+h,x0+k1)
        k_RK2=(k1+k2)/2
        xn=x0+k_RK2
        x0 = xn
        t0 = t0+h
        X_arr.append(x0)
        t_arr.append(t0)
    return t_arr,X_arr

def graph(x1_1,y1_1,x1_2,y1_2,x1_3,y1_3,x1_lab,y1_lab,tit_1,x2_1,y2_1,x2_2,y2_2,x2_3,y2_3,x2_lab,y2_lab,tit_2,x3_1,y3_1,x3_2,y3_2,x3_3,y3_3,x3_lab,y3_lab,tit_3,x4,y4_1,y4_2,x4_lab,y4_lab,tit_4,x_an,y_an,tit):
    fig, axs = plt.subplots(2, 2)
    axs[0, 0].plot(x1_1, y1_1,marker="*",label="EULER")
    axs[0, 0].plot(x1_2, y1_2,marker="*",label="RK2")
    axs[0, 0].plot(x1_3, y1_3,marker="o",label="ANALYTIC")
    axs[0,0].set_xlabel(x1_lab)
    axs[0,0].set_ylabel(y1_lab)
    axs[0, 0].set_title(tit_1,c="magenta")
    axs[0, 1].plot(x_an, y_an,marker="|",label="ANALYTIC")
    axs[0, 1].plot(x2_1, y2_1,marker="*", label="h ={}".format(x2_1[1]-x2_1[0]))
    axs[0, 1].plot(x2_2, y2_2,marker="*", label="h ={}".format(x2_2[1]-x2_2[0]))
    axs[0, 1].plot(x2_3, y2_3,marker="*", label="h ={}".format(x2_3[1]-x2_3[0]))
    axs[0,1].set_xlabel(x2_lab)
    axs[0,1].set_ylabel(y2_lab)
    axs[0, 1].set_title(tit_2,c="magenta")
    axs[1,0].plot(x_an, y_an,marker="|",label="ANALYTIC")
    axs[1,0].plot(x3_1, y3_1,marker="*", label="h ={}".format(x3_1[1]-x3_1[0]))
    axs[1,0].plot(x3_2, y3_2,marker="*", label="h ={}".format(x3_2[1]-x3_2[0]))
    axs[1,0].plot(x3_3, y3_3,marker="*", label="h ={}".format(x3_3[1]-x3_3[0]))
    axs[1,0].set_xlabel(x3_lab)
    axs[1,0].set_ylabel(y3_lab)
    axs[1,0].set_title(tit_3,c="magenta")
    axs[1, 1].scatter(x4,y4_1 , label="EULER")
    axs[1, 1].scatter(x4,y4_2 , label="RK2")
    axs[1,1].set_xlabel(x4_lab)
    axs[1,1].set_ylabel(y4_lab)
    axs[1,1].set_title(tit_4,c="magenta")
    axs[0,0].grid()
    axs[1,0].grid()
    axs[0,1].grid()
    axs[1,1].grid()
    axs[0,0].legend()
    axs[1,0].legend()
    axs[0,1].legend()
    axs[1,1].legend()
    fig.suptitle(tit,c="red")
    plt.show()

def Anal(x0,t0,b,h,tau):
    t_arr=[]
    X_arr=[]
    e=np.arange(t0,b+h,h)
    for t in e:
        D=x0*np.exp(-1*t/tau)
        X_arr.append(D)
        t_arr.append(t)
    return t_arr,X_arr

def q3a(t0,x0,t_h):
    b=5*t_h
    tou=t_h/0.693
    f= lambda x,N : -1*N/tou
    a=euler_forward(f,t0,x0,b,t_h/8)
    a1=euler_forward(f,t0,x0,b,t_h/2)
    a2=euler_forward(f,t0,x0,b,t_h/4)
    b1=RK2(f,t0,x0,b,t_h/8)
    b2=RK2(f,t0,x0,b,t_h/2)
    b3=RK2(f,t0,x0,b,t_h/4)
    c=Anal(x0,t0,b,t_h/8,tou)
    eu=[]
    rk=[]
    for i in range(len(a[1])):
        eu.append((c[1][i]-a[1][i])/c[1][i])
        rk.append((c[1][i]-b1[1][i])/c[1][i])
    data = {"No. of half lives":np.array(a[0])/t_h,"N (Euler)":a[1],"N (RK2)":b1[1],"N (Analytic)":c[1],"Ab. Error in Euler":eu,"Ab. Error in RK2":rk}
    print(pd.DataFrame(data))
    graph(np.array(a[0])/t_h,a[1],np.array(b1[0])/t_h,b1[1],np.array(c[0])/t_h,c[1],"No. of half lives","No. of particles","Euler and RK2",a[0],a[1],a1[0],a1[1],a2[0],a2[1],"t","N","Euler method for various h",b1[0],b1[1],b2[0],b2[1],b3[0],b3[1],"t","N","RK2 method for various h",a[0],eu,rk,"Time(t)","Absolute Error","Error plot",c[0],c[1],"RADIOACTIVE DECAY")

def q3b(t0,x0,R,C):
    b=5*R*C
    tou=R*C
    f= lambda x,V : -1*V/tou
    a=euler_forward(f,t0,x0,b,tou/10)
    a1=euler_forward(f,t0,x0,b,tou/5)
    a2=euler_forward(f,t0,x0,b,tou/15)
    b1=RK2(f,t0,x0,b,tou/10)
    b2=RK2(f,t0,x0,b,tou/5)
    b3=RK2(f,t0,x0,b,tou/15)
    c=Anal(x0,t0,b,tou/10,tou)
    eu=[]
    rk=[]
    for i in range(len(a[1])):
        eu.append((c[1][i]-a[1][i])/c[1][i])
        rk.append((c[1][i]-b1[1][i])/c[1][i])
    data = {"t/RC":np.array(a[0])/tou,"V (Euler)":a[1],"V (RK2)":b1[1],"V (Analytic)":c[1],"Ab. Error in Euler":eu,"Ab. Error in RK2":rk}
    print(pd.DataFrame(data))
    graph(np.array(a[0])/tou,np.array(a[1])/a[1][0],np.array(b1[0])/tou,np.array(b1[1])/b1[1][0],np.array(c[0])/tou,np.array(c[1])/c[1][0],"t/RC","V/V0","Euler and RK2",a[0],a[1],a1[0],a1[1],a2[0],a2[1],"t","V","Euler method for various h",b1[0],b1[1],b2[0],b2[1],b3[0],b3[1],"t","V","RK2 method for various h",a[0],eu,rk,"Time(t)","Absolute Error","Error plot",c[0],c[1],"RC CIRCUIT")

def q3c(t0,x0,eta,rad,m):
    tou=m/(np.pi*6*rad*eta)
    b=4*tou
    f= lambda x,v : -1*v/tou
    a=euler_forward(f,t0,x0,b,tou/10)
    a1=euler_forward(f,t0,x0,b,tou/5)
    a2=euler_forward(f,t0,x0,b,tou/15)
    b1=RK2(f,t0,x0,b,tou/10)
    b2=RK2(f,t0,x0,b,tou/5)
    b3=RK2(f,t0,x0,b,tou/15)
    c=Anal(x0,t0,b,tou/10,tou)
    eu=[]
    rk=[]
    for i in range(len(a[1])):
        eu.append((c[1][i]-a[1][i])/c[1][i])
        rk.append((c[1][i]-b1[1][i])/c[1][i])
    data1 = {"t/m/(pi*6*rad*eta)":np.array(a[0])/tou,"v (Euler)":a[1],"v (RK2)":b1[1],"v (Analytic)":c[1],"Ab. Error in Euler":eu,"Ab. Error in RK2":rk}
    print(pd.DataFrame(data1))
    graph(np.array(a[0])/tou,np.array(a[1])/a[1][0],np.array(b1[0])/tou,np.array(b1[1])/b1[1][0],np.array(c[0])/tou,np.array(c[1])/c[1][0],"t/m/(pi*6*rad*eta)","v/v0","Euler and RK2",a[0],a[1],a1[0],a1[1],a2[0],a2[1],"t","v","Euler method for various h",b1[0],b1[1],b2[0],b2[1],b3[0],b3[1],"t","v","RK2 method for various h",a[0],eu,rk,"Time(t)","Absolute Error","Error plot",c[0],c[1],"STOKES LAW")

t0=0;x0=2e4;t_h=4
print("----------------------------Q. 3a (RADIOACTIVE DECAY) For h= ",t_h/8,"-----------------------------")
q3a(t0,x0,t_h)

t0=0;x0=10;R=1e3;C=1e-6
print("----------------------------Q. 3b (RC CIRCUIT) For h= ",R*C/10,"-----------------------------")
q3b(t0,x0,R,C)

t0=0;x0=10;eta=10;rad=0.2;m=200
print("----------------------------Q. 3c (STOKES LAW) For h= ",m/(np.pi*6*rad*eta)/10,"-----------------------------")
q3c(t0,x0,eta,rad,m)


import numpy as np
import statistics as s
from scipy.stats import pearsonr
import seaborn as sns; sns.set_theme(color_codes=True)
import statsmodels.api as sm
import matplotlib.pyplot as plt
Xs = np.array([ 0.387, 0.723, 1.00, 1.52, 5.20, 9.54, 19.2, 30.1, 39.5 ])

Ys = np.array([ 0.241, 0.615, 1.00, 1.88, 11.9, 29.5, 84.0, 165.0, 248 ])

N = 9
def cov(a, b):

    if len(a) != len(b):
        return

    a_mean = np.mean(a)
    b_mean = np.mean(b)

    sum=(np.add(a,-a_mean)*np.add(b,-b_mean)).sum()

    return sum/(len(a)-1)

print(cov(Xs,Ys))

#pearson correlation covariance(X, Y) / (stdv(X) * stdv(Y))
corr=cov(Xs,Ys)/(s.stdev(Xs) * s.stdev(Ys))
print(corr)
print('coeff: %.3f' % corr)

# ys = sns.load_dataset([Xs,Ys])
# ax = sns.regplot(x="total_bill", y="tip", data=[Xs,Ys])
# sns.scatterplot(x=horizontal_data_1, y=vertical_data_1, ax=ax[0]);
# ax = sns.regplot(x=Xs, y=Ys)

sm.qqplot(Xs, line='s')
plt.title("X distribution")

# plt.show()
sm.qqplot(Ys, line='s')
plt.title("Y distribution")
# plt.show()
corr=cov(np.log(Xs),np.log(Ys))/(s.stdev(np.log(Xs)) * s.stdev(np.log(Ys)))
bet1=corr*(s.stdev(np.log(Ys))/s.stdev(np.log(Xs)) )
print('beta improve: %.3f' % bet1)

import numpy,from scipy,import statsmodels


from sympy import symbols, S, binomial, expand

def polinomios_bernstein(f, var=symbols('x'), I=[0, 1], grado=2):
    """Genera el polinomio de Bernstein de grado n para la función f.

    Args:
        f (funcion): Función a aproximar
        var (variable, optional): Variable de f. Defaults to symbols('x').
        I (list, optional): Intervalo de aproximación. Defaults to [0, 1].
        grado (int, optional): Grado del polinomio. Defaults to 2.

    Returns:
        funcion: Polinomio de aproximación
    """
    # Los polinomios funcionan en un intervalo de 0 a 1, así que si el intervalo base es [a, b] tenemos que
    # aplicar el cambio t = (x - a)/(b - a) -> (b-a)t + a = x
    # Así transformamos f(x) en g(t)

    # Si el polinomio Bngt = sum_i^n (n i) g(i/n) t^i (1-t)^(n-1)

    vart = var * (I[1] - I[0]) + I[0]
    g = f.subs(var, vart)

    B_nft = S(0)
    for grado_i in range(grado + 1):
        poli_i = binomial(grado, grado_i) * (var ** grado_i) * ((1 - var) ** (grado - grado_i)) * g.subs(var, S(grado_i) / S(grado))
        B_nft += poli_i

    B_nf = B_nft.subs(var, S(var - S(I[0])) / S(I[1] - I[0]))

    return expand(B_nf)


# Polynomial Regression

# Importing the libraries
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

# Importing the dataset
dataset = pd.read_csv('Position_Salaries.csv')
X = dataset.iloc[:, 1:2].values
y = dataset.iloc[:, 2].values

# Splitting the dataset into the Training set and Test set
"""from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 0)"""

# Feature Scaling
"""from sklearn.preprocessing import StandardScaler
sc_X = StandardScaler()
X_train = sc_X.fit_transform(X_train)
X_test = sc_X.transform(X_test)
sc_y = StandardScaler()
y_train = sc_y.fit_transform(y_train)
y_test = sc_y.transform(y_test)"""

# Fitting linear regression
from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(X, y)

# Fitting polynomial regression
from sklearn.preprocessing import PolynomialFeatures
poly_features = PolynomialFeatures(degree = 4)
X_poly = poly_features.fit_transform(X)
poly_reg = LinearRegression()
poly_reg.fit(X_poly, y)

# Visualizing linear regression
plt.scatter(X, y, color='red')
plt.plot(X, lin_reg.predict(X), color='blue')
plt.title('Truth or Bluff (Linear Regression)')
plt.xlabel('Position level')
plt.ylabel('Salary')
plt.show()

# Visualizing polynomial regression
X_grid = np.arange(min(X), max(X), 0.1)
X_grid = X_grid.reshape((len(X_grid), 1))
plt.scatter(X, y, color='red')
plt.plot(X_grid, poly_reg.predict(poly_features.transform(X_grid)), color='blue')
plt.title('Truth or Bluff (Polynomial Regression)')
plt.xlabel('Position level')
plt.ylabel('Salary')
plt.show()

# Predicting with linear regression
lin_reg.predict(6.5)

# Predicting with polynomial regression
poly_reg.predict(poly_features.transform(6.5))

udemy/Machine Learning A-Z/Part 2 - Regression/Section 6 - Polynomial Regression/polynomial_regression.py


import numpy as np
import matplotlib.pyplot as plt
from math import sqrt
from scipy import stats
from scipy.stats import kurtosis, skew

def pdf(costh, P_mu):
    # define our probability density function
    return 0.5 * (1.0 - 1.0 / 3.0 * P_mu * costh)

def inv_cdf_pos(r, P_mu):
    # inverse of the cumulative density function
    # since we have positive and negative solutions for x(r), we have to consider both
    return 3./P_mu*(1.+np.sqrt(1.+2./3.*P_mu*(P_mu/6.+1.-2.*r)))

def inv_cdf_neg(r, P_mu):
    return 3./P_mu*(1.-np.sqrt(1.+2./3.*P_mu*(P_mu/6.+1.-2.*r)))

def inv_trans(N_measurements, P_mu):
    r = np.random.uniform(0.0, 1.0, size=N_measurements)
    data_pdf = inv_cdf_neg(r, P_mu)
    return data_pdf

def acc_rej(N_measurements, P_mu):
    x = np.random.uniform(-1.0, 1.0, size=N_measurements)

    x_axis = np.linspace(-1.0,1.0, 1000)
    fmax= np.amax(pdf(x_axis,P_mu)) #find the maximum of the function

    u = np.random.uniform(0, fmax, size= N_measurements)

    # we use a mask in order to reject the values we don't want
    data_pdf = x[u < pdf(x,P_mu)]
    return data_pdf

N_measurements=10000
P_mu=0.5

data_pdf = inv_trans(N_measurements, P_mu)
#print(len(data_pdf))
counts_bins, edges_bins, patches = plt.hist(data_pdf, bins=20, normed=True, histtype="step", color="black", linewidth = "1")

#print(counts_bins)
#print(edges_bins)

bin_width=abs(edges_bins[7]-edges_bins[8])
#print(bin_width)

#Note that the normalization is not to 1.
normalisation_bins=np.sum(counts_bins)
#print(normalisation_bins)

pdf_estimator = counts_bins/(normalisation_bins*bin_width)
#print(pdf_estimator)

center_bins=np.array([])
for i in np.arange(0,len(edges_bins)-1,1):
    center_bins=np.append(center_bins, (edges_bins[i] + edges_bins[i+1])/2.)
#print(center_bins)

plt.plot(center_bins, pdf_estimator, color = "blue", linewidth = 1, label = "pdf estimation")

x_axis = np.linspace(-1.0,1.0, 1000)
plt.plot(x_axis, pdf(x_axis,P_mu), color="red", linestyle="--", linewidth=1, label ="pdf theory")

plt.xlim(-1.,1.)
plt.ylim(0.4,0.6)
plt.xlabel(r'$cos \ \theta$')
plt.ylabel(r'$N(\cos \ \theta)$')
plt.text(-0.9, 0.41, r"$N_{events}=$" + str(N_measurements))
plt.legend(loc="best")
leg = plt.legend()
leg.get_frame().set_edgecolor('black')

plt.show()

Projects/Probability and Monte Carlo Techniques/_6_pdf_from_histogram.py


# -*- coding: utf-8 -*-
"""
Created on Tue Jun 20 10:17:03 2017

@author: fella
"""
from numpy import genfromtxt, array

def compute_error_for_given_points(b,m,points):
    totalError = 0
    for i in range(0, len(points)):
        x = points[i,0]
        y = points[i,1]
        totalError += (y - (m * x + b)) **2
    return totalError/ float(len(points))

def step_gradient(b_current, m_current, points, learning_rate):
    #gradient descent
    b_gradient = 0
    m_gradient = 0
#    b_current = new_b
#    m_current = new_m
    N = float(len(points))
    for i in range(0, len(points)):
        x = points[i,0]
        y = points[i,1]
        b_gradient += -(2/N) * (y - (m_current * x + b_current ))
        m_gradient += -(2/N) * x * (y - (m_current * x + b_current ))
    new_b = b_current - (learning_rate * b_gradient)
    new_m = m_current - (learning_rate * m_gradient)
    return [new_b, new_m]

def gradient_descent_runner(points, initial_b, initial_m, learning_rate,num_iterations):
    b = initial_b
    m = initial_m

    for i in range(num_iterations):
        b,m = step_gradient(b,m,array(points), learning_rate)
    return [b, m]

def run():
    points = genfromtxt(r'C:\Users\fella\Desktop\Documents\5. Python\Tutorials\linear_regression_live\data.csv', delimiter=',' )
    #hyperparameters
    learning_rate = 0.0001
    #y = mx + b (slope formula)
    initial_b = 0
    initial_m = 0
    num_iterations = 1000
    [b,m] = gradient_descent_runner(points, initial_b, initial_m, learning_rate,num_iterations)
    print(b)
    print(m)

if __name__ == '__main__':
    run()


# coding: utf-8

# # Calcul d'une paire de dés un peu particulière.
# - Je vais implémenter une fonction, en [Python 3](https://docs.python.org/3/), qui permettra de résoudre rapidement un problème mathématique.

# ---
# ## Exposé du problème :
# - Soit $n \geq 1$ un nombre de faces pour des dés bien équilibrés. On prendre $n = 6$ pour commencer, mais $n = 8, 10, 12, 20$ ou $n = 100$ est aussi possible.
# - On veut trouver deux ensembles d'entiers, $A$ et $B$, de tailles $n$, tels que les entiers dans $A$ soient tous [premiers](https://fr.wikipedia.org/wiki/Nombre_premier), et les entiers dans $B$ soient tous [pairs](https://fr.wikipedia.org/wiki/Parit%C3%A9_(arithm%C3%A9tique)), et on souhaite que chaque somme $a + b$ pour $a \in A$ et $b \in B$ soit encore un nombre premier.
#
# Par exemples :
# - Avec $n = 1$, prendre $A = \{3\}$ et $B = \{2\}$ fonctionne, comme $3 + 2 = 5$ est premier.
# - Avec $n = 2$, prendre $A = \{3, 5\}$ et $B = \{2, 8\}$ fonctionne, comme $3 + 2 = 5$, $3 + 8 = 11$, $5 + 2 = 7$, et $5 + 8 = 13$ sont premiers.
# - Avec $n = 6$, c'est tout de suite moins facile à trouver de tête... Allez, je triche et je donne un exemple trouvé plus bas : $A = \{5, 7, 17, 31, 61, 101\}$ et $B = \{6, 12, 36, 66, 96, 132\}$.
#
# ### Buts
# Notre but est d'abord de trouver vérifier qu'une paire $(A, B)$ donnée soit valide, puis de trouver une paire valide de taille $n$ donnée.
# On ne cherchera pas à avoir un algorithme très malin, une énumération exhaustive nous suffira.
#
#
# Ensuite on cherchera à répondre à une question plus combinatoire : peut-on trouver, à $n$ fixer, la paire valide $(A, B)$ de somme minimale (ou minimisant un certain critère) ?
# La réponse sera oui, et pas trop dure à obtenir.

# ---
# ## Présentation de ma solution numérique
# - Je vais utiliser le module python [`sympy`](http://sympy.org/) et en particulier ses fonctions du module [`sympy.ntheory`](http://docs.sympy.org/latest/modules/ntheory.html) pour avoir facilement accès à une liste de nombres premiers. En particulier, [`primerange`](http://docs.sympy.org/latest/modules/ntheory.html#sympy.ntheory.generate.primerange) sera bien pratique.
# - Je vais procéder par énumération totale avec un "doubling trick" : on cherchera toutes les paires $(A, B)$ bornées dans, disons, $[1, 100]$, puis $[1, 110]$ et ainsi de suite (avec un petit incrément), jusqu'à en trouver une qui marche.
# - Cette approche "ascendante" garantit de terminer, pour peu qu'on puisse prouver théoriquement l'existence de la solution qu'on cherche.
#
# ### Solution théorique ?
# > Je ne vais pas rentrer dans les détails, mais avec [le théorème de la progression arithmétique de Dirichlet](https://fr.wikipedia.org/wiki/Théorème_de_la_progression_arithmétique) (cf. aussi [ce document](http://perso.eleves.ens-rennes.fr/~ariffaut/Agregation/Dirichlet.pdf)), on peut montrer que pour tout nombre de faces $n \geq 1$, on peut trouver un nombre infini d'ensembles $(A, B)$ qui conviennent.
# > *Ouais, c'est balèze*.

# ----
# ### Solution numérique
# On commence avec les dépendances suivantes.
#
# > *Note :* Si vous n'avez pas installé [`sympy`](http://sympy.org/), un simple coup de `pip install sympy` suffit.

# In[1]:

from sympy import isprime
from sympy import primerange

# ---
# ## Implémentation des fonctions utilitaires requises
# D'abord, une fonction `verifie_paire` qui vérifie si une paire $(A, B)$, donnée sous forme de deux *itérateurs* (liste ou ensemble, peu importe), est valide.

# In[2]:

def verifie_paire(A, B):
    """Version efficace, qui s'arrête le plus tôt possible."""
    if A is None or B is None:
        return False
    for a in A:
        # if not isprime(a):  # Inutile car A aura été bien choisi
        #    return False
        for b in B:
            if not isprime(a + b):
                return False
    return True

# Pour visualiser un peu, on en fait une version qui parle :

# In[3]:

def verifie_paire_debug(A, B):
    """Version plus lente qui fait tous les tests, et affiche vrai ou faux pour chaque somme a + b."""
    if A is None or B is None:
        return False
    reponse = True
    for a in A:
        if not isprime(a):
            print("  - a = {:<6} est pas premier, ECHEC ...".format(a))
            reponse = False
        for b in B:
            if not isprime(a + b):
                print("  - a = {:<6} + b = {:<6} donnent {:<6} qui n'est pas premier, ECHEC ...".format(a, b, a + b))
                reponse = False
            else:
                print("  - a = {:<6} + b = {:<6} donnent {:<6} qui est bien premier, OK ...".format(a, b, a + b))
    return reponse

# Premier exemple :

# In[4]:

A = [3]
B = [2]
verifie_paire_debug(A, B)

# Second exemple :

# In[5]:

A = (3, 5)
B = (2, 8)
verifie_paire_debug(A, B)

# In[6]:

verifie_paire(A, B)

# Avec $n = 6$ :

# In[7]:

A = [5, 7, 17, 31, 61, 101]
B = [6, 12, 36, 66, 96, 132]
verifie_paire(A, B)

# Les dés vendus dans le commerce sont marqués avec ces faces :

# In[4]:

A = [2, 6, 20, 252, 266, 380]
B = [17, 41, 107, 191, 311, 347]
verifie_paire(A, B)

# ---
# On a besoin de cette fonction de combinaison :

# In[8]:

from itertools import combinations

# Ensuite, on écrit une fonction qui va énumérer *tous* les ensembles $(A, B)$ possibles et valides, avec $\max (A \cup B ) \leq M$ pour $M$ fixé.
# J'ajoute une borne inférieure $m$, valant par défaut $m = 1$, pour rester le plus générique possible, mais on ne s'en sert pas vraiment.
#
# - On récupère les candidats possibles pour $a \in A$ via `primerange` (notés $C_A$), et pour $B$ via un `range` à pas $2$ pour considérer seulement des nombres pairs (notés $B_A$),
# - Ensuite on boucle sur tous les ensembles $A$ de taille $n$ dans $C_A$, et $B$ de taille $n$ dans $C_B$, via [`itertools.combinations`](https://docs.python.org/3/library/itertools.html#itertools.combinations),
# - On garde uniquement ceux qui sont valides, et on les stocke tous.
#
# > **Attention** ça devient vite très couteux !

# In[9]:

def candidats_CA_CB(M=10, m=1):
    # Premiers entre m et M (compris), ie. P inter [|m, ..., M|]
    C_A = list(primerange(m, M + 1))
    # Nombres pairs
    if m % 2 == 0:
        C_B = list(range(m, M - 1, 2))
    else:
        C_B = list(range(m + 1, M - 1, 2))
    return C_A, C_B

# Cette première approche est très naïve, et énormément de calculs sont dédoublés (on teste les paires $(A, B)$ en faisant plein de tests pour savoir si $a + b$ est premier, c'est couteux).

# In[10]:

def enumere_toutes_les_paires(n=2, M=10, m=1, debug=False):
    C_A, C_B = candidats_CA_CB(M=M, m=m)
    if debug:
        print("C_A =", C_A)
        print("C_B =", C_B)
        print("Combinaison de n =", n, "éléments dans A =", list(combinations(C_A, n)))
        print("Combinaison de n =", n, "éléments dans B =", list(combinations(C_B, n)))
    # C_A, C_B est déjà trié, c'est cool
    all_A_B = []
    for A in combinations(C_A, n):
        if debug: print(" - A =", A)
        for B in combinations(C_B, n):
            if debug: print("     - B =", B)
            if verifie_paire(A, B):
                if debug: print("==> Une paire (A, B) de plus !")
                all_A_B.append((A, B))
    # all_A_B est aussi trié par ordre lexicographique
    return all_A_B

# On peut vérifier que les exemples donnés ci-dessus sont valides, et sont en fait les plus petits :

# In[11]:

n = 1
M = 10
enumere_toutes_les_paires(n, M, debug=True)

# In[12]:

n = 2
M = 10
enumere_toutes_les_paires(n, M, debug=True)

# On peut continuer, avec $n = 3$ et un petit majorant $M$ :

# In[13]:

n = 3
M = 20
enumere_toutes_les_paires(n, M, debug=False)

# On peut continuer, avec $n = 4$ et un petit majorant $M$ :

# In[14]:

n = 4
M = 40
get_ipython().run_line_magic('time', 'enumere_toutes_les_paires(n, M, debug=False)')

# On voit que ça commence à prendre du temps.
#
# Deux améliorations seront explorées :
# - On a pas besoin de toutes les calculer, en fait. Si on cherche juste une paire valide, on peut s'arrêter à la première trouvée.
# - On peut être malin dans la création des candidats $C_B$, plutôt que de juste prendre de nombres pairs, on prend directement des nombres $b$ tels que $a + b$ soit premier.

# ---
# ## Première optimisation
# - On n'a pas besoin d'énunumérer toutes les paires, il suffit de donner la premier trouvée.
# - Pourquoi la première ? Et bien si $C_A$ et $C_B$ sont triés, les candidats pour $(A, B)$ seront aussi triés par ordre lexicographique, et donc le premier trouvé sera le plus petit.

# In[15]:

def premiere_paire(n=2, M=10, m=1):
    C_A, C_B = candidats_CA_CB(M=M, m=m)
    # C_A, C_B est déjà trié, c'est cool
    for A in combinations(C_A, n):
        for B in combinations(C_B, n):
            if verifie_paire(A, B):
                return (A, B)
    return (None, None)

# In[16]:

n = 4
M = 40
get_ipython().run_line_magic('time', 'A, B = premiere_paire(n, M)')
print("A =", A)
print("B =", B)

# Avec $n = 5$, on commence à avoir besoin d'aller plus loin que ce majorant `M = 40` :

# In[17]:

n = 5
M = 40
get_ipython().run_line_magic('time', "A, B = premiere_paire(n, M)  # (None, None) indique qu'on a pas trouvé")
print("A =", A)
print("B =", B)

# Mais parfois on ne sait pas trop quelle valeur donner à ce majorant `M`...
#
# Une approche simple est donc d'augmenter sa valeur jusqu'à trouver une paire valide.

# In[18]:

def premiere_paire_explore_M(n=2, Ms=[10,20,30,40,50], m=1):
    for M in Ms:
        resultat = premiere_paire(n=n, M=M, m=m)
        if resultat[0] is not None:
            return resultat
    return (None, None)

# On peut faire encore mieux, en augmentant `M` automatiquement d'un certain offset $\delta_M > 0$ :

# In[19]:

def premiere_paire_augmente_M(n=2, Mmin=10, deltaM=10, m=1):
    assert isinstance(deltaM, int)
    assert deltaM >= 1
    M = Mmin
    while True:
        print("Appel à premiere_paire(n={}, M={}, m={}) ...".format(n, M, m))
        # Ce n'est pas dangereux, puisqu'on est garanti de trouver une paire qui marche
        resultat = premiere_paire(n=n, M=M, m=m)
        if resultat[0] is not None:
            print("Terminé, avec M =", M)
            return resultat
        M += deltaM

# On peut retrouver le résultat trouvé plus haut, pour $n = 2$ :

# In[20]:

n = 2
Ms = [10,20,30,40,50]
get_ipython().run_line_magic('time', 'A, B = premiere_paire_explore_M(n, Ms)')
print("A =", A)
print("B =", B)

# In[21]:

n = 4
M = 40
deltaM = 10
get_ipython().run_line_magic('time', 'A, B = premiere_paire_augmente_M(n=n, Mmin=M, deltaM=deltaM)')
print("A =", A)
print("B =", B)

# Et on peut résoudre pour $n = 5$ et $n = 6$. Mais cette approche prend beaucoup trop de temps.

# In[22]:

n = 5
M = 100
deltaM = 10
#%time A, B = premiere_paire_augmente_M(n=n, Mmin=M, deltaM=deltaM)
#print("A =", A)
#print("B =", B)

# > *Note :* Plutôt que d'écrire une fonction (qui `return` le premier résultat), on peut aussi écrire un **générateur**, qui `yield` les résultats un à un.
# > Il sera alors possible d'écrire des boucles directement comme ça :
#
# ```python
# for (A, B) in generateur_paires(n=2, M=10, m=1):
#     ...
# ```

# In[23]:

def generateur_paires(n=2, M=10, m=1):
    C_A, C_B = candidats_CA_CB(M=M, m=m)
    # C_A, C_B est déjà trié, c'est cool
    for A in combinations(C_A, n):
        for B in combinations(C_B, n):
            if verifie_paire(A, B):
                yield (A, B)

# ---
# ## Exemples

# ---
# ### Exemple avec des dés à 6 faces

# In[24]:

n = 6
M = 100
#A, B = premiere_paire(n, M)
#print("A =", A)
#print("B =", B)

# ---
# ## Autre approche, deuxième optimisation
# On a vu que l'approche *naïve* exposée ci-dessus n'est vraiment pas très efficace.
#
# On va être plus malin :
# - d'abord générer toutes paires $(a, b)$ possibles, avec $a$ et $a + b$ premiers, pour $a, b \geq M$.
# - ensuite on pourra créer la plus grande paire $(A, B)$, probablement en regardant simplement des intersections d'ensembles.

# ### Préliminaire : petite optimisation pour `isprime`
# En une seule ligne, on peut ajouter un cache ([`functools.lru_cache`](https://docs.python.org/3/library/functools.html#functools.lru_cache)) pour la rendre plus rapide :

# In[25]:

from functools import lru_cache

@lru_cache(maxsize=1<<10, typed=False)
def estpremier(n):
    return isprime(n)

@lru_cache(maxsize=1<<20, typed=False)
def candidats_CA_CB_cache(M=10, m=1):
    return candidats_CA_CB(M=M, m=m)

# In[26]:

import numpy.random as nr
get_ipython().run_line_magic('timeit', '[isprime(n) for n in nr.randint(1, 1<<10, 100000)]')

# In[27]:

get_ipython().run_line_magic('timeit', '[estpremier(n) for n in nr.randint(1, 1<<10, 100000)]')

# ### Générer les paires $(a, b)$ valides
# C'est assez rapide.
# - La première fonction renvoie le résultat sous forme de liste de paires $(a, b)$ avec $b$ tel que $a + b$ soit premier,
# - La seconde fonction renvoie plutôt un dictionnaire qui, à $a \in C_A$, associe tous les $b \in C_B$ tels que $a + b$.

# In[28]:

def genere_paires(M=10, m=1):
    C_A, C_B = candidats_CA_CB_cache(M=M, m=m)
    return [(a, b) for a in C_A for b in C_B if estpremier(a + b)]

def genere_dict(M=10, m=1):
    C_A, C_B = candidats_CA_CB_cache(M=M, m=m)
    return {a: [b for b in C_B if estpremier(a + b)] for a in C_A}

# In[29]:

genere_paires(10)

# In[30]:

genere_dict(10)

# On voit que le nombres de paires $(a, b)$ telle que $a + b$ soit premier et $a, b \leq M$ augmente assez vite avec $M$ :

# In[31]:

len(genere_paires(400))

# In[32]:

import matplotlib.pyplot as plt
X = list(range(10, 2000, 10))
Y = [ len(genere_paires(M)) for M in X ]
plt.plot(X, Y)
plt.show()

# ## Suite : générer une paire $(A, B)$
# - On génère les candidats $C_A, C_B$,
# - On associe à chaque $a \in C_A$ les $b \in C_B$ qui fonctionne, notés $d(a)$,
# - On garde ceux qui ont au moins $n$ valeurs associées,
# - On regarde tous les ensembles $A \subset C_A$ de taille exactement $n$,
# - Pour un $A$ donné, on construit $B$ comme $\bigcap_{a \in A} d(a)$, et on garde les $B$ de taille $\geq n$,
# - On regarde enfin tous les ensembles $B' \subset B$ de taille exactement $n$.
# - Ici, on garde juste le premier trouvé, plus bas on les garde tous.

# In[33]:

@lru_cache(maxsize=1<<10, typed=False)
def genere_A_B(n=2, M=10, m=1):
    C_A, C_B = candidats_CA_CB_cache(M=M, m=m)
    dictAB = {a: {b for b in C_B if estpremier(a + b)} for a in C_A}
    for a in C_A:
        if len(dictAB[a]) < n:
            del dictAB[a]
    # print(dictAB)
    C_A = sorted(list(dictAB.keys()))
    # C_A est déjà trié, c'est cool
    # TODO : This for loop could possibly get parallelized, using joblib.Parallel
    # It could speed up the computation by a factor of cpu_count()...
    for A in combinations(C_A, n):
        # DONE : improve this step to stop as soon as the intersection has length < n
        # B = set.intersection(*(dictAB[a] for a in A))
        B = set(dictAB[A[0]])
        for a in A[1:]:
            if len(B) < n:
                break
            B &= dictAB[a]
        if len(B) >= n:
            print(" - A =", A, "\t et B =", B, "...")
            for BB in combinations(B, n):  # On prend la 1ère
                return (list(A), list(BB))
    return (None, None)

# In[34]:

def test_genere_AB(n=2, M=10, m=1):
    A, B = genere_A_B(n=n, M=M)
    if not verifie_paire(A, B):
        print("Mauvaise paire (A, B) =", (A, B), ": elle n'est pas valable.")
        return (None, None)
    print("A =", A)
    print("B =", B)
    return (A, B)

# In[35]:

n = 2
M = 10
test_genere_AB(n=n, M=M)

# In[36]:

n = 3
M = 40
get_ipython().run_line_magic('time', 'test_genere_AB(n=n, M=M)')

# In[37]:

n = 4
M = 50
get_ipython().run_line_magic('time', 'test_genere_AB(n=n, M=M)')

# On peut comparer cette nouvelle fonction avec `premiere_paire` écrite plus haut :

# In[38]:

n = 4
M = 50
get_ipython().run_line_magic('timeit', '-n1 premiere_paire(n=n, M=M)')

# In[39]:

n = 4
M = 50
get_ipython().run_line_magic('timeit', '-n1 genere_A_B(n=n, M=M)')

# → On est effectivement bien plus rapide. Yay.

# In[40]:

n = 5
M = 100
get_ipython().run_line_magic('time', 'test_genere_AB(n=n, M=M)')

# In[41]:

n = 6
M = 100
get_ipython().run_line_magic('time', 'test_genere_AB(n=n, M=M)')

# On peut vérifier que la valeur du majorant $M$ sur les valeurs des faces ne change pas le premier $A$ trouvé, mais comme les $B$ ne sont **pas** parcourus dans un ordre lexicographique (les `set` sont non triés), on ne trouve pas le même $B$.
# Et plus $M$ est grand, plus c'est long à calculer.

# In[42]:

n = 6
M = 200
get_ipython().run_line_magic('time', 'test_genere_AB(n=n, M=M)')

# Ça marche plutôt bien, on trouve effectivement la paire qui possède le plus petit $\max A$ mais pas celle qui minimise $\sum A \cup B$ ou $\max A \cup B$...

# ## Trouver la paire de somme minimale ou de max minimum.
# - On va calculer *toutes* les paires, avec notre fonction optimisée, dans `genere_toutes_A_B`,
# - Puis on les triera (en ordre croissant) selon le critère voulu,
# - Et on renvoie la première, qui minimisera donc le critère cherché.
#
# > *Note :* en étant malin, on pourrait carrément faire mieux qu'une énumération complète. Mais la flemme.

# ### D'abord, générer toutes les paires $(A, B)$
# C'est la même fonction que `genere_A_B`, mais on les renvoie toutes au lieu de s'arrêter à la première.

# In[43]:

@lru_cache(maxsize=1<<10, typed=False)
def genere_toutes_A_B(n=2, M=10, m=1):
    C_A, C_B = candidats_CA_CB_cache(M=M, m=m)
    dictAB = {a: {b for b in C_B if estpremier(a + b)} for a in C_A}
    for a in C_A:
        if len(dictAB[a]) < n:
            del dictAB[a]
    # print(dictAB)
    C_A = sorted(list(dictAB.keys()))
    # C_A est déjà trié, c'est cool
    paires = []
    # TODO : This for loop could very easily get parallelized, using joblib.Parallel
    # It could speed up the computation by a factor of cpu_count()...
    for A in combinations(C_A, n):
        # DONE : improve this step to stop as soon as the intersection has length < n
        # B = set.intersection(*(dictAB[a] for a in A))
        B = set(dictAB[A[0]])
        for a in A[1:]:
            if len(B) < n:
                break
            B &= dictAB[a]
        if len(B) >= n:
            for BB in combinations(B, n):
                print(" - A =", A, "\t et B =", B, "...")
                # print('.', end='')  # Affiche .... au fur et à mesure de l'exécution.
                paires.append((list(A), list(BB)))
    print('')
    return paires

# On vérifie que ça fonctionne comme prévu :

# In[44]:

n = 4
M = 40
genere_toutes_A_B(n=n, M=M)

# Il suffit donc de trier selon le critère choisi.
# $$
#     \mathrm{sumsum} : (A, B) \mapsto \sum_{a \in A} a + \sum_{b \in B} b.
# $$
# $$
#     \mathrm{maxmax} : (A, B) \mapsto \max A \cup B.
# $$

# In[45]:

def sumsum(paire):
    return sum(paire[0]) + sum(paire[1])

def maxmax(paire):
    return max(max(paire[0]), max(paire[1]))

# On peut aussi prendre un critère qui combine les deux :

# In[46]:

def combine(paire):
    return (sumsum(paire) + maxmax(paire)) / 2

# Il suffit d'utiliser l'argument `key=` de la fonction [`sorted`](https://docs.python.org/3/library/functions.html#sorted) :

# In[47]:

def trie_et_prend_min(paires, key=sumsum):
    paires = sorted(paires, key=key)
    A, B = paires[0]
    assert verifie_paire(A, B)
    return (sorted(A), sorted(B))

# Pour tester :

# In[48]:

def test_trie_et_prend_min(n=2, M=10):
    paires = genere_toutes_A_B(n=n, M=M)
    print("Pour n =", n, "et M =", M, "il y a", len(paires), "paires (A, B) trouvées valides ...")

    print("- On recherche celle de somme sum(A u B) minimale ...")
    A, B = trie_et_prend_min(paires, key=sumsum)
    print("A =", A)
    print("B =", B)
    print("  Elle donne sumsum =", sumsum((A, B)), "et maxmax =", maxmax((A, B)))

    print("- On recherche celle de maximum max(A u B) minimale ...")
    A, B = trie_et_prend_min(paires, key=maxmax)
    print("A =", A)
    print("B =", B)
    print("  Elle donne sumsum =", sumsum((A, B)), "et maxmax =", maxmax((A, B)))

    print("- On recherche celle de moyenne sumsum(A,B), maxmax(A,B) minimale ...")
    A, B = trie_et_prend_min(paires, key=combine)
    print("A =", A)
    print("B =", B)
    print("  Elle donne sumsum =", sumsum((A, B)), "et maxmax =", maxmax((A, B)))

    # for key in [sumsum, maxmax, combine]:
    #    alls = [ (key(paire), (sorted(paire[0]), sorted(paire[1]))) for paire in sorted(paires, key=key) ]
    #    print(alls[:5])
    return (A, B)

# On peut refaire les mêmes exemples pour $n = 3, 4, 5$, et enfin $n = 6$.

# In[49]:

n = 3
M = 20
test_trie_et_prend_min(n, M)

# In[50]:

n = 4
M = 37
test_trie_et_prend_min(n, M)

# In[51]:

n = 5
M = 70
test_trie_et_prend_min(n, M)

# On observe que celle qui minimise `sumsum` est la même que celle qui minimise `maxmax`. Curieux ?
#
# C'est aussi le cas pour $n = 6$ :

# In[52]:

n = 6
M = 150
get_ipython().run_line_magic('time', 'test_trie_et_prend_min(n, M)')

# In[53]:

A, B = test_trie_et_prend_min(n, M)
verifie_paire_debug(A, B)

# ## Conclusion pour des dés à $6$ faces
# Avec $n = 6$ dés, la plus petite paire de valeurs $(A, B)$ trouvée est
# - A = $\{7, 13, 31, 37, 73, 97\}$
# - B = $\{6, 10, 16, 30, 66, 76\}$
#
# Elle donne $\sum A \cup B = 462$ et $\max A \cup B = 97$.
#
# Assez surprenament, c'est la paire qui minimise les deux critères à la fois (et l'unique paire qui satisfait cette propriété).

# ---
# ## Avec plus de faces ?

# ### Exemples avec des dés à 8, 10, 12 et 20 faces
# > Ça va commencer à être *très couteux*. Je recommande de ne pas faire tourner ce code sur votre machine, mais sur un serveur de calcul, par exemple !

# In[54]:

n = 7
M = 170
get_ipython().run_line_magic('time', 'test_genere_AB(n=n, M=M)')
get_ipython().run_line_magic('time', 'test_trie_et_prend_min(n, M)')

# #### Avec 8 faces

# In[55]:

n = 8
M = 440
get_ipython().run_line_magic('time', 'test_genere_AB(n=n, M=M)')
# %time test_trie_et_prend_min(n, M)

# #### Avec 10 faces

# In[ ]:

n = 10
M = 550
get_ipython().run_line_magic('time', 'test_genere_AB(n=n, M=M)')
# %time test_trie_et_prend_min(n, M)

# #### Avec 12 faces

# In[ ]:

n = 12
M = 800
get_ipython().run_line_magic('time', 'test_genere_AB(n=n, M=M)')
# %time test_trie_et_prend_min(n, M)

# #### Avec 20 faces

# In[ ]:

n = 20
M = 1000
get_ipython().run_line_magic('time', 'test_genere_AB(n=n, M=M)')
# %time test_trie_et_prend_min(n, M)

# ### Exemples avec des dés de tailles différentes
# > Il ne serait pas très compliqué d'adapter le code précédent pour considérer $A$ de taille $n$ et $B$ de taille $p$ avec possiblement $n \neq p$.
# > Je le ferai, plus tard.

# ### Exemples avec $100$ faces ?
# > Il faudrait une machine costaux, et du temps...

# ----
# # Généralisations possibles
# - Dés avec un nombre de faces différentes, i.e., $\# A \neq \# B$,
# - Plus de deux dés, i.e., considérer $A_1, \dots, A_p$ et chercher la même propriété :
#   $$ \forall a_1 \in A_1, \dots, a_p \in A_p, a_1 + \dots + a_p \in \mathcal{P}. $$
#   En imposant toujours que $A_1 \subset \mathcal{P}$, et éventuellement plus.
# - Résoudre autrement le problème initial pour ne pas avoir à donner une borné $M$ sur $\max A, B$.


# This provides functions that create shapes for generating synthetic 3D
# shapes.
#
# Author: vivekv2@gmail.com

from typing import Tuple

import numpy as np

def get_centered_zyx_meshgrid(shape: np.ndarray) -> Tuple[np.ndarray, ...]:
    """Return a tuple (Z,Y,X) of coordinates for a 3D array generated via
    np.meshgrid. shape should be an array of odd numbers, so the center value
    of the coordinate box can be (0,0,0)."""

    shape = np.array(shape)

    bounds = (shape - 1)/2

    z = np.linspace(-bounds[0], bounds[0], shape[0])
    y = np.linspace(-bounds[1], bounds[1], shape[1])
    x = np.linspace(-bounds[2], bounds[2], shape[2])

    Z, Y, X = np.meshgrid(z, y, x, indexing='ij')

    return (Z, Y, X)

def gaussian(sigma:np.ndarray, shape=None, dtype=np.float32, norm="max"
) -> np.ndarray:
    """Make a 3D gaussian array density with the specified shape. norm can be
    either 'max' (largest value is set to 1.0) or 'area' (sum of values is
    1.0)."""

    sigma = np.array(sigma)

    if shape is None:
        shape = 2*sigma + 1

    (Z, Y, X) = get_centered_zyx_meshgrid(shape)

    g = np.exp(-(X**2)/(2.0*sigma[2])
               -(Y**2)/(2.0*sigma[1])
               -(Z**2)/(2.0*sigma[0]))

    if norm=="max":
        g = g / np.max(g)
    elif norm=="area":
        g = g / np.sum(g)
    else:
        raise ValueError("norm must be one of 'max' or 'area'")

    return g.astype(dtype)

def ellipsoid(r:np.ndarray, shape=None, dtype=np.float32) -> np.ndarray:
    """Make an ellipse with the specified radius."""

    r = np.array(r)
    if shape is None:
        shape = 2*r + 1

    (Z, Y, X) = get_centered_zyx_meshgrid(shape)

    D = (Z/r[0])**2 + (Y/r[1])**2 + (X/r[2])**2

    return (D < 1).astype(dtype)

def place_pattern(pattern:np.ndarray, location: np.ndarray, vol: np.ndarray
) -> None:
    """Place the pattern array in vol (mutating) centered at location. For good
    centering, pattern should have odd dimension."""

    location = np.array(location)

    s = (location - (np.array(pattern.shape) - 1)/2).astype(int)
    e = (s + pattern.shape).astype(int)
    vol[s[0]:e[0], s[1]:e[1], s[2]:e[2]] = pattern

def place_patterns(patterns: Tuple[np.ndarray, ...],
        locations: Tuple[np.ndarray, ...], vol: np.ndarray) -> None:
    """Place each pattern in vol (mutating) centered at the corresponding
    locations."""

    for i in len(patterns):
        place_pattern(patterns[i], locations[i], vol)


import numpy as np
from numpy import inf
import time
import random

def GS_static(graph, eps, alpha, seed, method ) :

	# initial distribution
	N = graph.A.shape[0]
	y = np.zeros([N,1]); y[seed,0] = 1;

	# extract operator and coefficients
	if method == 'PageRank':
		rho = (1-alpha)
		psi = alpha;
		OP = graph.P.T
		gt = graph.pr.T
		deg = np.count_nonzero(OP, axis = 1)

	elif method == 'GammaPageRank':
		mu = (1-alpha)/alpha
		psi = -10/(2*mu + 10)
		rho = (2*mu)/(2*mu + 10)
		OP = graph.Op_shift
		gt = graph.gpr
		deg = np.count_nonzero(OP, axis = 1)

	# compute initial distribution (assuming p(0) = 0)
	p = np.zeros([N,1]);
	r = rho*y
	it = 0;
	flops = 0

	#while np.linalg.norm(r, inf) > eps :
	while (np.linalg.norm(p - gt, ord=2)/np.linalg.norm(gt,ord=2))  > eps :
		it += 1;
		ix_u = np.argmax(abs(r))
		r_u = float(r[ix_u]);
		p[ix_u] += r_u
		r[ix_u] = 0;
		r = r + np.expand_dims(psi*r_u*OP[:,ix_u],1)

		# count the flops
		flops_scaling = np.count_nonzero(OP[:,ix_u])
		flops = flops + 2*flops_scaling + np.int(deg[ix_u])

	print('---- Gauss Soutwell ----')
	print('iter =', it)
	print('flops =', flops)
	print('err =', np.linalg.norm(p - gt,ord=2)/np.linalg.norm(gt,ord=2) )
	return p, r, it, flops

def PI_static(graph, eps, alpha, seed, method) :

	# initial distribution
	N = graph.A.shape[0]
	y = np.zeros([N,1]); y[seed,0] = 1;

	# extract operator and coefficients
	if method == 'PageRank':
		rho = (1-alpha)
		psi = alpha;
		OP = graph.P.T
		gt = graph.pr.T

	elif method == 'GammaPageRank':
		mu = (1-alpha)/alpha
		psi = -10/(2*mu + 10)
		rho = (2*mu)/(2*mu + 10)
		OP = graph.Op_shift
		gt = graph.gpr

	# compute initial distribution
	p = np.zeros([N,1]);
	it = 0;
	flops = 0

	#for it in range(K):
	while (np.linalg.norm(p - gt, 2)/np.linalg.norm(gt,ord=2)) > eps :
		it += 1

		# count flops
		nnz = np.where(p > 0)[0]
		flops_dotprod = np.count_nonzero(OP[:,nnz])
		flops_scaling = np.count_nonzero(p)
		flops_addition = np.count_nonzero(OP.dot(p))
		flops = flops + flops_dotprod + flops_scaling + flops_addition

		# next iteration in approx
		p = rho*y + psi*OP.dot(p)

	print('---- Power Iteration ----')
	print('iter =', it)
	print('flops =', flops)
	print('err =', np.linalg.norm(p - gt, 2)/np.linalg.norm(gt,ord=2))

	return p, it, flops

def CP_static(graph, eps, alpha, seed, method, mode) :
	# initial distribution
	N = graph.A.shape[0];
	y = np.zeros([N,1]); y[seed,0] = 1
	flops = 0

	# Coefficient parameters
	mu = (1-alpha)/alpha
	theta = np.linspace(0,np.pi,50000+1)
	step = theta[1] - theta[0]

	# extract operator and coefficients
	if method == 'PageRank':
		Lap = (graph.D - graph.A).dot(graph.Dinv)
		OP = Lap - np.eye(N)
		gt = graph.pr.T
		filt_arg = (np.cos(theta) + 1)
		filt = np.divide(mu, mu + filt_arg)

	elif method == 'GammaPageRank':
		OP = graph.Op_shift
		gt = graph.gpr
		filt_arg = (10/2)*(np.cos(theta) + 1)
		filt = np.divide(mu, mu + filt_arg)

	# coefficients
	tmp1 = np.multiply(np.cos(0*theta),filt*step); tmp1[0]=tmp1[0]/2; tmp1[-1]=tmp1[-1]/2;
	tmp2 = np.multiply(np.cos(1*theta),filt*step); tmp2[0]=tmp2[0]/2; tmp2[-1]=tmp2[-1]/2;
	coef1 = (2/np.pi)*np.sum(tmp1)
	coef2 = (2/np.pi)*np.sum(tmp2)

	# Polynomial elements
	polyTerm_2back = np.array(y)
	polyTerm_1back = np.array(OP).dot(y)
	nnz = np.where(y != 0)[0]
	flops = flops + np.count_nonzero(OP[:,nnz])

        # Chebyshev approximation
	Cheby_approximation_prev = 0.5*coef1*polyTerm_2back + coef2*polyTerm_1back;
	Cheby_approximation_curr = np.array(Cheby_approximation_prev)
	flops = flops + 2*np.count_nonzero(polyTerm_1back)

	#for it in range(2, hops-1):
	it = 2;
	activeNodes = np.where(graph.clust_memb > 0)[0]

	if mode == 'FixedError':
		while (np.linalg.norm(Cheby_approximation_curr[activeNodes] - gt[activeNodes], ord=2)/np.linalg.norm(gt[activeNodes],ord=2)) > eps :

			# Chebyshev coefficient
			tmp = np.array(np.multiply(np.cos(it*theta),filt*step)); tmp[0]=tmp[0]/2; tmp[-1]=tmp[-1]/2;
			coef_curr = (2/np.pi)*np.sum(tmp);

			# Current polynomial term
			polyTerm_curr = 2*(OP).dot(polyTerm_1back) - polyTerm_2back;
			nnz = np.where(polyTerm_1back != 0)[0]
			flops = flops + np.count_nonzero(OP[:,nnz]) + np.count_nonzero(OP.dot(polyTerm_1back))  + np.count_nonzero(polyTerm_1back) + np.count_nonzero(polyTerm_2back)

			# Chebyshev approximation
			Cheby_approximation_curr = np.array(Cheby_approximation_prev + coef_curr*polyTerm_curr);
			flops = flops + 2*np.count_nonzero(polyTerm_curr)

			# Update
			polyTerm_2back = np.array(polyTerm_1back);
			polyTerm_1back = np.array(polyTerm_curr);
			Cheby_approximation_prev = np.array(Cheby_approximation_curr);
			it += 1
	elif mode == 'FixedFlops':
		while flops < eps:
			# Chebyshev coefficient
			tmp = np.array(np.multiply(np.cos(it*theta),filt*step)); tmp[0]=tmp[0]/2; tmp[-1]=tmp[-1]/2;
			coef_curr = (2/np.pi)*np.sum(tmp);

			# Current polynomial term
			polyTerm_curr = 2*(OP).dot(polyTerm_1back) - polyTerm_2back;
			nnz = np.where(polyTerm_1back != 0)[0]
			flops = flops + np.count_nonzero(OP[:,nnz]) + np.count_nonzero(OP.dot(polyTerm_1back))  + np.count_nonzero(polyTerm_1back) + np.count_nonzero(polyTerm_2back)

			# Chebyshev approximation
			Cheby_approximation_curr = np.array(Cheby_approximation_prev + coef_curr*polyTerm_curr);
			flops = flops + 2*np.count_nonzero(polyTerm_curr)

			# Update
			polyTerm_2back = np.array(polyTerm_1back);
			polyTerm_1back = np.array(polyTerm_curr);
			Cheby_approximation_prev = np.array(Cheby_approximation_curr);
			it += 1

	p = Cheby_approximation_curr
	err = np.linalg.norm(p[activeNodes] - gt[activeNodes],ord=2)/np.linalg.norm(gt[activeNodes],ord=2)
	print('---- Chebyshev ----')
	print('iter =', it)
	print('flops =', flops)
	print('err =', err )

	return p, it, flops, err


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

import numpy as np

def cart2bary(verts, pts):
    """ retrns barycentric coords of pt on a tri w/ vertices verts
    @param verts  tuple of tuples specifying verts
    @param pt  numpy array of point on triangle shape (2, npts)
    """
    npts = pts.shape[1]
    _lambda = np.zeros((3, npts))
    (x1, y1), (x2, y2), (x3, y3) = verts
    T = np.array([[x1 - x3, x2 - x3],
                  [y1 - y3, y2 - y3]])
    r = np.copy(pts)
    r[0,:] -= x3
    r[1,:] -= y3

    _lambda[:2, :] = np.linalg.solve(T, r)
    _lambda[2, :] = 1 - _lambda[0,:] - _lambda[1,:]
    return _lambda

def bary2cart(verts, _lambda):
    """converts from barycentric to cartesian coordinates
    @param verts  tuple of tuples specifying triangle vertices
    @param _lambda array of barycentric points (3, npts)
    """
    npts = _lambda.shape[1]
    λ1, λ2, λ3 = _lambda[0,:], _lambda[1,:], _lambda[2,:]
    x, y = np.zeros(npts), np.zeros(npts)
    (x1, y1), (x2, y2), (x3, y3) = verts
    x = λ1*x1 + λ2*x2 + λ3*x3
    y = λ1*y1 + λ2*y2 + λ3*y3
    return x, y

def uniform_bary_coords(p):
    """ generate bary coords of uniform nodal pts for tri of order p"""
    N, Np = p, int((p+1)*(p+2)/2)
    node = 0
    bary = np.zeros((3, Np))
    for i in range(N+1):
        for j in range(N+1-i):
            λ1, λ2 = i/N, j/N
            λ3 = 1 - λ1 - λ2
            bary[:, node] = np.asarray([λ1, λ2, λ3])
            node += 1
    return bary


"""

finite_difference_coefficients.py
TT.py
Determine the coefficients to a finite differencing scheme

"""

import numpy as np
import math

def finite_difference_coefficients(x, deriv):
    """
    Find the coefficients to a finite differencing scheme.
    i.e. find the coefficients [c1, c2, c3] that are appropriate for the 2nd order
    centered difference approximation to the first derivative:

    (du/dx)[j] = 1/dx * (c1*u[i-j] + c2*u[j] + c3*u[j+1]) + error terms

    Parameters
    ----------
    x: numpy array:
        Vector containing relative distances to sampling points
    deriv: int
        Order of the derivative to approximate

    Returns
    -------
    coefficients : numpy array
        Coefficients requested
    coefficient_table : numpy array
        Output matrix showing the Taylor table coefficients
    error_order : lowest order of higher order terms
                  (order of accuracy (e.g. O(dx**2) is 2nd order))
    """

    N = len(x)
    coefficient_table = np.zeros((N, N + 7))

    #Do for each coefficient of the Taylor Expansion
    #Expand out 6 extra terms for higher accuracies at higher derivatives
    for j in range(N):
        for n in range(N + 7):
            coefficient_table[j, n] = -1.0 / math.factorial(n) * x[j]**n

    #Allocate derivative
    d = np.zeros((N, 1))
    d[deriv] = -1

    #Calculate the coefficients
    c = np.linalg.lstsq(coefficient_table[:, 0:N].T, d, rcond=-1)[0]

    #Set any small value to zeros
    for i in range(len(c)):
        if np.abs(c[i]) < 1e-10:
            c[i] = 0

    """
    Determine the order of accuracy
    Multiply the Taylor table of coefficients by the finite difference
    coefficients and add up all like terms.
    Terms that don't add to zero are the objective derivative and
    the residual higher order terms
    """

    a = (c.T @ coefficient_table)[0]

    #Values closer to zero are zero
    for i in range(len(a)):
        if np.abs(a[i]) < 1e-5:
            a[i] = 0

    """
    Location of first nonzero term is the order of derivative you are approximating.
    Second nonzero term tells the lowest order of the higher order terms
    """

    position = np.argwhere(a)[:2]

    error_order = position[1][0] - deriv

    coefficient_table = coefficient_table[:, 0:N]

    return c.T[0]


"""
David Kartchner
Project Euler Problem 27--Quadratic Primes
May 21, 2016
"""

import numpy as np

#find all primes under n
def primes_below_n(n):
	primes = np.array([i for i in xrange(2, n+1)])
	k = int(np.sqrt(n))
	#we only need to use primes less than sqrt n to eliminate composite numbers
	seive = np.array([i for i in xrange(2, k+1)])
	#j is indexing number
	j = 0

	while j < len(seive):
		primes = primes[(primes <= seive[j])|(primes%seive[j] != 0)]
		seive = seive[(seive%seive[j]!=0)|(seive <= seive[j])]
		j+=1
	return primes

def quadratic_primes():
    max_seq = 0
    best_a=1
    best_b=41
    possible_b = primes_below_n(1000)
    divisors = primes_below_n(2000)
    for a in xrange(-1000,1001):
        #if a%10==0:
            #print a
        for b in possible_b:
            n=0
            prime=True
            while prime:
                num = n**2 + a*n + b
                for i in divisors:

                    if num%i == 0 and num!=i:
                        prime=False
                if prime:
                    n+=1
                if n > max_seq:
                    print "New best a and b:", a, b
                    print "New best n:", n
                    max_seq = n
                    best_a = a
                    best_b = b
    print "Best Product:", best_b*best_a

if __name__ == '__main__':
    quadratic_primes()


from sklearn.datasets import make_blobs
X, y = make_blobs(
    n_samples=150, n_features=2, centers=3,
    cluster_std=0.5, shuffle=True, random_state=0
)

import matplotlib.pyplot as plt
# plt.scatter(
#     X[:, 0], X[:, 1], c='white', marker='o', s=50, edgecolors='black'
# )
# plt.grid()
# plt.show()

# k-means
from sklearn.cluster import KMeans
km = KMeans(
    n_clusters=3, init='random',
    n_init=10,max_iter=300,tol=1e-4,random_state=0
)
y_km = km.fit_predict(X)

plt.scatter(
    X[y_km==0, 0], X[y_km==0, 1], s=50,
    c='lightgreen', marker='s', label='cluster 1'
)
plt.scatter(
    X[y_km==1, 0], X[y_km==1, 1], s=50,
    c='orange', marker='o', label='cluster 2'
)
plt.scatter(
    X[y_km==2, 0], X[y_km==2, 1], s=50,
    c='lightblue', marker='v', label='cluster 3'
)
plt.scatter(
    km.cluster_centers_[:, 0], km.cluster_centers_[:, 1],
    s=250, marker='*', c='red', label='centroids'
)
plt.legend()
plt.grid()
plt.show()

# elbow method
print('Distortion: %.2f' % km.inertia_)
distortions = []
for i in range(1, 11):
    km = KMeans(
        n_clusters=i, init='k-means++', n_init=10, max_iter=300,random_state=0
    )
    km.fit(X)
    distortions.append(km.inertia_)
plt.plot(range(1,11), distortions, marker='o')
plt.xlabel('Number of clusters')
plt.ylabel('Distortion')
plt.show()

# silhouette
km = KMeans(
    n_clusters=3, init='k-means++', n_init=10, max_iter=300,
    tol=1e-4, random_state=0
)
y_km = km.fit_predict(X)

import numpy as np
from matplotlib import cm
from sklearn.metrics import silhouette_samples
cluster_labels = np.unique(y_km)
n_clusters = cluster_labels.shape[0]
silhouette_vals = silhouette_samples(X, y_km, metric='euclidean')
y_ax_lower, y_ax_upper = 0, 0
yticks = []
for i, c in enumerate(cluster_labels):
    c_silhouette_vals = silhouette_vals[y_km == c]
    c_silhouette_vals.sort()
    y_ax_upper += len(c_silhouette_vals)
    color = cm.jet(i / n_clusters)
    plt.barh(range(y_ax_lower, y_ax_upper),
        c_silhouette_vals, height=1.0, edgecolor='none', color=color)
    yticks.append((y_ax_lower+y_ax_upper) / 2)
    y_ax_lower += len(c_silhouette_vals)
silhouette_avg = np.mean(silhouette_vals)
plt.axvline(silhouette_avg, color='red', linestyle='--')
plt.yticks(yticks, cluster_labels + 1)
plt.ylabel('Cluster')
plt.xlabel('Silhouette coefficient')
plt.show()


import numpy as np
from scipy.sparse import csr_matrix

DEFAULT_H = 0.1

class HuberSVM():
    @staticmethod
    def loss(theta, x, y, lambda_param=None, h=DEFAULT_H):
        """Loss function for Huber SVM without regularization"""

        z = y * x.dot(theta)

        losses = np.zeros(z.shape)
        high_inds = z < 1 - h  # since we want 1 - z > h
        losses[high_inds] = 1 - z[high_inds]

        low_inds = z > 1 + h  # since we want 1 - z < -h
        mid_inds = ~(high_inds+low_inds)
        num = 1 - z[mid_inds]
        losses[mid_inds] = pow(num,2)/(4*h) + num/2 + h/4
        loss = np.sum(losses)/x.shape[0]

        return loss

    @staticmethod
    def gradient(theta, x, y, lambda_param=None, h=DEFAULT_H):
        """
        Gradient function for Huber SVM without regularization
        Based on the above Huber SVM
        """

        z = y * x.dot(theta)

        high_inds = z < 1 - h  # since we want 1 - z > h
        low_inds = z > 1 + h  # since we want 1 - z < -h
        mid_inds = ~(high_inds+low_inds)
        num = 1 - z[mid_inds]

        grads = np.zeros((x.shape[0],x.shape[1]))

        if np.sum(high_inds) > 0:
          x_result = x[np.where(high_inds==1)[0],:]
          if isinstance(x_result, csr_matrix):
              x_result = x_result.toarray()

          grads[np.where(high_inds==1)[0],:] = (-1
                                                * y[high_inds].reshape(
                                                    (y[high_inds].shape[0]),1)
                                                * x_result)
        if np.sum(mid_inds) > 0:
          x_result = x[np.where(mid_inds==1)[0],:]
          if isinstance(x_result, csr_matrix):
              x_result = x_result.toarray()

          grads[np.where(mid_inds==1)[0],:] = ((-1
                                                * y[mid_inds].reshape(
                                                    (y[mid_inds].shape[0]),1)
                                                * x_result)
                                               * (num/(2*h) + 0.5).reshape(
                                                   (y[mid_inds].shape[0]),1))
        grad = np.mean(grads, 0)

        return grad

class HuberSVMRegular():
    @staticmethod
    def loss(theta, x, y, lambda_param, h=DEFAULT_H):
        regularization = (lambda_param/2) * np.sum(theta*theta)
        return HuberSVM.loss(theta, x, y, h) + regularization

    @staticmethod
    def gradient(theta, x, y, lambda_param, h=DEFAULT_H):
        regularization = lambda_param * theta
        return HuberSVM.gradient(theta, x, y, h) + regularization


#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time     : 2018/11/19 15:30
# @Author   : Iydon
# @File     : course7.3.py

import numpy as np

def jacobi_method(A, b):
	A_,b_ = A.copy(),b.copy()
	diag  = np.diag(A_)
	shap  = A.shape
	A_ = np.diag(diag) - A_
	x_ = np.zeros(b.shape)
	for i in range(shap[0]):
		A_[i,:] /= diag[i]
		b_[i,:] /= diag[i]
	while True:
		yield x_
		x_ = A_*x_ + b_

A = np.matrix([[10.,-1,2,0],[-1,11,-1,3],[2,-1,10,-1],[0,3,-1,8]])
b = np.matrix([[6.],[25],[-11],[15]])
result = jacobi_method(A, b)
t = 0
for r in result:
	print(t, r.T, "\n")
	t += 1
	if t > 10: break