import math

import numpy as np
import pytest

from skspatial.objects import Circle
from skspatial.objects import Line
from skspatial.objects import Points

@pytest.mark.parametrize(
    ("point", "radius", "message_expected"),
    [
        ([0, 0, 0], 1, "The point must be 2D"),
        ([1, 2, 3], 1, "The point must be 2D"),
        ([0, 0], 0, "The radius must be positive"),
        ([0, 0], -1, "The radius must be positive"),
        ([0, 0], -5, "The radius must be positive"),
    ],
)
def test_failure(point, radius, message_expected):

    with pytest.raises(ValueError, match=message_expected):
        Circle(point, radius)

@pytest.mark.parametrize(
    ("radius", "circumference_expected", "area_expected"),
    [
        (1, 2 * np.pi, np.pi),
        (2, 4 * np.pi, 4 * np.pi),
        (3, 6 * np.pi, 9 * np.pi),
        (4.5, 9 * np.pi, 20.25 * np.pi),
        (10, 20 * np.pi, 100 * np.pi),
    ],
)
def test_circumference_area(radius, circumference_expected, area_expected):

    circle = Circle([0, 0], radius)

    assert math.isclose(circle.circumference(), circumference_expected)
    assert math.isclose(circle.area(), area_expected)

@pytest.mark.parametrize(
    ("circle", "point", "dist_expected"),
    [
        (Circle([0, 0], 1), [0, 0], 1),
        (Circle([0, 0], 1), [0.5, 0], 0.5),
        (Circle([0, 0], 1), [1, 0], 0),
        (Circle([0, 0], 1), [0, 1], 0),
        (Circle([0, 0], 1), [-1, 0], 0),
        (Circle([0, 0], 1), [0, -1], 0),
        (Circle([0, 0], 1), [2, 0], 1),
        (Circle([0, 0], 1), [1, 1], math.sqrt(2) - 1),
        (Circle([1, 1], 1), [0, 0], math.sqrt(2) - 1),
        (Circle([0, 0], 2), [0, 5], 3),
    ],
)
def test_distance_point(circle, point, dist_expected):

    assert math.isclose(circle.distance_point(point), dist_expected)

@pytest.mark.parametrize(
    ("circle", "point", "bool_expected"),
    [
        (Circle([0, 0], 1), [1, 0], True),
        (Circle([0, 0], 1), [0, 1], True),
        (Circle([0, 0], 1), [-1, 0], True),
        (Circle([0, 0], 1), [0, -1], True),
        (Circle([0, 0], 1), [0, 0], False),
        (Circle([0, 0], 1), [1, 1], False),
        (Circle([0, 0], 2), [1, 0], False),
        (Circle([1, 0], 1), [1, 0], False),
        (Circle([0, 0], math.sqrt(2)), [1, 1], True),
    ],
)
def test_contains_point(circle, point, bool_expected):

    assert circle.contains_point(point) == bool_expected

@pytest.mark.parametrize(
    ("circle", "point", "point_expected"),
    [
        (Circle([0, 0], 1), [1, 0], [1, 0]),
        (Circle([0, 0], 1), [2, 0], [1, 0]),
        (Circle([0, 0], 1), [-2, 0], [-1, 0]),
        (Circle([0, 0], 1), [0, 2], [0, 1]),
        (Circle([0, 0], 1), [0, -2], [0, -1]),
        (Circle([0, 0], 5), [0, -2], [0, -5]),
        (Circle([0, 1], 5), [0, -2], [0, -4]),
        (Circle([0, 0], 1), [1, 1], math.sqrt(2) / 2 * np.ones(2)),
        (Circle([0, 0], 2), [1, 1], math.sqrt(2) * np.ones(2)),
    ],
)
def test_project_point(circle, point, point_expected):

    point_projected = circle.project_point(point)
    assert point_projected.is_close(point_expected)

@pytest.mark.parametrize(
    ("circle", "point"),
    [
        (Circle([0, 0], 1), [0, 0]),
        (Circle([0, 0], 5), [0, 0]),
        (Circle([7, -1], 5), [7, -1]),
    ],
)
def test_project_point_failure(circle, point):

    message_expected = "The point must not be the center of the circle or sphere."

    with pytest.raises(ValueError, match=message_expected):
        circle.project_point(point)

@pytest.mark.parametrize(
    ("points", "circle_expected"),
    [
        ([[1, 1], [2, 2], [3, 1]], Circle(point=[2, 1], radius=1)),
        ([[2, 0], [-2, 0], [0, 2]], Circle(point=[0, 0], radius=2)),
        ([[1, 0], [0, 1], [1, 2]], Circle(point=[1, 1], radius=1)),
    ],
)
def test_best_fit(points, circle_expected):

    points = Points(points)
    circle_fit = Circle.best_fit(points)

    assert circle_fit.point.is_close(circle_expected.point, abs_tol=1e-9)
    assert math.isclose(circle_fit.radius, circle_expected.radius)

@pytest.mark.parametrize(
    ("points", "message_expected"),
    [
        ([[1, 0, 0], [-1, 0, 0], [0, 1, 0]], "The points must be 2D."),
        ([[2, 0], [-2, 0]], "There must be at least 3 points."),
        ([[0, 0], [1, 1], [2, 2]], "The points must not be collinear."),
    ],
)
def test_best_fit_failure(points, message_expected):

    with pytest.raises(ValueError, match=message_expected):
        Circle.best_fit(points)

@pytest.mark.parametrize(
    ("circle", "line", "point_a_expected", "point_b_expected"),
    [
        (Circle([0, 0], 1), Line([0, 0], [1, 0]), [-1, 0], [1, 0]),
        (Circle([0, 0], 1), Line([0, 0], [0, 1]), [0, -1], [0, 1]),
        (Circle([0, 0], 1), Line([0, 1], [1, 0]), [0, 1], [0, 1]),
        (
            Circle([0, 0], 1),
            Line([0, 0.5], [1, 0]),
            [-math.sqrt(3) / 2, 0.5],
            [math.sqrt(3) / 2, 0.5],
        ),
        (Circle([1, 0], 1), Line([0, 0], [1, 0]), [0, 0], [2, 0]),
        (Circle([1.5, 0], 1), Line([0, 0], [1, 0]), [0.5, 0], [2.5, 0]),
    ],
)
def test_intersect_line(circle, line, point_a_expected, point_b_expected):

    point_a, point_b = circle.intersect_line(line)

    assert point_a.is_close(point_a_expected)
    assert point_b.is_close(point_b_expected)

@pytest.mark.parametrize(
    ("circle", "line"),
    [
        (Circle([0, 0], 1), Line([0, 2], [1, 0])),
        (Circle([0, 0], 1), Line([0, -2], [1, 0])),
        (Circle([0, 0], 1), Line([2, 0], [0, 1])),
        (Circle([0, 0], 1), Line([3, 0], [1, 1])),
        (Circle([0, 0], 0.5), Line([0, 1], [1, 1])),
        (Circle([0, 1], 0.5), Line([0, 0], [1, 0])),
        (Circle([5, 2], 1), Line([2, -1], [1, 0])),
    ],
)
def test_intersect_line_failure(circle, line):

    message_expected = "The line does not intersect the circle."

    with pytest.raises(ValueError, match=message_expected):
        circle.intersect_line(line)


import random

def MCint(f, a, b, n):
    s = 0
    for i in xrange(n):
        x = random.uniform(a, b)
        s += f(x)
    I = (float(b-a)/n)*s
    return I

import numpy as np

def MCint_vec(f, a, b, n):
    x = np.random.uniform(a, b, n)
    s = np.sum(f(x))
    I = (float(b-a)/n)*s
    return I

def MCint_vec2(f, a, b, n, arraysize=1000000):
    s = 0
    # Split sum into batches of size arraysize
    # + a sum of size rest (note: n//arraysize is integer division)
    rest = n % arraysize
    batch_sizes = [arraysize]*(n//arraysize) + [rest]
    for batch_size in batch_sizes:
        x = np.random.uniform(a, b, batch_size)
        s += np.sum(f(x))
    I = (float(b-a)/n)*s
    return I

def MCint2(f, a, b, n):
    s = 0
    # Store the intermediate integral approximations in an
    # array I, where I[k-1] corresponds to k function evals.
    I = np.zeros(n)
    for k in range(1, n+1):
        x = random.uniform(a, b)
        s += f(x)
        I[k-1] = (float(b-a)/k)*s
    return I

def MCint3(f, a, b, n, N=100):
    s = 0
    # Store every N intermediate integral approximations in an
    # array I and record the corresponding k value.
    I_values = []
    k_values = []
    for k in range(1, n+1):
        x = random.uniform(a, b)
        s += f(x)
        if k % N == 0:
            I = (float(b-a)/k)*s
            I_values.append(I)
            k_values.append(k)
    return k_values, I_values

def demo():
    def f1(x):
        return 2 + 3*x

    a = 1; b = 2; n = 1000000; N = 10000
    k, I = MCint3(f1, a, b, n, N)

    from scitools.std import plot
    error = 6.5 - np.array(I)
    plot(k, error, title='Monte Carlo integration',
         xlabel='n', ylabel='error', savefig='tmp.pdf')

def test_MCint_all():
    """Test MCint, MCint_vec, MCint_vec2."""
    # Let all functions use np.random
    global random
    random = np.random
    f = np.sin
    np.random.seed(30)
    I = MCint(f, 0, 2*np.pi, n=10)
    np.random.seed(30)
    I_vec = MCint_vec(f, 0, 2*np.pi, n=10)
    np.random.seed(30)
    I_vec2 = MCint_vec2(f, 0, 2*np.pi, n=10, arraysize=4)
    tol = 1E-15
    assert abs(I - I_vec)  < tol
    assert abs(I - I_vec2) < tol

if __name__ == '__main__':
    #demo()
    test_MCint_all()


import seaborn as sns
print(sns.__version__)

"""# Analisando as notas em geral"""

import pandas as pd

notas = pd.read_csv("ratings.csv")
notas.head()

notas.shape

notas.columns = ["usuarioId", "filmeId", "nota", "momento"]
notas.head()

notas['nota'].unique()

notas['nota'].value_counts()

print("Media",notas['nota'].mean())
print("Mediana",notas['nota'].median())

notas.nota.head()

notas.nota.plot(kind='hist')

notas.nota.describe()

import seaborn as sns

sns.boxplot(notas.nota)

"""# Olhando os filmes"""

filmes = pd.read_csv("movies.csv")
filmes.columns = ["filmeId", "titulo", "generos"]
filmes.head()

notas.head()

"""# Analisando algumas notas especificas por filme"""

notas.query("filmeId==1").nota.mean()

notas.query("filmeId==2").nota.mean()

medias_por_filme = notas.groupby("filmeId").mean().nota
medias_por_filme.head()

medias_por_filme.plot(kind='hist')

import matplotlib.pyplot as plt

plt.figure(figsize=(5,8))
sns.boxplot(y=medias_por_filme)

medias_por_filme.describe()

sns.distplot(medias_por_filme)

plt.hist(medias_por_filme)
plt.title("Histograma das médias dos filmes")

tmdb = pd.read_csv("tmdb_5000_movies.csv")
tmdb.head()

tmdb.original_language.unique() # categorica nominal

# primeiro grau
# segundo grau
# terceiro grau
# 1 grau < 2 grau < 3 grau # categorica ordinal

# budget => orcamento => quantitativa continuo

# quantidade de votos => 1, 2, 3, 4, nao tem 2.5 votos.
# notas do movielens => 0.5, 1, 1.5, ... ,5 nao tem 2.7

tmdb["original_language"].value_counts().index

tmdb["original_language"].value_counts().values

contagem_de_lingua = tmdb["original_language"].value_counts().to_frame().reset_index()
contagem_de_lingua.columns = ["original_language", "total"]
contagem_de_lingua.head()

sns.barplot(x="original_language", y = "total", data = contagem_de_lingua)

sns.catplot(x = "original_language", kind="count", data = tmdb)

plt.pie(contagem_de_lingua["total"], labels = contagem_de_lingua["original_language"])

total_por_lingua = tmdb["original_language"].value_counts()
total_geral = total_por_lingua.sum()
total_de_ingles = total_por_lingua.loc["en"]
total_do_resto = total_geral - total_de_ingles
print(total_de_ingles, total_do_resto)

dados = {
    'lingua' : ['ingles','outros'],
    'total' : [total_de_ingles, total_do_resto]
}
dados = pd.DataFrame(dados)
sns.barplot(x="lingua", y="total", data = dados)

plt.pie(dados["total"], labels = dados["lingua"])

total_por_lingua_de_outros_filmes = tmdb.query("original_language != 'en'").original_language.value_counts()
total_por_lingua_de_outros_filmes

filmes_sem_lingua_original_em_ingles = tmdb.query("original_language != 'en'")

sns.catplot(x = "original_language", kind="count",
              data = filmes_sem_lingua_original_em_ingles)

sns.catplot(x = "original_language", kind="count",
              data = filmes_sem_lingua_original_em_ingles,
              aspect = 2,
              palette="GnBu_d",
              order = total_por_lingua_de_outros_filmes.index)

"""# Revisando o papel da média, mediana, medidas de tendência central, dispersão, desvio padrão, box plot, histograma"""

filmes.head(2)

notas_do_toy_story = notas.query("filmeId==1")
notas_do_jumanji = notas.query("filmeId==2")
print(len(notas_do_toy_story), len(notas_do_jumanji))

print("Nota média do Toy Story %.2f" % notas_do_toy_story.nota.mean())
print("Nota média do Jumanji %.2f" % notas_do_jumanji.nota.mean())

print(notas_do_toy_story.nota.std(), notas_do_jumanji.nota.std())

print("Nota mediana do Toy Story %.2f" % notas_do_toy_story.nota.median())
print("Nota mediana do Jumanji %.2f" % notas_do_jumanji.nota.median())

import numpy as np

filme1 = np.append(np.array([2.5] * 10), np.array([3.5] * 10))
filme2 = np.append(np.array([5] * 10), np.array([1] * 10))

print(filme1.mean(), filme2.mean())
print(np.std(filme1), np.std(filme2))
print(np.median(filme1), np.median(filme2))

plt.hist(filme1)
plt.hist(filme2)

plt.boxplot([filme1, filme2])

plt.boxplot([notas_do_toy_story.nota, notas_do_jumanji.nota])

sns.boxplot(x = "filmeId", y = "nota", data = notas.query("filmeId in [1,2,3,4,5]"))


import numpy as np
from scipy.stats import multivariate_normal
from typing import Optional

class GaussianMixture:
    """
    Gaussian mixture

    N: num of data
    D: dimension of each data
    K: num of cluster

    X.shape = (N, D)
    mu.shape = (K, D)
    sigma.shape = (K, D, D)
    pi.shape = (K,)
    prob.shape = (N, K)
    resp.shape = (N, K)
    """

    def __init__(self, n_components: int, random_state: int = 42,
                 max_iter: int = 100, verbose: int = 0,
                 init_params: str = "random",
                 means_init: Optional[np.array] = None) -> None:
        np.random.seed(random_state)
        if n_components <= 0:
            raise ValueError("n_components must be larger than 0")
        self.n_components = n_components
        self.max_iter = max_iter
        self.verbose = verbose
        self.init_params = init_params
        self.mu = means_init

    def _initialize(self, X) -> None:
        """
        initialization methods
        mu -> uniform from each dim
        sigma -> identity matrix
        pi -> uniform
        """
        if self.mu is None:
            idx = np.random.choice(
                np.arange(X.shape[0]), size=self.n_components, replace=False)
            self.mu = X[idx]
        self.sigma = np.array([np.eye(X.shape[1])
                               for _ in range(self.n_components)])
        self.pi = np.ones(self.n_components) / self.n_components
        self.prob = []
        for i in range(self.n_components):
            self.prob.append(multivariate_normal.pdf(
                X, mean=self.mu[i], cov=self.sigma[i]))
        self.prob = np.asarray(self.prob).T

    def fit(self, X, y=None) -> None:
        num_data, _ = X.shape
        if num_data < self.n_components:
            raise ValueError("size of X must be smaller than n_components")

        self._initialize(X)
        for i in range(self.max_iter):
            # E-step
            self.resp = self.pi * self.prob
            self.resp /= self.resp.sum(axis=1, keepdims=True)
            # M-step
            Nk = self.resp.sum(axis=0)
            self.mu = np.dot(self.resp.T, X) / Nk[:, np.newaxis]
            self.sigma = np.empty(self.sigma.shape)
            for k in range(self.n_components):
                diff = X - self.mu[k]
                self.sigma[k] = np.dot(self.resp[:, k] * diff.T, diff) / Nk[k]
            self.pi = Nk / num_data
            if self.verbose:
                print(f"[step {i+1}] loglike =", self.loglike())

    def loglike(self):
        return np.log((self.pi * self.prob).sum(axis=1)).sum()

    def get_hard_cluster(self):
        return self.resp.argmax(axis=1)


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

# # Calculators

# **CS1302 Introduction to Computer Programming**
# ___

# Run the following to load additional tools required for this lab.
# In particular, the `math` library provides many useful mathematical functions and constants.

# In[ ]:

get_ipython().run_line_magic('reset', '-f')
from ipywidgets import interact
import matplotlib.pyplot as plt
import numpy as np
import math
from math import log, exp, sin, cos, tan, pi

# The following code is a Python one-liner that creates a calculator.
# Evaluate the cell with `Ctrl+Enter`:

# In[ ]:

print(eval(input()))

# Try some calculations below using this calculator:
# 1. $2^3$ by entering `2**3`;
# 1. $\frac23$ by entering `2/3`;
# 1. $\left\lceil\frac32\right\rceil$ by entering `3//2`;
# 1. $3\mod 2$ by entering `3%2`;
# 1. $\sqrt{2}$ by entering `2**0.5`; and
# 1. $\sin(\pi/6)$ by entering `sin(pi/6)`;

# For this lab, you will create more powerful and dedicated calculators.
# We will first show you a demo. Then, it will be your turn to create the calculators.

# ## Hypotenuse Calculator (Demo)

# ![interact](https://www.cs.cityu.edu.hk/~ccha23/cs1302/Lab2/hypotenuse-calculator.gif)

# Using the Pythagoras theorem below, we can define the following function `length_of_hypotenus` to calculate the length `c` of the hypotenuse given the lengths `a` and `b` of the other sides of a right-angled triangle:
# $$c = \sqrt{a^2 + b^2}$$

# In[ ]:

def length_of_hypotenuse(a, b):
    c = (a**2 + b**2)**(0.5)  # Pythagoras
    return c

# - You need not understand how a function is defined, but
# - you should know how to *write the formula as a Python expression*, and
# - *assign to the variable* `c` the value of the expression (Line 2).

# For example, you may be asked to write Line 2, while Line 1 and 3 are given to you:

# **Exercise** Complete the function below to return the length `c` of the hypotenuse given the lengths `a` and `b`.

# In[ ]:

def length_of_hypotenuse(a, b):
    # YOUR CODE HERE
    raise NotImplementedError()
    return c

# Note that indentation affects the execution of Python code. The assignment statement must be indented to indicate that it is part of the *body* of the function.
# (Try removing the indentation and see what happens.)

# We will use widgets (`ipywidgets`) to let user interact with the calculator more easily:

# In[ ]:

# hypotenuse calculator
@interact(a=(0, 10, 1), b=(0, 10, 1))
def calculate_hypotenuse(a=3, b=4):
    print('c: {:.2f}'.format(length_of_hypotenuse(a, b)))

# After running the above cell, you can move the sliders to change the values of `a` and `b`. The value of `c` will be updated immediately.

# - For this lab, you need not know how write widgets, but
# - you should know how to *format a floating point number* (Line 3).

# You can check your code with a few cases listed in the test cell below.

# In[ ]:

# tests
def test_length_of_hypotenuse(a, b, c):
    c_ = length_of_hypotenuse(a, b)
    correct = math.isclose(c, c_)
    if not correct:
        print(f'For a={a} and b={b}, c should be {c}, not {c_}.')
    assert correct

test_length_of_hypotenuse(3, 4, 5)
test_length_of_hypotenuse(0, 0, 0)
test_length_of_hypotenuse(4, 7, 8.06225774829855)

# ## Quadratic Equation

# ### Graphical Calculator for Parabola

# ![plot parabola](https://www.cs.cityu.edu.hk/~ccha23/cs1302/Lab2/plot_parabola.gif)

# In mathematics, the collection of points $(x,y)$ satisfying the following equation forms a *parabola*:
#
# $$
# y=ax^2+bx+c
# $$
# where $a$, $b$, and $c$ are real numbers called the *coefficients*.

# **Exercise** Given the variables `x`, `a`, `b`, and `c` store the $x$-coordinate and the coefficients $a$, $b$, and $c$ respectively, assign to `y` the corresponding $y$-coordinate for the parabola.

# In[ ]:

def get_y(x, a, b, c):
    # YOUR CODE HERE
    raise NotImplementedError()
    return y

# In[ ]:

# tests
def test_get_y(y,x,a,b,c):
    y_ = get_y(x,a,b,c)
    correct = math.isclose(y,y_)
    if not correct:
        print(f'With (x, a, b, c)={x,a,b,c}, y should be {y} not {y_}.')
    assert correct

test_get_y(0,0,0,0,0)
test_get_y(1,0,1,2,1)
test_get_y(2,0,2,1,2)

# In[ ]:

# graphical calculator for parabola
@interact(a=(-10, 10, 1), b=(-10, 10, 1), c=(-10, 10, 1))
def plot_parabola(a, b, c):
    xmin, xmax, ymin, ymax, resolution = -10, 10, -10, 10, 50
    ax = plt.gca()
    ax.set_title(r'$y=ax^2+bx+c$')
    ax.set_xlabel(r'$x$')
    ax.set_ylabel(r'$y$')
    ax.set_xlim([xmin, xmax])
    ax.set_ylim([ymin, ymax])
    ax.grid()

    x = np.linspace(xmin, xmax, resolution)
    ax.plot(x, get_y(x, a, b, c))

# ### Quadratic Equation Solver

# ![quadratic equtaion soler](https://www.cs.cityu.edu.hk/~ccha23/cs1302/Lab2/quadratic-equation-solver.gif)

# For the quadratic equation
#
# $$
# ax^2+bx+c=0,
# $$
# the *roots* (solutions for $x$) are give by
#
# $$
# \frac{-b-\sqrt{b^2-4ac}}{2a},\frac{-b+\sqrt{b^2-4ac}}{2a}.
# $$

# **Exercise** Assign to `root1` and `root2` the values of the first and second roots above respectively.

# In[ ]:

def get_roots(a, b, c):
    # YOUR CODE HERE
    raise NotImplementedError()
    return root1, root2

# In[ ]:

# tests
def test_get_roots(roots, a, b, c):
    roots_ = get_roots(a, b, c)
    correct = all([math.isclose(roots[i], roots_[i]) for i in range(2)])
    if not correct:
        print(f'With (a, b, c)={a,b,c}, roots should be {roots} not {roots_}.')
    assert correct

test_get_roots((-1.0, 0.0), 1, 1, 0)
test_get_roots((-1.0, -1.0), 1, 2, 1)
test_get_roots((-2.0, -1.0), 1, 3, 2)

# In[ ]:

# quadratic equations solver
@interact(a=(-10,10,1),b=(-10,10,1),c=(-10,10,1))
def quadratic_equation_solver(a=1,b=2,c=1):
    print('Roots: {}, {}'.format(*get_roots(a,b,c)))

# ## Number Conversion

# ### Byte-to-Decimal Calculator

# ![byte-to-decimal](https://www.cs.cityu.edu.hk/~ccha23/cs1302/Lab2/byte-to-decimal.gif)

# Denote a binary number stored as a byte ($8$-bit) as
#
# $$
# b_7\circ b_6\circ b_5\circ b_4\circ b_3\circ b_2\circ b_1\circ b_0,
# $$
# where $\circ$ concatenates $b_i$'s together into a binary string.

# The binary string can be converted to a decimal number by the formula
#
# $$
# b_7\cdot 2^7 + b_6\cdot 2^6 + b_5\cdot 2^5 + b_4\cdot 2^4 + b_3\cdot 2^3 + b_2\cdot 2^2 + b_1\cdot 2^1 + b_0\cdot 2^0.
# $$

# E.g., the binary string `'11111111'` is the largest integer represented by a byte:
#
# $$
# 2^7+2^6+2^5+2^4+2^3+2^2+2^1+2^0=255=2^8-1.
# $$

# **Exercise** Assign to `decimal` the *integer* value represented by a byte.
# The byte is a sequence of bits assigned to the variables `b7,b6,b5,b4,b3,b2,b1,b0` as *characters*, either `'0'` or `'1'`.

# In[ ]:

def byte_to_decimal(b7, b6, b5, b4, b3, b2, b1, b0):
    # YOUR CODE HERE
    raise NotImplementedError()
    return decimal

# In[ ]:

# tests
def test_byte_to_decimal(decimal, b7, b6, b5, b4, b3, b2, b1, b0):
    decimal_ = byte_to_decimal(b7, b6, b5, b4, b3, b2, b1, b0)
    correct = decimal == decimal_ and isinstance(decimal_, int)
    if not correct:
        print(
            f'{b7}{b6}{b5}{b4}{b3}{b2}{b1}{b0} should give {decimal} not {decimal_}.'
        )
    assert correct

test_byte_to_decimal(38, '0', '0', '1', '0', '0', '1', '1', '0')
test_byte_to_decimal(20, '0', '0', '0', '1', '0', '1', '0', '0')
test_byte_to_decimal(22, '0', '0', '0', '1', '0', '1', '1', '0')

# In[ ]:

# byte-to-decimal calculator
bit = ['0', '1']

@interact(b7=bit, b6=bit, b5=bit, b4=bit, b3=bit, b2=bit, b1=bit, b0=bit)
def convert_byte_to_decimal(b7, b6, b5, b4, b3, b2, b1, b0):
    print('decimal:', byte_to_decimal(b7, b6, b5, b4, b3, b2, b1, b0))

# ### Decimal-to-Byte Calculator

# ![decimal-to-byte](https://www.cs.cityu.edu.hk/~ccha23/cs1302/Lab2/decimal-to-byte.gif)

# **Exercise** Assign to `byte` a *string of 8 bits* that represents the value of `decimal`, a non-negative decimal integer from $0$ to $2^8-1=255$.
# *Hint: Use `//` and `%`.*

# In[ ]:

def decimal_to_byte(decimal):
    # YOUR CODE HERE
    raise NotImplementedError()
    return byte

# In[ ]:

# tests
def test_decimal_to_byte(byte,decimal):
    byte_ = decimal_to_byte(decimal)
    correct = byte == byte_ and isinstance(byte, str) and len(byte) == 8
    if not correct:
        print(
            f'{decimal} should be represented as the byte {byte}, not {byte_}.'
        )
    assert correct

test_decimal_to_byte('01100111', 103)
test_decimal_to_byte('00000011', 3)
test_decimal_to_byte('00011100', 28)

# In[ ]:

# decimal-to-byte calculator
@interact(decimal=(0,255,1))
def convert_decimal_to_byte(decimal=0):
    print('byte:', decimal_to_byte(decimal))


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

'''Standard python modules'''
import sys

'''For scientific computing'''
from numpy import *
import scipy

'''For plotting'''
from matplotlib import pyplot, cm
from mpl_toolkits.mplot3d import Axes3D

def featureNormalizeLoop( data ):
	"""Normalize the features using loop, this is the slower version"""
	mu 	      = []
	sigma 	  = []
	data_norm = zeros( shape(data), data.dtype )

	for col in range( 0, shape(data)[1] ):
		mu.append	( mean( data[:, col] ) )
		sigma.append( std ( data[:, col], ddof=1 ) ) # if ddof = 0 sigma will be uncorrected sample standard deviation
		data_norm[:, col] = map( lambda x: (x - mu[col]) / sigma[col], data[:, col] )

	return data_norm, array(mu), array( sigma )

def featureNormalize( data ):
	"""A vectorized version of feature normalization"""
	mu 			= mean( data, axis=0 )
	data_norm 	= data - mu
	sigma 		= std( data_norm, axis=0, ddof=1 )
	data_norm 	= data_norm / sigma
	return data_norm, mu, sigma

def gradientDescent( X, y, theta, alpha, iters, m ):
	"""Run a gradient descent"""
	grad  = copy( theta )
	max_j 		= shape(X)[1]
	J_history 	= []
	alpha_div_m = alpha / m

	for counter in range( 0, iters ):
		inner_sum 	= X.T.dot(X.dot( grad ) - y)
		grad 		= grad - alpha_div_m * inner_sum
		J_history.append( computeCost(X, y, grad, m ) )

	return J_history, grad

def computeCost( X, y, theta, m ):
	term = X.dot( theta ) - y
	return ( term.T.dot( term ) / (2 * m) )[0, 0]

def normalEquation( X, y ):
	return linalg.inv(X.T.dot( X )).dot( X.T ).dot( y )

def part3_1():
	data = genfromtxt( "/Users/saburookita/Downloads/mlclass-ex1-004/mlclass-ex1/ex1data2.txt", delimiter = ',' )
	X = data[:, 0:2]
	y = data[:, 2:3]

	X, mu, sigma = featureNormalize( X )
	print X
	print mu
	print sigma

def part3_2():
	data = genfromtxt( "/Users/saburookita/Downloads/mlclass-ex1-004/mlclass-ex1/ex1data2.txt", delimiter = ',' )
	X = data[:, 0:2]
	y = data[:, 2:3]
	m = shape( X )[0]

	X, mu, sigma = featureNormalize( X )

	X 			= c_[ ones((m, 1)), X ] # add intercept to X
	iterations 	= 400
	alphas 		= [0.01, 0.03, 0.1, 0.3, 1.0]

	# for each alpha, try to do gradient descent and plot the convergence curve
	for alpha in alphas:
		theta = zeros( (3, 1) )
		J_history, theta = gradientDescent( X, y, theta, alpha, iterations, m )

		# create an array of number of iterations
		number_of_iterations = array( [x for x in range( 1, iterations + 1 )] ).reshape( iterations, 1)

		pyplot.plot( number_of_iterations, J_history, '-b' )
		pyplot.title( "Alpha = %f" % (alpha) )
		pyplot.xlabel('Number of iterations')
		pyplot.ylabel('Cost J')
		pyplot.xlim( [0, 50] )
		pyplot.show( block=True )

		# 1650 sq feet 3 bedroom house
		test = array([1.0, 1650.0, 3.0])
		# exclude intercept units
		test[1:] = (test[1:] - mu) / sigma
		print test.dot( theta )

def part3_3():
	data = genfromtxt( "/Users/saburookita/Downloads/mlclass-ex1-004/mlclass-ex1/ex1data2.txt", delimiter = ',' )
	X = data[:, 0:2]
	y = data[:, 2:3]
	m = shape( X )[0]

	X = c_[ ones((m, 1)), X ] # add intercept to X

	theta = normalEquation( X, y )
	# 1650 sq feet 3 bedroom house
	test = array([1.0, 1650.0, 3.0])
	print test.dot( theta )

def main():
	set_printoptions(precision=6, linewidth=200)

	part3_1()
	part3_2()
	part3_3()

if __name__ == '__main__':
	main()


import sympy as sym
import scipy as sci
import numpy as np
from math import *
import sys

#===================================================================================
# Phần thuật toán chính
class newton_mat_inversion:
#{
    max_attempt = 2;

    def __init__(self, A, n, eps):    # Khởi tạo
    #{
        self.A = np.reshape(np.array(A), (n, n));
        self.n = n;
        self.eps = eps;
        self.nr_iterations = 0;
    #}
    def norm(self, __A, __norm_type = 2):   # Chuẩn ma trận
    #{
        return np.linalg.norm(__A, __norm_type);
    #}
    def __refine_initial_approx(self):  # Tìm xấp xỉ đầu:
    #{
        # Gán các biến cơ bản
        E  = np.eye(self.n);
        A  = self.A;
        X  = self.A;

        # PP xấp xỉ đầu của Pan & Reif
        t1 = self.norm(X, 1);
        t2 = self.norm(X, inf);
        X  = (X / (t1 * t2)).T;

        # Hiệu chỉnh lại giá trị q của ma trận xấp xỉ đầu
        attempt = 0;
        while(attempt <= newton_mat_inversion.max_attempt):
        #{
            X = X @ (2 * E - A @ X);
            if(self.norm(E - A @ X) < 1): attempt += 1;
            self.nr_iterations += 1 * bool(attempt == 0);
        #}

        # Trả về ma trận xấp xỉ đầu
        return X;
    #}
    def __pure_newton(self, X_0):  # Lặp Newton nguyên bản
    #{

        # Gán các biến cơ bản
        norm_X0 = self.norm(X_0); # Do X_0 không đổi nên ta đặt 1 biến làm chuẩn của X_0
        E       = np.eye(self.n);
        A       = self.A;
        eps     = self.eps;

        # Bước 2 của thuật toán
        q2k = q = self.norm(E - A @ X_0);
        X = X_0;

        # Kiểm tra điều kiện hội tụ
        if(q >= 1):
        #{
            print("Xấp xỉ đầu không thỏa mãn nên không đưa ra được ma trận nghịch đảo.");
            return np.full((self.n, self.n), float("NaN"));
        #}

        # Lặp
        while(norm_X0 * q2k >= self.eps * (1 - q)):
        #{
            self.nr_iterations += 1;
            X = X @ (2 * E - A @ X);
            q2k = q2k ** 2
        #}

        # Đưa ra ma trận cuối cùng
        print(f"Phương pháp Newton kết thúc sau {self.nr_iterations} bước lặp", file=sys.stderr);
        return X;
    #}

    def improved_newton(self):  # PP Newton cải tiến với xấp xỉ đầu
    #{
        if(np.linalg.det(self.A) == 0):
        #{
            print("A không khả nghịch nên không đưa ra được ma trận nghịch đảo");
            return np.full((self.n, self.n), float("NaN"));
        #}
        return self.__pure_newton(self.__refine_initial_approx());
    #}
    def pure_newton(self, X_0):  # PP Newton nguyên bản
    #{
        if(np.linalg.det(self.A) == 0):
        #{
            print("A không khả nghịch nên không đưa ra được ma trận nghịch đảo");
            return np.full((self.n, self.n), float("NaN"));
        #}
        return self.__pure_newton(X_0);
    #}
#}

#===================================================================================
# Chương trình ví dụ
# n = # Kích cỡ ma trận
# A = # Ma trận đầu vào
# eps = # Sai số epsilon

# In ra A
# print(A);

# A = np.reshape(np.array(A), (n, n));
# uu = newton_mat_inversion(A, n, eps);

# B1 = uu.improved_newton();   # Tiến hành thuật toán
# print(B1);                   # In ra ma trận nghịch đảo
# print(B1 @ A);               # Kiểm tra

Topic 2.5 - Matrix Inversion/Newton Method/newton.py


"""
  Name     : c14_06_binary_call.py
  Book     : Python for Finance (2nd ed.)
  Publisher: Packt Publishing Ltd.
  Author   : Yuxing Yan
  Date     : 6/6/2017
  email    : yany@canisius.edu
             paulyxy@hotmail.com
"""
import random
import scipy as sp
#
def terminalStockPrice(S, T,r,sigma):
    tao=random.gauss(0,1.0)
    terminalPrice=S * sp.exp((r - 0.5 * sigma**2)*T+sigma*sp.sqrt(T)*tao)
    return terminalPrice
#
def binaryCallPayoff(x, sT,payoff):
    if sT >= x:
        return payoff
    else:
        return 0.0
# input area
S = 40.0            # asset price
x = 40.0            #  exericse price
T = 0.5             # maturity in years
r = 0.01            # risk-free rate
sigma = 0.2         # vol of 20%
fixedPayoff = 10.0  # payoff
nSimulations =10000 # number of simulatins
#
payoffs=0.0
for i in xrange(nSimulations):
    sT = terminalStockPrice(S, T,r,sigma)
    payoffs += binaryCallPayoff(x, sT,fixedPayoff)
#
price = sp.exp(-r * T) * (payoffs / float(nSimulations))
print('Binary options call= %.8f' % price)


# To add a new cell, type '#%%'
# To add a new markdown cell, type '#%% [markdown]'

#%% [markdown]

# # SciPy Family
#
# Python-based ecosystem [scipy.org](https://scipy.org)
#
# * SciPy Library - Fundamental library for scientific computing
# * NumPy - Numeric Python: Base N-dimensional array package
# * Pandas - Data structures (Dataframes) & analysis
# * Mathplotlib - Comprehensive 2D Plotting
# * Sympy - Symbolic mathematics
# * IPython - Enhanced Interactive Console
#
# The datatypes (dtype attribute) supported by Numpy is many:
# [Numpy basic data types](https://docs.scipy.org/doc/numpy/user/basics.types.html)

#%%
# might need to install numpy from the terminal
# %pip install numpy
# %pip3 install numpy
# %sudo pip install numpy
# %sudo pip3 install numpy
# %sudo -H pip3 install numpy
# %conda install numpy
# %pip freeze
# %pip list
# %pip show numpy

#%%
import numpy as np
# or from numpy import *
# import matplotlib.pyplot as plt
# import pandas as pd

#%%
#
# Review lists
list0 = [9,8,7]
list0b = [6,5,4]
#
# What are the lengths of list1 and list1b?

#%%
# What do you get with list0 + list0b?
#
list0+list0b

#%%
# explore data structures with list of list, how many dimensions?
list1 = [ [11,12,13,14], [21,22,23,24], [31,32,33,34]]
list1b = [ [41,42,43,44], [51,52,53,54], [61,62,63,64]]
#

#%%
# Again, what is list1 + list1b?
#
list1+list1b

#%%
# Question: How do you describe (in english) these two lists? What are the "shapes" of the objects?
#
# These are 3 by 4 matrices. Two-dimensional arrays.
#
# Question: how do you get the element '32' in list1?
#
#
#
# Question: how do you get the row of [31,32,33,34] in list1?
#
#
#
#%%
# Question: How to you get the column of 12, 22, 32 ???
#
#
#%%
[ row[1] for row in list1 ]

#%%
# OR Loop it
v3 = []
for row in list1:
  v3.append(row[1])
print(v3)

#%%

#%%
list2 = [ [11,12,13], [21,22,23], [31,32,33], [41,42,43] ] # two dimensional list (2-D array)  # (4,3)
# list2b = [ [51,52,53], [61,62,63], [71,72,73], [81,82,83]]

# How do you access the different parts of these two lists?

#%%
# How do you create a higher-dimensional list (say 2x3x4)?
#
# list3D = [ [ [111,112,113], [121,122,123], [131,132,133], [141,142,143] ]
# , [ [211,212,213], [221,222,223], [231,232,233], [241,242,243] ] ]

list3D = [ [ [ 111, 112, 113, 114 ], [ 121, 122, 123, 124 ], [131, 132, 133, 134] ] ,
           [ [ 211, 212, 213, 214 ], [ 221, 222, 223, 224 ], [231, 232, 233, 234] ] ]

#%%
# Now try numpy
import numpy as np
# Some basic attributes and simple functions of numpy arrays
a = np.arange(15) # numpy creates a range of 15 consecutive integers, like the range() function in basic python
print('a:',a)
a = np.arange(15).reshape(3,-1) # Using -1 for the last dimension lets numpy calculate directly
# a = np.arange(15).reshape(3,5) # Same result as line above
a = np.arange(24).reshape(2,3,-1) # 3d array
print('a:',a)

print('a.shape:',a.shape)
print('a.ndim:',a.ndim)
print('a.dtype:',a.dtype)
print('a.dtype.name:',a.dtype.name)
print('a.itemsize:',a.itemsize)
print('a.size:',a.size)
print('type(a):',type(a))
b = np.array([6, 7, 8])
print('b:',(b))
print('type(b):',type(b))
#
# The opposite of reshape, can use ravel()
print('a ravel:', a.ravel().shape)
print('a again:', a)
#
# IMPORTANT
# The a.ravel() function does NOT change a!!
# I create a true copy of a and ravel/unravel it only.
# Remember the differences in class/object definitions,
# it is critical what is the "return" value in
# those function/methods.
# If return self, you are getting back the object a.
# But this function return a separate true copy of
# the result instead. This is by design.
#
# A lot of other functions in numpy/pandas behave like that too.
#
# The same thing for reshape, for example
print('a reshape:', a.reshape(1,-1))
print('a.shape:',a.shape)
print('a:',a)
print('#',50*"-")

#%%
# If you really want to change a, try this:
# a = a.ravel() # exact same result as
a = a.reshape(-1)
print('a: ',a)
print('type a: ',type(a))
print('a.shape: ',a.shape)

print('#',50*"-")

#%%
# Other examples to create some simply numpy arrays
print('zeros:', np.zeros( (3,4) ))
print('ones:', np.ones( (2,3,4), dtype=np.int16 ))
print('empty:', np.empty( (2,3) ))
print('arange variation 1:', np.arange( 10, 30, 5 ))
print('arange variation 2:', np.arange( 0, 2, 0.3 ) )
print('complex:', np.array( [ [1,2], [3,4] ], dtype=complex ))
print('float:', np.arange(2, 10, dtype=float) )
from numpy import pi
x = np.linspace( 0, 2*pi, 100 )
f = np.sin(x)
print('sine of linspace:',f)
print('#',50*"-")

#%%
# import numpy as np
# Creating numpy arrays from python lists (and other list-like objects)
# Also look at the concept of "strides"
nparray1 = np.array(list1)
print("nparray1 = \n", nparray1)
print("type(nparray1) =", type(nparray1))
print("nparray1.dtype =", nparray1.dtype)  # int64
print("nparray1.shape =", nparray1.shape)
print("nparray1.strides =", nparray1.strides)  # each value is int64, hence 8-byte of memory, with four columns, it takes 8x4 = 32 bytes to the next row, same position. Strides = (32,8) to the next row and next column

#%%
# if we redo
nparray1 = np.array(list1, dtype= np.int32)
print("nparray1 = \n", nparray1)
print("type(nparray1) =", type(nparray1))
print("nparray1.dtype =", nparray1.dtype)  # int32
print("nparray1.shape =", nparray1.shape)
print("nparray1.strides =", nparray1.strides)  # now each value is int32, 4-byte, with four columns, it takes 4x4 = 16 bytes to next row.

#%%
# Try others
nparray2 = np.array(list2)
print("nparray2 = \n", nparray2)
print("type(nparray2) =", type(nparray2))
print("nparray2.dtype =", nparray2.dtype)  # int64
print("nparray2.shape =", nparray2.shape)

#%%
import sys
try:
  nparray12 = nparray1+nparray2
except ValueError as err :  # except (RuntimeError, TypeError, NameError):
  print("Value Error: {0}".format(err), " Try transpose...")
  nparray12 = nparray1+nparray2.T
except TypeError as err :  # except (RuntimeError, TypeError, NameError):
  print("Type Error: {0}".format(err))
except:
  print("unexpected error:", sys.exc_info()[0])

#%%
list4 = [ 5, 'a', 2, 3.5, True ]
list5 = [ 5, [1,4], 3, 1 ]

nparray4 = np.array(list4)
print("nparray4 = \n", nparray4)
print("type(nparray4) =", type(nparray4))
print("nparray4.dtype =", nparray4.dtype)
print("nparray4.shape =", nparray4.shape)

#%%
# list5 = [ 5, [1,4], 3, 1 ]
nparray5 = np.array(list5)
print("nparray5 = \n", nparray5)
print("type(nparray5) =", type(nparray5))
print("nparray5.dtype =", nparray5.dtype)
print("nparray5.shape =", nparray5.shape)

#%%
# If they are 2D-arrays, and have compatible dimensions, you can multiply them as matrices
tprod12 = np.dot(nparray1,nparray2)
print("tprod12.shape =", tprod12.shape)
mprod21 = np.dot(nparray2,nparray1)
print("mprod21.shape =", mprod21.shape)

#%%
# Also try the 3d-array that we constructed...
# In physics, those are called tensors.
nparray3D = np.array(list3D)
print("nparray3D = \n", nparray3D)
print("type(nparray3D) =", type(nparray3D))
print("nparray3D.dtype =", nparray3D.dtype)
print("nparray3D.shape =", nparray3D.shape)

#%%
# If they are 2D-arrays, and have compatible dimensions, you can multiply them as matrices
tprod32 = np.dot(nparray3D,nparray2)
print("tprod32.shape =", tprod32.shape)

#%%[markdown]
# speed and ease of use is the strength of numpy array, compared to python lists.
# The entire array must be of a single type, however.
# If we try to time or clock the code execution times, you will find similar functions
# is much faster than looping thru a python list.
# This is mainly because NumPy is written in C, and optimized these specialized
# operations in a well-designed library.

#%%
# filtering and indexing
print(nparray1[0:2,:2],'\n')
print(nparray1[:,-1:])

#%%
# Let us do something simpler.
# Obtain the third column of nparray1
print(nparray1)
v3 = nparray1[:,2]
print(v3) # it is a column vector, or array one by three (3,1)
print(v3.shape) # it is a column vector, or array one by three (3,1)

# Much easier than dealing with lists on the coding side of things. Speed is also maximized.

#%%
# BROADCASTING
#
# Let's practice slicing numpy arrays and using NumPy's broadcasting concept.
# Remember, broadcasting refers to a numpy array's ability to VECTORIZE operations,
# so they are performed on all elements of an object at once.
# If you need to perform some simple operations on all array elements,
#
nparray1squared = nparray1 ** 2
print(nparray1squared)

#%%
nparray1mod7 = nparray1 % 7 # remainder from dividing by 7
print(nparray1mod7)

#%%
nparray1b = np.array(list1b)
nparray1bovera = nparray1b / nparray1
print(nparray1bovera)

# Try some other operations, see if they work.

# Next try to do the above with loops or comprehensions?

#%%
# boolean indexing
print(nparray1)
npbool1greater = nparray1 > 21
print(npbool1greater)

#%%
print(nparray1[npbool1greater])

#%%
print(nparray1[npbool1greater].shape)

#%%
npbool1mod = nparray1 %2 ==1
print(npbool1mod)
print(nparray1[npbool1mod])
print(nparray1[npbool1mod].shape)

# Again, try to do these with loops or comprehensions?

#%%
# Let us look at filtering again.
x = np.arange(10)
print('x:\n',x)
print('x[1]:',x[1])
print('x[-2]:',x[-2])
x.shape = (2, 5)
print('x[1,3]:',x[1, 3])
print('x[1,-1]:',x[1,-1])
print('x[0]:',x[0])
print('x[1][2]:',x[1][2])
print('x[1,2]:',x[1,2]) # SAME as above
print('x[2:5]:',x[2:5])
print('x[-7]:',x[:-7])
print('x[1:7:2]:',x[1:7:2])
y = np.arange(35).reshape(5,7)
print('\ny:\n',y)
print('y[1:5:2,::3]:',y[1:5:2,::3])
v = np.arange(10,1,-1)
print('\nv:\n',v)
print('v[np.array([3, 3, 1, 8])]:',v[np.array([3, 3, 1, 8])])
print('#',50*"-")
#%%
# Some other basic numpy operations
a = np.array( [20,30,40,50] )
b = np.arange( 4 )
print('b:',b)
c = a-b
print('c:',c)
print('b**2:',b**2)
print('sine:',10*np.sin(a))
print('#',50*"-")

#%%
a = np.random.random((2,3))
print('a:',a)
print('a.sum:',a.sum())
print('a.min:',a.min())
print('a.max:',a.max())
print('#',50*"-")
#%%
b = np.arange(24).reshape(3,4,2)
print('b:',b)
print('b.sum axis 0:',b.sum(axis=0)) # sum of each column
print('b.min axis 1:',b.min(axis=1)) # min of each row
print('b.max axis 2:',b.min(axis=2)) # min of each row
print('b.cumsum axis 1:',b.cumsum(axis=1)) # cumulative sum along each row
print('#',50*"-")

#%%
# We looked at mostly the functions/methods within the numpy object, like
# a.sum()
# a.min() etc.
# As we discussed in OOP class, a lot of times, we can also have methods for the class itself.
# In this case, we can use the universal functions in the numpy library directly,
# like np.exp(), np.sqrt(). np.add() etc.
a = np.arange(3)
print('a:',a)
e = np.exp(a); print('exp:',e)
rt = np.sqrt(a); print('root:',rt)
b = np.array([2., -1., 4.])
sum = np.add(a, b)
print('sum:',sum)
print('#',50*"-")

#%%
# Indexing and slicing in Numpy
a = np.arange(10)**3;
print('a:', a)
print('a[2]:', a[2])
print('a[2:5]:', a[2:5]) # same as basic python syntax, not including 5.
a[:6:2] = -1000
print('a[::-1] :', a[ : :-1])
# reset a
a = np.arange(10)**3
for i in a:
  print('i-cube rt:', np.power(i,1/3))
#
#%%
# def f(i,j):
#   return 10*i+j
# b = np.fromfunction(f,(5,4),dtype=int); # The array values for point/coordinate (i,j,k) will be f(i,j,k).
#
# OR
# use the lambda function
b = np.fromfunction(lambda i,j: 10*i+j,(5,4),dtype=int); # The array values for point/coordinate (i,j,k) will be f(i,j,k).
print('b:', b)
print('b[2,3]:', b[2,3])
print('b[0.5,1]:', b[0:5, 1])
print('b[:,1]:', b[ : ,1])
print('b[1:3, :]:', b[1:3, : ])
print('b[-1]:', b[-1])  # same as b[4]  # same as b[4, : ]
print('b[4]:', b[4])
print('b[4,:]:', b[4, :])
print('#',50*"-")
#
# There is also a np.fromfile() function  to create.
#
# Remeber we tried slicing of 2-D list/array with basic python
# at the beginning of this file, and it was super difficult to
# get what we need.

#%%


## @file
#
# Functions related to spectral bounds of matrices.
#
# @author Nicolas Bock <nicolas.bock@freeon.org>
# @author Matt Challacombe <matt.challacombe@freeon.org>

import numpy as np

## Calculate the spectral bounds using the Gershgorin circle theorem.
#
# @param A The matrix.
#
# @return The bounds.
def gershgorin (A):
  bounds = np.zeros(2, dtype = 'float')
  bounds[0] = A[0, 0]
  bounds[1] = A[0, 0]

  for i in range(A.shape[0]):
    R = 0
    for j in range(A.shape[1]):
      if i != j:
        R += abs(A[i, j])

    if A[i, i]-R < bounds[0]:
      bounds[0] = A[i, i]-R
    if A[i, i]+R > bounds[1]:
      bounds[1] = A[i, i]+R

  return bounds

## Calculate the spectral bounds using a full eigensolve.
#
# @param A The matrix.
#
# @return The bounds.
def eigensolve (A):
  (eigenvalues, _) = np.linalg.eig(A)
  bounds = np.array([ np.min(eigenvalues), np.max(eigenvalues) ])
  return bounds

## Calculate the spectral bounds.
#
# @param A The matrix.
# @param estimate_type The type to use.
#
# @return The bounds.
def spectral_bounds (A, estimate_type = gershgorin):
  if A.ndim != 2:
    raise Exception("ndim has to be 2")
  if A.shape[0] != A.shape[1]:
    raise Exception("only square matrices are supported")

  return estimate_type(A)


import math
import numpy as np

'''a = [
        [14,6,-2,3,12],
        [3,15,2,-5,32],
        [-7,4,-23,2,-24],
        [1,-3,-2,16,14]
        ]'''

'''a = [
        [1,1/2,1/3,1],
        [1/2,1/3,1/4,0],
        [1/3,1/4,1/5,0],
        ]'''
a = [
        [-7,2,-3,4,-12],
        [5,-1,14,-1,13],
        [1,9,-7,13,31],
        [-12,13,-8,-4,-32]
        ]

n = len(a)
marcas = [i for i in range(0,n)]

def intercambioDeFilas(filaMayor,k):
    for i in range(0,len(a)+1):
        aux = a[k][i]
        a[k][i] = a[filaMayor][i]
        a[filaMayor][i]= aux

def intercambioDeColumnas(columnaMayor,k):
    #print(marcas)
    aux = marcas[k]
    marcas[k] = marcas[columnaMayor]
    marcas[columnaMayor] = aux
    for j in range(0,len(a)):
        aux = a[j][k]
        a[j][k] = a[j][columnaMayor]
        a[j][columnaMayor] = aux

def pivoteoTotal(k):
    mayor = 0
    filaMayor = k
    columnaMayor = k
    for r in range(k,n):
        for s in range (k,n):
            if (math.fabs(a[r][s]) > mayor):
                mayor = math.fabs(a[r][s])
                filaMayor = r
                columnaMayor = s

    if(mayor == 0):
        print("division 0")
        return;
    else:
        if(filaMayor != k):
            intercambioDeFilas(filaMayor,k)
        if(columnaMayor != k):
            intercambioDeColumnas(columnaMayor,k)
            #intercambioDeMarcas()

def elimination():
    for k  in range(0,n-1):
        pivoteoTotal(k)
        for i  in range (k + 1, n):
            multiplicador = a[i][k]/a[k][k]
            for j in range (k,n + 1):
                a[i][j] = a[i][j]-(multiplicador*a[k][j])

    return sustitucion()

def orderning(x):
    return [i for _,i in sorted(zip(marcas,x))]

def sustitucion():
    n = len(a) -1
    x = [i for i in range(n+1)]
    x[len(x)-1] = a[n][n+1]/a[n][n]

    for i in range(0,n+1):
        sumatoria = 0
        auxi = n - i
        sumatoria = 0
        for p in range(auxi+1,n+1):
            sumatoria = sumatoria + a[auxi][p]*x[p]
        x[auxi]=(a[auxi][n+1]-sumatoria)/a[auxi][auxi]
    #print marcas
    #duda error numero proximo a cero
    marcas.reverse()
    x.reverse()
    x = orderning(x)
    for i in range(0,len(x)):
        if(math.fabs(x[i]) < 10**-15):
            print("ojo numero muy proximo a 0 o es 0, lo aproximare a 0:",x[i])
            x[i] = 0
        print("X",i," =",x[i])

    return x

#elimination()
#for i in a:
#    print(i)
#intercambioDeFilas(0,2)


#!/usr/bin/env python3

import numpy as np

def normalEqn(X, y):
    #NORMALEQN Computes the closed-form solution to linear regression
    #   NORMALEQN(X,y) computes the closed-form solution to linear
    #   regression using the normal equations.

    #theta = zeros(size(X, 2), 1);

    # ====================== YOUR CODE HERE ======================
    # Instructions: Complete the code to compute the closed form solution
    #               to linear regression and put the result in theta.
    #

    # ---------------------- Sample Solution ----------------------

    theta = np.dot(np.dot(np.linalg.pinv(np.dot(X.T, X)), X.T), y)

    # -------------------------------------------------------------

    # ============================================================

    return theta


import numpy as np
import matplotlib.pyplot as plt
import scipy
import scipy.linalg as la
from copy import copy
import matplotlib.cm as cm
import matplotlib

from scipy.linalg import lu as lu1
from mpl_toolkits.mplot3d import Axes3D

def e_generator(i,j,size):
    length = (size-1)**2
    e = np.zeros(length)
    e[(i-1)*(size-1)+j-1] = 1
    return e

def A_generator(N):
    Q = np.zeros([(N-1)**2, (N-1)**2])

    for i in range((N-1)**2):
        Q[i][i] = 4.0
        if i % (N-1) != 0:
            Q[i][i-1] = -1.0;
        if i % (N-1) != N-2:
            Q[i][i+1] = -1.0;
        if i // (N-1) != 0.0:
            if(i == 1):
                print(i / (N-1))
            Q[i][(i-(N-1))] = -1.0;
        if i // (N-1) < N-2:
            Q[i][(i+(N-1))] = -1.0;

    return Q
    '''notice that Q is the matrix. Q graph is for plt.
    Black: value 4
    Gray: value -1
    White: value 0
    '''

def lu(A):
    '''actually this is p^-1'''
    n = A.shape[0]
    U = A.copy()
    L = np.eye(n, dtype=np.double)
    P = np.eye(n, dtype=np.double)
    #index of largest abs value

    for i in range(n-1):
        #find the largest pivot
        max = abs(U[i,i])
        index = i
        for k in range(i,n):
            if abs(U[k,i])>max:
                max = abs(U[k,i])
                index = k

        #permutation
        if index != i:
            #swap in U, notice that swap in python is weird
            U[[index,i],i:n] = U[[i,index],i:n]
            P[[index,i]]=P[[i,index]]
            L[[index,i],:i] = L[[i,index],:i]

        for j in range(i+1,n):
            L[j,i] = U[j,i] / U[i,i]
            U[j,i:] -= L[j,i]*U[i,i:]
            U[j,i] = 0
    return P,L,U

def plot_0_white(A):
    plt.imshow(A,cmap = 'PiYG',norm = matplotlib.colors.Normalize(vmin = -1,vmax = 1),interpolation = 'none')
    plt.show()

def find_nonzero_entries(A):
    n = A.shape[0]
    count = 0
    for i in range(n):
        for(j) in range(n):
            if A[i][j] != 0:
                count = count + 1
    return count

def find_lower_bandwidth(A):
    n = A.shape[0]
    lower_bandwidth = 0
    flag = -1
    for i in range(1,n):
        for j in range(0, n-i):
            if A[i+j][j] != 0:
                flag = 1
                break;
        if flag == 1:
            lower_bandwidth = lower_bandwidth + 1
            flag = -1
        else:
            return lower_bandwidth;
    return lower_bandwidth+1;
'''L is matrix, b is array, return value x is also array'''
def fsolve(L,b):
    n = L.shape[0]
    b[0] = b[0]/L[0,0]
    for i in range(1,n):
        for j in range(0, i):
            b[i] = b[i] - L[i,j]*b[j]

        b[i] = b[i]/L[i,i]
    return b

def bsolve(U,b):
    n = U.shape[0]
    b[n-1] = b[n-1]/U[n-1,n-1]
    for i in range(n-2,-1,-1):
        for j in range(n-1,i,-1):
            b[i] = b[i] - U[i,j]*b[j]
        b[i] = b[i]/U[i,i]
    return b

'''sol on question 1'''

Q = A_generator(10)

plt.imshow(Q,cmap = 'PiYG',norm = matplotlib.colors.Normalize(vmin = -1,vmax = 1),interpolation = 'none')
plt.show()

'''test on question 2, lu(A) is my func'''
'''
A = np.array([[2, 5, 8, 7], [5, 2, 2, 8], [7, 5, 6, 6], [5, 4, 4, 8]])
L,U,P = lu(A)
print(P)
print(L)
print(U)
'''

'''question3 uncomment this to see all pics'''
#
Q_10 = A_generator(10)
P1,L1,U1 = lu(Q_10)
num1 = find_nonzero_entries(L1)
lb1 = find_lower_bandwidth(L1)
print(num1)
print(lb1)

Q_20 = A_generator(20)
P2,L2,U2 = lu(Q_20)
num2 = find_nonzero_entries(L2)
lb2 = find_lower_bandwidth(L2)
print(num2)
print(lb2)

Q_30 = A_generator(30)
P3,L3,U3= lu(Q_30)
num3 = find_nonzero_entries(L3)
lb3 = find_lower_bandwidth(L3)

Q_40 = A_generator(40)
P4,L4,U4= lu(Q_40)
num4 = find_nonzero_entries(L4)
lb4= find_lower_bandwidth(L4)
print(num4)
print(lb4)

Q_50 = A_generator(50)
P5,L5,U5 = lu(Q_50)
num5 = find_nonzero_entries(L5)
lb5 = find_lower_bandwidth(L5)
print(num5)
print(lb5)
x = np.array([num1,num2,num3,num4,num5])
y = np.array([10,20,30,40,50])
plt.title("nonzeros over N")
plt.scatter(y, x, color="black")
plt.show()
x = np.array(([lb1,lb2,lb3,lb4,lb5]))
plt.title("bandwidth over N")
plt.scatter(y, x, color="black")
plt.show()
'''sanity test for q3, IGNORE FOLLOWING THINGS  '''
# print(find_nonzero_entries(A))
# print(find_lower_bandwidth(A))
'''question4'''
'''sanity check for bsolve, fsolve, egenerator, please ignore'''
i = 2
j = 2
size = 4
matrix_e = e_generator(i,j,size)
print(matrix_e)
a = np.array([[1, 3, 5, 9], [0, 1, 0, 11], [0, 0, 2, 9], [0, 0, 0, 4]])
b = np.array([4.0, 2.0, 4.0, 2.0])
sol = bsolve(a,b)
print(sol)
print(a.dot(sol))
size = 20
Q_50 = A_generator(size)
e = e_generator(5,5,size)
P,L,U  = lu(Q_50)
e = e.dot(P)
ux = fsolve(L,e)
x = bsolve(U,ux)
grid = np.zeros([(size-1),(size-1)])
ind = 0
for i in range(0,(size-1)):
    for j in range(0,(size-1)):
        grid[i,j] = x[ind]
        ind += 1
nx, ny = size-1, size-1
x = range(nx)
y = range(ny)

hf = plt.figure()
ha = hf.add_subplot(111, projection='3d')
X, Y = np.meshgrid(x, y)  # `plot_surface` expects `x` and `y` data to be 2D
ha.plot_surface(X, Y, grid)
plt.show()


import os
import sys
import scipy.io
import scipy.misc
import matplotlib.pyplot as plt
from matplotlib.pyplot import imshow
from PIL import Image
from nst_utils import *
import numpy as np
import tensorflow as tf

'''
How do you ensure the generated image G matches the content of the image C?

As we saw in lecture, the earlier (shallower) layers of a ConvNet tend to detect lower-level features such as edges and simple textures,
and the later (deeper) layers tend to detect higher-level features such as more complex textures as well as object classes.

We would like the "generated" image G to have similar content as the input image C.
Suppose you have chosen some layer's activations to represent the content of an image.
In practice, you'll get the most visually pleasing results if you choose a layer in the middle of the network--neither too shallow nor too deep.
(After you have finished this exercise, feel free to come back and experiment with using different layers, to see how the results vary.)

So, suppose you have picked one particular hidden layer to use. Now, set the image C as the input to the pretrained VGG network, and run forward propagation.
Let a(C)a(C) be the hidden layer activations in the layer you had chosen. (In lecture, we had written this as a[l](C)a[l](C),
but here we'll drop the superscript [l][l] to simplify the notation.)
This will be a nH×nW×nCnH×nW×nC tensor. Repeat this process with the image G: Set G as the input, and run forward progation. Let
a(G)
a(G)
be the corresponding hidden layer activation. We will define as the content cost function as:

Jcontent(C,G)=14×nH×nW×nC∑all entries(a(C)−a(G))2(1)
Jcontent(C,G)=14×nH×nW×nC∑all entries(a(C)−a(G))2

Here, nH,nWnH,nW and nCnC are the height, width and number of channels of the hidden layer you have chosen, and appear in a normalization term in the cost.
For clarity, note that a(C)a(C) and a(G)a(G) are the volumes corresponding to a hidden layer's activations.
In order to compute the cost Jcontent(C,G)Jcontent(C,G), it might also be convenient to unroll these 3D volumes into a 2D matrix, as shown below.
(Technically this unrolling step isn't needed to compute JcontentJcontent,
but it will be good practice for when you do need to carry out a similar operation later for computing the style const JstyleJstyle.)
'''
def compute_content_cost(a_C, a_G):
    """
    Computes the content cost

    Arguments:
    a_C -- tensor of dimension (1, n_H, n_W, n_C), hidden layer activations representing content of the image C
    a_G -- tensor of dimension (1, n_H, n_W, n_C), hidden layer activations representing content of the image G

    Returns:
    J_content -- scalar that you compute using equation 1 above.
    """

    m, n_H, n_W, n_C = a_G.shape

    new_shape = [int(m), int(n_H * n_W), int(n_C)]

    # Reshape a_C and a_G
    a_C_unrolled = tf.reshape(a_C, new_shape)
    a_G_unrolled = tf.reshape(a_G, new_shape)

    # compute the cost with tensorflow
    J_content = (.25 / float(int(n_H * n_W * n_C))) * tf.reduce_sum(np.power(a_G_unrolled - a_C_unrolled, 2))

    return J_content

'''
The style matrix is also called a "Gram matrix."
In linear algebra, the Gram matrix G of a set of vectors (v1,…,vn)(v1,…,vn) is the matrix of dot products, whose entries are Gij=vTivj=np.dot(vi,vj)Gij=viTvj=np.dot(vi,vj).
In other words, Gij compares how similar vivi is to vjvj: If they are highly similar, you would expect them to have a large dot product, and thus for Gij to be large.

Note that there is an unfortunate collision in the variable names used here.
We are following common terminology used in the literature, but GG is used to denote the Style matrix (or Gram matrix) as well as to denote the generated image GG.
We will try to make sure which GG we are referring to is always clear from the context.

In NST, you can compute the Style matrix by multiplying the "unrolled" filter matrix with their transpose:

The result is a matrix of dimension (nC,nC)(nC,nC) where nCnC is the number of filters. The value Gij measures how similar the activations of filter ii are to the activations of filter jj.

One important part of the gram matrix is that the diagonal elements such as Gii also measures how active filter ii is.
For example, suppose filter ii is detecting vertical textures in the image.
Then Gii measures how common vertical textures are in the image as a whole: If Gii is large, this means that the image has a lot of vertical texture.

By capturing the prevalence of different types of features (Gii), as well as how much different features occur together (Gij), the Style matrix GG measures the style of an image.
'''
def gram_matrix(A):
    """
    Argument:
    A -- matrix of shape (n_C, n_H*n_W)

    Returns:
    GA -- Gram matrix of A, of shape (n_C, n_C)
    """

    GA = tf.matmul(A, tf.transpose(A))

    return GA

'''
Style cost

After generating the Style matrix (Gram matrix), your goal will be to minimize the distance between the Gram matrix of the "style" image S and that of the "generated" image G.
For now, we are using only a single hidden layer a[l]a[l], and the corresponding style cost for this layer is defined as:

J[l]style(S,G)=14×nC2×(nH×nW)2∑i=1nC∑j=1nC(G(S)ij−G(G)ij)2(2)
Jstyle[l](S,G)=14×nC2×(nH×nW)2∑i=1nC∑j=1nC(Gij(S)−Gij(G))2

where G(S)G(S) and G(G)G(G) are respectively the Gram matrices of the "style" image and the "generated" image,
computed using the hidden layer activations for a particular hidden layer in the network.
'''
def compute_layer_style_cost(a_S, a_G):
    """
    Arguments:
    a_S -- tensor of dimension (1, n_H, n_W, n_C), hidden layer activations representing style of the image S
    a_G -- tensor of dimension (1, n_H, n_W, n_C), hidden layer activations representing style of the image G

    Returns:
    J_style_layer -- tensor representing a scalar value, style cost defined above by equation (2)
    """

    # Retrieve dimensions from a_G
    m, n_H, n_W, n_C = a_G.get_shape().as_list()

    # Reshape the images to have them of shape (n_C, n_H*n_W)
    a_S = tf.reshape(a_S, [n_H * n_W, n_C])
    a_S = tf.transpose(a_S)
    a_G = tf.reshape(a_G, [n_H * n_W, n_C])
    a_G = tf.transpose(a_G)

    # Computing gram_matrices for both images S and G
    GS = gram_matrix(a_S)
    GG = gram_matrix(a_G)

    # Computing the loss
    factor = (.5 / (n_H * n_W * n_C)) ** 2
    J_style_layer = factor * tf.reduce_sum(np.power(GS - GG, 2))

    return J_style_layer

'''
Style Weights

So far you have captured the style from only one layer. We'll get better results if we "merge" style costs from several different layers.
After completing this exercise, feel free to come back and experiment with different weights to see how it changes the generated image GG. But for now, this is a pretty reasonable default:

STYLE_LAYERS = [
    ('conv1_1', 0.2),
    ('conv2_1', 0.2),
    ('conv3_1', 0.2),
    ('conv4_1', 0.2),
    ('conv5_1', 0.2)]

You can combine the style costs for different layers as follows:

Jstyle(S,G)=∑lλ[l]J[l]style(S,G)
Jstyle(S,G)=∑lλ[l]Jstyle[l](S,G)

where the values for λ[l]λ[l] are given in STYLE_LAYERS.

    - The style of an image can be represented using the Gram matrix of a hidden layer's activations.
        However, we get even better results combining this representation from multiple different layers.
        This is in contrast to the content representation, where usually using just a single hidden layer is sufficient.
    - Minimizing the style cost will cause the image GG to follow the style of the image SS.

In the inner-loop of the for-loop below, a_G is a tensor and hasn't been evaluated yet.
It will be evaluated and updated at each iteration when we run the TensorFlow graph in model_nn() .below

The style of an image can be represented using the Gram matrix of a hidden layer's activations.
However, we get even better results combining this representation from multiple different layers.
This is in contrast to the content representation, where usually using just a single hidden layer is sufficient.

Minimizing the style cost will cause the image GG to follow the style of the image SS.
'''
def compute_style_cost(model, STYLE_LAYERS):
    """
    Computes the overall style cost from several chosen layers

    Arguments:
    model -- our tensorflow model
    STYLE_LAYERS -- A python list containing:
                        - the names of the layers we would like to extract style from
                        - a coefficient for each of them

    Returns:
    J_style -- tensor representing a scalar value, style cost defined above by equation (2)
    """

    # initialize the overall style cost
    J_style = 0

    for layer_name, coeff in STYLE_LAYERS:
        # Select the output tensor of the currently selected layer
        out = model[layer_name]

        # Set a_S to be the hidden layer activation from the layer we have selected, by running the session on out
        a_S = sess.run(out)

        # Set a_G to be the hidden layer activation from same layer. Here, a_G references model[layer_name]
        # and isn't evaluated yet. Later in the code, we'll assign the image G as the model input, so that
        # when we run the session, this will be the activations drawn from the appropriate layer, with G as input.
        a_G = out

        # Compute style_cost for the current layer
        J_style_layer = compute_layer_style_cost(a_S, a_G)

        # Add coeff * J_style_layer of this layer to overall style cost
        J_style += coeff * J_style_layer

    return J_style

def total_cost(J_content, J_style, alpha=10, beta=40):
    """
    Computes the total cost function

    Arguments:
    J_content -- content cost coded above
    J_style -- style cost coded above
    alpha -- hyperparameter weighting the importance of the content cost
    beta -- hyperparameter weighting the importance of the style cost

    Returns:
    J -- total cost as defined by the formula above.
    """

    ### START CODE HERE ### (≈1 line)
    J = alpha * J_content + beta * J_style
    ### END CODE HERE ###

    return J

#
# model = load_vgg_model("pretrained-model/imagenet-vgg-verydeep-19.mat")
#
# content_image = scipy.misc.imread("images/louvre.jpg")
# imshow(content_image)
#
# style_image = scipy.misc.imread("images/monet_800600.jpg")
# imshow(style_image)
#

# Reset the graph
tf.reset_default_graph()

# Start interactive session
sess = tf.InteractiveSession()

content_image = scipy.misc.imread("images/louvre_small.jpg")
content_image = reshape_and_normalize_image(content_image)
style_image = scipy.misc.imread("images/monet.jpg")
style_image = reshape_and_normalize_image(style_image)

# Now, we initialize the "generated" image as a noisy image created from the content_image.
# By initializing the pixels of the generated image to be mostly noise but still slightly correlated with the content image,
# this will help the content of the "generated" image more rapidly match the content of the "content" image.
generated_image = generate_noise_image(content_image)
imshow(generated_image[0])

model = load_vgg_model("pretrained-model/imagenet-vgg-verydeep-19.mat")

'''
Cost Content computing:
'''

# Assign the content image to be the input of the VGG model.
# Select the output tensor of layer conv4_2
# Set a_C to be the hidden layer activation from the layer we have selected. This has been evaluated since sess.run(out) and value assigned to a_C.
# In the next step, a_G is added as part of the grapth, being the output at that layer.
sess.run(model['input'].assign(content_image))
out = model['conv4_2']
a_C = sess.run(out)
# Set a_G to be the hidden layer activation from same layer. Here, a_G references model['conv4_2']
# and isn't evaluated yet. Later in the code, we'll assign the image G as the model input, so that
# when we run the session, this will be the activations drawn from the appropriate layer, with G as input.
a_G = out
# Compute the content cost
J_content = compute_content_cost(a_C, a_G)

'''
Style Content computing:
'''
# Assign the input of the model to be the "style" image
sess.run(model['input'].assign(style_image))
# Compute the style cost
J_style = compute_style_cost(model, STYLE_LAYERS)
J = total_cost(J_content, J_style, alpha = 10, beta = 40)
# define optimizer (1 line)
optimizer = tf.train.AdamOptimizer(2.0)
# define train_step (1 line)
train_step = optimizer.minimize(J)

# Right now, the grap consists a tree which top is the function tran_step -> J_total_cost ->
#           J_content: a_C value assigned already, and a_G attached at the chosen layer.
#           J_style: Inside function compute_style_cost. a_G is attached to each of the layers at the list and attached to the graph at the function compute_style_cost

def model_nn(sess, input_image, num_iterations=200):
    # Initialize global variables (you need to run the session on the initializer)
    ### START CODE HERE ### (1 line)
    sess.run(tf.global_variables_initializer())
    ### END CODE HERE ###

    # Run the noisy input image (initial generated image) through the model. Use assign().
    ### START CODE HERE ### (1 line)
    sess.run(model['input'].assign(input_image))
    ### END CODE HERE ###

    for i in range(num_iterations):

        # Run the session on the train_step to minimize the total cost
        ### START CODE HERE ### (1 line)
        somthing = sess.run(train_step)
        ### END CODE HERE ###

        # Compute the generated image by running the session on the current model['input']
        ### START CODE HERE ### (1 line)
        generated_image = sess.run(model['input'])
        ### END CODE HERE ###

        # Print every 20 iteration.
        if i % 20 == 0:
            Jt, Jc, Js = sess.run([J, J_content, J_style])
            print("Iteration " + str(i) + " :")
            print("total cost = " + str(Jt))
            print("content cost = " + str(Jc))
            print("style cost = " + str(Js))

            # save current generated image in the "/output" directory
            save_image("output/" + str(i) + ".png", generated_image)

    # save last generated image
    save_image('output/generated_image.jpg', generated_image)

    return generated_image

model_nn(sess, generated_image)

Convolutional Neural Networks/Week 4/Art Generation/Art_Generation_Neural_Transfer.py


import random
import numpy as np
import matplotlib.pyplot as plt
import math
import time
import os
import collections.abc
container_abcs = collections.abc
import scipy.io as sio
import numpy.linalg as LA
import scipy.spatial.distance as scipy_spatial_dist

def intx(x):
    return(int(x[0]), int(x[1]))

def cos_cdis(x, y, semi_sphere=False):
    # input: x: mxp, y: nxp
    # output: y, mxn
    # https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.cdist.html#scipy.spatial.distance.cdist
    ### compute cosine distance
    ### scipy: same 0, opposite 2, orthorgonal 1, dist = 1-AB/(|A||B|)
    dist_cos = scipy_spatial_dist.cdist(x, y, 'cosine')  # num_points_ x num_points
    # ### map to: same 1, opposite -1, orthorgonal 0, dist = AB/(|A||B|)
    dist_cos *= -1.0
    dist_cos += 1.0

    if semi_sphere is True: dist_cos = np.abs(dist_cos) #  dist = abs|AB/(|A||B|)|
    dist_cos_arc = np.arccos(dist_cos)
    return dist_cos_arc

def catersian_to_sphere(xyz):
    #  input: xyz, nx3, in the gaussian_catersian coordinate
    #  output: angles, (alpha, beta), nx2, in the gaussian sphere coordinate
    #  beta: elevation; alpha: azimuth.
    ### norm = [sin(alpha)cos(beta), sin(beta), cos(alpha)cos(beta)]
    num_points = len(xyz)
    angle = np.zeros((num_points,2))
    angle[:,1] = np.arcsin(xyz[:,1])
    inner = xyz[:,0] / np.cos(angle[:,1])
    inner = np.clip(inner, a_min=-1.0, a_max=1.0)
    angle[:,0] = np.arcsin(inner)
    return angle

def sphere_to_catesian(angles):
    #  input: angles (alphas, betas), nx2, in the gasussian sphere coordinate
    #  output: xyz (x,y,z), nx3, in the gaussian_catersian coordinate
    #  beta: elevation; alpha: azimuth.
    ### n = [sin(alpha)cos(beta), sin(beta), cos(alpha)cos(beta)]
    num_points = len(angles)
    xyz = np.zeros((num_points,3))
    xyz[:,0] = np.sin(angles[:,0]) * np.cos(angles[:,1])
    xyz[:,1] = np.sin(angles[:,1])
    xyz[:,2] = np.cos(angles[:,0]) * np.cos(angles[:,1])

    return xyz

def gold_spiral_sampling_patch(v, alpha, num_pts):
    v1 = orth(v)
    v2 = np.cross(v, v1)
    v, v1, v2 = v[:, None], v1[:, None], v2[:, None]
    # indices = np.arange(num_pts) + 0.66
    indices = np.arange(num_pts) + 0.5
    phi = np.arccos(1 + (math.cos(alpha) - 1) * indices / num_pts)
    theta = np.pi * (1 + 5 ** 0.5) * indices
    r = np.sin(phi)
    return (v * np.cos(phi) + r * (v1 * np.cos(theta) + v2 * np.sin(theta))).T

def orth(v):
    x, y, z = v
    o = np.array([0.0, -z, y] if abs(x) < abs(y) else [-z, 0.0, x])
    o /= LA.norm(o)
    return o


import random
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
import pandas as pd
from sklearn.datasets.samples_generator import make_blobs
# %matplotlib inline

np.random.seed(0)
X, y = make_blobs(n_samples=5000, centers=[[4,4], [-2, -1], [2, -3], [1, 1]], cluster_std=0.9)
plt.scatter(X[:, 0], X[:, 1], marker='.')
plt.show()

# K Means Code
k_means = KMeans(init = "k-means++", n_clusters = 4, n_init = 12)
k_means.fit(X)
k_means_labels = k_means.labels_
k_means_labels
k_means_cluster_centers = k_means.cluster_centers_
k_means_cluster_centers

# VISUALIZE CLUSTER PLOT
# Initialize the plot with the specified dimensions.
fig = plt.figure(figsize=(6, 4))

# Colors uses a color map, which will produce an array of colors based on
# the number of labels there are. We use set(k_means_labels) to get the
# unique labels.
colors = plt.cm.Spectral(np.linspace(0, 1, len(set(k_means_labels))))

# Create a plot
ax = fig.add_subplot(1, 1, 1)

# For loop that plots the data points and centroids.
# k will range from 0-3, which will match the possible clusters that each
# data point is in.
for k, col in zip(range(len([[4, 4], [-2, -1], [2, -3], [1, 1]])), colors):
    # Create a list of all data points, where the data poitns that are
    # in the cluster (ex. cluster 0) are labeled as true, else they are
    # labeled as false.
    my_members = (k_means_labels == k)

    # Define the centroid, or cluster center.
    cluster_center = k_means_cluster_centers[k]

    # Plots the datapoints with color col.
    ax.plot(X[my_members, 0], X[my_members, 1], 'w', markerfacecolor=col, marker='.')

    # Plots the centroids with specified color, but with a darker outline
    ax.plot(cluster_center[0], cluster_center[1], 'o', markerfacecolor=col, markeredgecolor='k', markersize=6)

# Title of the plot
ax.set_title('KMeans')

# Remove x-axis ticks
ax.set_xticks(())

# Remove y-axis ticks
ax.set_yticks(())

# Show the plot
plt.show()

# PRACTICE
# write your code here
# k_means2 = KMeans(init = "k-means++", n_clusters = 3, n_init = 12)
# k_means2.fit(X)
# k_means_labels2 = k_means2.labels_
# # k_means_labels2
# k_means_cluster_centers2 = k_means2.cluster_centers_
# # k_means_cluster_centers2
# fig = plt.figure(figsize=(6, 4))
# colors = plt.cm.Spectral(np.linspace(0, 1, len(set(k_means2.labels_))))
# ax = fig.add_subplot(1, 1, 1)
# for k, col in zip(range(len(k_means2.cluster_centers_)), colors):
#     my_members = (k_means2.labels_ == k)
#     cluster_center = k_means2.cluster_centers_[k]
#     ax.plot(X[my_members, 0], X[my_members, 1], 'w', markerfacecolor=col, marker='.')
#     ax.plot(cluster_center[0], cluster_center[1], 'o', markerfacecolor=col,  markeredgecolor='k', markersize=6)
# plt.show()

cust_df = pd.read_csv('Cust_Segmentation.csv')
cust_df.head()

df = cust_df.drop('Address', axis=1)
df.head()

from sklearn.preprocessing import StandardScaler
X = df.values[:,1:]
X = np.nan_to_num(X)
Clus_dataSet = StandardScaler().fit_transform(X)
Clus_dataSet

clusterNum = 3
k_means = KMeans(init = "k-means++", n_clusters = clusterNum, n_init = 12)
k_means.fit(X)
labels = k_means.labels_
print(labels)

df["Clus_km"] = labels
df.head(5)
df.groupby('Clus_km').mean()

area = np.pi * ( X[:, 1])**2
plt.scatter(X[:, 0], X[:, 3], s=area, c=labels.astype(np.float), alpha=0.5)
plt.xlabel('Age', fontsize=18)
plt.ylabel('Income', fontsize=16)

plt.show()

from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure(1, figsize=(8, 6))
plt.clf()
ax = Axes3D(fig, rect=[0, 0, .95, 1], elev=48, azim=134)

plt.cla()
# plt.ylabel('Age', fontsize=18)
# plt.xlabel('Income', fontsize=16)
# plt.zlabel('Education', fontsize=16)
ax.set_xlabel('Education')
ax.set_ylabel('Age')
ax.set_zlabel('Income')

ax.scatter(X[:, 1], X[:, 0], X[:, 3], c= labels.astype(np.float))


"""N-Dimensional Norm

Defines the norm of 2 points in N dimensional space with different norms.
"""

import numpy as np

def norm(a, pow=2):
    """ Lp Norm

    Arguments:
        a (numpy.ndarray): A numpy array of shape (2,). Defines a point in 2-D space.
        pow (float): The norm used for distance (Default: 2)

    Returns:
        (float) The distance between point a and point b with Lp Norm.
    """
    return np.float_power(np.sum([np.float_power(np.abs(a[i]), pow) for i in range(len(a))]), 1./pow)

def manhattan(a):
    """Manhattan Norm

    Arguments:
        a (numpy.ndarray): A numpy array of shape (2,) or (2, 1). Defines a point in 2-D space.

    Returns:
        The distance between points a and b with L1 norm.
    """
    return norm(a, pow=1)

def euclidean(a):
    """Euclidean Norm

    Arguments:
        a (numpy.ndarray): A numpy array of shape (2,) or (2, 1). Defines a point in 2-D space.

    Returns:
        The distance between points a and b with L2 norm.
    """
    return norm(a, pow=2)

def minkowski(a):
    """Minkowski Norm

    Arguments:
        a (numpy.ndarray): A numpy array of shape (2,) or (2, 1). Defines a point in 2-D space.

    Returns:
        The distance between points a and b with L3 norm.
    """
    return norm(a, pow=3)

def chebyshev(a):
    """Chebyshev Norm

    Arguments:
        a (numpy.ndarray): A numpy array of shape (2,) or (2, 1). Defines a point in 2-D space.

    Returns:
        The distance between points a and b with L-Infinity norm.
    """
    return np.max([np.abs(a[i]) for i in range(len(a))])


## module powell
''' xMin,nCyc = powell(F,x,h=0.1,tol=1.0e-6)
    Powell's method of minimizing user-supplied function F(x).
    x    = starting point
    h   = initial search increment used in 'bracket'
    xMin = mimimum point
    nCyc = number of cycles
'''
import numpy as np
from goldSearch import *
import math

def powell(F,x,h=0.1,tol=1.0e-6):

    def f(s): return F(x + s*v)    # F in direction of v

    n = len(x)                     # Number of design variables
    df = np.zeros(n)               # Decreases of F stored here
    u = np.identity(n)             # Vectors v stored here by rows
    for j in range(30):            # Allow for 30 cycles:
        xOld = x.copy()            # Save starting point
        fOld = F(xOld)
      # First n line searches record decreases of F
        for i in range(n):
            v = u[i]
            a,b = bracket(f,0.0,h)
            s,fMin = search(f,a,b)
            df[i] = fOld - fMin
            fOld = fMin
            x = x + s*v
      # Last line search in the cycle
        v = x - xOld
        a,b = bracket(f,0.0,h)
        s,fLast = search(f,a,b)
        x = x + s*v
      # Check for convergence
        if math.sqrt(np.dot(x-xOld,x-xOld)/n) < tol: return x,j+1
      # Identify biggest decrease & update search directions
        iMax = np.argmax(df)
        for i in range(iMax,n-1):
            u[i] = u[i+1]
        u[n-1] = v
    print("Powell did not converge")


import numpy as np

def student_id():
    """
    Returns a tuple of student's ID and TAU email address.
    """
    return 123456789, r'reference@mail.tau.ac.il'

class QuadraticFunction:

    """
    Implementation of a quadratice function
    f(x) = 1/2 * x^T*Q*x + b^T*x
    with its gradient and Hessian matrix.
    """

    def __init__(self, Q, b):

        """
        Initializes a quadratic function.

        Arguments:
            Q : (N,N) numpy array

            b : (N,) numpy array
        """

        self.Q = Q
        self.b = b

    def __call__(self, x):

        """
        Evaluate f(x).

        Arguments:
            x : (N,) numpy array

        Return:
            fx : evaluation of f(x)
        """

        fx = 0.5 * np.dot(x, np.dot(self.Q, x)) + np.dot(self.b,x)

        return fx

    def grad(self, x):

        """
        Evaluates g(x), the gradient of f(x).

        Arguments:
            x : (N,) numpy array

        Output:
            gx : (N,) numpy array
                 evaluation of the function's gradient at x
        """

        gx = 0.5 * np.dot(self.Q.T + self.Q, x) + self.b

        return gx

    def hessian(self, x):

        """
        Evaluate H(x), the Hessian matrix of f(x).

        Arguments:
            x : (N,) numpy array

        Return:
            hx : (N,N) numpy array,
                 evaluation of the function's Hessian at point x
        """

        hx = 0.5 * (self.Q.T + self.Q)

        return hx

class NewtonOptimizer:

    """
    Newton's method optimizer with constant step size.
    """

    def __init__(self, func, alpha, init):

        """
        Arguments:
            func : objective function handle/reference

            alpha : a scalar step size

            init : (N,) numpy array, initial guess
        """

        self.func = func
        self.alpha = alpha
        self.curr_x = init

    def step(self):

        """
        Executes a single step of Newton's method.

        Return:
            next_x : (N,) numpy array, next x
        """

        gx = self.func.grad(self.curr_x)
        hx = self.func.hessian(self.curr_x)
        self.curr_x = self.curr_x - self.alpha * np.dot(np.linalg.inv(hx), gx)

        return self.curr_x, gx, hx

    def optimize(self, threshold, max_iters):

        """
        Execution of optimization flow

        Arguments:
            threshold : stopping criteria |x_k-▁x_(k-1) |<threshold

            max_iters : maximal number of iterations (stopping criteria)

        Return:
            fmin : objective function evaluated at the minimum

            minimizer : the optimal ▁x

            num_iters : number of iterations
        """

        for iter_idx in range(max_iters):
            prev_x = self.curr_x
            _, _, _ = self.step()  # update self.curr_x internally
            if np.linalg.norm(self.curr_x - prev_x) < threshold:
                break

        return self.func(self.curr_x), self.curr_x, iter_idx+1

class ConjGradOptimizer:

    """
    Conjugate Gradients optimizer.
    """

    def __init__(self, func, init):

        """
        Arguments:

            func : quadratice function handle/reference

            init : (N,) numpy array, initial guess
        """

        self.func = func
        self.Q = self.func.Q
        self.curr_x = init
        self.curr_grad = func.grad(self.curr_x)
        self.curr_dir = -self.curr_grad
        self.curr_alpha = self.update_alpha()

    def optimize(self):

        """
        Executation of optimization flow

        Return:
            fmin : objective function evaluated at the minimum

            minimizer : the optimal ▁x

            num_iters : number of iterations
        """

        num_iters = self.curr_x.shape[0]  # number of iteration is constant
        for iter_idx in range(num_iters):
            _, _, _, _ = self.step()

        return self.func(self.curr_x), self.curr_x, num_iters

    def step(self):

        """
        Executes a single step of Newton's method.

        Return:
            next_x : (N,) numpy array

            next_grad_x : (N,) numpy array

            next_dir : (N,) numpy array

            next_alpha : scalar
        """

        self.curr_x = self.update_x()
        self.curr_grad, prev_grad = self.update_grad()
        self.curr_dir = self.update_dir(prev_grad)
        self.curr_alpha = self.update_alpha()

        return self.curr_x, self.curr_grad, self.curr_dir, self.curr_alpha

    def update_grad(self):

        """
        Calculates and returns (g_k, g_(k-1)) the new and previous gradients respctively.
        """

        return self.func.grad(self.curr_x), self.curr_grad

    def update_dir(self, prev_grad):

        """
        Calculates and returns the new direction d_k
        """

        beta = np.dot(self.curr_grad, self.curr_grad - prev_grad) / np.dot(prev_grad, prev_grad)

        return -self.curr_grad + beta * self.curr_dir

    def update_alpha(self):

        """
        Calculates and returns the new step size alpha_k
        """

        numer = np.dot(self.curr_dir, self.curr_grad)
        denom = np.dot(np.dot(self.curr_dir, self.Q), self.curr_dir)

        return - numer / denom

    def update_x(self):

        """
        Calculates and returns x_k
        """

        return self.curr_x + self.curr_alpha * self.curr_dir

class FastRoute:

    """
    Implementation of fast route problem.
    """

    def __init__(self, start_x, start_y, finish_x, finish_y, velocities):

        """
        Initializes a FastRoute board.

        Arguments:
            start_x : x coordinate of starting point

            start_y : y coordinate of starting point

            finish_x : x coordinate of finish point

            finish_y : y coordinate of finish point

            velocities : an N-dimensional numpy vector,
                         the i-th element is the velocity at the i-th layer
        """

        self.start_x, self.start_y = start_x, start_y
        self.finish_x, self.finish_y = finish_x, finish_y
        self.velocities = velocities
        self.y = np.linspace(self.start_y, self.finish_y, len(velocities)+1)

    def __call__(self, x):

        """
        Calculates the time of the route given by a vector of x's

        Arguments:
            x : (N-1,) numpy array of the x coordinates of crossing points

        Return:
            total_time : the total travel time of the route
        """

        # Expand x with start and finish points
        x = self.expand_x(x)

        # distances vectors
        distances = self.find_distances(x)
        times = distances / self.velocities
        total_time = np.sum(times)

        return total_time

    def grad(self, x):
        """
        Evaluates the gradient of the objective function at (x[1],...,x[n-1])
        for the route given by a vector of x's

        Argument:
            x : (N-1,) numpy array of the x coordinates of cross points

        Return:
            grad : (N-1,) numpy array, the gradient of the objective w.r.t to (x[1],...,x[n-1])

            dt/dx_k = (x_k - x_(k-1)) / (v_k * d_k) - (x_(k+1) - x_k / (v_(k+1) * d_(k+1)) for k={1,2,...,n-1}
        """

        # Expand x with start and finish points
        x = self.expand_x(x)

        # distances vectors
        distances = self.find_distances(x)
        grad = (x[1:-1] - x[:-2]) / (self.velocities[:-1] * distances[:-1]) - (x[2:] - x[1:-1]) / (self.velocities[1:] * distances[1:])

        return grad

    def hessian(self, x):

        """
        Evaluates the hessian of the objective function at x=(x[1],...,x[n-1])
        for the route given by a vector of x's.
        See detailed calculation in solution pdf file.

        Arguemtns:
            x : (N-1,) numpy array of the x coordinates of cross points

        Return:
            hx : (N-1,N-1) numpy array, the hessian of the objective evaluated at (x[1],...,x[n-1])
        """

        # Expand x with start and finish points
        x = self.expand_x(x)

        # distances vectors
        distances = self.find_distances(x)
        term_1 = np.power(self.y[1:-1] - self.y[:-2], 2) / (self.velocities[:-1] * np.power(distances[:-1], 3))
        term_2 = np.power(self.y[2:] - self.y[1:-1], 2) / (self.velocities[1:] * np.power(distances[1:], 3))
        N = len(self.velocities)
        hx = np.zeros((N-1, N-1))
        for i in range(N-1):
            hx[i,i] = term_1[i] + term_2[i]
            if i > 0:
                hx[i-1,i] = -term_1[i]
            if i < N-2:
                hx[i+1,i] = -term_2[i]

        return hx

    def find_distances(self, x):

        """
        Calculates a vector of N distances.

        Arguments:
            x : (N-1,) numpy array of the x coordinates of cross points

        Return:
            (N-1,) numpy array of the distance traveled in each segment
        """

        return np.sqrt(np.power(x[1:] - x[:-1], 2) + np.power(self.y[1:] - self.y[:-1], 2))

    def expand_x(self, x):

        """
        Adds the starting point and finish point to vector x

        Arguments:
            x : (N-1,) numpy array of the x coordinates of cross points

        Return:
            (N-1,) numpy array with x coordinates of route
            including start and finsh point
        """

        return np.append(np.insert(x, 0, self.start_x), self.finish_x)

def find_fast_route(objective, init, alpha=1, threshold=1e-3, max_iters=1e3):

    """
    Optimizes FastRoute objective using Newton’s method optimizer to
    find a fast route between the starting point and finish point.

    Arguments:
    	objective : an initialized FastRoute object with preset start and finish points,
                    velocities and initialization vector.

        init : (N-1,) numpy array, initial guess for the crossing points

	    alpha : step size for the NewtonOptimizer

	    threshold : stopping criteria |x_(k+1)- x_k |<threshold

	    max_iters : maximal number of iterations (stopping criteria)

    Return:
        route_time : scalar

        route : (N-1,) numpy array with x coordinate of the optimal route,
                    i.e., a vector of x-coordinates of crossing points (not including start and finish point)

        num_iters : number of iteration
    """

    opt = NewtonOptimizer(func=objective, alpha=alpha, init=init)
    route_time, route, num_iters = opt.optimize(threshold=threshold, max_iters=max_iters)

    return route_time, route, num_iters


import numpy as np

def LogCoshMetric(XGBoost=False):
    """
    Calculates the [Log Cosh Error](https://openreview.net/pdf?id=rkglvsC9Ym) as an alternative to
    Mean Absolute Error.
    Args:
        XGBoost (Bool): Set to True if using XGBoost. We assume LightGBM as default use.
                        Note that you should also set `maximize=False` in the XGBoost train function

    """
    def log_cosh_error(yhat, dtrain, XGBoost=XGBoost):
        """
        Root Mean Squared Log Error.
        All input labels are required to be greater than -1.

        yhat: Predictions
        dtrain: The XGBoost / LightGBM dataset
        XGBoost (Bool): If XGBoost is to be implemented
        """

        y = dtrain.get_label()
        elements = np.log(np.cosh(yhat - y))
        if XGBoost:
            return 'LogCosh', float(np.sum(elements) / len(y))
        else:
            return 'LogCosh', float(np.sum(elements) / len(y)), False

    return log_cosh_error

def RMSPEMetric(XGBoost=False):
    """
    Calculates the Root Mean Squared Percentage Error:
    https://www.kaggle.com/c/optiver-realized-volatility-prediction/overview/evaluation

    The corresponding Loss function is Squared Percentage Error.
    Args:
        XGBoost (Bool): Set to True if using XGBoost. We assume LightGBM as default use.
                        Note that you should also set `maximize=False` in the XGBoost train function

    """
    def RMSPE(yhat, dtrain, XGBoost=XGBoost):
        """
        Root Mean Squared Log Error.
        All input labels are required to be greater than -1.

        yhat: Predictions
        dtrain: The XGBoost / LightGBM dataset
        XGBoost (Bool): If XGBoost is to be implemented
        """

        y = dtrain.get_label()
        elements = ((y - yhat) / y) ** 2
        if XGBoost:
            return 'RMSPE', float(np.sqrt(np.sum(elements) / len(y)))
        else:
            return 'RMSPE', float(np.sqrt(np.sum(elements) / len(y))), False

    return RMSPE


#!/usr/bin/python3
"""
Generate/plot a circle with radius that varies as a sine wave.

If output of image is desired, include any string as a
command line argument.

"""
import sys

import matplotlib.pyplot as plt
import numpy as np

#### Circle-Sine Parameters ####
xCenter = 5.0
yCenter = 2.0
Radius = 5.0
nPts = 300
freq = 11 # Number of "lumps" on the circle
amplitude = 0.75
################################

if len(sys.argv) == 2:
    outFlag = True
else:
    outFlag = False

fig = plt.figure(facecolor='lightgrey')
ax = fig.add_subplot(111)
ax.patch.set_facecolor('black')

ax.set_aspect('equal')
ax.grid(True, which='both')

# Insert X and Y axes
ax.axhline(y=0, color='w')
ax.axvline(x=0, color='w')

# Remove #'s to suppress tick labels
#ax.set_xticklabels([])
#ax.set_yticklabels([])

# Incremental angles around the circle
angle = np.linspace(0, 2*np.pi, nPts, dtype=float)

# Treat the full circumference as 1 second, i.e. basis for Hz
# Array of points in "one second"
t = np.linspace(0, 1.0, nPts, dtype=float)

# Vary the radius according to sine wave frequency and amplitude
delRadius = amplitude*np.sin(2.0*np.pi*freq*t)

# Convert to x-y coordinates
x = (Radius+delRadius)*np.cos(angle)
x = x + xCenter
y = (Radius+delRadius)*np.sin(angle)
y = y + yCenter

print('len(x) = ', len(x))
print('xmin = {0:8.2f}  xmax = {1:8.2f}'.format(min(x), max(x)))
print('ymin = {0:8.2f}  ymax = {1:8.2f}'.format(min(y), max(y)))

# s is marker size
ax.scatter(x, y, marker='o', s=4.0, color='red')

# Conditionally output image
if outFlag == True:
    fig.savefig('genCircleSine.png', facecolor='lightgrey', format='png')

plt.show()


from simplenn.layer import Layer
from simplenn.optimizers.optimizer import Optimizer
from typing import List
from typing import Union

import numpy as np

class AdaGrad(Optimizer):
    """
    Adaptive gradient -> Compute a learning rate for every parameter

    AdaGrad shrinks the learning rate according to the entire history
    of the squared gradient and may have made the learning rate too
    small before arriving at such a convex structure.

    Args:
        lr (float, optional): learning rate. Defaults to 0.1.
        decay (Union[float, None], optional): factor of linear decay per iteration to learning rate. Defaults to None.
        eps (float, optional): Epsilon. Avoids division by zero. Defaults to 1e-7 .
    """

    def __init__(
        self,
        lr: float = 0.1,
        decay: Union[float, None] = None,
        eps: float = 1e-7,
    ) -> None:
        super().__init__()
        self.lr = lr
        self.decay = decay
        self.eps = eps

    def _get_cache(self, layer: Layer):
        layer._init_cache()
        self._update_cache(layer)

        W_cache = np.sqrt(layer.W_cache) + self.eps
        b_cache = np.sqrt(layer.b_cache) + self.eps
        return W_cache, b_cache

    def _update_cache(self, layer: Layer):
        layer.W_cache += layer.dW**2
        layer.b_cache += layer.db**2

    def step(self, layers: List[Layer]) -> None:
        lr = self.get_adjusted_lr()
        for layer in layers:

            W_cache, b_cache = self._get_cache(layer)

            W_update = -(lr * layer.dW) / W_cache
            b_update = -(lr * layer.db) / b_cache

            layer.W += W_update
            layer.b += b_update

        self.iteration += 1


import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import binom
from sklearn.metrics import auc

def standardize(grid, p):
	#using stupid rectangular rule, much smarter numerical integration methods exist
	#should also work for grids of variable width

	assert p.shape[0] == grid.shape[0]

	mid = 0.5 * (p[1:] + p[:-1])

	x = grid[1:] - grid[:-1]

	standardizationConstant = np.sum(mid * x)

	#print(auc(grid, p / standardizationConstant))

	return p / standardizationConstant

N = 200
grid = np.linspace(0.0, 1.0, N)
binWidth = grid[1] - grid[0]

prior = np.ones(grid.shape[0])
#prior /= np.sum(prior)
prior = standardize(grid, prior)

#W,W,W
likelihood = binom.pmf(3, 3, grid)

posterior = likelihood * prior
#posterior /= np.sum(posterior)
posterior = standardize(grid, posterior)

fig = plt.figure()
ax = fig.add_subplot(111)

ax.plot(grid, prior, 'k--', label='prior')

ax.plot(grid, posterior, color='C0', label='WWW')

#W,W,W,L
likelihood = binom.pmf(3, 4, grid)

posterior = likelihood * prior
#posterior /= np.sum(posterior)
posterior = standardize(grid, posterior)

ax.plot(grid, posterior, color='C1', label='WWWL')

#L,W,W,L,W,W,W
likelihood = binom.pmf(5, 7, grid)

posterior = likelihood * prior

#posterior /= np.sum(posterior)
posterior = standardize(grid, posterior)

ax.plot(grid, posterior, color='C2', label='LWWLWWW')

ax.set_title('2M1')

plt.legend()

##---------------------------------------------------------

prior = np.ones(grid.shape[0])
prior[grid<0.5] = 0.0
#prior /= np.sum(prior)
prior = standardize(grid, prior)

#W,W,W
likelihood = binom.pmf(3, 3, grid)

posterior = likelihood * prior

#posterior /= np.sum(posterior)
posterior = standardize(grid, posterior)

fig = plt.figure()
ax = fig.add_subplot(111)

ax.plot(grid, prior, 'k--', label='prior')

ax.plot(grid, posterior, color='C0', label='WWW')

#W,W,W,L
likelihood = binom.pmf(3, 4, grid)

posterior = likelihood * prior
#posterior /= np.sum(posterior)
posterior = standardize(grid, posterior)

ax.plot(grid, posterior, color='C1', label='WWWL')

#L,W,W,L,W,W,W
likelihood = binom.pmf(5, 7, grid)

posterior = likelihood * prior

#posterior /= np.sum(posterior)
posterior = standardize(grid, posterior)

ax.plot(grid, posterior, color='C2', label='LWWLWWW')

ax.set_title('2M2')
plt.legend()

plt.show()


import numpy as np

# B/B,B/W,W/W
#
# ways black = b/b, b/b,b/w = 3 ways to produce black from bag~ 3/6 ~ 0.5
# ways_white = b/w,w/w,w/w = 3 ways to produce white~ 0.5
#

# ways when one side is black, that the other side is black -> b/b,b/b
# it cant be b/w from the ways of producing black card, because it would
# produce a white card when turned

#so form all ways a card could be black, those which could produce a black on the\
# other side are 2/3

#[b/b, b/w, w/w]
# inspired by https://github.com/cavaunpeu/statistical-rethinking/blob/master/chapter-2/homework.R
# ways cards can produce first black
ways_producing_black = np.array([2.,1.,0.])

priors = ways_producing_black / ways_producing_black.sum()
print(priors)

#[b/b,b/w,w/w]
# ways cards produce second black
ways_can_produce_black_on_other_side = np.array([2., 0., 0.])
posterior = ways_can_produce_black_on_other_side*priors/ways_can_produce_black_on_other_side.sum()
print(posterior)
print('prob that other side is black = '+str(posterior[0]))
print(posterior[0] == 2./3.)

# inspired by https://rpubs.com/andersgs/my_solutions_chapter2_statrethink
# b1/b2, b1/w1, w1/w2
#
# card |pick|other side
#
# b1/b2| 1 | 1
# b2/b1| 1 | 1
# b1/w1| 1 | 0
# w1/b1| 0 | 0
# w1/w2| 0 | 0
# w2/w1| 0 | 0
#
# to produce black on other side 2
# from to produce black on the first side 3
# 2/3

#bayes theorem
# probability that the second side is black given that the first side is black
# P(2black|1black) = P(1black|2black)*P(2black)/P(1black)
# probability that a card has black on both sides is 1 card from 3 cards
# P(2black) = 1/3
# probability that a card has 1 black side from 6 producing ways of card settings
# is 3, so 3/6 = 1/2
# P(1black) = 1/2
# probability that 1st side is black given the second side is black, e.g.
# that both sides are black is 1
# P(1black|2black) = 1 (hm)
# 1*1/3 / 1/2 ~ 2/3


# Approximate 2d posterior using PyMc3
# https://www.ritchievink.com/blog/2019/06/10/bayesian-inference-how-we-are-able-to-chase-the-posterior/
# We use the same data and model as in posteriorGrid2d.py

import numpy as np
import matplotlib.pyplot as plt
from scipy import stats

import pymc3 as pm

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

data = np.array([195, 182])

# lets create a grid of our two parameters
mu = np.linspace(150, 250)
sigma = np.linspace(0, 15)[::-1]
mm, ss = np.meshgrid(mu, sigma)  # just broadcasted parameters
likelihood = stats.norm(mm, ss).pdf(data[0]) * stats.norm(mm, ss).pdf(data[1])
aspect = mm.max() / ss.max() / 3
extent = [mm.min(), mm.max(), ss.min(), ss.max()]
# extent = left right bottom top

prior = stats.norm(200, 15).pdf(mm) * stats.cauchy(0, 10).pdf(ss)
# Posterior - grid
unnormalized_posterior = prior * likelihood
posterior = unnormalized_posterior / np.nan_to_num(unnormalized_posterior).sum()

plt.figure()
plt.imshow(posterior, cmap='Blues', aspect=aspect, extent=extent)
plt.xlabel(r'$\mu$')
plt.ylabel(r'$\sigma$')
plt.title('Grid approximation')
save_fig('bayes_unigauss_2d_grid.pdf')
plt.show()

with pm.Model():
    # priors
    mu = pm.Normal('mu', mu=200, sd=15)
    sigma = pm.HalfCauchy('sigma', 10)
    # likelihood
    observed = pm.Normal('observed', mu=mu, sd=sigma, observed=data)
    # sample
    trace = pm.sample(draws=10000, chains=1)

pm.traceplot(trace);

plt.figure()
plt.scatter(trace['mu'], trace['sigma'], alpha=0.01)
plt.xlim([extent[0], extent[1]])
plt.ylim([extent[2], extent[3]])
plt.ylabel('$\sigma$')
plt.xlabel('$\mu$')
plt.title('MCMC samples')
save_fig('bayes_unigauss_2d_pymc3_post.pdf')
plt.show()


import numpy as np
from prml.nn.tensor.constant import Constant
from prml.nn.tensor.tensor import Tensor
from prml.nn.function import Function

class Determinant(Function):

    def forward(self, x):
        x = self._convert2tensor(x)
        self.x = x
        self._equal_ndim(x, 2)
        self.output = np.linalg.det(x.value)
        if isinstance(self.x, Constant):
            return Constant(self.output)
        return Tensor(self.output, function=self)

    def backward(self, delta):
        dx = delta * self.output * np.linalg.inv(self.x.value.T)
        self.x.backward(dx)

def det(x):
    """
    determinant of a matrix
    Parameters
    ----------
    x : (d, d) tensor_like
        a matrix to compute its determinant
    Returns
    -------
    output : (d, d) tensor_like
        determinant of the input matrix
    """
    return Determinant().forward(x)