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

# # Calculating the Bilingual Evaluation Understudy (BLEU) score: Ungraded Lab

# In this ungraded lab, we will implement a popular metric for evaluating the quality of machine-translated text: the BLEU score proposed by Kishore Papineni, et al. in their 2002 paper ["BLEU: a Method for Automatic Evaluation of Machine Translation"](https://www.aclweb.org/anthology/P02-1040.pdf), the BLEU score works by comparing "candidate" text to one or more "reference" translations. The result is better the closer the score is to 1. Let's see how to get this value in the following sections.

# # Part 1:  BLEU Score

# ## 1.1  Importing the Libraries
#
# We will first start by importing the Python libraries we will use in the first part of this lab. For learning, we will implement our own version of the BLEU Score using Numpy. To verify that our implementation is correct, we will compare our results with those generated by the [SacreBLEU library](https://github.com/mjpost/sacrebleu). This package provides hassle-free computation of shareable, comparable, and reproducible BLEU scores. It also knows all the standard test sets and handles downloading, processing, and tokenization.

# In[1]:

import numpy as np                  # import numpy to make numerical computations.
import nltk                         # import NLTK to handle simple NL tasks like tokenization.
nltk.download("punkt")
import math
from nltk.util import ngrams
from collections import Counter     # import a counter.

import sacrebleu                    # import sacrebleu in order compute the BLEU score.
import matplotlib.pyplot as plt     # import pyplot in order to make some illustrations.

# ## 1.2  Defining the BLEU Score
#
# You have seen the a way for calculating the BLEU score in this week's lectures. More formally, we can express the BLEU score as:
#
# $$BLEU = BP\times\Bigl(\prod_{i=1}^{4}precision_i\Bigr)^{(1/4)}$$
#
#
# We used the productory and the denominator of the exponent as 4 because we are assuming that we are going to work up to 4-grams. The Brevity Penalty defined as an exponential decay:
#
# $$BP = min\Bigl(1, e^{(1-({ref}/{cand}))}\Bigr)$$
#
# In the previous equation, ${ref}$ and ${cand}$ refers to the length or count of words in the reference and candidate translations. The brevity penaly $BP$ helps to handle very short translations.
#
# The precision is defined as :
#
# $$precision_i = \frac {\sum_{snt \in{cand}}\sum_{i\in{snt}}min\Bigl(m^{i}_{cand}, m^{i}_{ref}\Bigr)}{w^{i}_{t}}$$
#
# where:
#
# * $m^{i}_{cand}$, is the count of i-gram in candidate matching the reference translation.
# * $m^{i}_{ref}$, is the count of i-gram in the reference translation.
# * $w^{i}_{t}$, is the total number of i-grams in candidate translation.
#

# ## 1.3 Explaining the BLEU score

# ### Brevity Penalty (example):

# In[2]:

ref_length = np.ones(100)
can_length = np.linspace(1.5, 0.5, 100)
x = ref_length / can_length
y = 1 - x
y = np.exp(y)
y = np.minimum(np.ones(y.shape), y)

# Code for in order to make the plot
fig, ax = plt.subplots(1)
lines = ax.plot(x, y)
ax.set(
    xlabel="Ratio of the length of the reference to the candidate text",
    ylabel="Brevity Penalty",
)
plt.show()

# The brevity penalty penalizes generated translations that are too short compared to the closest reference length with an exponential decay. The brevity penalty compensates for the fact that the BLEU score has no recall term.

# ### N-Gram Precision (example):

# In[3]:

# Mocked dataset showing the precision for different n-grams
data = {"1-gram": 0.8, "2-gram": 0.7, "3-gram": 0.6, "4-gram": 0.5}
names = list(data.keys())
values = list(data.values())

fig, ax = plt.subplots(1)
bars = ax.bar(names, values)
ax.set(ylabel="N-gram precision")

plt.show()

# The n-gram precision counts how many unigrams, bigrams, trigrams, and four-grams (i=1,...,4) match their n-gram counterpart in the reference translations. This term acts as a precision metric. Unigrams account for adequacy while longer n-grams account for fluency of the translation. To avoid overcounting, the n-gram counts are clipped to the maximal n-gram count occurring in the reference ($m_{n}^{ref}$). Typically precision shows exponential decay with the degree of the n-gram.

# ### N-gram BLEU score (example):

# In[4]:

# Mocked dataset showing the precision multiplied by the BP for different n-grams
data = {"1-gram": 0.8, "2-gram": 0.77, "3-gram": 0.74, "4-gram": 0.71}
names = list(data.keys())
values = list(data.values())

fig, ax = plt.subplots(1)
bars = ax.bar(names, values)
ax.set(ylabel="Modified N-gram precision")

plt.show()

# When the n-gram precision is multiplied by the BP, then the exponential decay of n-grams is almost fully compensated. The BLEU score corresponds to a geometric average of this modified n-gram precision.

# ## 1.4 Example Calculations of the BLEU score

# In this example we will have a reference translation and 2 candidates translations. We will tokenize all sentences using the NLTK package introduced in Course 2 of this NLP specialization.

# In[5]:

reference = "The NASA Opportunity rover is battling a massive dust storm on planet Mars."
candidate_1 = "The Opportunity rover is combating a big sandstorm on planet Mars."
candidate_2 = "A NASA rover is fighting a massive storm on planet Mars."

tokenized_ref = nltk.word_tokenize(reference.lower())
tokenized_cand_1 = nltk.word_tokenize(candidate_1.lower())
tokenized_cand_2 = nltk.word_tokenize(candidate_2.lower())

print(f"{reference} -> {tokenized_ref}")
print("\n")
print(f"{candidate_1} -> {tokenized_cand_1}")
print("\n")
print(f"{candidate_2} -> {tokenized_cand_2}")

# ### STEP 1: Computing the Brevity Penalty

# In[6]:

def brevity_penalty(candidate, reference):
    ref_length = len(reference)
    can_length = len(candidate)

    # Brevity Penalty
    if ref_length < can_length: # if reference length is less than candidate length
        BP = 1 # set BP = 1
    else:
        penalty = 1 - (ref_length / can_length) # else set BP=exp(1-(ref_length/can_length))
        BP = np.exp(penalty)

    return BP

# ### STEP 2: Computing the Precision

# In[7]:

def clipped_precision(candidate, reference):
    """
    Clipped precision function given a original and a machine translated sentences
    """

    clipped_precision_score = []

    for i in range(1, 5):
        ref_n_gram = Counter(ngrams(reference,i))
        cand_n_gram = Counter(ngrams(candidate,i))

        c = sum(cand_n_gram.values())

        for j in cand_n_gram: # for every n-gram up to 4 in candidate text
            if j in ref_n_gram: # check if it is in the reference n-gram
                if cand_n_gram[j] > ref_n_gram[j]: # if the count of the candidate n-gram is bigger
                                                   # than the corresponding count in the reference n-gram,
                    cand_n_gram[j] = ref_n_gram[j] # then set the count of the candidate n-gram to be equal
                                                   # to the reference n-gram
            else:
                cand_n_gram[j] = 0 # else set the candidate n-gram equal to zero

        clipped_precision_score.append(sum(cand_n_gram.values())/c)

    weights =[0.25]*4

    s = (w_i * math.log(p_i) for w_i, p_i in zip(weights, clipped_precision_score))

    s = math.exp(math.fsum(s))
    return s

# ### STEP 3: Computing the BLEU score

# In[8]:

def bleu_score(candidate, reference):
    BP = brevity_penalty(candidate, reference)
    precision = clipped_precision(candidate, reference)
    return BP * precision

# ### STEP 4: Testing with our Example Reference and Candidates Sentences

# In[9]:

print(
    "Results reference versus candidate 1 our own code BLEU: ",
    round(bleu_score(tokenized_cand_1, tokenized_ref) * 100, 1),
)

print(
    "Results reference versus candidate 2 our own code BLEU: ",
    round(bleu_score(tokenized_cand_2, tokenized_ref) * 100, 1),
)

# ### STEP 5: Comparing the Results from our Code with the Sacrebleu Library

# In[10]:

print(
    "Results reference versus candidate 1 sacrebleu library sentence BLEU: ",
    round(sacrebleu.corpus_bleu(candidate_1, reference).score, 1),
)
print(
    "Results reference versus candidate 2 sacrebleu library sentence BLEU: ",
    round(sacrebleu.corpus_bleu(candidate_2, reference).score, 1),
)

# # Part 2:  BLEU computation on a corpus

# ## Loading Data Sets for Evaluation Using the BLEU Score

# In this section, we will show a simple pipeline for evaluating machine translated text. Due to storage and speed constraints, we will not be using our own model in this lab (you'll get to do that in the assignment!). Instead, we will be using [Google Translate](https://translate.google.com) to generate English to German translations and we will evaluate it against a known evaluation set. There are three files we will need:
#
# 1. A source text in English. In this lab, we will use the first 1671 words of the [wmt19](http://statmt.org/wmt19/translation-task.html) evaluation dataset downloaded via SacreBLEU. We just grabbed a subset because of limitations in the number of words that can be translated using Google Translate.
# 2. A reference translation to German of the corresponding first 1671 words from the original English text. This is also provided by SacreBLEU.
# 3. A candidate machine translation to German from the same 1671 words. This is generated by feeding the source text to a machine translation model. As mentioned above, we will use Google Translate to generate the translations in this file.
#
# With that, we can now compare the reference and candidate translation to get the BLEU Score.

# In[11]:

# Loading the raw data
wmt19_src = open("data/wmt19_src.txt", "r")
wmt19_src_1 = wmt19_src.read()
wmt19_src.close()

wmt19_ref = open("data/wmt19_ref.txt", "r")
wmt19_ref_1 = wmt19_ref.read()
wmt19_ref.close()

wmt19_can = open("data/wmt19_can.txt", "r")
wmt19_can_1 = wmt19_can.read()
wmt19_can.close()

tokenized_corpus_src = nltk.word_tokenize(wmt19_src_1.lower())
tokenized_corpus_ref = nltk.word_tokenize(wmt19_ref_1.lower())
tokenized_corpus_cand = nltk.word_tokenize(wmt19_can_1.lower())

# Inspecting the first sentence of the data.

# In[12]:

print("English source text:")
print("\n")
print(f"{wmt19_src_1[0:170]} -> {tokenized_corpus_src[0:30]}")
print("\n")
print("German reference translation:")
print("\n")
print(f"{wmt19_ref_1[0:219]} -> {tokenized_corpus_ref[0:35]}")
print("\n")
print("German machine translation:")
print("\n")
print(f"{wmt19_can_1[0:199]} -> {tokenized_corpus_cand[0:29]}")

# In[13]:

print(
    "Results reference versus candidate 1 our own BLEU implementation: ",
    round(bleu_score(tokenized_corpus_cand, tokenized_corpus_ref) * 100, 1),
)

# In[14]:

print(
    "Results reference versus candidate 1 sacrebleu library BLEU: ",
    round(sacrebleu.corpus_bleu(wmt19_can_1, wmt19_ref_1).score, 1),
)

# **BLEU Score Interpretation on a Corpus**
#
# |Score      | Interpretation                                                |
# |:---------:|:-------------------------------------------------------------:|
# | < 10      | Almost useless                                                |
# | 10 - 19   | Hard to get the gist                                          |
# | 20 - 29   | The gist is clear, but has significant grammatical errors     |
# | 30 - 40   | Understandable to good translations                           |
# | 40 - 50   | High quality translations                                     |
# | 50 - 60   | Very high quality, adequate, and fluent translations          |
# | > 60      | Quality often better than human                               |

# From the table above (taken [here](https://cloud.google.com/translate/automl/docs/evaluate)), we can see the translation is high quality (*if you see "Hard to get the gist", please open your workspace, delete `wmt19_can.txt` and get the latest version via the Lab Help button*). Moreover, the results of our coded BLEU score are almost identical to those of the SacreBLEU package.


from typing import Tuple, Union

from sympy import Symbol, sympify
from sympy.core.function import Function
from sympy.parsing.sympy_parser import (convert_xor, function_exponentiation,
                                        implicit_multiplication_application,
                                        parse_expr, standard_transformations)

from ..error_handler import AGreatB, SameSigns

x = Symbol('x')
transformations = standard_transformations + \
    (convert_xor, implicit_multiplication_application, function_exponentiation)

def bisection(f: Function, a: float, b: float, error: float,
              rational: bool = False, swap: bool = False,
              iterated_data: bool = False) -> Union[float, Tuple[float, dict]]:
    """
    Using bisection method, returns root (or tupled with iterated data).
    - Repeatedly bisects an interval and then selects a
    sub-interval in which a root must lie for further
    processing.

    ..Note:
        - Return iterated_data defaults to False
        - Always use 'x' as symbol

    Algorithm
    =========
    1. Find two numbers (a, b) of which f(x) has different signs
    2. Define c = (a+b)/2
    3. if (b-c) <= error tolerance, accept c as the root,
        stop iteration
    4. if f(a)f(c) <= 0, b = c else a = c

    Examples
    ========
    ** Non-pythonic expressions will be parsed by SymPy module
    >>> from newton_raphson import newton_raphson
    >>> bisection('x^2-8x+11', 1, 2, 0.001)
    1.7646484375

    ** Using pythonic expressions is also accepted
    >>> bisection('x**2-8*x+11', 1, 2, 0.001)
    1.7646484375

    ** Turning rational True
    >>> bisection('x^3-3x+1', 0, 1, 0.001, rational=True)
    355/1024

    ** Turning iterated_data True
    >>> bisection('x^3-3x+1', 0, 1, 0.001, iterated_data=True)
    (0.3466796875, {1: {'a': 0, 'b': 1, 'c': 1/2, 'fa': 1, 'fc': -3/8, 'bc': 1/2, 'swap': 'b = c'}, ...)

    Iterated data
    =============
    count / iteration :
        - The first key you see in the dictionary

    a :
        - Value of the first interval

    b :
        - Value of the second interval

    c :
        - Value of (a + b)/2 [value of the root]

    fa :
        - Value of the f(a)

    fc :
        - Value of the f(c)

    bc :
        - Value of (b - c)

    swap :
        - What values swapped

    Exceptions
    ==========
    SameSign :
        - Raised when values of f(a) and f(b) has the same sign

    AGreatB :
        - Raised when the value of a is greater than b
        - [Optional flag] - Swap the values

    Parameters
    ==========
    f :
        - Should be STRING
        - Mathematical expression
        - A mathematical function
        - Example:
            'x^2-8x+11'
            'x**3-3*x+1'
            'x^3+10x^2-5'

    a :
        - The first interval

    b :
        - The second interval

    error :
        - Error tolerance

    rational :
        - Returns fraction/rational value
        - Defaults to False

    swap :
        - Flag to prevent AGreatB Exception
        - Defaults to False

    iterated_data:
        - Returns the iterated data in dictionary
        - Defaults to False
    """

    f = parse_expr(f, transformations=transformations)
    a = sympify(a)
    b = sympify(b)
    error = sympify(error)

    if a > b and swap:
        z = a
        a = b
        b = z
        del z
    elif a > b:
        raise AGreatB

    if f.subs(x, a) * f.subs(x, b) > 0.0:
        raise SameSigns

    data = {}
    count = 1

    while True:
        c = (a + b) / 2
        fa = f.subs(x, a)
        fc = f.subs(x, c)
        bc = b-c

        data[count] = {'a': a, 'b': b, 'c': c, 'fa': fa, 'fc': fc, 'bc': bc,
                       'swap': 'b = c' if fa * fc <= 0 else 'a = c'}

        if bc <= error or round(bc, 4) <= error:
            break

        if fa*fc <= 0:
            b = c
        else:
            a = c

        count += 1

    if not rational:
        c = float(c)

    if iterated_data:
        return c, data
    else:
        return c

def false_position(f: Function, a: float, b: float, rational: bool = False,
                   swap: bool = False, iterated_data: bool = False) -> Union[float, Tuple[float, dict]]:
    """
    Using false-position method, returns root (or tupled with iterated data)
    - The same as bisection method, because the size of an interval
    containing a root is reduced through iteration.

    ..Note:
        - Return iterated_data defaults to False
        - Always use 'x' as symbol

    Algorithm
    =========
    1. Find two numbers (a, b) of which f(x) has different signs
    2. Define c = (a*fb-b*fa)/(fb-fa)
    3. if b and c are converging, return c as root
    4. if f(a)f(c) <= 0, b = c else a = c

    Examples
    ========
    ** Non-pythonic expressions will be parsed by SymPy module
    >>> false_position('x^2-8x+11', 1, 2)
    1.7639484978540771

    ** Using pythonic expressions is also accepted
    >>> false_position('x**2-8*x+11', 1, 2)
    1.7639484978540771

    ** Turning rational True
    >>> false_position('x^3-3x+1', 0, 1, rational=True)
    119624146949151814554649/344443180703677909347131

    ** Turning iterated_data True
    >>> false_position('x^3-3x+1', 0, 1, iterated_data=True)
    (0.3472971847047936, {1: {'a': 0, 'b': 1, 'fa': 1, 'fb': -1, 'c': 1/2, 'fc': -3/8, 'CoS': '-'}, ...)

    Iterated data
    =============
    count / iteration :
        - The first key you see in the dictionary

    a :
        - Value of the first interval

    b :
        - Value of the second interval

    fa :
        - Value of the f(a)

    fb :
        - Value of the f(b)

    c :
        - Value of (a*fb-b*fa)/(fb-fa) [value of the root]

    fc :
        - Value of the f(c)

    Exceptions
    ==========
    SameSign :
        - Raised when values of f(a) and f(b) has the same sign

    AGreatB :
        - Raised when the value of a is greater than b
        - [Optional flag] - Swap the values

    Parameters
    ==========
    f :
        - Should be STRING
        - Mathematical expression
        - A mathematical function
        - Example:
            'x^2-8x+11'
            'x**3-3*x+1'
            'x^3+10x^2-5'

    a :
        - The first interval

    b :
        - The second interval

    rational :
        - Returns fraction/rational value
        - Defaults to False

    swap :
        - Flag to prevent AGreatB Exception
        - Defaults to False

    iterated_data:
        - Returns the iterated data in dictionary
        - Defaults to False
    """
    from math import isclose

    f = parse_expr(f, transformations=transformations)
    a = sympify(a)
    b = sympify(b)

    if a > b and swap:
        z = a
        a = b
        b = z
        del z
    elif a > b:
        raise AGreatB

    if f.subs(x, a) * f.subs(x, b) > 0.0:
        raise SameSigns

    data = {}
    count = 1

    while True:
        fa = f.subs(x, a)
        fb = f.subs(x, b)
        c = (a*fb-b*fa)/(fb-fa)
        fc = f.subs(x, c)

        data[count] = {'a': a, 'b': b, 'fa': fa, 'fb': fb, 'c': c,
                       'fc': fc}

        if isclose(b, c, rel_tol=1e-4):
            break
        if fa*fc <= 0:
            b = c
        else:
            a = c

        count += 1

    if not rational:
        c = float(c)

    if iterated_data:
        return c, data
    else:
        return c


import matplotlib
matplotlib.use("TKagg")
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats

# use seaborn plotting defaults
import seaborn as sns; sns.set()

#Motivating Support Vector Machines
#SVMs are powerful and flexible supervised algorithmes
#for classification and regression
#This is an example of  discriminative classification

from sklearn.datasets.samples_generator import make_blobs
X, y = make_blobs(n_samples=50, centers=2,
                  random_state=0, cluster_std=0.60)
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn');
plt.show()
plt.clf()

#Theres a couple dividing lines we could draw
xfit = np.linspace(-1, 3.5)
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
plt.plot([0.6], [2.1], 'x', color='red', markeredgewidth=2, markersize=10)

for m, b in [(1, 0.65), (0.5, 1.6), (-0.2, 2.9)]:
    plt.plot(xfit, m * xfit + b, '-k')

plt.xlim(-1, 3.5);
plt.show()
plt.clf()

#Maximizing the margin
xfit = np.linspace(-1, 3.5)
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')

for m, b, d in [(1, 0.65, 0.33), (0.5, 1.6, 0.55), (-0.2, 2.9, 0.2)]:
    yfit = m * xfit + b
    plt.plot(xfit, yfit, '-k')
    plt.fill_between(xfit, yfit - d, yfit + d, edgecolor='none',
                     color='#AAAAAA', alpha=0.4)

plt.xlim(-1, 3.5);
plt.show()
plt.clf()

from sklearn.svm import SVC #'Support Vector Classifier'
model = SVC(kernel='linear', C=1E10)
model.fit(X, y)

def plot_svc_decision_function(model, ax=None, plot_support=True):
    """Plot the decision function for a 2D SVC"""
    if ax is None:
        ax = plt.gca()
    xlim = ax.get_xlim()
    ylim = ax.get_ylim()

    # create grid to evaluate model
    x = np.linspace(xlim[0], xlim[1], 30)
    y = np.linspace(ylim[0], ylim[1], 30)
    Y, X = np.meshgrid(y, x)
    xy = np.vstack([X.ravel(), Y.ravel()]).T
    P = model.decision_function(xy).reshape(X.shape)

    # plot decision boundary and margins
    ax.contour(X, Y, P, colors='k',
               levels=[-1, 0, 1], alpha=0.5,
               linestyles=['--', '-', '--'])

    # plot support vectors
    if plot_support:
        ax.scatter(model.support_vectors_[:, 0],
                   model.support_vectors_[:, 1],
                   s=300, linewidth=1, facecolors='none');
    ax.set_xlim(xlim)
    ax.set_ylim(ylim)
    plt.show()
    plt.clf()

plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
plot_svc_decision_function(model);
print(model.support_vectors_) # points circled in the graph

#compare the model learned from first 60 and first 120 points
def plot_svm(N=10, ax=None):
    X, y = make_blobs(n_samples=200, centers=2,
                      random_state=0, cluster_std=0.60)
    X = X[:N]
    y = y[:N]
    model = SVC(kernel='linear', C=1E10)
    model.fit(X, y)

    ax = ax or plt.gca()
    ax.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
    ax.set_xlim(-1, 4)
    ax.set_ylim(-1, 6)
    plot_svc_decision_function(model, ax)

fig, ax = plt.subplots(1, 2, figsize=(16, 6))
fig.subplots_adjust(left=0.0625, right=0.95, wspace=0.1)
for axi, N in zip(ax, [60, 120]):
    plot_svm(N, axi)
    axi.set_title('N = {0}'.format(N))

#the extra data points dont change the model
#since none cross the support vecotrs margins

###Beyond linear boundaries: kernel SVM
#some data thats not linearly seperatable

from sklearn.datasets.samples_generator import make_circles
X, y = make_circles(100, factor=.1, noise=.1)

clf = SVC(kernel='linear').fit(X, y)

plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
plot_svc_decision_function(clf, plot_support=False);

#radial basis function on the center clump
r = np.exp(-(X ** 2).sum(1))

from mpl_toolkits import mplot3d

def plot_3D(elev=30, azim=30, X=X, y=y):
    ax = plt.subplot(projection='3d')
    ax.scatter3D(X[:, 0], X[:, 1], r, c=y, s=50, cmap='autumn')
    ax.view_init(elev=elev, azim=azim)
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    ax.set_zlabel('r')
    plt.show()
    plt.clf()

plot_3D()

# the radial bias fuctions centering made this possible
#we need a way to automate this so its correct
#we can use the kernel trick

clf = SVC(kernel='rbf', C=1E6) #change linear to rbf for this
clf.fit(X, y)

plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
plot_svc_decision_function(clf)
plt.scatter(clf.support_vectors_[:, 0], clf.support_vectors_[:, 1],
            s=300, lw=1, facecolors='none');
plt.show()
plt.clf()

#Tuning the SVM:Softening margins
X, y = make_blobs(n_samples=100, centers=2,
                  random_state=0, cluster_std=1.2)
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn');

#Example: Face Recognition
from sklearn.datasets import fetch_lfw_people
faces = fetch_lfw_people(min_faces_per_person=60)
print(faces.target_names)
print(faces.images.shape)

fig, ax = plt.subplots(3, 5)
for i, axi in enumerate(ax.flat):
    axi.imshow(faces.images[i], cmap='bone')
    axi.set(xticks=[], yticks=[],
            xlabel=faces.target_names[faces.target[i]])

plt.show()
plt.clf()

from sklearn.svm import SVC
from sklearn.decomposition import PCA as RandomizedPCA
from sklearn.pipeline import make_pipeline
#going to extract 150 features instead of using raw pixel data
pca = RandomizedPCA(n_components=150, whiten=True, random_state=42)
svc = SVC(kernel='rbf', class_weight='balanced')
model = make_pipeline(pca, svc)

from sklearn.model_selection import train_test_split
Xtrain, Xtest, ytrain, ytest = train_test_split(faces.data, faces.target,
                                                random_state=42)

from sklearn.model_selection import GridSearchCV
param_grid = {'svc__C': [1, 5, 10, 50],
              'svc__gamma': [0.0001, 0.0005, 0.001, 0.005]}
grid = GridSearchCV(model, param_grid)
grid.fit(Xtrain, ytrain)
print(grid.best_params_)

model = grid.best_estimator_
yfit = model.predict(Xtest)
fig, ax = plt.subplots(4, 6)
for i, axi in enumerate(ax.flat):
    axi.imshow(Xtest[i].reshape(62, 47), cmap='bone')
    axi.set(xticks=[], yticks=[])
    axi.set_ylabel(faces.target_names[yfit[i]].split()[-1],
                   color='black' if yfit[i] == ytest[i] else 'red')
fig.suptitle('Predicted Names; Incorrect Labels in Red', size=14);
plt.show()
plt.clf()

#we can better assert our estimators performance using
#a classification report

from sklearn.metrics import classification_report
print(classification_report(ytest, yfit,
                            target_names=faces.target_names))

#we can also check th econfusion matrix

from sklearn.metrics import confusion_matrix
mat = confusion_matrix(ytest, yfit)
sns.heatmap(mat.T, square=True, annot=True, fmt='d', cbar=False,
            xticklabels=faces.target_names,
            yticklabels=faces.target_names)
plt.xlabel('true label')
plt.ylabel('predicted label');
plt.show()
plt.clf()


import numpy as np

def delayed_adstock(x, L, P, D):
    '''
    params:
    x: original media variable, array
    L: length
    P: peak, delay in effect
    D: decay, retain rate
    returns:
    array, adstocked media variable
    '''
    # https://github.com/sibylhe/mmm_stan#12-adstock
    # prepend x with zeros equal to the length -1
    x = np.append(np.zeros(L-1), x)

    # create an array of zeros equal to the length
    weights = np.zeros(L)

    # loop through each day in length
    for l in range(L):
        # weight is decay to the power of index - peak squared
        weight = D**((l-P)**2)
        # add weight to weights in the right place (from back to front)
        weights[L-1-l] = weight

    # create an empty list
    adstocked_x = []
    # loop through length - 1 up to len(x)
    for i in range(L-1, len(x)):
        # get array of x from index - length + 1 to index + 1
        x_array = x[i-L+1:i+1]
        # sum the x_array * weights / sum(weights) to get adstock value
        xi = sum(x_array * weights)/sum(weights)
        # append adstocked value to adstocked_x
        adstocked_x.append(xi)

    # convert adstocked_x into an np.array
    adstocked_x = np.array(adstocked_x)

    # return adstocked_x
    return adstocked_x


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

import cv2
from matplotlib import pyplot as plt
import math
import numpy as np

"""
Devuelve el valor de la gaussiana
centrada en el origen en el punto x

x: punto en el que se evalúa la gaussiana
sigma: desviación típica de la gaussiana
"""
def gaussiana(x, sigma):
  return math.exp((-x*x)/(2.0*sigma*sigma))

"""
Devuelve el valor de la primera derivada de la gaussiana
centrada en el origen en el punto x

x: punto en el que se evalúa la gaussiana
sigma: desviación típica de la gaussiana
"""
def gaussiana1Deriv(x, sigma):
  return (-x/(sigma*sigma))*math.exp((-x*x)/(2.0*sigma*sigma))

"""
Devuelve el valor de la segunda derivada de la gaussiana
centrada en el origen en el punto x

x: punto en el que se evalúa la gaussiana
sigma: desviación típica de la gaussiana
"""

def gaussiana2Deriv(x, sigma):
  return (-1.0/(sigma*sigma) + x*x/pow(sigma,4))*math.exp((-x*x)/(2.0*sigma*sigma))

"""
Devuelve la máscara de la gaussiana o de alguna de sus
dos primeras derivadas discretizada

valor: parámetro para calcular la máscara (desv. típica o tamaño de la máscara)
flag_valor: valor = desviación típica si True, valor = tamaño máscara si False
derivada_mascara: derivada de la gaussiana usada para el cálculo de la máscara
  (0 si se usa la propia gaussiana)
"""
def mascaraGaussiana1D(valor, flag_valor, derivada_mascara):
  # Calcular sigma a partir de valor
  sigma = None
  if flag_valor == True:
    sigma = valor
  else:
    if valor%2 == 0:
      print("Error: la máscara no puede tener un tamaño par")
      exit(-1)
    # Máximo valor de sigma tal que, al truncar hacia arriba 3*sigma,
    # obtenemos (tamaño_mascara-1)/2
    sigma = (valor-1.0)/6.0
  # t = (tamaño_mascara-1)/2
  t = int(math.ceil(3*sigma))

  # Inicializar máscara vacía
  mascara = np.zeros(2*t+1)

  # Calcular máscara en función de la derivada especificada
  if derivada_mascara == 0:
    mascara[t] = 1.0
    suma = mascara[t]
    for i in range(1, t+1):
      mascara[t+i] = mascara[t-i] = gaussiana(i, sigma)
      suma += 2*mascara[t+i]
    # En este caso, se divide por la suma para que la suma de los valores sea 1
    mascara /= suma
  elif derivada_mascara == 1:
    mascara[t] = 0.0
    for i in range(1, t+1):
      mascara[t+i] = gaussiana1Deriv(i, sigma)
      mascara[t-i] = -mascara[t+i]
  elif derivada_mascara == 2:
    mascara[t] = -1.0/(sigma*sigma)
    for i in range(1, t+1):
      mascara[t+i] = mascara[t-i] = gaussiana2Deriv(i, sigma)
  else:
    print("Error: el valor derivada_mascara debe ser 0, 1 ó 2")
    exit(-1)

  return mascara

"""
Devuelve la imagen orlada a izquierda y derecha por bordes de ceros

img: imagen a orlar
t: número de columnas que se añaden tanto a izquierda como a derecha
"""
def orlaCeros(img, t):
  orla = np.zeros((img.shape[0],t))
  return np.hstack((orla,img,orla))

"""
Devuelve la imagen orlada a izquierda y derecha por bordes reflejados (101)

img: imagen a orlar
t: número de columnas que se añaden tanto a izquierda como a derecha
"""
def orlaReflejo(img, t):
  reflejo_izq = np.flip(img[:,0:t], axis=1)
  reflejo_der = np.flip(img[:,img.shape[1]-t:img.shape[1]], axis=1)
  return np.hstack((reflejo_izq,img,reflejo_der))

"""
Devuelve la imagen orlada a izquierda y derecha por bordes replicados

img: imagen a orlar
t: número de columnas que se añaden tanto a izquierda como a derecha
"""
def orlaReplicados(img, t):
  reflejo_izq = np.outer(img[:,0], np.ones(t))
  reflejo_der = np.outer(img[:,img.shape[1]-1], np.ones(t))
  return np.hstack((reflejo_izq,img,reflejo_der))

"""
Devuelve una imagen sobre la que se ha aplicado una máscara replicada por filas

img: imagen sobre la que se aplica la máscara replicada
mascara: máscara a aplicar
flag_borde: tipo de borde con el que se orla la imagen
  0 --> borde con ceros
  1 --> borde reflejo 101
  2 --> borde replicados
"""
def aplicarMascaraReplicada(img, mascara, flag_borde):
  # Obtener una matriz con la máscara replicada por filas
  mascara_replicada = np.outer(np.ones(img.shape[0]), mascara)

  # Orlar la imagen según el borde especificado
  t = int((len(mascara)-1)/2)
  img_orlada = None
  if flag_borde == 0:
    img_orlada = orlaCeros(img, t)
  elif flag_borde == 1:
    img_orlada = orlaReflejo(img, t)
  elif flag_borde == 2:
    img_orlada = orlaReplicados(img, t)
  else:
    print('Error: valor no válido para flag_borde')
    exit(-1)

  # Aplicar la máscara replicada a la imagen
  img_final = np.zeros(img.shape, dtype=img.dtype)
  for i in range(t,img.shape[1]+t):
    img_final[:,i-t] = np.sum(np.multiply(img_orlada[:,i-t:i+t+1], mascara_replicada), axis=1)

  return img_final

"""
Devuelve la imagen que resulta de aplicar una correlación a partir de dos
máscaras de dimensión 1 con la misma longitud, una por filas y otra por columnas

img: imagen sobre la que se aplica la máscara replicada
masc_x: máscara a aplicar por filas
masc_y: máscara a aplicar por columnas
flag_borde: tipo de borde con el que se orla la imagen
  0 --> borde con ceros
  1 --> borde reflejo 101
  2 --> borde replicados
"""
def correlacionMascaras1D(img, masc_x, masc_y, flag_borde):
  if len(masc_x) != len(masc_y):
    print("Error: las máscaras no tienen la misma longitud")
    exit(-1)

  if img.shape[0] < len(masc_x) or img.shape[1] < len(masc_x):
    print("Error: las máscaras tienen dimensiones superiores a las de la imagen")
    exit(-1)

  # Aplicar masc_x por filas
  img_corr = aplicarMascaraReplicada(img, masc_x, flag_borde)
  # Trasponer matriz, aplicar masc_y por filas y trasponer de nuevo
  # (Equivalente a aplicar masc_y por columnas)
  img_corr = img_corr.transpose()
  img_corr = aplicarMascaraReplicada(img_corr, masc_y, flag_borde)
  img_corr = img_corr.transpose()

  return img_corr

"""
Devuelve la imagen que resulta de aplicar el operador laplaciano

img: imagen sobre la que se calcula su laplaciana
valor: parámetro para calcular las máscaras (desv. típica o tamaño de la máscara)
flag_valor: valor = desviación típica si True, valor = tamaño máscara si False
flag_borde: tipo de borde con el que se orla la imagen
  0 --> borde con ceros
  1 --> borde reflejo 101
  2 --> borde replicados
"""
def laplaciana(img, valor, flag_valor, flag_borde):
  # Calcular las máscaras de la gaussiana y de su segunda derivada
  masc_gauss = mascaraGaussiana1D(valor, flag_valor, 0)
  masc_gauss_2deriv = mascaraGaussiana1D(valor, flag_valor, 2)

  # Obtener imágenes aplicando las máscaras de las derivadas parciales segundas
  img_deriv_xx = correlacionMascaras1D(img, masc_gauss, masc_gauss_2deriv, flag_borde)
  img_deriv_yy = correlacionMascaras1D(img, masc_gauss_2deriv, masc_gauss, flag_borde)

  # Calcular sigma según valor
  sigma = valor
  if flag_valor == False:
    sigma = (valor-1.0)/6.0

  # Sumar derivadas parciales y normalizar (multiplicar por sigma^2)
  img_laplaciana = (sigma*sigma)*(img_deriv_xx + img_deriv_yy)

  return img_laplaciana

"""
Devuelve una submuestra reducida compuesta por los píxeles que
están en una fila y una columna impares en la imagen original

img: imagen a submuestrear
"""
def reducirSubmuestra(img):
  # Creo una submuestra con la mitad de filas y de columnas
  img_submuestra = np.zeros((int(img.shape[0]/2),int(img.shape[1]/2)))
  # Los píxeles usados para la submuestra son aquellos que en la
  # imagen original se encuentran en una fila y una columna impares
  for i in range(img_submuestra.shape[0]):
    for j in range(img_submuestra.shape[1]):
      img_submuestra[i][j] = img[2*i+1][2*j+1]

  return img_submuestra

"""
Devuelve una submuestra ampliada en la que los píxeles de la imagen original
se encuentran en una fila y una columna impares en la imagen ampliada. El resto
de píxeles se calculan interpolando los píxeles más próximos obtenidos
directamente de la imagen original.

ig: imagen a submuestrear
flag_fila: si True, se añade una fila adicional para que el número de filas sea impar
flag_columna: si True, se añade una columna adicional para que el número de columnas sea impar
"""
def ampliarSubmuestra(img, flag_fila=False, flag_columna=False):
  fila_adicional = columna_adicional = 0
  if flag_fila == True:
    fila_adicional = 1
  if flag_columna == True:
    columna_adicional = 1

  # Crear la submuestra ampliada
  img_submuestra = np.zeros((img.shape[0]*2+fila_adicional,img.shape[1]*2+columna_adicional))

  # Tratamiento especial para las dos primeras filas y columnas
  img_submuestra[0][0] = img_submuestra[0][1] = img_submuestra[1][0] = img_submuestra[1][1] = img[0][0]
  for i in range(1,img.shape[0]):
    img_submuestra[2*i][0] = img_submuestra[2*i+1][0] = img_submuestra[2*i+1][1] = img[i][0]
    img_submuestra[2*i][1] = (img[i][0]+img[i-1][0])/2
  for j in range(1,img.shape[1]):
    img_submuestra[0][2*j] = img_submuestra[0][2*j+1] = img_submuestra[1][2*j+1] = img[0][j]
    img_submuestra[1][2*j] = (img[0][j]+img[0][j-1])/2

  # Asignación de valores al resto de píxeles
  for i in range(1,img.shape[0]):
    for j in range(1,img.shape[1]):
      img_submuestra[2*i][2*j] = (img[i][j]+img[i-1][j]+img[i-1][j]+img[i-1][j-1])/4
      img_submuestra[2*i+1][2*j] = (img[i][j]+img[i][j-1])/2
      img_submuestra[2*i][2*j+1] = (img[i][j]+img[i-1][j])/2
      img_submuestra[2*i+1][2*j+1] = img[i][j]

  #Si es necesario, se añade una fila o una columna adicional según los flags
  if flag_fila == True:
    img_submuestra[-1,:] = img_submuestra[-2,:]
  if flag_columna == True:
    img_submuestra[:,-1] = img_submuestra[:,-2]

  return img_submuestra

"""
Devuelve la pirámide gaussiana de tamaño 4 de una imagen

img: imagen de la que se calcula su pirámide gaussiana
valor: parámetro para calcular la máscara (desv. típica o tamaño de la máscara)
flag_valor: valor = desviación típica si True, valor = tamaño máscara si False
"""
def piramideGaussiana(img, valor, flag_valor):
  # Crear la imagen de la pirámide gaussiana
  piramide = np.zeros((img.shape[0],img.shape[1]+int(img.shape[1]/2)))
  # Copiar la imagen original a la izquierda de la imagen pirámide
  piramide[0:img.shape[0],0:img.shape[1]] = img

  altura = 0
  img_subm = img.copy()
  mascara_gaussiana = mascaraGaussiana1D(valor, flag_valor, 0)
  for i in range(3):
    # Aplicar filtro gaussiano
    img_subm = correlacionMascaras1D(img_subm, mascara_gaussiana, mascara_gaussiana, 2)
    # Obtener submuestra reducida
    img_subm = reducirSubmuestra(img_subm)
    # Guardar submuestra justo abajo de la submuestra anterior
    # y a la izquierda de la original
    piramide[altura:altura+img_subm.shape[0],img.shape[1]:img.shape[1]+img_subm.shape[1]] = img_subm
    altura += img_subm.shape[0]

  return piramide

"""
Devuelve la imagen normalizada en el intervalo [0,1]

img: imagen a normalizar
"""
def normalizarIntervalo01(img):
  # Obtener máximo y mínimo
  minimo, maximo = 0, 1
  for i in range(img.shape[0]):
    for j in range(img.shape[1]):
      if minimo > img[i][j]:
        minimo = img[i][j]
      elif maximo < img[i][j]:
        maximo = img[i][j]

  # Normalizar si es necesario
  img_normalizada = img.copy()
  if minimo < 0 or maximo > 1:
    img_normalizada = (1/(maximo-minimo))*(img-minimo*np.ones(img.shape))

  return img_normalizada

"""
Devuelve la lista de imágenes normalizada en el intervalo [0,1], usando como
máximo y mínimo los valores máximo y mínimo globales de entre todas las
imágenes, respectivamente.

lista_imgs: lista de imágenes a normalizar
"""
def normalizarListaImagenes01(lista_imgs):
  # Obtener máximo y mínimo globales
  minimo, maximo = 0, 1
  for img in lista_imgs:
    for i in range(img.shape[0]):
       for j in range(img.shape[1]):
        if minimo > img[i][j]:
          minimo = img[i][j]
        elif maximo < img[i][j]:
          maximo = img[i][j]

  # Normalizar si es necesario
  lista_norm = []
  if minimo < 0 or maximo > 1:
    for img in lista_imgs:
      lista_norm.append( (1/(maximo-minimo))*(img-minimo*np.ones(img.shape)) )
  else:
    lista_norm = lista_imgs.copy()

  return lista_norm

"""
Devuelve la pirámide laplaciana de tamaño 4 de una imagen

img: imagen de la que se calcula su pirámide laplaciana
valor: parámetro para calcular la máscara (desv. típica o tamaño de la máscara)
flag_valor: valor = desviación típica si True, valor = tamaño máscara si False
"""
def piramideLaplaciana(img, valor, flag_valor):
  # Crear la imagen de la pirámide laplaciana
  piramide = np.zeros((img.shape[0],img.shape[1]+int(img.shape[1]/2)))

  # Crear una lista con todas las submuestras a añadir a la pirámide
  lista_submuestras = []

  # Crear las tres primeras imágenes laplacianas y añadirlas a la lista
  img_subm = img.copy()
  mascara_gaussiana = mascaraGaussiana1D(valor, flag_valor, 0)
  for i in range(3):
    # Crear copia de submuestra reducida anterior con filtro gaussiano
    img_subm_auxiliar = correlacionMascaras1D(img_subm, mascara_gaussiana, mascara_gaussiana, 2)
    # Reducir submuestra anterior con filtro gaussiano
    img_subm = reducirSubmuestra(img_subm_auxiliar)
    # Actualizar flags de filas y columnas impares
    flag_fila = flag_columna = False
    if img_subm_auxiliar.shape[0]%2 == 1:
      flag_fila = True
    if img_subm_auxiliar.shape[1]%2 == 1:
      flag_columna = True
    # Añadir nuevaa laplaciana a la lista:
    # submuestra con filtro gaussiano menos ella misma reducida y luego ampliada
    lista_submuestras.append(
        img_subm_auxiliar - ampliarSubmuestra(img_subm, flag_fila, flag_columna) )

  # Normalizar lista de submuestras laplacianas al intervalo [0,1]
  lista_submuestras = normalizarListaImagenes01(lista_submuestras)

  # Añadir la última imagen obtenida con filtro gaussiano, previamente normalizada
  lista_submuestras.append( normalizarIntervalo01(img_subm) )

  # Guardar la primera submuestra laplaciana a la izquierda de la pirámide
  piramide[0:lista_submuestras[0].shape[0],0:lista_submuestras[0].shape[1]] = (
      lista_submuestras[0] )

  # Guardar el resto de submuestras justo debajo de la submuestra anterior
  # y a la izquierda de la primera
  h = 0
  for i in range(1,4):
    dims = lista_submuestras[i].shape
    piramide[h:h+dims[0],img.shape[1]:img.shape[1]+dims[1]] = (
        lista_submuestras[i] )
    h += lista_submuestras[i].shape[0]

  return piramide

"""
Devuelve la imagen híbrida que resulta de hacer la suma de una imagen con
un filtro gaussiano y otra imagen con un filtro laplaciano.

img1: imagen a la que se le aplica el filtro gaussiana
img2: imagen a la que se le aplica el filtro laplaciano
sigma1: desviación típica de las máscaras del filtro gaussiano
sigma1: desviación típica de las máscaras del filtro laplaciano
peso1: valor por el que se multiplica la imagen 1 filtrada antes de sumarla
peso1: valor por el que se multiplica la imagen 2 filtrada antes de sumarla
flag_dibujar: True si se quieren dibujar las tres imágenes en una misma ventana
"""
def imagenHibrida(img1, img2, sigma1, sigma2, peso1=1, peso2=1, flag_dibujar=True):
  if img1.shape != img2.shape:
    print("Error: las imágenes no tienen las mismas dimensiones")
    exit(-1)

  #Aplicar filtro de paso bajo a imagen 1
  masc_gauss = mascaraGaussiana1D(sigma1, True, 0)
  img1_frec_bajas = correlacionMascaras1D(img1, masc_gauss, masc_gauss, 2)

  #Aplicar filtro de paso alto a imagen 2
  img2_frec_altas = laplaciana(img2, sigma2, True, 2)

  #Normalizar las dos imágenes al intervalo [0,1]
  img1_frec_bajas = normalizarIntervalo01(img1_frec_bajas)
  img2_frec_altas = normalizarIntervalo01(img2_frec_altas)

  #Crear la imagen híbrida y normalizarla al intervalo [0,1]
  img_hibrida = peso1*img1_frec_bajas + peso2*img2_frec_altas
  img_hibrida = normalizarIntervalo01(img_hibrida)

  #Mostrar las tres imágenes en una misma ventana
  if flag_dibujar == True:
    plt.imshow(np.hstack((img1_frec_bajas,img_hibrida,img2_frec_altas)), cmap="gray")
    plt.show()

  return img_hibrida

"""
Función que opera con imágenes a color.
Devuelve la imagen híbrida que resulta de hacer la suma de una imagen con
un filtro gaussiano y otra imagen con un filtro laplaciano.

img1: imagen a color a la que se le aplica el filtro gaussiana
img2: imagen a color a la que se le aplica el filtro laplaciano
sigma1: desviación típica de las máscaras del filtro gaussiano
sigma1: desviación típica de las máscaras del filtro laplaciano
peso1: valor por el que se multiplica la imagen 1 filtrada antes de sumarla
peso1: valor por el que se multiplica la imagen 2 filtrada antes de sumarla
"""
def imagenHibridaColor(img1, img2, sigma1, sigma2, peso1=1, peso2=1):
  if img1.shape != img2.shape:
    print("Error: las imágenes no tienen las mismas dimensiones")
    exit(-1)

  img_hibrida = np.zeros(img1.shape)
  for i in range(3):
    componente_img1 = img1[:,:,i]
    componente_img2 = img2[:,:,i]
    img_hibrida[:,:,i] = imagenHibrida(
      componente_img1, componente_img2, sigma1, sigma2, peso1, peso2, False )

  return img_hibrida

if __name__ == "__main__":
  print("///// APARTADO 1A /////")
  print()

  sigma = 3.0
  print("SIGMA =", sigma)
  print()
  cv2.waitKey(0)

  mascara_gauss = mascaraGaussiana1D(sigma, True, 0)
  print("Máscara de la gaussiana:\n", mascara_gauss)
  plt.figure(figsize=(5,5))
  plt.plot(np.arange(-(len(mascara_gauss)-1)/2, (len(mascara_gauss)-1)/2+1),mascara_gauss,'o',markersize=2)
  plt.show()
  print()
  cv2.waitKey(0)

  mascara_gauss_1deriv = mascaraGaussiana1D(sigma, True, 1)
  print("Máscara de la primera derivada de la gaussiana:\n", mascara_gauss_1deriv)
  plt.figure(figsize=(5,5))
  plt.plot(np.arange(-(len(mascara_gauss_1deriv)-1)/2, (len(mascara_gauss_1deriv)-1)/2+1),mascara_gauss_1deriv,'o',markersize=2)
  plt.show()
  print()
  cv2.waitKey(0)

  mascara_gauss_2deriv = mascaraGaussiana1D(sigma, True, 2)
  print("Máscara de la segunda derivada de la gaussiana:\n", mascara_gauss_2deriv)
  plt.figure(figsize=(5,5))
  plt.plot(np.arange(-(len(mascara_gauss_2deriv)-1)/2, (len(mascara_gauss_2deriv)-1)/2+1),mascara_gauss_2deriv,'o',markersize=2)
  plt.show()
  print()
  cv2.waitKey(0)

  tam = 15
  print("TAMAÑO MÁSCARA =", tam)
  print()
  cv2.waitKey(0)

  mascara_gauss = mascaraGaussiana1D(tam, False, 0)
  print("Máscara de la gaussiana:\n", mascara_gauss)
  plt.figure(figsize=(5,5))
  plt.plot(np.arange(-(len(mascara_gauss)-1)/2, (len(mascara_gauss)-1)/2+1),mascara_gauss,'o',markersize=2)
  plt.show()
  print()
  cv2.waitKey(0)

  mascara_gauss_1deriv = mascaraGaussiana1D(tam, False, 1)
  print("Máscara de la primera derivada de la gaussiana:\n", mascara_gauss_1deriv)
  plt.figure(figsize=(5,5))
  plt.plot(np.arange(-(len(mascara_gauss_1deriv)-1)/2, (len(mascara_gauss_1deriv)-1)/2+1),mascara_gauss_1deriv,'o',markersize=2)
  plt.show()
  print()
  cv2.waitKey(0)

  mascara_gauss_2deriv = mascaraGaussiana1D(tam, False, 2)
  print("Máscara de la segunda derivada de la gaussiana:\n", mascara_gauss_2deriv)
  plt.figure(figsize=(5,5))
  plt.plot(np.arange(-(len(mascara_gauss_2deriv)-1)/2, (len(mascara_gauss_2deriv)-1)/2+1),mascara_gauss_2deriv,'o',markersize=2)
  plt.show()
  print()
  cv2.waitKey(0)

  print("///// FIN APARTADO 1A /////")
  print()
  print()
  print()
  cv2.waitKey(0)

  print("///// APARTADO 1B /////")
  print()

  tam = 15
  print("TAMAÑO MÁSCARA =", tam)
  print()

  print("Imagen original: ")
  img = cv2.imread('./imagenes/bicycle.bmp', 0)
  plt.imshow(img, cmap='gray')
  plt.show()
  print()
  cv2.waitKey(0)

  print("Imagen convolucionada: ")
  mascara_gauss = mascaraGaussiana1D(tam, False, 0)
  img_conv = correlacionMascaras1D(img, mascara_gauss, mascara_gauss, 0)
  plt.imshow(img_conv, cmap='gray')
  plt.show()
  print()
  cv2.waitKey(0)

  print("Imagen convolucionada con GaussianBlur: ")
  img_conv_gaussianblur = cv2.GaussianBlur(img, (tam,tam), (tam-1)/6.0)
  plt.imshow(img_conv_gaussianblur, cmap='gray')
  plt.show()
  print()
  cv2.waitKey(0)

  print("///// FIN APARTADO 1B /////")
  print()
  print()
  print()
  cv2.waitKey(0)

  print("///// APARTADO 1C /////")
  print()

  for tam in range(3, 15, 4):
    print("TAMAÑO MÁSCARA =", tam)
    mascara_gauss = mascaraGaussiana1D(tam,False,0)
    mascara_gauss1deriv = mascaraGaussiana1D(tam,False,1)
    mascaras_cv2 = cv2.getDerivKernels(1, 0, tam)
    mascara_gauss1deriv_cv2 = mascaras_cv2[0].transpose()[0]
    mascara_gauss_cv2 = mascaras_cv2[1].transpose()[0]
    print("Gaussiana:")
    print("Máscara del apartado 1A:")
    print(mascara_gauss)
    plt.figure(figsize=(5,5))
    plt.plot(np.arange(-(tam-1)/2, (tam-1)/2+1),mascara_gauss,'o',markersize=2)
    plt.show()
    print("Máscara de cv2.getDerivKernels:", )
    print(mascara_gauss_cv2)
    plt.figure(figsize=(5,5))
    plt.plot(np.arange(-(tam-1)/2, (tam-1)/2+1),mascara_gauss_cv2,'o',markersize=2)
    plt.show()

    print()
    cv2.waitKey(0)

    print("Derivada de la gaussiana:")
    print("Máscara del apartado 1A:")
    print(mascara_gauss1deriv)
    plt.figure(figsize=(5,5))
    plt.plot(np.arange(-(tam-1)/2, (tam-1)/2+1),mascara_gauss1deriv,'o',markersize=2)
    plt.show()
    print("Máscara de cv2.getDerivKernels:", )
    print(mascara_gauss1deriv_cv2)
    plt.figure(figsize=(5,5))
    plt.plot(np.arange(-(tam-1)/2, (tam-1)/2+1),mascara_gauss1deriv_cv2,'o',markersize=2)
    plt.show()

    print()
    cv2.waitKey(0)

  for sigma in range(1, 6, 2):
    print("SIGMA =", sigma)
    tam = 2*math.ceil(3*sigma)+1
    mascara_gauss = mascaraGaussiana1D(sigma,True,0)
    mascara_gauss1deriv = mascaraGaussiana1D(sigma,True,1)
    mascaras_cv2 = cv2.getDerivKernels(1, 0, tam)
    mascara_gauss1deriv_cv2 = mascaras_cv2[0].transpose()[0]
    mascara_gauss_cv2 = mascaras_cv2[1].transpose()[0]
    print("Gaussiana:")
    print("Máscara del apartado 1A:")
    print(mascara_gauss)
    plt.figure(figsize=(5,5))
    plt.plot(np.arange(-(tam-1)/2, (tam-1)/2+1),mascara_gauss,'o',markersize=2)
    plt.show()
    print("Máscara de cv2.getDerivKernels:", )
    print(mascara_gauss_cv2)
    plt.figure(figsize=(5,5))
    plt.plot(np.arange(-(tam-1)/2, (tam-1)/2+1),mascara_gauss_cv2,'o',markersize=2)
    plt.show()

    print()
    cv2.waitKey(0)

    print("Derivada de la gaussiana:")
    print("Máscara del apartado 1A:")
    print(mascara_gauss1deriv)
    plt.figure(figsize=(5,5))
    plt.plot(np.arange(-(tam-1)/2, (tam-1)/2+1),mascara_gauss1deriv,'o',markersize=2)
    plt.show()
    print("Máscara de cv2.getDerivKernels:", )
    print(mascara_gauss1deriv_cv2)
    plt.figure(figsize=(5,5))
    plt.plot(np.arange(-(tam-1)/2, (tam-1)/2+1),mascara_gauss1deriv_cv2,'o',markersize=2)
    plt.show()

    print()
    cv2.waitKey(0)

  print("///// FIN APARTADO 1C /////")
  print()
  print()
  print()
  cv2.waitKey(0)

  print("///// APARTADO 1D /////")
  print()

  sigma = 3.0

  # Calcular la máscara de la gaussiana
  mascara_gauss = mascaraGaussiana1D(sigma, True, 0)
  print("Máscara de la gaussiana con sigma =", sigma, ":\n", mascara_gauss)
  plt.figure(figsize=(5,5))
  plt.plot(np.arange(-(len(mascara_gauss)-1)/2, (len(mascara_gauss)-1)/2+1),mascara_gauss,'o',markersize=2)
  plt.show()

  # Calcular la máscara normalizada de la segunda derivada de la gaussiana
  mascara_gauss_2deriv = (sigma*sigma)*mascaraGaussiana1D(sigma, True, 2)
  print("Máscara normalizada de la segunda derivada de la gaussiana con sigma =", sigma, ":\n", mascara_gauss_2deriv)
  plt.figure(figsize=(5,5))
  plt.plot(np.arange(-(len(mascara_gauss_2deriv)-1)/2, (len(mascara_gauss_2deriv)-1)/2+1),mascara_gauss_2deriv,'o',markersize=2)
  plt.show()
  print()
  cv2.waitKey(0)

  # Laplaciana sigma=1.0, bordes cero
  print("Laplaciana con sigma = 1.0 y bordes cero: ")
  plt.imshow(laplaciana(img, 1.0, True, 0), cmap='gray')
  plt.show()
  print()
  cv2.waitKey(0)
  # Laplaciana sigma=1.0, bordes replicados
  print("Laplaciana con sigma = 1.0 y bordes replicados: ")
  plt.imshow(laplaciana(img, 1.0, True, 2), cmap='gray')
  plt.show()
  print()
  cv2.waitKey(0)
  # Laplaciana sigma=3.0, bordes cero
  print("Laplaciana con sigma = 3.0 y bordes cero: ")
  plt.imshow(laplaciana(img, 3.0, True, 0), cmap='gray')
  plt.show()
  print()
  cv2.waitKey(0)
  # Laplaciana sigma=3.0, bordes replicados
  print("Laplaciana con sigma = 3.0 y bordes replicados: ")
  plt.imshow(laplaciana(img, 3.0, True, 2), cmap='gray')
  plt.show()
  print()
  cv2.waitKey(0)

  print("///// FIN APARTADO 1D /////")
  print()
  print()
  print()
  cv2.waitKey(0)

  print("///// APARTADO 2A /////")
  print()

  print("Pirámide gaussiana con tamaño de máscara = 5:")
  img = cv2.imread('./imagenes/bicycle.bmp', 0)
  piramide = piramideGaussiana(img, 5, False)
  plt.imshow(piramide, cmap='gray')
  plt.show()
  print()
  cv2.waitKey(0)

  print("Pirámide gaussiana con tamaño de máscara = 11:")
  img = cv2.imread('./imagenes/bicycle.bmp', 0)
  piramide = piramideGaussiana(img, 11, False)
  plt.imshow(piramide, cmap='gray')
  plt.show()
  print()
  cv2.waitKey(0)

  print("Pirámide gaussiana con sigma = 1.0:")
  img = cv2.imread('./imagenes/bicycle.bmp', 0)
  piramide = piramideGaussiana(img, 1.0, True)
  plt.imshow(piramide, cmap='gray')
  plt.show()
  print()
  cv2.waitKey(0)

  print("Pirámide gaussiana con sigma = 3.0:")
  img = cv2.imread('./imagenes/bicycle.bmp', 0)
  piramide = piramideGaussiana(img, 3.0, True)
  plt.imshow(piramide, cmap='gray')
  plt.show()
  print()
  cv2.waitKey(0)

  print("///// FIN APARTADO 2A /////")
  print()
  print()
  print()
  cv2.waitKey(0)

  print("///// APARTADO 2B /////")
  print()

  print("Pirámide laplaciana con tamaño de máscara = 5:")
  img = cv2.imread('./imagenes/bicycle.bmp', 0)
  piramide = piramideLaplaciana(img, 5, False)
  plt.imshow(piramide, cmap='gray')
  plt.show()
  print()
  cv2.waitKey(0)
  print()

  print("Pirámide laplaciana con tamaño de máscara = 11:")
  img = cv2.imread('./imagenes/bicycle.bmp', 0)
  piramide = piramideLaplaciana(img, 11, False)
  plt.imshow(piramide, cmap='gray')
  plt.show()
  print()
  cv2.waitKey(0)
  print()

  print("Pirámide laplaciana con sigma = 1.0:")
  img = cv2.imread('./imagenes/bicycle.bmp', 0)
  piramide = piramideLaplaciana(img, 1.0, True)
  plt.imshow(piramide, cmap='gray')
  plt.show()
  print()
  cv2.waitKey(0)
  print()

  print("Pirámide laplaciana con sigma = 3.0:")
  img = cv2.imread('./imagenes/bicycle.bmp', 0)
  piramide = piramideLaplaciana(img, 3.0, True)
  plt.imshow(piramide, cmap='gray')
  plt.show()
  print()
  cv2.waitKey(0)
  print()

  print("///// FIN APARTADO 2B /////")
  print()
  print()
  print()
  cv2.waitKey(0)

  print("///// APARTADO 3 /////")
  print()

  print("Imagen híbrida bicicleta-moto:")
  sigma1, sigma2 = 2.0, 33/20
  img1 = cv2.imread('./imagenes/motorcycle.bmp', 0)
  img2 = cv2.imread('./imagenes/bicycle.bmp', 0)
  img_hibrida = imagenHibrida(img1, img2, sigma1, sigma2, 4, 3)
  plt.imshow(piramideGaussiana(img_hibrida, 7, False), cmap='gray')
  plt.show()
  print()
  cv2.waitKey(0)
  print()

  print("Imagen híbrida gato-perro:")
  sigma1, sigma2 = 3.0, 33/20
  img1 = cv2.imread('./imagenes/cat.bmp', 0)
  img2 = cv2.imread('./imagenes/dog.bmp', 0)
  img_hibrida = imagenHibrida(img1, img2, sigma1, sigma2, 4, 3)
  plt.imshow(piramideGaussiana(img_hibrida, 7, False), cmap='gray')
  plt.show()
  print()
  cv2.waitKey(0)
  print()

  print("Imagen híbrida Marilyn-Einstein:")
  sigma1, sigma2 = 1.0, 3.0
  img1 = cv2.imread('./imagenes/marilyn.bmp', 0)
  img2 = cv2.imread('./imagenes/einstein.bmp', 0)
  img_hibrida = imagenHibrida(img1, img2, sigma1, sigma2, 4, 5)
  plt.imshow(piramideGaussiana(img_hibrida, 7, False), cmap='gray')
  plt.show()
  print()
  cv2.waitKey(0)
  print()

  print("Imagen híbrida submarino-pez:")
  sigma1, sigma2 = 3.0, 11/20
  img1 = cv2.imread('./imagenes/submarine.bmp', 0)
  img2 = cv2.imread('./imagenes/fish.bmp', 0)
  img_hibrida = imagenHibrida(img1, img2, sigma1, sigma2, 1, 1)
  plt.imshow(piramideGaussiana(img_hibrida, 7, False), cmap='gray')
  plt.show()
  print()
  cv2.waitKey(0)
  print()

  print("///// FIN APARTADO 3 /////")
  print()
  print()
  print()
  cv2.waitKey(0)

  print("///// BONUS 1 /////")
  print()

  print("Imagen híbrida bicicleta-moto a color:")
  sigma1, sigma2 = 2.0, 1.0
  img1 = cv2.imread('./imagenes/motorcycle.bmp')
  img2 = cv2.imread('./imagenes/bicycle.bmp')
  img_hibrida = imagenHibridaColor(img1, img2, sigma1, sigma2, 4, 3)
  plt.imshow( cv2.cvtColor((255*img_hibrida).astype(np.uint8), cv2.COLOR_BGR2RGB) )
  plt.show()
  print()
  cv2.waitKey(0)
  print()

  print("Imagen híbrida gato-perro a color:")
  sigma1, sigma2 = 3.0, 33/20
  img1 = cv2.imread('./imagenes/cat.bmp')
  img2 = cv2.imread('./imagenes/dog.bmp')
  img_hibrida = imagenHibridaColor(img1, img2, sigma1, sigma2, 4, 3)
  plt.imshow( cv2.cvtColor((255*img_hibrida).astype(np.uint8), cv2.COLOR_BGR2RGB) )
  plt.show()
  print()
  cv2.waitKey(0)
  print()

  print("Imagen híbrida submarino-pez a color:")
  sigma1, sigma2 = 3.0, 11/20
  img1 = cv2.imread('./imagenes/submarine.bmp')
  img2 = cv2.imread('./imagenes/fish.bmp')
  img_hibrida = imagenHibridaColor(img1, img2, sigma1, sigma2, 1, 1)
  plt.imshow( cv2.cvtColor((255*img_hibrida).astype(np.uint8), cv2.COLOR_BGR2RGB) )
  plt.show()
  print()
  cv2.waitKey(0)
  print()

  print("///// FIN BONUS 1 /////")
  print()
  print()
  print()
  cv2.waitKey(0)

  print("///// BONUS 2 /////")
  print()

  sigma1, sigma2 = 3.0, 61/30
  peso1, peso2 = 4, 3

  img1 = cv2.imread('./imagenes/monte_cervino.jpg', 0)
  img2 = cv2.imread('./imagenes/torre_eiffel.jpg', 0)

  img1 = img1[20:583,0:800]
  img2 = img2[:,290:690]

  print("Imagen monte Cervino restringida: ")
  plt.imshow(img1, cmap="gray")
  plt.show()
  print()
  print("Imagen torre Eiffel restringida: ")
  plt.imshow(img2, cmap="gray")
  plt.show()
  print()

  masc_gauss = mascaraGaussiana1D(1.0, True, 0)
  img1_auxiliar = correlacionMascaras1D(img1, masc_gauss, masc_gauss, 2)
  img1 = np.zeros((img1_auxiliar.shape[0], int(img1_auxiliar.shape[1]/2)))
  for j in range(img1.shape[1]):
    img1[:,j] = img1_auxiliar[:,2*j]

  print("Imagen monte Cervino restringida y estrechada: ")
  plt.imshow(img1, cmap="gray")
  plt.show()
  print()
  cv2.waitKey(0)
  print()

  print("Imagen híbrida: ")
  img_hibrida = imagenHibrida(img1, img2, sigma1, sigma2, peso1, peso2, False)
  plt.imshow(img_hibrida, cmap="gray")
  plt.show()
  print()
  cv2.waitKey(0)
  print()

  print("Pirámide gaussiana: ")
  plt.imshow(piramideGaussiana(img_hibrida, 7, False), cmap='gray')
  plt.show()
  print()
  print()
  print()
  cv2.waitKey(0)

  img1 = cv2.imread('./imagenes/monte_cervino.jpg')
  img2 = cv2.imread('./imagenes/torre_eiffel.jpg')

  img1 = img1[20:583,0:800,:]
  img2 = img2[:,290:690,:]

  print("Imagen monte Cervino restringida: ")
  plt.imshow( cv2.cvtColor(img1, cv2.COLOR_BGR2RGB) )
  plt.show()
  print()
  print("Imagen torre Eiffel restringida: ")
  plt.imshow( cv2.cvtColor(img2, cv2.COLOR_BGR2RGB) )
  plt.show()
  print()

  masc_gauss = mascaraGaussiana1D(1.0, True, 0)
  img1_auxiliar = np.zeros(img1.shape)
  for i in range(3):
    img1_auxiliar[:,:,i] = correlacionMascaras1D(img1[:,:,i], masc_gauss, masc_gauss, 2)
  img1 = np.zeros((img1_auxiliar.shape[0], int(img1_auxiliar.shape[1]/2), 3))
  for j in range(img1.shape[1]):
    img1[:,j,:] = img1_auxiliar[:,2*j,:]

  print("Imagen monte Cervino restringida y estrechada: ")
  plt.imshow( cv2.cvtColor(img1.astype(np.uint8), cv2.COLOR_BGR2RGB) )
  plt.show()
  print()
  cv2.waitKey(0)
  print()

  print("Imagen híbrida: ")
  img_hibrida = imagenHibridaColor(img1, img2, sigma1, sigma2, peso1, peso2)
  plt.imshow( cv2.cvtColor((255*img_hibrida).astype(np.uint8), cv2.COLOR_BGR2RGB) )
  plt.show()
  print()
  cv2.waitKey(0)
  print()

  print("///// FIN BONUS 2 /////")
  print()
  print()
  print()
  cv2.waitKey(0)


from scipy import signal
from scipy.signal import find_peaks
import matplotlib.pyplot as plt
import numpy as np

from scipy.fftpack import dct, idct

if __name__ == "__main__":
    t = np.linspace(0, 1, 500)
    tri = signal.sawtooth(2 * np.pi * 5 * t, 0.5)

    hs = dct(tri, type=2)
    fs = (0.5 + np.arange(len(hs))) / 2

    compressed_len = 40

    # inverse dct, lossy compression
    hs_compressed = hs[0:compressed_len]
    tri_compressed = idct(hs_compressed, type=2)
    # scale it back to -1, 1
    tri_compressed = tri_compressed / 500
    t_compressed = np.linspace(0, 1, len(tri_compressed))

    peaks, _ = find_peaks(-hs, height=10)

    plt.figure()
    plt.subplot(411)
    plt.plot(t, tri)
    plt.subplot(412)
    plt.plot(fs, hs)
    plt.plot(peaks, hs[peaks])
    plt.subplot(413)
    plt.plot(fs[:compressed_len], hs[:compressed_len])
    plt.subplot(414)
    plt.plot(t_compressed, tri_compressed)
    plt.show()


import numpy as np
import matplotlib.pyplot as plt

def gaussian(probs, means, covs, count=100):
	counts= np.random.multinomial(count, probs, size=1)[0] #how many points are there in each component
	#generate the data
	data= [np.random.multivariate_normal(means[i], covs[i],count) for i, count in enumerate(counts)]
	#combine the data
	data= np.concatenate(data)

	#shuffling the data
	index_new= np.arange(data.shape[0])
	np.random.shuffle(np.arange(data.shape[0]))
	data= data[index_new]
	return data

def generate(n, rate_anomaly):

	# Calculate how many data points there will be in each category
    typ_count, count_anomaly = np.random.multinomial(n, [1-rate_anomaly, rate_anomaly], size=1)[0]

    # Describe typ data with a 2 component Gaussian mixture
    typ_means = [[8, 8],      # mean of component 1
                    [-8, -8]]     # mean of component 2
    typ_covs = [[[1,0],[0,1]],  # covariance matrix of component 1
                    [[1,0],[0,1]]]  # covariance matrix of compoinent 2
    typ_probs = [0.5,              # probability of component 1
                  0.5]              # probability of component 2
    typ_data = gaussian(typ_probs, typ_means, typ_covs, count=typ_count)

    # Describe anomalous data with a 2 component Gaussian mixture
    anomalous_means = [[20, -20],            # mean of component 1
                      [0, 0]]                # mean of component 2
    anomalous_covs = [[[0.5, 0], [0, 0.5]],  # covariance of component 1
                      [[10, 0], [0, 10]]]    # covariance of component 2
    anomalous_ps = [0.1,                      # probability of component 1
                  0.9]                       # probability of component 2
    anomalous_data = gaussian(anomalous_ps, anomalous_means, anomalous_covs, count=count_anomaly)

  # Combine the data but preserve the labeling
    x = np.concatenate([typ_data, anomalous_data])
    y = np.concatenate([np.repeat(0, typ_count), np.repeat(1, count_anomaly)])
    new_index = np.arange(y.shape[0])
    np.random.shuffle(new_index)
    y = y[new_index]
    x = x[new_index]
    return x, y

def plot_anom(x, y):
    """ plots anomalies with red and typ with green"""
    fig, ax = plt.subplots()
    ax.scatter(x[:,0], x[:,1], s=3,
               c=['red' if yy else 'green' for yy in y])
    fig.show()

n = 500  # number of data points to generate
rate_anomaly = 0.1  # the rate at which anomalous points occur
x, y = (n, rate_anomaly)
plot_anom(x, y)


# Lab 5 Logistic Regression Classifier
import tensorflow as tf
import numpy as np
tf.set_random_seed(777)  # seed 지정

# 데이터 read
xy = np.loadtxt('data-03-diabetes.csv', delimiter=',', dtype=np.float32)
x_data = xy[:, 0:-1]    # predictor
y_data = xy[:, [-1]]    # response

print(x_data.shape, y_data.shape)   # (759, 8) (759, 1)

# placeholder 변수 지정
X = tf.placeholder(tf.float32, shape=[None, 8])
Y = tf.placeholder(tf.float32, shape=[None, 1])

W = tf.Variable(tf.random_normal([8, 1]), name='weight')
b = tf.Variable(tf.random_normal([1]), name='bias')

# logistic regression model(post.prob에 modeling)
hypothesis = tf.sigmoid(tf.matmul(X, W) + b)

# cost/loss function
cost = -tf.reduce_mean(Y * tf.log(hypothesis) + (1 - Y) *
                       tf.log(1 - hypothesis))

# Minimize
train = tf.train.GradientDescentOptimizer(learning_rate=0.01).minimize(cost)

# Accuracy 계산
# post.prob > 0.5 => 1
# post.prob < 0.5 => 0
predicted = tf.cast(hypothesis > 0.5, dtype=tf.float32)
# predicted == Y (True)이면 1
# predicted != Y (False)이면 0
accuracy = tf.reduce_mean(tf.cast(tf.equal(predicted, Y), dtype=tf.float32))

# Session 만들고 실행
with tf.Session() as sess:
    # 변수 초기화
    sess.run(tf.global_variables_initializer())

    for step in range(10001):
        cost_val, _ = sess.run([cost, train], feed_dict={X: x_data, Y: y_data})
        if step % 200 == 0:
            print(step, cost_val)

    # training된 model로 predict, accuracy 계산
    h, c, a = sess.run([hypothesis, predicted, accuracy],
                       feed_dict={X: x_data, Y: y_data})
    print("\nHypothesis: ", h, "\nCorrect (Y): ", c, "\nAccuracy: ", a)

'''
0 0.82794
200 0.755181
400 0.726355
600 0.705179
800 0.686631
...
9600 0.492056
9800 0.491396
10000 0.490767

...

 [ 1.]
 [ 1.]
 [ 1.]]
Accuracy:  0.762846
'''


import numpy as np
from matplotlib import pyplot as plt
from PIL import Image

def gris(img: np.ndarray) -> np.ndarray:
    n, p, *_ = img.shape
    mat_gris = np.zeros((n, p), dtype=float)

    for i in range(n):
        for j in range(p):

            r, g, b, *_ = img[i, j]

            mat_gris[i, j] = r / 3 + g / 3 + b / 3

    return mat_gris

def negatif(img: np.ndarray) -> np.ndarray:
    return 255 - img

def floutage(img: np.ndarray, n: int = 5) -> np.ndarray:
    M = img.copy()

    for _ in range(n):
        M[1:-1, 1:-1, :] = (
            + 0.25 * M[2:, 1:-1, :]
            + 0.25 * M[:-2, 1:-1, :]
            + 0.25 * M[1:-1, :-2, :]
            + 0.25 * M[1:-1, 2:, :]
        )

    return M

def floutage2(img: np.ndarray, n: int = 5) -> np.ndarray:
    M = img.copy()

    p, q, *_ = M.shape

    for _ in range(n):
        for i in range(1, p - 1):
            for j in range(1, q - 1):
                M[i, j] = 0.25 * M[i + 1, j] + 0.25 * M[i - 1, j] + 0.25 * M[i, j + 1] + 0.25 * M[i, j -1]

    return M

def contours(img: np.ndarray) -> np.ndarray:
    grey = gris(img).astype(int)

    n, p = grey.shape
    out = grey.copy()

    for i in range(1, n-1):
        for j in range(1, p-1):
            out[i, j] = abs(grey[i, j+1] - grey[i, j-1]) / 2 + abs(grey[i+1, j] - grey[i-1, j]) / 2

    return out

def test():
    img = Image.open("cat.png")
    mat_img = np.asarray(img)

    img_gris = gris(mat_img)
    img_negatif = negatif(gris(mat_img))
    img_floutee = floutage(mat_img)
    img_contours = contours(mat_img)
    img_contours_neg = negatif(img_contours)

    _, ax = plt.subplots(nrows=2, ncols=3)
    ax[0, 0].imshow(mat_img)
    ax[0, 1].imshow(img_gris, cmap="gray")
    ax[1, 0].imshow(img_negatif, cmap="gray")
    ax[1, 1].imshow(img_floutee, cmap="gray")
    ax[0, 2].imshow(img_contours, cmap="gray")
    ax[1, 2].imshow(img_contours_neg, cmap="gray")

    plt.show()

test()


"""
@author: Muhammad Febrian Ardiansyah
"""

# from my_pca import Pca
from sklearn.decomposition import PCA
from sklearn.preprocessing import normalize
import pandas as pd
import matplotlib.pyplot as plt
import glob
from matplotlib.image import imread
import numpy as np

X = []
for filename in glob.glob("four_dataset/*.jpg"):
    img = imread(filename)
    X.append(img.flatten('F'))

# Image is stored in "four_dataset"
X = np.array(X)
X = pd.DataFrame(X)

# Normalize data by subtracting mean and scaling
X_norm = normalize(X)

# Set pca to find principal components that explain 99%
# of the variation in the data
n_comp = 2
pca = PCA(n_components=n_comp)
# pca = Pca(X_norm)

# Run PCA on normalized image data
lower_dimension_data = pca.fit_transform(X_norm)

# Project lower dimension data onto original features
approximation = pca.inverse_transform(lower_dimension_data)

# Reshape approximation and X_norm to 5000x32x32 to display images
approximation = approximation.reshape(-1, 28, 28)
X_norm = X_norm.reshape(-1, 28, 28)

# Rotate pictures
for i in range(0,X_norm.shape[0]):
    X_norm[i, ] = X_norm[i, ].T
    approximation[i, ] = approximation[i, ].T

# Display the image
# f, (ax1, ax2) = plt.subplots(1, 2, sharey=True)
f, (ax1, ax2) = plt.subplots(1, 2)
f.suptitle('PCA comparison with #Dim = %s' % n_comp)
ax1.imshow(X_norm[0, ], cmap='gray')
ax1.set_title('Original Image')
ax2.imshow(approximation[0, ], cmap='gray')
ax2.set_title('Compressed Image')
plt.show()


import numpy as np
from sklearn.preprocessing import LabelBinarizer

class GaussianNB(object):
    """
    朴素贝叶斯分类器，适用于连续型数据。
    """

    @staticmethod
    def gaussfunc(x, mu, singma):
        """高斯函数
        :param x: 数据集
        :param mu: 均值
        :param singma: 方差
        :return:
        """
        sqsingma = singma @ singma
        numerator = -np.exp(np.sum((x - mu) ** 2, axis=1) / (2 * sqsingma))
        return numerator / np.sqrt(2 * np.pi * sqsingma)

    def fit(self, X, y):
        """
        :param X_: shape = [n_samples, n_features]
        :param y: shape = [n_samples]
        :return: self
        """
        self.classes, self.classes_count = np.unique(y, return_counts=True)
        self.mean = np.zeros((self.classes_count.shape[0],
                              X.shape[1]), dtype=np.float64)
        self.var = np.zeros((self.classes_count.shape[0],
                             X.shape[1]), dtype=np.float64)
        for i, label in enumerate(self.classes):
            x_i = X[y == label]
            self.mean[i, :] = np.mean(x_i, axis=0)
            self.var[i, :] = np.var(x_i, axis=0)

        return self

    def predict(self, X):
        """
        :param X: shape = [n_samples, n_features]
        :return: shape = [n_samples]
        """
        likelihood = []
        for i in range(self.classes.shape[0]):
            likelihood.append(self.classes_count[i] *
                              GaussianNB.gaussfunc(X, self.mean[i, :],
                                                   self.var[i, :]))
        likelihood = np.array(likelihood).T
        return np.argmax(likelihood, axis=1)

class MultinomialNB(object):
    """
    朴素贝叶斯分类器，适用于离散型数据。
    """

    def __init__(self, alpha=1.0):
        self.alpha = alpha

    def fit(self, X, y):
        """
        :param X_: shape = [n_samples, n_features]
        :param y: shape = [n_samples]
        :return: self
        """
        labelbin = LabelBinarizer()
        Y = labelbin.fit_transform(y)
        self.classes = labelbin.classes_
        self.class_count = np.zeros(Y.shape[1], dtype=np.float64)
        self.feature_count = np.zeros((Y.shape[1], X.shape[1]),
                                      dtype=np.float64)

        self.feature_count += Y.T @ X
        self.class_count += Y.sum(axis=0)
        smoothed_fc = self.feature_count + self.alpha
        smoothed_cc = smoothed_fc.sum(axis=1)

        self.feature_log_prob = (np.log(smoothed_fc) -
                                 np.log(smoothed_cc.reshape(-1, 1)))

    def predict(self, X):
        """
        :param X: shape = [n_samples, n_features]
        :return: shape = [n_samples]
        """
        likelihood = X @ self.feature_log_prob.T
        return np.argmax(likelihood, axis=1)


import numpy as np
from prml.linear.regression import Regression

class RidgeRegression(Regression):
    """
    Ridge regression model

    w* = argmin |t - X @ w| + alpha * |w|_2^2
    """

    def __init__(self, alpha:float=1.):
        self.alpha = alpha

    def fit(self, X:np.ndarray, t:np.ndarray):
        """
        maximum a posteriori estimation of parameter

        Parameters
        ----------
        X : (N, D) np.ndarray
            training data independent variable
        t : (N,) np.ndarray
            training data dependent variable
        """

        eye = np.eye(np.size(X, 1))
        self.w = np.linalg.solve(self.alpha * eye + X.T @ X, X.T @ t)

    def predict(self, X:np.ndarray):
        """
        make prediction given input

        Parameters
        ----------
        X : (N, D) np.ndarray
            samples to predict their output

        Returns
        -------
        (N,) np.ndarray
            prediction of each input
        """
        return X @ self.w


import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
from scipy.ndimage.filters import convolve

img = cv.imread('img.png')#read Image
img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
img_RGB =  cv.cvtColor(img, cv.COLOR_BGR2RGB)
plt.imshow(img_RGB)
plt.title("Original IMG")
plt.show()

#Smoothing using Gaussian fillter for better output
gaussian = cv.GaussianBlur(img_gray,(3,3),0)

plt.imshow(gaussian,cmap='gray')
plt.title("Gaussian IMG")
plt.show()

# Sobel Edge Detector
sobelx = cv.Sobel(gaussian,cv.CV_8U,1,0,ksize=5)
sobely = cv.Sobel(gaussian,cv.CV_8U,0,1,ksize=5)
sobel = sobelx + sobely

plt.imshow(sobel,cmap='gray')
plt.title("Sobel IMG")
plt.show()

# Prewitt Gradient Operator
x_kernel = np.array([[1,1,1],[0,0,0],[-1,-1,-1]])
y_kernel = np.array([[-1,0,1],[-1,0,1],[-1,0,1]])
prewitt_x = cv.filter2D(gaussian, -1, x_kernel)
prewitt_y = cv.filter2D(gaussian, -1, y_kernel)
prewitt = prewitt_x + prewitt_y

plt.imshow(prewitt,cmap='gray')
plt.title("Prewitt IMG")
plt.show()

# Laplacian of Gaussian
laplacian = cv.Laplacian(gaussian,cv.CV_64F)
plt.imshow(laplacian,cmap='binary')
plt.title("Laplacian IMG")
plt.show()
#Canny simple
canny = cv.Canny(gaussian,100,100)
plt.imshow(canny,cmap='gray')
plt.title("Canny IMG")
plt.show()

#canny with multi-step algorithm
#1.Noise reduction
def gaussian_kernel(size, sigma=1):
    size = int(size) // 2
    x, y = np.mgrid[-size:size+1, -size:size+1]
    normal = 1 / (2.0 * np.pi * sigma**2)
    g =  np.exp(-((x**2 + y**2) / (2.0*sigma**2))) * normal
    return g

gaussian_img = convolve(img_gray,gaussian_kernel(5))
plt.imshow(gaussian_img,cmap='gray')
plt.title("1.Noise reduction")
plt.show()
#2.Gradient calculation
def sobel_filters(img):
    Kx = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]], np.float32)
    Ky = np.array([[1, 2, 1], [0, 0, 0], [-1, -2, -1]], np.float32)
    Ix = convolve(img, Kx)
    Iy = convolve(img, Ky)
    G = np.hypot(Ix, Iy)
    G = G / G.max() * 255
    theta = np.arctan2(Iy, Ix)
    img=Ix+Iy
    return (img, theta)

sobel_img,theta=sobel_filters(gaussian_img)
plt.imshow(sobel_img,cmap='gray')
plt.title("2.Gradient calculation")
plt.show()
#3.Non-maximum suppression
def non_max_suppression(img, D):
    M, N = img.shape
    Z = np.zeros((M,N), dtype=np.int32)
    angle = D * 180. / np.pi
    angle[angle < 0] += 180
    for i in range(1,M-1):
        for j in range(1,N-1):
            try:
                q = 255
                r = 255
               #angle 0
                if (0 <= angle[i,j] < 22.5) or (157.5 <= angle[i,j] <= 180):
                    q = img[i, j+1]
                    r = img[i, j-1]
                #angle 45
                elif (22.5 <= angle[i,j] < 67.5):
                    q = img[i+1, j-1]
                    r = img[i-1, j+1]
                #angle 90
                elif (67.5 <= angle[i,j] < 112.5):
                    q = img[i+1, j]
                    r = img[i-1, j]
                #angle 135
                elif (112.5 <= angle[i,j] < 157.5):
                    q = img[i-1, j-1]
                    r = img[i+1, j+1]
                if (img[i,j] >= q) and (img[i,j] >= r):
                    Z[i,j] = img[i,j]
                else:
                    Z[i,j] = 0
            except IndexError:
                pass
    return Z
non_max_img = non_max_suppression(sobel_img,theta)
plt.imshow(non_max_img,cmap='gray')
plt.title("3.Non-maximum suppression")
plt.show()
#4.Double threshold
def threshold(img, lowThresholdRatio=0.05, highThresholdRatio=0.09):
    highThreshold = img.max() * highThresholdRatio;
    lowThreshold = highThreshold * lowThresholdRatio;
    M, N = img.shape
    res = np.zeros((M,N), dtype=np.int32)
    weak = np.int32(25)
    strong = np.int32(255)
    strong_i, strong_j = np.where(img >= highThreshold)
    zeros_i, zeros_j = np.where(img < lowThreshold)
    weak_i, weak_j = np.where((img <= highThreshold) & (img >= lowThreshold))
    res[strong_i, strong_j] = strong
    res[weak_i, weak_j] = weak
    return (res, weak, strong)
d_threshold_img,weak,strong = threshold(sobel_img,0.07,0.09)
plt.imshow(d_threshold_img,cmap='gray')
plt.title("4.Double threshold")
plt.show()
#5.Edge Tracking by Hysteresis
def hysteresis(img, weak, strong=255):
    M, N = img.shape
    for i in range(1, M-1):
        for j in range(1, N-1):
            if (img[i,j] == weak):
                try:
                    if ((img[i+1, j-1] == strong) or (img[i+1, j] == strong) or (img[i+1, j+1] == strong)
                        or (img[i, j-1] == strong) or (img[i, j+1] == strong)
                        or (img[i-1, j-1] == strong) or (img[i-1, j] == strong) or (img[i-1, j+1] == strong)):
                        img[i, j] = strong
                    else:
                        img[i, j] = 0
                except IndexError:
                    pass
    return img
hys_img = hysteresis(d_threshold_img,weak,strong)
plt.imshow(hys_img,cmap='gray')
plt.title("5.Edge Tracking by Hysteresis")
plt.show()


import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import Axes3D

# 简单方法画出漂亮的圆柱体（半径和高度均为1）

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

# 生成圆柱数据，底面半径为r，高度为h。
# 先根据极坐标方式生成数据
u = np.linspace(0,2*np.pi,50)  # 把圆分按角度为50等分
h = np.linspace(0,5,20)        # 把高度1均分为20份
x = np.outer(np.sin(u),np.ones(len(h)))  # x值重复20次
y = np.outer(np.cos(u),np.ones(len(h)))  # y值重复20次
z = np.outer(np.ones(len(u)),h)   # x，y 对应的高度

# Plot the surface
ax.plot_surface(x, y, z, cmap=plt.get_cmap('rainbow'))

plt.show()


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

def get_parallel_L_C(C_v_min, C_v_max, C_s, f_min, f_max):

    # Circuit schematic
    schematic = """
     -----------------------
     |       |        |
    C        {0} C_v    |
    C  L     |        = C_p
    C        |        |
    C        = C_s    |
     |       |        |
     -----------------------
     |
     _
     -
    """.format(u"\u2260")

    # Variable capacitance range
    C_v_t_min = (1/C_s+1/C_v_min)**-1
    C_v_t_max = (1/C_s+1/C_v_max)**-1

    # Calculate required parallel capacitance and inductance
    C_v = C_v_t_max-C_v_t_min
    r = (f_min/f_max)**2
    C_f = r/(1-r)*C_v
    C_p = C_f-C_v_t_min
    L = 1/(4*np.pi**2*f_min**2*(C_v_t_max+C_p))

    return C_p, L, schematic

class ProgressiveVFO:
    """VFO from Hayward and Lawson "A Progressive Communications Receiver" QST 1981-11."""

    def __init__(self, L, C1, Cv, f_min, f_max):

        # Store params
        self.L = L
        self.C1 = C1
        self.Cv = Cv

        # Circuit schematic
        self.schematic = """
         ---------------------------
         |       |        |
        C        = C3  C1 =
        C  L     |        |_____
        C        |        |    |
        C        |     C2 =    {0} Cv
         |       |        |    |
         ---------------------------
         |
         _
         -
        """.format(u"\u2260")

        # Calculate required capacitances
        wl = 2.0*np.pi*f_min
        wu = 2.0*np.pi*f_max
        Cl = 1.0/(wl**2*L)
        Cu = 1.0/(wu**2*L)
        dC = Cl-Cu
        x = 2.0*C1+Cv
        y = C1*Cv+C1**2*(1-Cv/dC)
        self.C2 = 0.5*(-x+np.sqrt(x**2-4*y))
        self.C3 = Cv-1.0/(1.0/C1+1.0/self.C2)

    def plot_tuning(self):
        """Plots the tuning range of the VFO over a linear variation in Cv."""

        # Calculate frequencies
        Cv_range = np.linspace(0.0, self.Cv, 1000)
        C = self.C3+1.0/(1.0/self.C1+1.0/(self.C2+Cv_range))
        f = 1.0/(2.0*np.pi*np.sqrt(self.L*C))

        # PLot
        plt.figure()
        plt.plot(Cv_range*1e12, f*1e6)
        plt.xlabel('$C_v$ [pF]')
        plt.ylabel('f [MHz]')
        plt.show()

if __name__=="__main__":

    vfo = ProgressiveVFO(0.784e-6, 15.0e-12, 145.0e-12, 14.225e6, 14.350e6)
    vfo.plot_tuning()

    ## Get parameters
    #f_min = float(input("Minimum desired frequency in MHz?"))*1e6
    #f_max = float(input("Maximum desired frequency in MHz?"))*1e6
    #C_v_min = float(input("Minimum variable capacitance in pF?"))*1e-12
    #C_v_max = float(input("Maximum variable capacitance in pF?"))*1e-12

    ## Series capacitances
    #C_s = np.logspace(-12, -10, 10)

    #plt.figure()
    #print("{0:<20}{1:<20}{2:<20}".format("Series C [pF]", "Parallel C [pF]", "Parallel L [uH]"))
    #print("".join(["-"]*60))

    #for i in range(10):
    #    C_p, L, schematic = get_parallel_L_C(C_v_min, C_v_max, C_s[i], f_min, f_max)
    #    print("{0:<20}{1:<20}{2:<20}".format(C_s[i]*1e12, C_p*1e12, L*1e6))

    #    # Get frequency over range of C_v
    #    C_v = np.linspace(C_v_min, C_v_max, 1000)
    #    C = (1/C_s[i]+1/C_v)**-1+C_p
    #    f = 1/(2.0*m.pi*np.sqrt(L*C))
    #    plt.plot(C_v*1e12, f*0.000001, label=str(C_s[i]*1e12))

    #print(schematic)
    #plt.legend(title="Series C [pF]")
    #plt.xlabel('Variable Capacitance [pF]')
    #plt.ylabel('Frequency [MHz]')
    #plt.show()


import math
import numpy as np

def reduce_dimension(x, Orig, method='Mean'):
    '''
    ## Reduce the dimension of the metrix
    ### Parameters
        x: the input ndarray.
        method: choose the method used to reduce dimension of x.
    ### Method:\n
        1. Mean: Get the abs value of a row, and use the mean of the row to replace the row.
        2. Euclidean:
        3. Manhattan:
        4. Chebyshev:
        5. Cosine:
    '''
    ny, nx = x.shape
    Xrd = abs(x)
    if method == 'Mean':
        Xrd = abs(Orig-x)
        Xrd = Xrd.sum(axis=1)

    elif method == 'Euclidean':
        Xrd = [np.linalg.norm(Orig[t]-x[t]) for t in range(ny)]

    elif method == 'Manhattan':
        Xrd = [np.linalg.norm(Orig[t]-x[t], ord=1) for t in range(ny)]

    elif method == 'Chebyshev':
        Xrd = [np.linalg.norm(Orig[t]-x[t], ord=np.inf) for t in range(ny)]

    elif method == 'Cosine':
        Xrd = [np.dot(Orig[t], x[t])/(np.linalg.norm(Orig[t]) *
                                      (np.linalg.norm(x[t]))) for t in range(ny)]

    return Xrd

# from sklearn.preprocessing import normalize

def norm(x, method='linear'):
    '''
    ## Normalized
    ### Parameters
        x: the input vector
        method: ...
    ### Methods:
        1. linear
        2. z-score
        3. atan
        4. sigmod
        5. tanh
    '''
    if method == 'linear':
        maxVal = max(x)
        minVal = min(x)
        dis = maxVal - minVal
        x = (x-minVal)/dis

    elif method == 'z-score':
        x = (x-np.mean(x))/np.std(x)

    elif method == 'atan':
        y = [math.atan(t)*2/math.pi for t in x]
        x = y

    elif method == 'sigmod':
        x = [1 / (1 + np.exp(-t)) for t in x]

    elif method == 'tanh':
        s1 = [np.exp(t) - np.exp(-t) for t in x]
        s2 = [np.exp(t) + np.exp(-t) for t in x]
        x = [s1[t] / s2[t] for t in range(len(s1))]

    return x


import numpy as np
import cmath

def directFT(x):
    # direct discrete Fourier transform
    N = x.size
    n = np.arange(N)
    k = n.reshape((N, 1))
    M = np.exp(-2j * np.pi * k * n / N)
    return np.dot(M, x)

def recursiveFT(x):
    # recursive FFT
    N = x.size

    if N % 2 > 0:
        raise ValueError("size of x must be a power of 2")
    elif N <= 2:
        return directFT(x)
    else:
        X_even = recursiveFT(x[::2])
        X_odd = recursiveFT(x[1::2])
        factor = np.exp(-2j * np.pi * np.arange(N) / N)
        return np.concatenate([X_even + factor[:N / 2] * X_odd,
                               X_even + factor[N / 2:] * X_odd])

def bitreversalFT(vector, inverse=False):
    # Returns the integer whose value is the reverse of the lowest 'bits' bits of the integer 'x'.
    def reverse(x, bits):
        y = 0
        for i in range(bits):
            y = (y << 1) | (x & 1)
            x >>= 1
        return y

    # Initialization
    n = len(vector)
    levels = 0
    while True:
        if 1 << levels == n:
            break
        elif 1 << levels > n:
            raise ValueError("Length is not a power of 2")
        else:
            levels += 1
    # Now, levels = log2(n)
    exptable = [cmath.exp((2j if inverse else -2j) * cmath.pi * i / n) for i in range(n // 2)]
    vector = [vector[reverse(i, levels)] for i in range(n)]  # Copy with bit-reversed permutation

    # Radix-2 decimation-in-time FFT
    size = 2
    while size <= n:
        halfsize = size // 2
        tablestep = n // size
        for i in range(0, n, size):
            k = 0
            for j in range(i, i + halfsize):
                temp = vector[j + halfsize] * exptable[k]
                vector[j + halfsize] = vector[j] - temp
                vector[j] += temp
                k += tablestep
        size *= 2
    return vector


import matplotlib as mpl
import matplotlib.pyplot as plt
import time

from itertools import product
from IPython.display import display
from util import displayBoard
from z3 import *
from itertools import chain

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

from sympy import *

runtimes = []
solutions = []
sizes = [8, 16, 32, 64]

def Abs(x):
    return If(x >= 0, x, -x)

def nqueens(N):
    nq_solver = Solver()
    queens = [Int('Q{}'.format(i)) for i in range(N)]
    nq_solver.add(*chain(*[(0 <= q, q < N)
                           for q in queens]))  # valid range constraint
    nq_solver.add(Distinct(queens))  # different row constraint
    for i, q1 in enumerate(queens):
        for j, q2 in enumerate(queens):
            if i == j:
                continue
            # different diagonal constraint
            nq_solver.add(Abs(q1 - q2) != abs(i - j))
    return nq_solver

for N in sizes:
    nq_solver = nqueens(N)
    start = time.perf_counter()
    assert nq_solver.check(
    ), "Uh oh...The solver failed to find a solution. Check your constraints."
    end = time.perf_counter()
    print("{}-queens: {}ms".format(N, (end-start) * 1000))
    runtimes.append((end - start) * 1000)
    solutions.append(nq_solver.model())

plt.plot(sizes, runtimes)

s = solutions[0]
displayBoard([(int(str(v)[1:]), s[v].as_long()) for v in s], len(s))


import numpy as np
from scipy import polyfit

from podostemaceous.constants import kB
from podostemaceous.molecule import VHSMolecule

def reference_diameter_VHS(mass, eta_ref, T_ref, omega):
    """
    Calculates the reference diameter for a VHS model so that
        eta(T_ref) == eta_ref

    Parameters:
        mass: in kilograms
        eta_ref: viscosity at the reference temperature, in Pa s
        T_ref: reference temperature, in Kelvin
        omega: Unitless. Previous best fit to viscosity: eta ~ T^{omega}

    Returns:
        Reference diameter in meters.

    Reference:
        Bird, G. A., The DSMC Method, Version 1.2, 2013.
        Chapter 2, Equation (43)
    Formula:
        Note that the reference uses $\mu$ for viscosity; here we use $\eta$.
        $$d_{\mathrm{ref}} = \left(\frac{15(m k_B T_{\mathrm{ref}} / \pi)^{1/2}}{2 (5 - 2 \omega)(7 - 2 \omega) \eta_{\mathrm{ref}}}\right)^{1/2}$$

    """
    numerator = 15 * (mass * kB * T_ref / np.pi) ** (1 / 2)
    denominator = 2 * (5 - 2 * omega) * (7 - 2 * omega) * eta_ref
    d_ref = (numerator / denominator) ** (1 / 2)
    return d_ref

def VHS_model_from_viscosity(visc_func, mass, T_min, T_max, T_ref=0):
    """
    Calculates the reference diameter for a VHS model.

    Parameters:
        visc_func: a function of one variable, temperature in Kelvin,
            returns viscosity at that temperature in Pa s.
        mass: mass of the particle, in kilograms
        T_min, T_max: range over which to perform the fit.
        T_ref: reference temperature, in Kelvin.
            Default is the mean of T_min and T_max.
    Returns:
        A dictionary:
            mass : in kilograms
            T_ref: in Kelvin
            omega: Unitless. Best fit to viscosity: eta ~ T^{omega}
            d_ref: Best fit for the reference diameter, in meters.
                Note that because this is a best fit, the fit VHS model's
                viscosity at the reference temperature is likely different
                from that of the supplied viscosity function.
    """
    NUM_POINTS_TO_FIT_OVER = 100 # bignum
    if T_ref == 0:
        T_ref = (T_min + T_max) / 2
    TK = np.linspace(T_min, T_max, NUM_POINTS_TO_FIT_OVER)
    eta = visc_func(TK)
    # fit in log - logspace
    x = np.log(TK)
    y = np.log(eta)
    omega, b = polyfit(x, y, deg=1)

    # Calculate d_ref
    A_best = np.exp(b) # eta = A_best T^omega
    numerator = 15 * np.sqrt(mass * kB * T_ref / np.pi)
    denominator = 2 * A_best * (7 - 2 * omega) * (5 - 2 * omega) * T_ref ** omega
    d_best = (numerator / denominator)**(1/2)
    return VHSMolecule('best_fit', mass, d_best, omega=omega, t_ref=T_ref)


import math
import numpy as np
from tabulate import tabulate
from numpy.linalg import solve

#Вычисленный u(x) при z(s)=1
def u(x):
    return (math.exp(3*x) - 1) / 3 * x

#Ядро:
def K(x, s):
    return math.exp(3 * x * s)

#Вычисление узлов для формулы средних прямоугольников:
def build_nodes(N):
    result = []
    for i in range(N):
        result.append(i/N+1/(2*N))
    return result

#Построение матрицы С:
def build_matrix(N):
    result = np.zeros((N,N))
    nodes = build_nodes(N)
    for i in range(N):
        for j in range(N):
            result[i,j] = K(nodes[i],nodes[j])*(1/N)
    return result

#Построение значений u(x) в узлах квадратурной формулы средних прямоугольников при z(s)=1
def build_right_part(N):
    result = np.zeros((N, 1))
    nodes = build_nodes(N)
    for i in range(N):
            result[i] = u(nodes[i])
    return result

#Решение СЛАУ методом регуляризации матрицы c параметром а:
def build_solution(N, a):
    C = build_matrix(N)
    right = build_right_part(N)
    return solve(np.transpose(C)@C+a*np.identity(N), np.transpose(C)@right)

#Рассмотрим погрешность при различных параметрах N (число узлов) и a (параметр метода регуляризации) и запишем результат в файл в виде таблицы.
N = 10
ans = np.zeros((N,N+1))
for i in range(1,N+1):
    ans[i-1,0] = i*10
headers = [r'n\a']
for j in range(6,6+N):
    headers.append(10**(-j))
residuals = []
for i in range(1,1+N):
    u0 = np.ones((N*i,1))
    residuals.append(N*i)
    for j in range(6,6+N):
        solution = build_solution(N*i,10**(-j))
        ans[i-1,j-5] = abs(u0-solution).max()

res_table = tabulate(ans,headers=headers,
               tablefmt='github',numalign="right")
with open('result3.txt','w') as file:
    file.write(res_table)

#Посмотрим на индекс минимального элемента таблицы. Таким образом, наименьшая погрешность достигается при числе узлов N=100 и параметре а=1e-10, и именно они являются оптимальными параметрами для данной задачи.
ind = np.unravel_index(np.argmin(ans, axis=None), ans.shape)
ind
#Сама погрешность:

ans[ind]


import numpy as np

# TODO: Mapping features from linear to polynomial regressions
# Return: New X with "p" polynomial features
# X_poly[i, :] = [X[i], X[i]**2, X[i]**3, ..., X[i]**p]
def poly_features(X, p):      # X: (m,)
    # Initialize parameter
    X_poly = np.zeros((X.shape[0], p))

    # Mapping features
    for i in range(p):
        X_poly[:, i] = X**(i+1)
    return X_poly  # (m, p)

# TODO: Normalize the features in X using Z-score normalization
# Return: Normalize version of X where mean of feature = 0; std of features = 1
def feature_normalize(X):  # X: (m, p)
    # Compute norm, mean, std of each feature in array
    norm = np.linalg.norm(X, axis=0)
    mean = np.mean(X, axis=0)
    std = np.std(X, axis=0, ddof=1)

    # Normalize feature of X
    # X_normal = X / norm
    X_normal = (X-mean) / std
    return X_normal, mean, std


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

import numpy as np

class MinHeap(object):

    def __init__(self, sz, data):
        self.cnt = len(data)
        self.sz = sz
        self.heap = [0] * (self.sz + 1)
        self.heap[1 : self.cnt + 1] = data
        self.build_heap()

    def build_heap(self):
        last_non_leaf = self.cnt // 2
        for i in range(last_non_leaf, 0, -1):
            self.heaplify(self.heap, self.cnt, i)

    def heaplify(self, heap, sz, ind):
        while True:
            max_pos = ind
            if (2 * ind <= sz) and heap[ind] > heap[2 * ind]:
                max_pos = 2 * ind
            if ((2 * ind + 1) <= sz) and heap[max_pos] > heap[2 * ind + 1]:
                max_pos = 2 * ind + 1
            if max_pos == ind:
                break
            heap[max_pos], heap[ind] = heap[ind], heap[max_pos]
            ind = max_pos

    def remove_min(self):
        if self.cnt == 0:
            return -1
        self.heap[1] = self.heap[self.cnt]
        self.heap[self.cnt] = 0
        self.cnt -= 1
        self.heaplify(self.heap, self.cnt, 1)

    def insert(self, item):
        if self.cnt >= self.sz:
            return False
        self.cnt += 1
        self.heap[self.cnt] = item
        i = self.cnt
        while True:
            if i // 2 > 0 and self.heap[i] < self.heap[i // 2]:
                self.heap[i // 2], self.heap[i] = self.heap[i], self.heap[i // 2]
                i = i // 2
            else:
                return

class MaxHeap(object):

    def __init__(self, sz, data):
        self.sz  = sz
        self.heap = [0] * (sz + 1)
        self.cnt = len(data)
        self.heap[1: self.cnt + 1] = data
        self.build_heap()

    def build_heap(self):
        last_non_leaf = self.cnt // 2
        for i in range(last_non_leaf, 0, -1):
            self.heapify(self.heap, self.cnt, i)

    def heapify(self, heap, sz, ele_ind):
        while True:
            max_pos = ele_ind
            if (2 * ele_ind <= sz) and heap[ele_ind] < heap[2 * ele_ind]:
                max_pos = 2 * ele_ind
            if (2 * ele_ind + 1) <= sz and heap[max_pos] < heap[2 * ele_ind + 1]:
                max_pos = 2 * ele_ind + 1
            if max_pos == ele_ind:
                break
            heap[ele_ind], heap[max_pos] = heap[max_pos], heap[ele_ind]
            ele_ind = max_pos

    def remove_max(self):
        if self.cnt == 0 :
            return  - 1
        self.heap[1] = self.heap[self.cnt]
        self.heap[self.cnt] = 0
        self.cnt -= 1
        self.heapify(self.heap, self.cnt, 1)
        return 0

    def insert(self, item):
        if self.cnt >= self.sz:
            return False
        self.cnt += 1
        self.heap[self.cnt] = item
        i = self.cnt
        while True:
            if i // 2 > 0 and self.heap[i] > self.heap[i // 2]:
                self.heap[i], self.heap[i // 2] = self.heap[i // 2] , self.heap[i]
                i = i // 2
            else:
                return True

if __name__ == '__main__':
    data = np.random.randint(0, 100, 10)
    print(data)
    print('-' * 20 + 'test max heap' + '-' * 20)
    maxheap = MaxHeap(20, data)
    print(maxheap.heap)
    maxheap.remove_max()
    print(maxheap.heap)
    maxheap.insert(1000)
    print(maxheap.heap)
    print('-' * 20 + 'test min heap' + '-' * 20)
    minheap = MinHeap(20, data)
    print(minheap.heap)
    minheap.remove_min()
    print(minheap.heap)
    minheap.insert(1000)
    print(minheap.heap)


import numpy as np
import scipy as sp
import pandas as pd

def test_sctrs():
    print('# test_sctrs')
    return

def compute_div(v_x, v_y, opt='kl', n_bin=100):
    """compute divergence between two sets of samples

    Args:
        v_x (list or np.adarray): sample 1
        v_y (list or np.adarray): sample 2
        opt (str): divergence measure, one of ['kl', 'l1']
        n_bin=100 (int): number of bins

    Returns:
        div: divergence between two sets of samples
    """

    if opt not in ['kl', 'l1']:
        raise ValueError('# compute_divergence: %s not in [kl, l1]'%opt)

    v_x = np.array(v_x, dtype=float)
    v_y = np.array(v_y, dtype=float)

    min_ = min(v_x.min(), v_y.min())
    max_ = max(v_x.max(), v_y.max())
    bins_ = np.linspace(min_, max_, n_bin+1)

    p_x = np.histogram(v_x, bins=bins_)[0]+1/(bins_.shape[0]-1)
    p_y = np.histogram(v_y, bins=bins_)[0]+1/(bins_.shape[0]-1)
    p_x = p_x/p_x.sum()
    p_y = p_y/p_y.sum()

    if opt=='kl':
        div = (p_x*np.log(p_x/p_y)).sum()

    if opt=='l1':
        div = np.absolute(p_x-p_y).sum()

    return div


import numpy as np
from numpy import genfromtxt

def load_dataset(fname):
    # loads a dataset assuming the last column is the labels
    # return the number of labels, the number of feats and the labels as strings
    my_data = genfromtxt(fname, delimiter=',', dtype='S', encoding=None)
    num_feats = my_data.shape[1]-1
    my_data_feats = np.array(my_data[:, :num_feats], dtype='float32')
    labels, indexed_data = np.unique(my_data[:, -1], return_inverse=True)
    my_data_labels = np.array(indexed_data)
    dataset = np.hstack((my_data_feats, my_data_labels[:, None]))
    return [dataset, labels.shape[0], my_data_feats.shape[1], labels]

def transform_labels(dataset, lb):
    # in order to transform the problem to a binary classification
    # change lb label to 1 and all others to -1
    # return a copy of the original dataset with the transformed labels
    t_dataset = dataset.copy()
    transf = np.where(t_dataset[:, -1].astype(int) == lb, 1, -1)
    t_dataset[:, -1] = transf[:]
    return t_dataset

def perceptron_classifier(patterns, targets, lr):
    # implementation of a single neuron perceptron as described in the lecture notes
    # try to converge for 10k epochs
    # return the weights and bias(w0) after 10k epochs or if converged
    epochs = 10000
    aug_patterns = np.hstack((patterns, -1 * np.ones((patterns.shape[0], 1))))
    t_aug_patterns = aug_patterns[:, :] * targets[:, None]
    weights = np.zeros(t_aug_patterns.shape[1])
    num_patterns = patterns.shape[0]
    for j in range(0, epochs):
        errors = 0
        for i in range(t_aug_patterns.shape[0]):
            y = np.dot(weights, t_aug_patterns[i, :])
            if y <= 0:
                errors += 1
                weights = weights + (lr * t_aug_patterns[i, :])
        acc = (num_patterns - errors) / num_patterns
        if errors == 0:
            print("Converged at epoch %d" % (j + 1))
            break
        if errors != 0 and j >= epochs -1:
            print("Not converged")
    print('Epoch %d: Accuracy = %f, errors = %d' % (j + 1, acc, errors))
    return weights[:-1], weights[-1]

# load dataset
dataset, num_classes, num_feats, labels_str = load_dataset('iris.data')

# with lb we choose which class is 1 and all others are considered -1
# for example if lb = 0 Iris-setosa will be labeled with 1 and the others with -1
lb_list = [0, 1, 2]
lr = 1
for lb in lb_list:
    print('For labeling-> %s = 1 Others = -1' % (labels_str[lb].decode("utf-8")))
    tr_dataset = transform_labels(dataset, lb)

    data = tr_dataset[:, :num_feats]
    targets = tr_dataset[:, -1].astype(int)

    w, bias = perceptron_classifier(data, targets, lr)
    print('weights: ', w)
    print('bias(w0): ', bias)