from math import sqrt, floor
from numpy import var
from scipy.stats import ttest_ind
from statsmodels.stats.power import tt_ind_solve_power
from oeda.log import warn, error
from oeda.analysis import Analysis
from numpy import mean

class TwoSampleTest(Analysis):

    def run(self, data, knobs):

        if len(data) < 2:
            error("Cannot run " + self.name + " on less than two samples.")
            return False

        if len(data) > 2:
            warn("Cannot run " + self.name + " on more than two samples.")
            warn("Comparing only the first two samples.")

        self.y1 = [d for d in data[0]]
        self.y2 = [d for d in data[1]]

        return True

class Ttest(TwoSampleTest):

    name = "t-test"

    def __init__(self, stage_ids, y_key, alpha=0.05):
        super(self.__class__, self).__init__(stage_ids, y_key)
        self.alpha = alpha

    def run(self, data, knobs):

        if not super(self.__class__, self).run(data, knobs):
            error("Aborting analysis.")
            return

        statistic, pvalue = ttest_ind(self.y1, self.y2, equal_var=False)

        different_averages = bool(pvalue <= self.alpha)

        result = dict()
        result["statistic"] = statistic
        result["pvalue"] = pvalue
        result["alpha"] = self.alpha
        result["different_averages"] = different_averages

        result["mean_diff"] = mean(self.y1) - mean(self.y2)

        # calculating cohen's d effect size ():
        pooled_std = sqrt((var(self.y1) + var(self.y2)) / 2)
        result["effect_size"] = result["mean_diff"] / pooled_std

        return result

class TtestPower(TwoSampleTest):

    name = "t-test-power"

    def __init__(self, stage_ids, y_key, effect_size, alpha=0.05, alternative='two-sided'):
        super(self.__class__, self).__init__(stage_ids, y_key)
        self.alpha = alpha
        self.effect_size = effect_size
        self.alternative = alternative

    def run(self, data, knobs):

        if not super(self.__class__, self).run(data, knobs):
            error("Aborting analysis.")
            return

        # pooled_std = sqrt((var(self.y1) + var(self.y2)) / 2)
        # effect_size = self.mean_diff / pooled_std

        sample_size = len(self.y1)

        power = tt_ind_solve_power(effect_size=self.effect_size, nobs1=sample_size, alpha=self.alpha, alternative=self.alternative)

        result = dict()
        result["effect_size"] = self.effect_size
        result["sample_size"] = sample_size
        result["alpha"] = self.alpha
        result["power"] = power
        # result["mean_diff"] = self.mean_diff
        # result["pooled_std"] = pooled_std

        return result

class TtestSampleSizeEstimation(TwoSampleTest):

    name = "t-test-sample-estimation"

    def __init__(self, stage_ids, y_key, effect_size, mean_diff, alpha=0.05, power=0.8, alternative='two-sided'):
        super(self.__class__, self).__init__(stage_ids, y_key)
        self.alpha = alpha
        self.power = power
        self.effect_size = effect_size
        self.mean_diff = mean_diff
        self.alternative = alternative

    def run(self, data, knobs):

        if not super(self.__class__, self).run(data, knobs):
            error("Aborting analysis.")
            return

        if not self.effect_size:
            if not self.mean_diff:
                raise Exception("You cannot leave both mean_diff and effect_size paramaters empty")
            pooled_std = sqrt((var(self.y1) + var(self.y2)) / 2)
            effect_size = self.mean_diff / pooled_std
        else:
            effect_size = self.effect_size

        sample_size = tt_ind_solve_power(effect_size=effect_size, nobs1=None, alpha=self.alpha, power=self.power, alternative=self.alternative)

        result = dict()
        result["effect_size"] = effect_size
        result["sample_size"] = floor(sample_size)
        result["alpha"] = self.alpha
        result["power"] = self.power
        # result["mean_diff"] = self.mean_diff
        # result["pooled_std"] = pooled_std

        return result


"""a little helper module
"""
__author__ = "Reed Essick <reedessick@kicp.uchicago.edu>"

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

import numpy as np

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

DEFAULT_ALPHA = 0.1

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

def tukey(N, alpha=DEFAULT_ALPHA):
    """
    generate a tukey window
    The Tukey window, also known as the tapered cosine window, can be regarded as a cosine lobe of width \alpha * N / 2
        that is convolved with a rectangle window of width (1 - \alpha / 2). At \alpha = 1 it becomes rectangular, and
        at \alpha = 0 it becomes a Hann window.

    """
    # Special cases
    if alpha <= 0:
        return np.ones(N) #rectangular window
    elif alpha >= 1:
        return np.hanning(N)

    # Normal case
    x = np.linspace(0, 1, N)
    w = np.ones(x.shape)

    # first condition 0 <= x < alpha/2
    first_condition = x<alpha/2
    w[first_condition] = 0.5 * (1 + np.cos(2*np.pi/alpha * (x[first_condition] - alpha/2) ))

    # second condition already taken care of

    # third condition 1 - alpha / 2 <= x <= 1
    third_condition = x>=(1 - alpha/2)
    w[third_condition] = 0.5 * (1 + np.cos(2*np.pi/alpha * (x[third_condition] - 1 + alpha/2)))

    return w

def lowpass(f, fo, m):
    """return 1./(1+(f/fo)**m)
    """
    return 1./(1+(f/fo)**m)

def highpass(f, fo, m):
    """return (f/fo)**m/(1+(f/fo)**m)
    """
    return 1. - lowpass(f, fo, m)


"""Determine the bhattacharyya distance beetween the given dictionaries."""
import numpy as np
from math import sqrt
from typing import Dict
from .distances_utils import sort

def bhattacharyya_coefficient(a: Dict, b: Dict) -> float:
    """Determine the Bhattacharyya coefficient beetween the given dictionaries.

    Parameters
    ----------------------------
    a: Dict,
        First dictionary to consider.
    b: Dict,
        Second dictionary to consider.

    Returns
    ----------------------------
    Return the Bhattacharyya coefficient beetween the given dictionaries.
    """
    total = 0
    big, small = sort(a, b)
    big_get = big.__getitem__
    for k, small_value in small.items():
        try:
            total += sqrt(big_get(k) * small_value)
        except KeyError:
            pass
    return total

def bhattacharyya(a: Dict, b: Dict) -> float:
    """Determine the Bhattacharyya distance beetween the given dictionaries.

    Parameters
    ----------------------------
    a: Dict,
        First dictionary to consider.
    b: Dict,
        Second dictionary to consider.

    Returns
    ----------------------------
    Return the Bhattacharyya distance beetween the given dictionaries.
    """
    distance = -np.log(bhattacharyya_coefficient(a, b))
    if np.isinf(distance):
        return 0
    return distance


import numpy as np

data_path = "data/problem_03.txt"
# data_path = "data/problem_03_test.txt"

data = np.loadtxt(data_path, dtype="S")
input_width = len(data[0])
data = data.view(dtype=np.uint8).reshape(-1, input_width) == ord("1")

# part 1
exponents = 2 ** np.arange(data.shape[1])[::-1].reshape(-1, 1)
gamma = (np.sum(data, axis=0) > len(data) * 0.5).astype(np.int32)
power_consumption = ((1 - gamma) @ exponents) * (gamma @ exponents)
print(f"Part 1 solution: {power_consumption[0]}\n")

# part 2
o2_data = data[:]
co2_data = data[:]

o2_candidates = np.arange(len(data))
co2_candidates = np.arange(len(data))

for col_index in range(data.shape[1]):
    o2_gamma = int(np.sum(data[o2_candidates, col_index]) >= len(o2_candidates) * 0.5)
    co2_gamma = int(np.sum(data[co2_candidates, col_index]) < len(co2_candidates) * 0.5)

    o2_candidates = o2_candidates[data[o2_candidates, col_index] == o2_gamma]
    co2_candidates = co2_candidates[data[co2_candidates, col_index] == co2_gamma]

    if len(o2_candidates) == 1:
        o2_value = data[o2_candidates[0]]
    if len(co2_candidates) == 1:
        co2_value = data[co2_candidates[0]]

o2_value = (o2_value @ exponents)[0]
co2_value = (co2_value @ exponents)[0]
print(f"o2_value: {o2_value}")
print(f"co2_value: {co2_value}")
print(f"Part 2 solution: {o2_value * co2_value}\n")


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

def combinedValue(a, b, p, q):
    return (a-b)/(p-q)

def newtonDividedDifference(x, y, degree, val):
    table = []
    limit = degree+1
    for i in range(degree+1):
        temp = []
        for j in range(limit-i):
            #append in temp
            if i == 0:
                temp.append(y[j])
            else:
                temp.append(combinedValue(table[i-1][j+1], table[i-1][j], x[i+j], x[j]))

        table.append(temp)

    b = []
    for i in range(degree+1):
        b.append(table[i][0])

    prod = []
    prod.append(1)
    for i in range(1, degree+1):
        prod.append(prod[i-1]*(val-x[i-1]))

    ans = 0
    for i in range(0, degree+1):
        ans += prod[i]*b[i]

    return ans

m = []
time_mass = []
v = []
time_vel = []

massFile = open("mass.txt", "r")
myline = massFile.readline()
while myline:
    l = myline.split()
    time_mass.append(int(l[0]))
    m.append(float(l[1]))
    myline = massFile.readline()
massFile.close()

velFile = open("velocity.txt", "r")
myline = velFile.readline()
while myline:
    l = myline.split()
    time_vel.append(int(l[0]))
    v.append(float(l[1]))
    myline = velFile.readline()
velFile.close()

massAns = newtonDividedDifference(time_mass[4:9], m[4:9], 4, 25)
velAns = newtonDividedDifference(time_vel[4:9], v[4:9], 4, 25)

prevMass = newtonDividedDifference(time_mass[5:9], m[5:9], 3, 25)
prevVel = newtonDividedDifference(time_vel[5:9], v[5:9], 3, 25)

print('Mass(in metric ton unit) at t=25s:', massAns)
print('Velocity(in m/sec unit) at t=25s:', velAns)

relMass = abs(massAns - prevMass)/massAns
relVel = abs(velAns - prevVel)/velAns

print('Relative mass error:', relMass*100)
print('Relative velocity error:', relVel*100)

plt.subplot(1, 2, 1)
plt.scatter(time_mass, m, c='blue')
plt.plot(25, massAns, 'ro')
plt.title('Mass')
plt.xlabel('time')
plt.ylabel('mass')

plt.subplot(1, 2, 2)
plt.scatter(time_vel, v, c = 'green')
plt.plot(25, velAns, 'ro')
plt.title('Velocity')
plt.xlabel('time')
plt.ylabel('velocity')

plt.show()


"""Clock matrix."""
from cmath import exp, pi

import numpy as np

def clock(dim: int) -> np.ndarray:
    r"""
    Produce clock matrix [WikClock]_.

    Returns the clock matrix of dimension `dim` described in [WikClock]_. The
    clock matrix generates the following `dim`-by-`dim` matrix

    .. math::
        \Sigma_{1, d} = \begin{pmatrix}
                        1 & 0 & 0 & \ldots & 0 \\
                        0 & \omega & 0 & \ldots & 0 \\
                        0 & 0 & \omega^2 & \ldots & 0 \\
                        \vdots & \vdots & \vdots & \ddots & \vdots \\
                        0 & 0 & 0 & \ldots & \omega^{d-1}
                   \end{pmatrix}

    where :math:`\omega` is the n-th primitive root of unity.

    The clock matrix is primarily used in the construction of the generalized
    Pauli operators.

    Examples
    ==========

    The clock matrix generated from :math:`d = 3` yields the following matrix:

    .. math::
        \Sigma_{1, 3} = \begin{pmatrix}
            1 & 0 & 0 \\
            0 & \omega & 0 \\
            0 & 0 & \omega^2
        \end{pmatrix}

    >>> from toqito.matrices import clock
    >>> clock(3)
    [[ 1. +0.j       ,  0. +0.j       ,  0. +0.j       ],
     [ 0. +0.j       , -0.5+0.8660254j,  0. +0.j       ],
     [ 0. +0.j       ,  0. +0.j       , -0.5-0.8660254j]]

    References
    ==========
    .. [WikClock] Wikipedia: Generalizations of Pauli matrices,
        https://en.wikipedia.org/wiki/Generalizations_of_Pauli_matrices

    :param dim: Dimension of the matrix.
    :return: `dim`-by-`dim` clock matrix.
    """
    c_var = 2j * pi / dim
    omega = (exp(k * c_var) for k in range(dim))
    return np.diag(list(omega))


"""
Filters are used to filter events based on some kind of trigger. For example a structural break filter can be
used to filter events where a structural break occurs. This event is then used to measure the return from the event
to some event horizon, say a day.
"""

import numpy as np
import pandas as pd

# Snippet 2.4, page 39, The Symmetric CUSUM Filter.
def cusum_filter(raw_time_series, threshold, time_stamps=True):
    """
    Snippet 2.4, page 39, The Symmetric CUSUM Filter.

    The CUSUM filter is a quality-control method, designed to detect a shift in the
    mean value of a measured quantity away from a target value. The filter is set up to
    identify a sequence of upside or downside divergences from any reset level zero.

    We sample a bar t if and only if S_t >= threshold, at which point S_t is reset to 0.

    One practical aspect that makes CUSUM filters appealing is that multiple events are not
    triggered by raw_time_series hovering around a threshold level, which is a flaw suffered by popular
    market signals such as Bollinger Bands. It will require a full run of length threshold for
    raw_time_series to trigger an event.

    Once we have obtained this subset of event-driven bars, we will let the ML algorithm determine
    whether the occurrence of such events constitutes actionable intelligence.

    Below is an implementation of the Symmetric CUSUM filter.

    Note: As per the book this filter is applied to closing prices but we extended it to also work on other
    time series such as volatility.

    :param raw_time_series: (series) of close prices (or other time series, e.g. volatility).
    :param threshold: (float) when the abs(change) is larger than the threshold, the function captures
    it as an event.
    :param time_stamps: (bool) default is to return a DateTimeIndex, change to false to have it return a list.
    :return: (datetime index vector) vector of datetimes when the events occurred. This is used later to sample.
    """

    t_events = []
    s_pos = 0
    s_neg = 0

    # log returns
    diff = np.log(raw_time_series).diff()

    # Get event time stamps for the entire series
    for i in diff.index[1:]:
        pos = float(s_pos + diff.loc[i])
        neg = float(s_neg + diff.loc[i])
        s_pos = max(0.0, pos)
        s_neg = min(0.0, neg)

        if s_neg < -threshold:
            s_neg = 0
            t_events.append(i)

        elif s_pos > threshold:
            s_pos = 0
            t_events.append(i)

    # Return DatetimeIndex or list
    if time_stamps:
        event_timestamps = pd.DatetimeIndex(t_events)
        return event_timestamps

    return t_events


import numpy as np
#Succesive relaxation method iteration
#this program is Write By Endurance ogun and is part of the Sfip program
#CopyRight:: can be use for any of your project but one should not delete this comment or claim ownship of the program.
#sign by the Sfip.inc

def SOR(A,B):
    w=float(input("the relaxation factor::"))
    t = float(input("the toll::"))
    input_x=str(input("Do you want to input for the initial_value of x (y or n)::"))
    if input_x=="y":
        x=get_matrixX(len(np.ones_like(B)),float)
    else:
        x = np.zeros_like(B)
        print("initail values are \n"+str(x))

    M = 0
    j = 0
    while True:
        x_old= x.copy()
        j = j + 1  # The number of Iteration
        print("Iteration::" + str(j))
        for i in range(A.shape[0]):
            x[i] = (x[i]*(1-w))+((w/A[i,i])*(B[i]-np.dot(A[i,:i],x[:i])-np.dot(A[i,(i+1):],x_old[(i+1):])))
            M = np.allclose(x, x_old, t)
        print(x)

        if M:
            break

def get_matrixA(m_len,variable_type=float):
    A=[]
    for i in range(1,m_len+1):
        for j in range(1,m_len+1):
            m_col_row= float(input("enter value for \n a"+str(i)+str(j)+"::"))
            A.append(m_col_row)

    A=np.array(A).reshape(m_len,m_len)

    print(A)
    return A

def get_matrixB(m_len, variable_type=float):
    B = []
    for h in range(1, m_len + 1):
        m_col = float((input("enter value for  \n b" + str(h) + "::")))
        B.append(m_col)

    B = np.array(B).reshape(m_len, 1)
    print(B)
    return B
def get_matrixX(m_len, variable_type=float):
    B = []
    for h in range(1, m_len + 1):
        m_col = float((input("enter value for  \n X" + str(h) + "::")))
        B.append(m_col)

    B = np.array(B).reshape(m_len, 1)
    print(B)
    return B

def get_solution():
    N=int(input("NxN dimension of Matrix::"))
    A=get_matrixA(N)
    B=get_matrixB(N)
    SOR(A,B)

get_solution()


# Upper bounds for sigmoid function

import superimport

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

sigmoid = lambda x: np.exp(x) / (1 + np.exp(x))
fstar = lambda eta: -eta * math.log(eta) - (1 - eta) * math.log(1 - eta)
sigmoid_upper = lambda eta, x: np.exp(eta * x - fstar(eta))

eta1, eta2 = 0.2, 0.7
start, stop, step = -6, 6, 1 / 10
xs = np.arange(start, stop + step, step)

plt.plot(xs, sigmoid(xs), 'r', linewidth=3)
plt.plot(xs, sigmoid_upper(eta1, xs), 'b', linewidth=3)
plt.plot(xs, sigmoid_upper(eta2, xs), 'b', linewidth=3)
plt.text(1 / 2 + 1 / 2, sigmoid_upper(eta1, 1 / 2), 'eta=0.2')
plt.text(0 + 1 / 2, sigmoid_upper(eta2, 0), 'eta=0.7')
plt.xlim([start, stop])
plt.ylim([0, 1])
pml.savefig('sigmoid_upper_bound.pdf', dpi=300)
plt.show()


from __future__ import annotations
import numpy as np
from numpy.linalg import inv, det, slogdet

class UnivariateGaussian:
    """
    Class for univariate Gaussian Distribution Estimator
    """
    def __init__(self, biased_var: bool = False) -> UnivariateGaussian:
        """
        Estimator for univariate Gaussian mean and variance parameters

        Parameters
        ----------
        biased_var : bool, default=True
            Should fitted estimator of variance be a biased or unbiased estimator

        Attributes
        ----------
        fitted_ : bool
            Initialized as false indicating current estimator instance has not
            been fitted.
            To be set as True in `UnivariateGaussian.fit` function.

        mu_: float
            Estimated expectation initialized as None. To be set in
            `UnivariateGaussian.fit`
            function.

        var_: float
            Estimated variance initialized as None. To be set in
            `UnivariateGaussian.fit`
            function.
        """
        self.biased_ = biased_var
        self.fitted_, self.mu_, self.var_ = False, None, None

    def fit(self, X: np.ndarray) -> UnivariateGaussian:
        """
        Estimate Gaussian expectation and variance from given samples

        Parameters
        ----------
        X: ndarray of shape (n_samples, )
            Training data

        Returns
        -------
        self : returns an instance of self.

        Notes
        -----
        Sets `self.mu_`, `self.var_` attributes according to calculated
        estimation (where estimator is either biased or unbiased). Then sets
        `self.fitted_` attribute to `True`
        """
        self.mu_ = np.mean(X)
        if not self.biased_:
            self.var_ = np.var(X, ddof=1)
        else:
            self.var_ = np.var(X)
        self.fitted_ = True
        return self

    def pdf(self, X: np.ndarray) -> np.ndarray:
        """
        Calculate PDF of observations under Gaussian model with fitted
        estimators

        Parameters
        ----------
        X: ndarray of shape (n_samples, )
            Samples to calculate PDF for

        Returns
        -------
        pdfs: ndarray of shape (n_samples, )
            Calculated values of given samples for PDF function of N(mu_, var_)

        Raises
        ------
        ValueError: In case function was called prior fitting the model
        """
        if not self.fitted_:
            raise ValueError("Estimator must first be fitted before calling "
                             "`pdf` function")
        exp_power = (-0.5 / self.var_) * ((X - self.mu_) ** 2)
        pdf_values = np.exp(exp_power) / np.sqrt(2 * np.pi * self.var_)
        return np.array(pdf_values)

    @staticmethod
    def log_likelihood(mu: float, sigma: float, X: np.ndarray) -> float:
        """
        Calculate the log-likelihood of the data under a specified Gaussian
        model

        Parameters
        ----------
        mu : float
            Expectation of Gaussian
        sigma : float
            Variance of Gaussian
        X : ndarray of shape (n_samples, )
            Samples to calculate log-likelihood with

        Returns
        -------
        log_likelihood: float
            log-likelihood calculated
        """
        sum_in_exp = 0
        for x in X:
            sum_in_exp += (x - mu) ** 2
        # for this part doesnt the log cancels the exp
        exp_power = (-0.5) * sum_in_exp / (sigma ** 2)

        # log(x*y) = log(x)+log(y)
        return exp_power + ((-X.shape[0] / 2) * (np.log(2) + np.log(np.pi) +
                                                        np.log(sigma ** 2)))

class MultivariateGaussian:
    """
    Class for multivariate Gaussian Distribution Estimator
    """
    def __init__(self):
        """
        Initialize an instance of multivariate Gaussian estimator

        Attributes
        ----------
        fitted_ : bool
            Initialized as false indicating current estimator instance has not
            been fitted.
            To be set as True in `MultivariateGaussian.fit` function.

        mu_: float
            Estimated expectation initialized as None. To be set in
            `MultivariateGaussian.ft`
            function.

        cov_: float
            Estimated covariance initialized as None. To be set in
            `MultivariateGaussian.ft`
            function.
        """
        self.mu_, self.cov_ = None, None
        self.fitted_ = False

    def fit(self, X: np.ndarray) -> MultivariateGaussian:
        """
        Estimate Gaussian expectation and covariance from given samples

        Parameters
        ----------
        X: ndarray of shape (n_samples, n_features)
            Training data

        Returns
        -------
        self : returns an instance of self

        Notes
        -----
        Sets `self.mu_`, `self.cov_` attributes according to calculated
        estimation. Then sets `self.fitted_` attribute to `True`
        """
        self.mu_ = np.mean(X, axis=0)
        self.cov_ = np.cov(X, rowvar=False)
        self.fitted_ = True
        return self

    def pdf(self, X: np.ndarray):
        """
        Calculate PDF of observations under Gaussian model with fitted
        estimators

        Parameters
        ----------
        X: ndarray of shape (n_samples, n_features)
            Samples to calculate PDF for

        Returns
        -------
        pdfs: ndarray of shape (n_samples, )
            Calculated values of given samples for PDF function of N(mu_, cov_)

        Raises
        ------
        ValueError: In case function was called prior fitting the model
        """
        if not self.fitted_:
            raise ValueError("Estimator must first be fitted before calling "
                             "`pdf` function")
        sqr = np.sqrt(
            ((2 * np.pi) ** self.cov_.shape[0]) * det(self.cov_))
        sum1 = np.sum((X - self.mu_.T) @ inv(self.cov_) * (X - self.mu_.T),
                      axis=1)
        return np.array((1 / sqr) * np.exp(-0.5 * sum1))

    @staticmethod
    def log_likelihood(mu: np.ndarray, cov: np.ndarray, X: np.ndarray) -> \
            float:
        """
        Calculate the log-likelihood of the data under a specified Gaussian
        model

        Parameters
        ----------
        mu : ndarray of shape (n_features,)
            Expectation of Gaussian
        cov : ndarray of shape (n_features, n_features)
            covariance matrix of Gaussian
        X : ndarray of shape (n_samples, n_features)
            Samples to calculate log-likelihood with

        Returns
        -------
        log_likelihood: float
            log-likelihood calculated over all input data and under given parameters of Gaussian
        """
        sum1 = np.sum((X - mu.T) @ np.linalg.inv(cov) * (X - mu.T))
        cov_log = slogdet(cov)
        logs = X.shape[1] * np.log(2 * np.pi) + cov_log[0] * cov_log[1]
        return (-0.5) * X.shape[0] * logs + (-0.5 * sum1)


"""
Filter the ECG leads to remove noise

Sources of Noise
1. Baseline wander (remove frequencies below 0.7Hz)
2. High frequency noise (remove frequencies above 50Hz)

Technique: Bandpass Butterworth filter (6 poles, I found this to be most stable type of filter and no. of poles)
"""

import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
from scipy.fft import fft
from src.utils.plotting_utils import set_font_size
from src.utils import h5_interface

def remove_noise(time, lead_data, plots=False):
    """
    Removes noise from input data. A 6-pole Butterworth bandpass filter with 0.7Hz and 50Hz cutoff frequencies is
    implemented. Filter is implemented twice for zero-phase output.

    :param time: [1xN numpy array] input time data in seconds
    :param lead_data: [1xN numpy array] input lead ECG data
    :param plots: [boolean] set to True if you want to see relevant plots
    :return: [1xN numpy array] lead_data_filtered with noise removed
    """
    lowf = 0.7
    highf = 50
    fs = 240

    b, a = signal.butter(6, [lowf, highf], btype='bandpass', fs=fs)
    lead_data_filtered = signal.filtfilt(b, a, lead_data, padlen=150)

    if plots:
        set_font_size()
        # Transfer function of filter ##################################################################################
        w, h = signal.freqz(b, a, worN=4096, fs=240)

        plt.semilogx(w, abs(h)**2)
        plt.axvline(lowf, color='green')  # cutoff frequency
        plt.axvline(highf, color='green')  # cutoff frequency

        plt.title('Butterworth filter transfer function', fontsize=18)
        plt.xlabel('Frequency (Hz)', fontsize=12)
        plt.xticks(fontsize=10)
        plt.xlim(0.1, 120)
        plt.ylabel('Amplitude', fontsize=12)
        plt.yticks(fontsize=10)
        plt.grid(which='both', axis='both')

        plt.savefig('images//butterworth_transfer_function.png', dpi=500)
        plt.show()

        # Original signal spectrum #####################################################################################
        freq = np.linspace(0.0, fs / 2, len(time) // 2)
        lead_spectrum = fft(lead_data)

        plt.loglog(freq[1:len(time) // 2], 2 / len(time) * np.abs(lead_spectrum[1:len(time) // 2]), '-b', alpha=0.7)
        plt.title('ECG Spectrum', fontsize=18)
        plt.xlabel('Frequency (Hz)', fontsize=12)
        plt.xticks(fontsize=10)
        plt.xlim(0.1, 120)

        plt.ylabel('Amplitude', fontsize=12)
        plt.yticks(fontsize=10)
        plt.grid(which='both', axis='both')

        # Filtered signal spectrum #####################################################################################
        freq = np.linspace(0.0, fs / 2, len(time) // 2)
        lead_spectrum_filtered = fft(lead_data_filtered)

        plt.loglog(freq[1:len(time) // 2], 2 / len(time) * np.abs(lead_spectrum_filtered[1:len(time) // 2]), '-r', alpha=0.7)
        plt.legend(['Original', 'Filtered'], fontsize=12)
        plt.savefig('images//ECG_spectrum_filtering.png', dpi=500)
        plt.show()

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

    return lead_data_filtered

if __name__ == "__main__":
    # # EXAMPLE: Test the noise removal on a patient
    filename = 'Reference_idx_16_Time_block_1.h5'
    h5f = h5_interface.readh5(filename)

    four_lead, time, heartrate = h5_interface.ecg_np(h5f)
    lead1, lead2, lead3, lead4 = np.vsplit(four_lead, 4)
    lead1, lead2, lead3, lead4 = [lead1[0], lead2[0], lead3[0], lead4[0]]

    lead1_filtered = remove_noise(time, lead1, plots=True)

# def preprocess(filename, curr_index, double_beats=False):
#     curr_index = str(curr_index)
#     print("Starting on file : " + filename)
#
#     h5f = h5_interface.readh5(filename)
#
#     four_lead, time, heartrate = h5_interface.ecg_np(h5f)
#     lead1, lead2, lead3, lead4 = np.vsplit(four_lead, 4)
#     lead1, lead2, lead3, lead4 = [lead1[0], lead2[0], lead3[0], lead4[0]]
#
#     pos_sum = dsp_utils.combine_four_lead(four_lead)
#
#     peaks = dsp_utils.get_peaks_dynamic(pos_sum, heartrate)  # indices on the signal where we found a peak
#     peaks = peaks.astype(int)
#
#     # get the bad "heartbeats"
#     bad_hbs = detect_gaps(pos_sum, peaks)
#
#     # Delete the bad heartbeats
#     if len(bad_hbs) > 0:
#         if heartrate is not None:
#             pos_sum, time, heartrate, lead1, lead2, lead3, lead4 = delete_slices(bad_hbs, len(pos_sum),
#                                                                                [pos_sum, time, heartrate, lead1,
#                                                                                lead2, lead3, lead4])
#         else:
#             pos_sum, time, lead1, lead2, lead3, lead4 = delete_slices(bad_hbs, len(pos_sum),
#                                                                       [pos_sum, time, lead1, lead2, lead3, lead4])
#         # refind peaks
#         peaks = dsp_utils.get_peaks_dynamic(pos_sum, heartrate)  # indices on the signal where we found a peak
#         peaks = peaks.astype(int)
#
#     # hb_lengths = find_lengths(peaks, len(pos_sum), pos_sum = pos_sum)
#     # print(min(hb_lengths), sum(hb_lengths) / len(hb_lengths))
#     # try to refind peaks on long heartbeats, mostly to split missed edge beats
#     # edge beats are peaks on the edge of a dynamic window in dynamic peakfind, they get missed
#     new_peaks = []
#     new_inds = []
#     for i in range(1, len(peaks)):
#         if peaks[i] - peaks[i - 1] > 200:
#             extra_peaks = dsp_utils.get_peaks_dynamic(pos_sum[peaks[i - 1]:peaks[i]], heartrate)
#             if len(extra_peaks) == 1:
#                 if extra_peaks[0] > 40:
#                     new_peaks.append(peaks[i - 1] + extra_peaks[0])
#                     new_inds.append(i)
#     peaks = np.insert(peaks, new_inds, new_peaks)
#
#     # hb_lengths = find_lengths(peaks, len(pos_sum))
#     # print(min(hb_lengths), sum(hb_lengths) / len(hb_lengths))
#     # print(len(peaks))
#     too_longs, peaks = find_long_heartbeats(peaks, len(pos_sum), 200)
#     if too_longs:
#         if heartrate is not None:
#             pos_sum, time, heartrate, lead1, lead2, lead3, lead4 = delete_slices(too_longs, len(pos_sum),
#                                                                                [pos_sum, time, heartrate, lead1,
#                                                                                lead2, lead3, lead4])
#         else:
#             pos_sum, time, lead1, lead2, lead3, lead4 = delete_slices(too_longs, len(pos_sum),
#                                                                       [pos_sum, time, lead1, lead2, lead3, lead4])
#     # print(len(peaks), len(pos_sum),peaks[-1])
#     four_lead = np.vstack((lead1, lead2, lead3, lead4))
#     # print(four_lead.shape)
#
#     """
#     #Visual Test for R-Peak identification
#     plt.plot(pos_sum)
#     # plt.vlines(x = peaks, ymin = 0, ymax = 8, colors = "red", linewidth = 2)
#     plt.plot(peaks, pos_sum[peaks], "x")
#     plt.show()
#     """
#
#     # logging setup
#     log_filepath = os.path.join("Working_Data", "Heartbeat_Stats_Idx" + curr_index + ".txt")
#     os.makedirs(os.path.dirname(log_filepath), exist_ok=True)
#     log = open(log_filepath, 'w')
#
#     # Find the lengths of the heartbeats
#     hb_lengths = find_lengths(peaks, four_lead.shape[1])
#
#     # print(sum(hb_lengths) / len(hb_lengths))
#
#     # print(len(hb_lengths), len(hb_lengths[hb_lengths > 150]), len(hb_lengths[hb_lengths > 175]), len(hb_lengths[hb_lengths > 200]))
#
#     # Constant dimension to interpolate heartbeats to
#     maximum_hb_len = 100
#
#     log.write("Average valid heartbeat length : " + str(np.average(hb_lengths)) + "\n")
#     log.write("Total valid heartbeats : " + str(len(peaks)) + "\n")
#     log.write("Total invalid heartbeats : " + str(len(bad_hbs)) + "\n")
#
#     if double_beats:
#         peaks = np.take(peaks, list(range(1, len(peaks), 2)))
#     # Save an array of dimension Num heartbeats x 100 (heartbeat length) x Leads (4)
#     fixed_dimension_hbs = np.zeros((len(peaks) + 1, maximum_hb_len, 4))
#     for lead_num in range(4):
#         # First heartbeat in data
#         fixed_dimension_hbs[0, :, lead_num] = dsp_utils.change_dim(four_lead[lead_num, 0:peaks[0]], maximum_hb_len)
#         # Last heartbeat in data
#         fixed_dimension_hbs[len(peaks), :, lead_num] = dsp_utils.change_dim(four_lead[lead_num, peaks[-1]:],
#                                                                             maximum_hb_len)
#         # iterate through the rest of heartbeats
#         for hb_num, peak in enumerate(peaks[:-1], start=1):
#             individual_hb = four_lead[lead_num, peaks[hb_num - 1]:peaks[hb_num]]
#             try:
#                 fixed_dimension_hbs[hb_num, :, lead_num] = dsp_utils.change_dim(individual_hb, maximum_hb_len)
#             except:
#                 print("interpolation failed on heartbeat:" + str(hb_num))
#             """
#             #Periodic Visual inspection of dimension fixed heartbeat
#             if hb_num % 15000 == 0:
#                 plt.plot(fixed_dimension_hbs[hb_num,:,lead_num])
#                 plt.show()
#             """
#
#     # Save the four lead signals with gaps cut out
#     mod_four_lead_savename = os.path.join("Working_Data", "Mod_Four_Lead_Idx" + curr_index + ".npy")
#     # Save the processed heartbeat arrays
#     data_savename = os.path.join("Working_Data", "Fixed_Dim_HBs_Idx" + curr_index + ".npy")
#     # Save the clipped heartrate vector from the ECG machine
#     hr_savename = os.path.join("Working_Data", "Cleaned_HR_Idx" + curr_index + ".npy")
#     # Save the peak indicies
#     peaks_savename = os.path.join("Working_Data", "HB_Peaks_Idx" + curr_index + ".npy")
#     # Save the heartbeat lengths
#     HB_lens_savename = os.path.join("Working_Data", "HB_Lens_Idx" + curr_index + ".npy")
#     # Save the heartbeat timestamps
#     HB_timestamps_savename = os.path.join("Working_Data", "HB_Timestamps_Idx" + curr_index + ".npy")
#
#     np.save(mod_four_lead_savename, four_lead)
#     np.save(data_savename, fixed_dimension_hbs)
#     np.save(hr_savename, heartrate)
#     np.save(peaks_savename, peaks)
#     np.save(HB_lens_savename, hb_lengths)
#     np.save(HB_timestamps_savename, time[peaks])
#     log.close()
#
#
# if __name__ == "__main__":
#     for idx, filename in enumerate(get_filenames()):
#         preprocess(filename, idx, double_beats=False)


"""
_normalize_columns.py : Normalize columns.
"""
from ulab import numpy as np

def normalize_columns(columns):
    """
    Normalize columns of matrix.

    Parameters
    ----------
    columns : 2d array (M x N)
        Matrix with columns

    Returns
    -------
    normalized_columns : 2d array (M x N)
        columns/np.sum(columns, axis=0)
    """

    # broadcast sum over columns
    normalized_columns = columns/np.sum(columns, axis=0)

    return normalized_columns

def normalize_power_columns(x, exponent):
    """
    Calculate normalize_columns(x**exponent)
    in a numerically safe manner.

    Parameters
    ----------
    x : 2d array (M x N)
        Matrix with columns
    n : float
        Exponent

    Returns
    -------
    result : 2d array (M x N)
        normalize_columns(x**n) but safe

    """

    # assert np.all(x >= 0.0) ::CANT::

    # x = x.astype(np.float64) ::CANT::

    # values in range [0, 1]
    x = x/np.max(x, axis=0)

    if exponent < 0:
        # values in range [1, 1/eps]
        x /= np.min(x, axis=0)

        # values in range [1, (1/eps)**exponent] where exponent < 0
        # this line might trigger an underflow warning
        # if (1/eps)**exponent becomes zero, but that's ok
        x = x**exponent
    else:
        # values in range [eps**exponent, 1] where exponent >= 0
        x = x**exponent

    result = normalize_columns(x)

    return result


"""
    SINE, COSINE, SQUARE WAVE GENERATION

    BY:- GSS

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

time= np.arange(0, 1, 0.01);
sig_freq =float(input("Enter the frequency for sine and cosine waves:\t"))

#plotting Sine wave
amplitude_sine = np.sin(2*math.pi*sig_freq*time)
plt.plot(time, amplitude_sine)
plt.title('Sine wave')
plt.xlabel('Time')
plt.ylabel('Amplitude ')
plt.grid(True, which='both')
plt.axhline(y=0, color='k')
plt.show()

#plotting the cosine wave
amplitude_cosine = np.cos(2*math.pi*sig_freq*time)
plt.plot(time, amplitude_cosine)
plt.title('Cosine wave')
plt.xlabel('Time')
plt.ylabel('Amplitude ')
plt.grid(True, which='both')
plt.axhline(y=0, color='k')
plt.show()

#plotting the square wave
#frequency parameter is not used to plot the square wave
from scipy import signal as sg
time= np.arange(0,10*2*np.pi, 0.01)
signal = sg.square(time)
plt.plot(time, signal)
plt.xlabel('Time (s)')
plt.ylabel('Amplitude')
plt.show()


#  Demonstration Python module for Week 3
#  Scientific Computing, Fall 2021, goodman@cims.nyu.edu
#  Compute the exponential of a matrix three ways

import numpy        as np      # general numpy
import numpy.linalg as la      # linear algebra routines

def mee(L,t):                  #  compute the matrix exponential using eigenvalues
   [d,d] = L.shape
   lam, R = la.eig(L)          #  lam = eigenvalues, R = right eigenvectors
   d_elamt = np.zeros([d,d])   #  diagonal matrix with e^{-lambda_j t}
   for j in range(d):
      d_elamt[j,j] = np.exp( lam[j]*t)
   return( R@d_elamt@la.inv(R), la.cond(d_elamt)) #cond computed using |A||A^-1|

def med(L, t, k):              #  compute the matrix exponential, differential equation
   [d,d] = L.shape
   n = 2**k                    #  number of intervals
   dt = t/n                    #  time step
   I = np.identity(d)          #  to make the next formula simpler

#    S(dt) computed using 4 Taylor series terms in Horner's rule form

   S = I + dt*L@( I + (1/2)*dt*L@( I + (1/3)*dt*L@( I + (1/4)*dt*L)))

#   Raise this to the power n using repeated doublings S_j = S^{2^j} until j=k

   for j in range(k):
      S = S@S                  #  S^{2^{j-1}} becomes S^{2^j} when you square it
   return( S )

def meT(L,t, n):               #  compute the matrix exponential using Taylor series
   [d,d] = L.shape
   tLk   = np.identity(d)      #  will be (tL)^k
   kf    = 1                   #  will be k! = k factorial
   S     = np.identity(d)      #  will be the answer = sum (1/k!)((tL)^k
   max_norm = 0
   for k in range(1,n):
      kf  = k*kf               #  multiply by k to turn (k-1)! into k!
      tLk = t*( (tLk)@L)       #  turn (k-1)-th power into k-th power(tL)^k,
      S  += (1/kf)*tLk         #  (1/k!)(tL)^k is the k-th Taylor series term
      max_norm = max(max_norm, la.norm((1/kf)*tLk))
   return( S, max_norm )

#    Physical parameters

d   = 5         # size of the matrix
r_u = 2.         # rate of going up:   k -: k+1 transition
r_d = 5.         # rate of going down: k -> k-1 transition
r_l = (r_u+r_d)  # "loss rate" = rate to leave state k
t   = 3.         # time: Compute exp(tL)

#     Computational parameters

n_T = 100        #  number of Taylor series terms
k   = 11         #  n = 2^k intervals for Runge Kutta, dt = t/n

#    The generator matrix L, with up rate on the super-diagonal
#    and down rate on the sub-diagonal.

L = np.zeros([d,d])
for i in range(1,d):
   L[ i ,i-1] = r_d
   L[i-1, i ] = r_u
L[ 0 , 0 ] = - r_u
L[d-1,d-1] = - r_d
for i in range(1,d-1):
   L[i,i] = -r_l

#    Compute S = exp(tL) three ways

Se, cond = mee(L, t)
ST, max_norm = meT(L, t, n_T)
Sd = med(L, t, k)

#     The RMS differences between the computed matrices

rms_eT = np.sqrt( np.sum( (Se - ST)**2))
rms_ed = np.sqrt( np.sum( (Se - Sd)**2))
rms_Td = np.sqrt( np.sum( (ST - Sd)**2))

#     Formatted output

print("\nRMS differences between computed matrix exponentials\n")
runInfo = "up rate = {r_u:8.1f}, down rate = {r_d:8.1f}, dimension is {d:3d}\n"
runInfo = runInfo.format( r_u = r_u, r_d = r_d, d = d)
print( runInfo )
eT_info = "eigenvalue  vs. Taylor series: {rms_eT:14.6e}, \
    with {c:8.1e} condition number, {m:8.1e} and {n:4d} terms"
ed_info = "eigenvalue  vs. RungeKutta:    {rms_ed:14.6e}, \
    with k = {k:2d}"
Td_info = "Runge Kutta vs. Taylor series: {rms_Td:14.6e}"
eT_info = eT_info.format( rms_eT = rms_eT, c = cond, m = max_norm, n = n_T)
ed_info = ed_info.format( rms_ed = rms_ed, k = k)
Td_info = Td_info.format( rms_Td = rms_Td)

print( eT_info)
print( ed_info)
print( Td_info)

Scientific Computing Projects (Code Parts)/3/Problem 1/MatrixExponential.py


import numpy as np

def recursive_mean(data, chunk):
    """
    Calculate mean of data array recursively by averaging a "chunk" of the data.

    Parameters
    ----------
    data : array, float
        Input data
    chunk : integer
        Size of chunk

    Returns
    -------
    time_sample_average : array
        Data array averaged over time.

    """

    # Return the largest integer smaller or equal to the division of the first dimension of data array and chunk size.
    divider = np.floor_divide(data.shape[0], chunk)
    # Computes the remainder complementary to the floor_divide function.
    remainder = np.remainder(data.shape[0], chunk)

    # Initialize array that is returned.
    time_sample_average = np.zeros((data.shape[1], data.shape[2]), dtype=np.float16)

    # Calculate mean of data array recursively by taking averaging a "chunk" of the data.
    for zzz in range(0,divider+1):
        # If remainder only left, calculate take the average of the remainder.
        if zzz == divider:
            if remainder == 0:
                break
            elif remainder == 1:
                temp_mean = data[chunk * zzz + remainder - 1, :, :]
            else:
                temp_mean = np.mean(data[chunk * zzz:chunk * zzz + remainder -1, :, :], axis=0, dtype=np.float16)
            time_sample_average = (time_sample_average * chunk * zzz + temp_mean * remainder) /\
                                        (chunk * zzz + remainder)
        else:
            if chunk == 1:
                temp_mean = data[chunk * zzz, :, :]
            else:
                temp_mean = np.mean(data[chunk * zzz:chunk * (zzz+1)-1, :, :], axis=0, dtype=np.float16)
            time_sample_average = (time_sample_average * chunk * zzz + temp_mean * chunk) / \
                                        (chunk * (zzz + 1))

    return time_sample_average


import numpy as np
from scipy.optimize import linprog

def load_data(file_name: str = "sample"):
    file = open(file_name)
    data, result = [], []
    try:
        for line in file.read().splitlines():
            data.append(line.split(" "))
    except Exception as e:
        print(e)
    finally:
        file.close()
    for line in data:
        result.append([float(i) for i in line])
    return np.array(result)

def find_dominant_row(data):
    for firstrow in range(len(data)):
        for secondrow in range(len(data)):
            if firstrow != secondrow:
                result = np.less_equal(data[firstrow], data[secondrow])
                if all(result):
                    data = np.delete(data, firstrow, 0)
                    return find_dominant_row(data)
    return data

def find_dominant_column(data):
    data = data.T
    for firstrow in range(len(data)):
        for secondrow in range(len(data)):
            if firstrow != secondrow:
                result = np.greater_equal(data[int(firstrow)], data[secondrow])
                if all(result):
                    data = np.delete(data, firstrow, 0)
                    return find_dominant_column(data)

    return data.T

def maxmin(data):
    list_max = []
    for row in data:
        list_max.append(min(row))
    return max(list_max), list_max.index(max(list_max)) + 1

def minmax(data):
    data = data.T
    list_min = []
    for row in data:
        list_min.append(max(row))
    return min(list_min), list_min.index(min(list_min)) + 1

def saddle_point(data):
    var_maxmin = maxmin(data)
    var_minmax = minmax(data)
    if var_maxmin == var_minmax:
        return True
    else:
        return False

def linprog_A(data):
    data_transpose = data.transpose()
    negative_t_data = np.negative(data_transpose)
    b = np.ones(len(negative_t_data[0]))
    c = np.ones(len(negative_t_data))
    c = np.append(c, np.zeros(len(negative_t_data[0])))
    c = np.negative(c)
    for i in range(len(negative_t_data[0])):
        y = np.zeros(len(negative_t_data[0]))
        y[i] = -1
        negative_t_data = np.append(negative_t_data, [y], axis=0)

    res = linprog(b, A_ub=negative_t_data, b_ub=c)

    v = 0
    return_results = []

    for y in res.x:
        v += y
    v = 1 / v

    for y in res.x:
        return_results.append(v * y)
    return return_results

def linprog_B(data):
    b = np.ones(len(data[0]))
    c = np.ones(len(data))
    c = np.append(c, np.zeros(len(data[0])))
    for i in range(len(data[0])):
        y = np.zeros(len(data[0]))
        y[i] = -1
        data = np.append(data, [y], axis=0)
    res = linprog(-b, A_ub=data, b_ub=c, method="simplex")

    v = 0
    return_results = []

    for y in res.x:
        v += y
    v = 1 / v

    for y in res.x:
        return_results.append((v * y))
    return return_results


from typing import List
import numpy as np
import pandas as pd
import math

"""
        lagrange is a mathematical interpolation methods that approximate
        a polynomial of degree n given n-1 points
"""

def lagrange(X,Y) -> List:
    """
        the lagrange method that takes n points as input and return a pandas dataframe
    """
    my_columns = ['x','y']
    dataframe = pd.DataFrame(columns=my_columns)

    for pt in np.arange(-1,2,0.001):
        condition = False
        for i in range(0,len(X)):
            check = False
            for j in range(0,len(X)):
                if (i is not j and check==False):
                    Lp = (pt-X[j])/(X[i]-X[j])
                    L = Lp
                    check = True
                elif (i is not j):
                    L = L* (pt-X[j])/(X[i]-X[j])
            if (condition ==False):
                P = Y[i]* L
                condition = True
            else:
                P = Y[i]* L + P
        dataframe = dataframe.append(
            pd.Series(
                [pt,P],
                index=my_columns
            ),
            ignore_index=True)
    return dataframe

if __name__=="__main__":
    """
        creating the polynome that we want to approximate
        using lagrange method
    """
    my_columns = ['x','y']
    dataframe = pd.DataFrame(columns=my_columns)
    for i in np.arange(-1,2,0.001):
        dataframe = dataframe.append(
        pd.Series(
            [i,pow(i,3)],
            index=my_columns
        ),
        ignore_index=True)
    """
        calculating the mean square error from the given polynome and the result
    """
    res = lagrange(np.array([-1,1,2]),np.array([-1,1,8]))
    m = len(dataframe)
    diff = pow(dataframe['y']-res['y'],2).sum()
    RMSE = 1/m * (math.sqrt(diff))
    print(RMSE)


import numpy as np

Imat = np.eye(2)

def x_(nqubits, index):
    """Create an X gate
    Args:
        nqubits (int): The number of qubits on a quantum circuit
        index (int): The index of a qubit that a gate is applied
    Returns:
        ndarray : The matrix of an X gate
    """
    matrix = 1
    X = np.array([[0, 1], [1, 0]])
    for i in range(nqubits):
        if i == index:
            matrix = np.kron(matrix, X)
        else:
            matrix = np.kron(matrix, Imat)

    return matrix

def y_(nqubits, index):
    """Create an Y gate
    Args:
        nqubits (int): The number of qubits on a quantum circuit
        index (int): The index of a qubit that a gate is applied
    Returns:
        ndarray : The matrix of an Y gate
    """
    matrix = 1
    Y = np.array([[0, -1j], [1j, 0]])
    for i in range(nqubits):
        if i == index:
            matrix = np.kron(matrix, Y)
        else:
            matrix = np.kron(matrix, Imat)

    return matrix

def z_(nqubits, index):
    """Create an Z gate
    Args:
        nqubits (int): The number of qubits on a quantum circuit
        index (int): The index of a qubit that a gate is applied
    Returns:
        ndarray : The matrix of an Z gate
    """
    matrix = 1
    Z = np.array([[1, 0], [0, -1]])
    for i in range(nqubits):
        if i == index:
            matrix = np.kron(matrix, Z)
        else:
            matrix = np.kron(matrix, Imat)

    return matrix

def h_(nqubits, index):
    """Create an H gate
    Args:
        nqubits (int): The number of qubits on a quantum circuit
        index (int): The index of a qubit that a gate is applied
    Returns:
        ndarray : The matrix of an H gate
    """
    matrix = 1
    H = np.array([[1 / np.sqrt(2), 1 / np.sqrt(2)],
                 [1 / np.sqrt(2), - 1 / np.sqrt(2)]])
    for i in range(nqubits):
        if i == index:
            matrix = np.kron(matrix, H)
        else:
            matrix = np.kron(matrix, Imat)

    return matrix

def cnot_(nqubits, control_index, target_index):
    """Create an CNOT gate
    Args:
        nqubits (int): The number of qubits on a quantum circuit
        control_index (int): The index of a controlled qubit
        target_index (int): The index of a target qubit
    Returns:
        ndarray : The matrix of an X gate
    """
    cx = np.zeros((2**nqubits, 2**nqubits))
    control_bin_list = [format(num, 'b').zfill(nqubits) for num in range(2**nqubits)]
    target_bin_list = []
    for control_bin in control_bin_list:
        target_bin = list(control_bin)
        if control_bin[control_index] == '1':
            if target_bin[target_index] == '0':
                target_bin[target_index] = '1'
            else:
                target_bin[target_index] = '0'
        target_bin = "".join(target_bin)
        target_bin_list.append(target_bin)

    for index in range(2**nqubits):
        control_ = int(control_bin_list[index], 2)
        target_ = int(target_bin_list[index], 2)
        cx[control_][target_] = 1

    return cx

def rx_(nqubits, index, theta):
    """Create an RX gate
    Args:
        nqubits (int): The number of qubits on a quantum circuit
        index (int): The index of a qubit that a gate is applied
    Returns:
        ndarray : The matrix of an Z gate
    """
    matrix = 1
    rx = np.array([[np.cos(theta / 2), -1j * np.sin(theta / 2)],
                  [-1j * np.sin(theta / 2), np.cos(theta / 2)]])
    for i in range(nqubits):
        if i == index:
            matrix = np.kron(matrix, rx)
        else:
            matrix = np.kron(matrix, Imat)

    return matrix

def ry_(nqubits, index, theta):
    """Create an RY gate
    Args:
        nqubits (int): The number of qubits on a quantum circuit
        index (int): The index of a qubit that a gate is applied
    Returns:
        ndarray : The matrix of an Z gate
    """
    matrix = 1
    ry = np.array([[np.cos(theta / 2), -np.sin(theta / 2)],
                  [np.sin(theta / 2), np.cos(theta / 2)]])
    for i in range(nqubits):
        if i == index:
            matrix = np.kron(matrix, ry)
        else:
            matrix = np.kron(matrix, Imat)

    return matrix

def rz_(nqubits, index, theta):
    """Create an RZ gate
    Args:
        nqubits (int): The number of qubits on a quantum circuit
        index (int): The index of a qubit that a gate is applied
    Returns:
        ndarray : The matrix of an Z gate
    """
    matrix = 1
    rz = np.array([[np.exp(-1j * theta / 2), 0],
                  [0, np.exp(1j * theta / 2)]])
    for i in range(nqubits):
        if i == index:
            matrix = np.kron(matrix, rz)
        else:
            matrix = np.kron(matrix, Imat)

    return matrix

def phase_(nqubits, index, theta):
    """Create a phase gate
    Args:
        nqubits (int): The number of qubits on a quantum circuit
        index (int): The index of a qubit that a gate is applied
    Returns:
        ndarray : The matrix of an Z gate
    """
    matrix = 1
    phase = np.array([[1, 0],
                      [0, np.exp(1j * theta)]])
    for i in range(nqubits):
        if i == index:
            matrix = np.kron(matrix, phase)
        else:
            matrix = np.kron(matrix, Imat)

    return matrix


import numpy as np
import itertools
from math import exp, sqrt, log

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

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

    Returns:
      loss, single value - l2 regularization loss
      gradient, np.array same shape as W - gradient of weight by l2 loss
    '''
    # TODO: Copy from previous assignment
    loss = reg_strength*sum(sum(W**2));
    grad = reg_strength*2*W;

    return loss, grad

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

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

    Returns:
      loss: single value
    '''
    # TODO implement cross-entropy
    #print("probs:", probs);

    return -log(probs[target_index - 1]);

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

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

    Returns:
      loss, single value - cross-entropy loss
      dprediction, np array same shape as predictions - gradient of predictions by loss value
    '''
    # TODO copy from the previous assignment
    if predictions.ndim == 1:
        predictions_ = predictions - np.max(predictions);
        dprediction = np.array(list(map(exp, predictions_)));
        summ = sum(dprediction);
        dprediction /= summ;

        loss = cross_entropy_loss(dprediction, target_index);
        dprediction[target_index - 1] -= 1;

        return loss, dprediction;
    else:

        predictions_ = predictions - np.max(predictions, axis = 1)[:, np.newaxis];
        exp_vec = np.vectorize(exp);
        #print("predictions_:", predictions_);

        dprediction = np.apply_along_axis(exp_vec, 1, predictions_);
        #print("dprediction before division: ", dprediction);

        summ = sum(dprediction.T);
        #print("summ: ", summ);
        dprediction /= summ[:, np.newaxis];

        #print("dprediction after division: ", dprediction);

        loss = np.array([cross_entropy_loss(x,y) for x,y in zip(dprediction, target_index)]);
        #print("loss: ", loss);

        #print("target_index - 1:", target_index - 1);
        it = np.nditer(target_index - 1, flags = ['c_index'] )
        while not it.finished:
            #print("it[0] = ", it[0]);
            dprediction[it.index, it[0]] -= 1
            it.iternext()

        dprediction /= len(target_index);
        #print("dprediction after subtraction: ", dprediction);

        return loss.mean(), dprediction;

class Param:
    '''
    Trainable parameter of the model
    Captures both parameter value and the gradient
    '''
    def __init__(self, value, grad = None):
        self.value = value
        if grad is None:
            self.grad = np.zeros_like(value)
        else:
            self.grad = grad

class ReLULayer:
    def __init__(self):
        pass

    def forward(self, X):
        # TODO copy from the previous assignment
        self.X = X;
        return (X > 0)*X;

    def backward(self, d_out):
        # TODO copy from the previous assignment
        return (self.X > 0)*d_out;

    def params(self):
        return {}

class FullyConnectedLayer:
    def __init__(self, n_input, n_output, isfull = True):
        if isfull:
            self.W = Param(0.01 * np.random.randn(n_input, n_output))
            self.B = Param(0.01 * np.random.randn(1, n_output))
        else:
            self.W = None;
            self.B = None;
        self.X = None;
        self.isfull = isfull;

    def forward(self, X):
        # TODO: Implement forward pass
        # Your final implementation shouldn't have any loops
        self.X = X;
        #if np.any(self.W.init != self.W.value) or np.any(self.B.init != self.B.value):
        if self.isfull:
            self.W.grad = np.zeros_like(self.W.value);
            self.B.grad = np.zeros_like(self.B.value);
        #    self.W.init = self.W.value;
        #    self.B.init = self.B.value;
        return np.dot(self.X, self.W.value) + self.B.value;

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

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

        Returns:
        d_result: np array (batch_size, n_input) - gradient
          with respect to input
        """
        # TODO: Implement backward pass
        # Compute both gradient with respect to input
        # and gradients with respect to W and B
        # Add gradients of W and B to their `grad` attribute

        # It should be pretty similar to linear classifier from
        # the previous assignment

        dW = np.dot(self.X.T, d_out);
        dB = np.dot(np.ones((1, d_out.shape[0])), d_out);

        d_input = np.dot(d_out, self.W.value.T);
        #print("self.X = ", self.X);
        #print("self.W.grad.T = ", self.W.grad.T);
        #print("dW.T = ", dW.T);

        self.W.grad += dW;
        self.B.grad += dB;

        return d_input;

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

class ConvolutionalLayer:
    def __init__(self, in_channels, out_channels,
                 filter_size, padding):
        '''
        Initializes the layer

        Arguments:
        in_channels, int - number of input channels
        out_channels, int - number of output channels
        filter_size, int - size of the conv filter
        padding, int - number of 'pixels' to pad on each side
        '''

        self.filter_size = filter_size;
        self.in_channels = in_channels;
        self.out_channels = out_channels;
        self.W = Param(
            np.random.randn(filter_size, filter_size,
                            in_channels, out_channels)
        );

        self.B = Param(np.zeros(out_channels));
        self.X = None;
        self.FCLs = None;
        self.padding = padding;

    def forward(self, X):

        batch_size, height, width, channels = (X.shape[0], X.shape[1] + 2*self.padding, X.shape[2] + 2*self.padding, X.shape[3]);
        self.X = np.zeros((batch_size, height, width, channels));
        self.X[:, self.padding : X.shape[1] + self.padding, self.padding : X.shape[2] + self.padding, :] = X;
        out_height = height - self.filter_size + 1;
        out_width = width - self.filter_size + 1;

        self.W.grad = np.zeros_like(self.W.value);
        self.B.grad = np.zeros_like(self.B.value);

        self.FCLs = np.tile(FullyConnectedLayer(self.in_channels*self.filter_size**2, self.out_channels, isfull = False), (out_height, out_width));

        for y in range(out_height):
            for x in range(out_width):
                self.FCLs[y, x].W = Param(self.W.value.reshape((-1, self.out_channels)), self.W.grad.reshape((-1, self.out_channels)));
                self.FCLs[y, x].B = Param(self.B.value.reshape((-1, self.out_channels)), self.B.grad.reshape((-1, self.out_channels)));

        result = np.zeros((batch_size, out_height, out_width, self.out_channels));

        # TODO: Implement forward pass
        # Hint: setup variables that hold the result
        # and one x/y location at a time in the loop below

        # It's ok to use loops for going over width and height
        # but try to avoid having any other loops
        for y in range(out_height):
            for x in range(out_width):
                result[:, y, x, :] = self.FCLs[y, x].forward(self.X[:, y:y+self.filter_size, x:x+self.filter_size, :].reshape((batch_size, -1)));

        return result;

    def backward(self, d_out):
        # Hint: Forward pass was reduced to matrix multiply
        # You already know how to backprop through that
        # when you implemented FullyConnectedLayer
        # Just do it the same number of times and accumulate gradients

        batch_size, height, width, channels = self.X.shape
        _, out_height, out_width, out_channels = d_out.shape

        # TODO: Implement backward pass
        # Same as forward, setup variables of the right shape that
        # aggregate input gradient and fill them for every location
        # of the output
        result = np.zeros_like(self.X);

        # Try to avoid having any other loops here too
        for y in range(out_height):
            for x in range(out_width):
                result[:, y : y + self.filter_size, x : x + self.filter_size, :] += self.FCLs[y, x].backward(d_out[:, y, x, :]).reshape(batch_size, self.filter_size, self.filter_size, channels);
                # TODO: Implement backward pass for specific location
                # Aggregate gradients for both the input and
                # the parameters (W and B)
        #print(result[:, 0 : 0 + self.filter_size, 0 : 0 + self.filter_size, :]);

        return result[:, self.padding : height - self.padding, self.padding : width - self.padding, :];

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

class MaxPoolingLayer:
    def __init__(self, pool_size, stride):
        '''
        Initializes the max pool

        Arguments:
        pool_size, int - area to pool
        stride, int - step size between pooling windows
        '''
        self.pool_size = pool_size
        self.stride = stride
        self.X = None
        self.argmax = None

    def forward(self, X):
        batch_size, height, width, channels = X.shape
        # TODO: Implement maxpool forward pass
        # Hint: Similarly to Conv layer, loop on
        # output x/y dimension
        assert (height - self.pool_size) % self.stride == 0, "height = %i, self.pool_size = %i, self.stride = %i" % (height, self.pool_size, self.stride)

        out_height = int((height - self.pool_size)/self.stride + 1);
        out_width = int((width - self.pool_size)/self.stride + 1);
        self.X = X;

        #self.argmax = [np.argmax(self.X[:, i : i + self.pool_size, j : j + self.pool_size, :],                  axis = (1, 2)) for i, j in zip(range(0, height, self.stride), range(0, width, self.stride)) ];

        result = np.zeros((batch_size, out_height, out_width, channels));
        i = 0;
        for y in range(out_height):
            j = 0;
            for x in range(out_width):
                result[:, y, x, :] += np.amax(self.X[:, i : i + self.pool_size, j : j + self.pool_size, :],                  (1, 2));
                j += self.stride;
            i += self.stride;

        return result;

    def backward(self, d_out):
        # TODO: Implement maxpool backward pass
        batch_size, height, width, channels = self.X.shape;
        _, out_height, out_width, _ = d_out.shape
        result = np.zeros_like(self.X);
        #print("d_out = ", d_out);

        for m in range(batch_size):
            for k in range(channels):
                i = 0;
                for y in range(out_height):
                    j = 0;
                    for x in range(out_width):
                        ind = np.unravel_index(np.argmax(self.X[m, i : i + self.pool_size, j : j +                                  self.pool_size, k], axis=None), (self.pool_size, self.pool_size));
                        result[m, i : i + self.pool_size, j : j + self.pool_size, k][ind] += d_out[m, y, x, k];
                        #print(result[m, i : i + self.pool_size, j : j + self.pool_size, k][ind]);
                        #ind = np.where(abs(self.X[m, i : i + self.pool_size, j : j + self.pool_size, k] - np.max(self.X[m, i : i + self.pool_size, j : j + self.pool_size, k])) <= 1e-5);
                        #print(ind);
                        #if len(ind[0]) > 1 or len(ind[1]) > 1:
                        #    result[m, i : i + self.pool_size, j : j + self.pool_size, k][ind] += 0.5*d_out[m, y, x, k];
                        #else:
                        #        result[m, i : i + self.pool_size, j : j + self.pool_size, k][ind] += d_out[m, y, x, k];
                        j += self.stride;
                    i += self.stride;
        #print("result = ", result);
        return result;

    def params(self):
        return {}

class Flattener:
    def __init__(self):
        self.X_shape = None

    def forward(self, X):
        batch_size, height, width, channels = X.shape
        self.X_shape = X.shape;
        # TODO: Implement forward pass
        # Layer should return array with dimensions
        # [batch_size, hight*width*channels]
        return X.reshape(batch_size, height*width*channels);

    def backward(self, d_out):
        # TODO: Implement backward pass
        return d_out.reshape(self.X_shape);

    def params(self):
        # No params!
        return {}


import numpy as np

__all__ = ["RectangularFacet"]

## Fast triple product from https://stackoverflow.com/questions/42158228/fast-scalar-triple-product-in-numpy
# Levi-Civita symbol
eijk = np.zeros((3, 3, 3))
eijk[0, 1, 2] = eijk[1, 2, 0] = eijk[2, 0, 1] = 1
eijk[0, 2, 1] = eijk[2, 1, 0] = eijk[1, 0, 2] = -1

def scalar_triple(a, b, c):
    #return a.dot(np.cross(b, c))
    return np.einsum('ijk,i,j,k->', eijk, a, b, c)

def omega_simple(l, w, h): # corner at (0, 0, 0), point at (0, 0, h)
    return np.arctan((l * w) / (h * np.sqrt(l ** 2 + w ** 2 + h ** 2)))

def triangle_sa(R1, R2, R3):
    l = np.linalg.norm(np.vstack((R1, R2, R3)), axis=1)
    p = np.array([R2.dot(R3), R1.dot(R3), R1.dot(R2)])

    N = scalar_triple(R1, R2, R3)
    D = l.prod() + l.dot(p)

    return 2. * np.arctan2(N, D)

class RectangularFacet(object):
    def __init__(self, corner, r1, r2, sense=1.0, name=None):
        self.name = name
        self.corner = corner
        self.r1 = r1
        self.r2 = r2

        # Normal vector
        self.sense = sense
        self.n = np.cross(self.r1, self.r2)
        self.n /= np.linalg.norm(self.n)
        self.n *= sense

        # These are the vectors in terms of the Van Oosterom definitions
        self.a = self.corner
        self.a_p = self.corner + self.r1 + self.r2
        self.b = self.corner + self.r1
        self.c = self.corner + self.r2

        self._V = np.vstack((self.a, self.a_p, self.b, self.c))

        # self.b_p = self.corner + self.r2
        # self.c_p = self.corner + self.r1

    @property
    def edges(self):
        return np.vstack((self.r1, self.r2))

    def is_facing(self, P):
        if self.n.dot(P - self.c) > 0.0: return True
        else: return False

    def __call__(self, P):
        a, a_p, b, c = self._V - P

        return -self.sense * (triangle_sa(a, b, c) + triangle_sa(a_p, c, b))


import numpy as np

##################################################################################
#   Two class or binary SVM                                                      #
##################################################################################

def binary_svm_loss(theta, X, y, C):
    """
    SVM hinge loss function for two class problem

    Inputs:
    - theta: A numpy vector of size d containing coefficients.
    - X: A numpy array of shape mxd
    - y: A numpy array of shape (m,) containing training labels; +1, -1
    - C: (float) penalty factor

    Returns a tuple of:
    - loss as single float
    - gradient with respect to theta; an array of same shape as theta
  """

    m, d = X.shape
    grad = np.zeros(theta.shape)
    J = 0

    ############################################################################
    # TODO                                                                     #
    # Implement the binary SVM hinge loss function here                        #
    # 4 - 5 lines of vectorized code expected                                  #
    ############################################################################
    h = X@theta
    J = 1.0 / 2.0 * np.sum(np.square(theta)) / m + C / m * np.sum(np.max([np.zeros(m), 1 - y * h], axis=0))
    grad = 1.0 / m * theta + C / m * X.T@(-y * (y * h < 1))
    #############################################################################
    #                             END OF YOUR CODE                              #
    #############################################################################

    return J, grad

##################################################################################
#   Multiclass SVM                                                               #
##################################################################################

# SVM multiclass

def svm_loss_naive(theta, X, y, C):
    """
    Structured SVM loss function, naive implementation (with loops).

    Inputs have dimension d, there are K classes, and we operate on minibatches
    of m examples.

    Inputs:
    - theta: A numpy array of shape d X K containing parameters.
    - X: A numpy array of shape m X d containing a minibatch of data.
    - y: A numpy array of shape (m,) containing training labels; y[i] = k means
      that X[i] has label k, where 0 <= k < K.
    - C: (float) penalty factor

    Returns a tuple of:
    - loss J as single float
    - gradient with respect to weights theta; an array of same shape as theta
    """

    K = theta.shape[1]  # number of classes
    m = X.shape[0]  # number of examples

    J = 0.0
    dtheta = np.zeros(theta.shape)  # initialize the gradient as zero
    delta = 1.0

    #############################################################################
    # TODO:                                                                     #
    # Compute the loss function and store it in J.                              #
    # Do not forget the regularization term!                                    #
    # code above to compute the gradient.                                       #
    # 8-10 lines of code expected                                               #
    #############################################################################
    for i in range(m):
        h = X[i, :]@theta
        hy = h[y[i]]
        for j in range(K):
            if j == y[i]:
                continue
            cur = h[j] - hy + delta
            if cur > 0:
                J += cur
                dtheta[:, j] += X[i, :]
                dtheta[:, y[i]] -= X[i, :]
    J = np.sum(np.square(theta)) / 2 / m + C * J / m
    dtheta = theta / m + C * dtheta / m
    # For jupyter notebook
    # J = C * np.sum(np.square(theta)) / 2 / m + J / m
    # dtheta = C * theta / m + dtheta / m
    #############################################################################
    # TODO:                                                                     #
    # Compute the gradient of the loss function and store it dtheta.            #
    # Rather that first computing the loss and then computing the derivative,   #
    # it may be simpler to compute the derivative at the same time that the     #
    # loss is being computed. As a result you may need to modify some of the    #
    # code above to compute the gradient.                                       #
    #############################################################################

    return J, dtheta

def svm_loss_vectorized(theta, X, y, C):
    """
    Structured SVM loss function, vectorized implementation.

    Inputs and outputs are the same as svm_loss_naive.
    """
    J = 0.0
    dtheta = np.zeros(theta.shape)  # initialize the gradient as zero
    delta = 1.0
    m = X.shape[0]
    K = theta.shape[1]
    d = theta.shape[0]

    #############################################################################
    # TODO:                                                                     #
    # Implement a vectorized version of the structured SVM loss, storing the    #
    # result in variable J.                                                     #
    # 8-10 lines of code                                                        #
    #############################################################################
    h = X@theta
    hy = h - h[range(len(y)), y].reshape((-1, 1))
    hy[hy != 0] += delta
    cur = np.maximum(0.0, hy)
    J = np.sum(np.square(theta)) / 2 / m + C * np.sum(cur) / m
    # For jupyter notebook
    # J = C * np.sum(np.square(theta)) / 2 / m + np.sum(cur) / m
    #############################################################################
    #                             END OF YOUR CODE                              #
    #############################################################################

    #############################################################################
    # TODO:                                                                     #
    # Implement a vectorized version of the gradient for the structured SVM     #
    # loss, storing the result in dtheta.                                       #
    #                                                                           #
    # Hint: Instead of computing the gradient from scratch, it may be easier    #
    # to reuse some of the intermediate values that you used to compute the     #
    # loss.                                                                     #
    #############################################################################
    cc = (hy > 0) * 1
    cc[range(len(y)), y] = -np.sum(cc, axis=1)
    dtheta = theta / m + C * X.T@cc / m
    # For jupyter notebook
    # dtheta = C * theta / m + X.T @ cc / m
    #############################################################################
    #                             END OF YOUR CODE                              #
    #############################################################################

    return J, dtheta


import math
import numpy as np

class VectorSimilarity():

    def __init__(self):
        print('class init')

    def Cosine(self, vec1, vec2) :
        result = self.InnerProduct(vec1,vec2) / (self.VectorSize(vec1) * self.VectorSize(vec2))
        return result

    def VectorSize(self, vec) :
        return math.sqrt(np.sum(np.power(vec, 2)))

    def InnerProduct(self, vec1, vec2) :
        return np.inner(vec1, vec2)

    def Euclidean(self, vec1, vec2) :
        return np.linalg.norm(vec1-vec2)

    def Theta(self, vec1, vec2) :
        return math.acos(self.Cosine(vec1,vec2)) + math.radians(10)

    def Triangle(self, vec1, vec2) :
        theta = math.radians(self.Theta(vec1,vec2))
        return (self.VectorSize(vec1) * self.VectorSize(vec2) * math.sin(theta)) / 2

    def Magnitude_Difference(self, vec1, vec2) :
        return abs(self.VectorSize(vec1) - self.VectorSize(vec2))

    def Sector(self, vec1, vec2) :
        ED = self.Euclidean(vec1, vec2)
        MD = self.Magnitude_Difference(vec1, vec2)
        theta = self.Theta(vec1, vec2)
        return math.pi * math.pow((ED+MD),2) * theta/360

    def TS_SS(self, vec1, vec2) :
        return self.Triangle(vec1, vec2) * self.Sector(vec1, vec2)


from __future__ import division
from operations import accuracy_score
import numpy as np

class SquareLoss():
    def __init__(self, y, y_pred):
    	self.y = y
    	self.y_pred = y_pred

    def loss(self, y, y_pred):
        return (0.5 * np.power((y_pred - y), 2))

    def gradient(self, y, y_pred):
        return (y_pred - y)

class CrossEntropy():
    def __init__(self, label, predict_proba):
    	''' label : for example in a binary setting, label = 0 for man and label = 1 for woman
    	predict_proba : the predicted probability
    	You can think of label as "y_true" and predict_proba as "y_pred" '''
    	self.label = label
    	self.predict_proba = predict_proba

    def loss(self, label, predict_proba):
        # Avoid division by zero
        predict_proba = np.clip(predict_proba, 1e-15, 1 - 1e-15)
        return (-label * np.log(predict_proba) - (1 - label) * np.log(1 - predict_proba))

    def accuracy(self, label, predict_proba)
    	return accuracy_score(np.argmax(label, axis=1), np.argmax(predict_proba, axis=1))
    	# 'label' and 'predict_proba' being of dimensions (n_samples, n_classes)
    	# we basically pick the index for which the probability is maximum in each row

    def gradient(self, label, predict_proba):
        # Avoid division by zero
        predict_proba = np.clip(predict_proba, 1e-15, 1 - 1e-15)
        return (-(label / predict_proba) + (1 - label) / (1 - predict_proba))


from math import log
import numpy as np

class Regrezio:
    """Base class for linear regression models"""

    def __init__(self, model='lin'):
        # set the regression model
        self.model = model.lower()

    def fit(self, x, y):
        """ Fits values to linear regression model and calculates
            coefficients and intercept """
        self.y = np.array(y)
        self.x = np.array(x)

        # Calculate the lns of the values for function models
        if self.model == 'log-lin':
            self.y = np.array([[log(i[0])] for i in self.y])
        elif self.model == 'log-log':
            self.y = np.array([[log(i[0])] for i in self.y])
            self.x = np.array([[log(cell) for cell in row] for row in self.x])
        elif self.model == 'lin-log':
            self.x = np.array([[log(cell) for cell in row] for row in self.x])
        elif self.model == 'quadratic':
            self.x = np.array([[xij[0], xij[0] ** 2] for xij in self.x])
        elif self.model == 'lin' or self.model == 'lin-lin':
            pass

        # insert ones to first column
        self.x1 = np.concatenate((np.ones((len(self.x), 1)), self.x), axis=1)
        # transpose of x
        self.xt = np.transpose(self.x1)
        # X'X
        self.xtx = self.xt @ self.x1
        # X'X inversed
        self.xtx_inv = np.linalg.inv(self.xtx)

        # X'Y
        self.xty = self.xt @ self.y
        # (X'X)^-1(X'Y)
        self.coefficient_vector = self.xtx_inv @ self.xty
        # independent value in the model
        self.intercept = self.coefficient_vector[0]
        # estimated coefficients of the model
        self.coefficients = self.coefficient_vector[1:]
        # set the length of the series
        self.n = len(self.y)

    def y_func(self, xi):
        return float(self.intercept + sum([xij * ci for xij, ci in zip([xi], self.coefficients)]))

    def predict(self, x):
        """ Calculates predicted Y values and error values """
        # predicted Y values. Y^
        self.y_pred = np.array(
            [[self.y_func(xi[0])] for xi in x])
        # mean of Y's
        self.y_bar = float(sum(self.y) / self.n)

        # To calculate the determination coefficient r^2
        # error values
        self.errors = np.array(
            [[float(yi - yi_pred)] for yi, yi_pred in zip(self.y, self.y_pred)]
        )
        self.squared_errors = sum(np.array(
            [[e**2] for e in self.errors]
        ))
        self.total_variation = sum([(yi - self.y_bar)**2 for yi in self.y])
        self.score =  float(1 - self.squared_errors / self.total_variation)
        return self.y_pred


from typing import Union
import numpy as np

class Vector:

    def __init__(self, values: Union[list, tuple, iter]) -> None:
        self.values = tuple(values)
        self.n_dim = len(self.values)
        self.module = sum([x ** 2 for x in self.values])**(1/2)

    def pp(self, other_vector: 'Vector') -> int:
        """_summary_

        Args:
            other_vector (Vector): other vector.
        """
        try:
            if other_vector.n_dim == self.n_dim:
                return sum([i * k for i, k in zip(other_vector.values, self.values)])
                # for i, k in zip(other_vector.values, self.values):
                #     aux.append(i * k)
                # return sum(aux)
            else:
                raise ValueError(f"El vector ingresado es de dimensión "
                                 f"[{other_vector.n_dim}], se esperaba [{self.n_dim}]")
        except ValueError as ve:
            print(ve)
            return 0

    def pe(self, a: int) -> object:
        """_summary_

        Args:
            a (int):
        """
        return Vector([i * a for i in self.values])

    def angle(self, other_vector: 'Vector') -> float:
        """_summary_

        Args:
            other_vector (Vector): other vector.
        """
        return np.cos(self.pp(other_vector) / (self.module * other_vector.module))


import numpy as np

def get_distance_between_two_points(from_x=None, from_y=None, to_x=None, to_y=None):
    if (from_x is None) or (from_y is None) or (to_x is None) or (to_y is None):
        raise ValueError("Provide from_x, from_y, to_x and to_y values!")

    distance = np.sqrt(np.power(from_y - to_y, 2) + np.power(from_x - to_x, 2))
    return distance


import numpy as np

class Tensors:

    @staticmethod
    def get_first_invariant(tensor):
        result = None
        result = np.trace(tensor)

        return result

    @staticmethod
    def get_second_invariant(tensor):
        result = 0.0

        for i in range(3):
            for j in range(3):
                result += (
                    tensor[i, i]*tensor[j, j] - tensor[i, j]*tensor[j, i]
                )

        return result/2

    @staticmethod
    def get_third_invariant(tensor):
        result = 0.0
        result = np.linalg.det(tensor)

        return result

    @staticmethod
    def principal_stresses(tensor):
        coeff = [
            1,
            -Tensors.get_first_invariant(tensor),
            Tensors.get_second_invariant(tensor),
            -Tensors.get_third_invariant(tensor)
        ]

        sol = np.roots(coeff)

        return sol

    @staticmethod
    def principal_directions(tensor):
        eigenvect = np.linalg.eig(tensor)[0]

        return eigenvect


import numpy as np
import math

# 一些初值
initial1 = [0.1, 0.2, 0.9, 9.0]
initial2 = [[-0.1, 0.1], [-0.2, 0.2], [-2.0, 0.9], [0.9, 9.0]]
e = 1.0e-8

def f(x):
    return x**3/3-x

def df(x):
    return x**2-1

root = [0, 3**0.5, -3**0.5]

def Newton(x0, f, df, e):
    n = 1
    x1 = x0-f(x0)/df(x0)
    es = []
    while abs(x1-x0) >= e:
        es.append(abs(x1-x0))
        x0 = x1
        n += 1
        x1 = x0-f(x0)/df(x0)
    return x1, n, es

def Secant(x0, x1, f, e):
    n = 1
    x2 = x1-f(x1)*(x1-x0)/(f(x1)-f(x0))
    es = []
    while abs(x2-x1) >= e and abs(f(x2)) >= e:
        es.append(abs(x2-x1))
        x1, x0 = x2, x1
        n += 1
        x2 = x1-f(x1)*(x1-x0)/(f(x1)-f(x0))
    return x2, n, es

def ErrorOrder(es, order):
    return [es[i+1]/es[i]**order for i in range(len(es)-1)]

def main():
    for x in initial1:
        r, n, es = Newton(x, f, df, e)
        print("初值={{x0={:.1f}}}, 根={:.15e}, 迭代步数={:d}".format(x, r, n))
        print(ErrorOrder(es, 2))

    for x in initial2:
        r, n, es = Secant(x[0], x[1], f, e)
        print("初值={{x0={:.1f},x1={:.1f}}}, 根={:.15e}, 迭代步数={:d}".format(x[0], x[1], r, n))
        #print(ErrorOrder(es, 2))

if __name__ == '__main__':
    main()


# -*- coding: utf-8 -*-
"""
Created on Mon Nov  2 09:57:31 2020

@author: Larisa
"""
import numpy as np
import sistemeEcuatiiLiniare as scl

# Lab 4
# Ex1
A = np.array([[2, -2, 0, 0],
              [2, 1, 0, 0],
              [-4, 4, 0, 0],
              [4, -1, 10 ** (-20), 10 ** (-20)]])
tol = 0
rang = scl.rang(A, tol)
print(f'Rang: {rang}')

tol = 10 ** (-20)
rang = scl.rang(A, tol)
print(f'Rang: {rang}')

tol = 10 ** (-10)
rang = scl.rang(A, tol)
print(f'Rang: {rang}\n')

# Ex3
A = np.array([[0, -3, -6, 4],
              [-1, -2, -1, 3],
              [-2, -3, 0, 3],
              [1, 4, 5, -9]])
tol = 0
rang = scl.rang(A, tol)
print(f'Rang: {rang}')

tol = 10 ** (-20)
rang = scl.rang(A, tol)
print(f'Rang: {rang}')

tol = 10 ** (-10)
rang = scl.rang(A, tol)
print(f'Rang: {rang}\n')

# Ex2
A = np.array([[0., 1., -2.],
              [1., -1., 1.],
              [1., 0., -1.]])
b = np.array([[4.], [6.], [2.]])
tol = 10 ** (-10)
scl.naturaSi(A, b, tol)

A = np.array([[1., -2., 3.],
              [-2., 3., -4.],
              [3., -4., 5.]])
b = np.array([[4.], [-5.], [6.]])
scl.naturaSi(A, b, tol)

A = np.array([[0., 1., 1.],
              [2., 1., 5.],
              [4., 2., 1.]])
b = np.array([[3.], [5.], [1.]])
tol = 10 ** (-10)
scl.naturaSi(A, b, tol)

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


# EM.py is the Expectation-Maximization algorithm, written from Andrew Ng's notes: http://cs229.stanford.edu/notes/cs229-notes7b.pdf. Also see http://cs229.stanford.edu/notes/cs229-notes8.pdf for mathematical derivations and http://cs229.stanford.edu/section/gaussians.pdf for a description of Gaussian isocontours.

import random,pickle
from copy import copy
from math import fmod

import numpy as np
import matplotlib.pyplot as plot

# tol - Stopping criteria.
# k - The number of gaussian densities.
# multinomial - The latent random variable.
# gaussians - The current estimates for the k gaussian densities.

class EM():

    def __init__(self):
        self.X = None
        self.tol = None
        self.k = None
        self.multinomial = None
        self.gaussians = None

    def set_X(self,X):
        self.X = X

    def set_tolerance(self,tol=0.1):
        self.tol = tol

    def initialize_EM(self,k):
        self.k = k

        # initialize multinomial
        self.multinomial = {}
        prob = 1.0/self.k
        for j in xrange(self.k):
            self.multinomial[j] = prob

        # initialize gaussians
        # build hypercube from min/max of dataset
        number_of_rows = self.X.shape[0]
        number_of_columns = self.X.shape[1]
        mins = [float('inf'),]*number_of_columns
        maxes = [float('-inf'),]*number_of_columns
        for i in xrange(number_of_rows):
            x = self.X[i,:]
            for j in xrange(number_of_columns):
                val = x[j]
                current_min = mins[j]
                current_max = maxes[j]

                if val < current_min:
                    mins[j] = val

                if val > current_max:
                    maxes[j] = val

        # select a random mean from the min/max hypercube. also, initialize covariance, inverse, and determinant.
        self.gaussians = {}
        for j in xrange(self.k):
            self.gaussians[j] = {}

            # mean
            mean = []
            for inx in xrange(number_of_columns):
                min_j = mins[inx]
                max_j = maxes[inx]
                rand = random.uniform(min_j,max_j)
                mean.append(rand)

            mean = np.array(mean)
            mean.resize((number_of_columns,1))
            self.gaussians[j]['mean'] = mean

            # cov
            cov = []
            for inx in xrange(number_of_columns):
                vals = [0.0,]*number_of_columns
                vals[inx] = 1.0
                cov.append(vals)
            cov = np.array(cov)
            self.gaussians[j]['cov'] = cov
            self.gaussians[j]['inv'] = np.linalg.inv(cov)
            self.gaussians[j]['det'] = np.linalg.det(cov)

    def run_EM(self):
        current_likelihood = self._calculate_likelihood()
        while True:
            weights = self._E_step()
            self._M_step(weights)

            new_likelihood = self._calculate_likelihood()
            if np.isnan(new_likelihood):
                print 'POOR INITIALIZATION. REINITIALIZING...'
                self.initialize_EM(self.k)
                current_likelihood = self._calculate_likelihood()
                continue

            diff = new_likelihood - current_likelihood
            print 'DIFF LIKELIHOOD:',diff,new_likelihood
            if diff < self.tol:
                return new_likelihood

            current_likelihood = new_likelihood

    def _E_step(self):
        weights = {}
        for j in xrange(self.k):
            weights[j] = []

        number_of_rows = self.X.shape[0]
        number_of_columns = self.X.shape[1]
        for j in weights:
            weights_j = []
            for i in xrange(number_of_rows):
                x = self.X[i]
                x.resize((number_of_columns,1))
                prob_z_given_x = self._prob_z_given_x(x,j)
                weights_j.append((prob_z_given_x,i))

            weights[j] = weights_j

        return weights

    def _M_step(self,weights):
        self._update_phi(weights)
        self._update_mean(weights)
        self._update_cov(weights)

    def _calculate_likelihood(self):
        likelihood = 0.0

        number_of_rows = self.X.shape[0]
        number_of_columns = self.X.shape[1]
        for i in xrange(number_of_rows):
            x = self.X[i]
            x.resize((number_of_columns,1))
            for j in xrange(self.k):
                prob_x_given_z = self._prob_x_given_z(x,j)
                prob_z_given_x = self._prob_z_given_x(x,j)
                prob_z = self._prob_z(j)

                likelihood = likelihood + prob_z_given_x*np.log(prob_x_given_z*prob_z/prob_z_given_x)

        return likelihood

    # use Bayes' rule to get prob_z_given_x
    def _prob_z_given_x(self,x,j):
        prob_x_given_z = self._prob_x_given_z(x,j)
        prob_z = self._prob_z(j)

        sum_probs = 0.0
        for l in xrange(self.k):
            sum_probs = sum_probs + self._prob_x_given_z(x,l)*self._prob_z(l)

        prob_z_given_x = prob_x_given_z*prob_z/sum_probs

        return prob_z_given_x

    # I had to write the gaussian pdf. I could not get the gaussians pdf in scipy to work.
    def _prob_x_given_z(self,x,j):
        gaussian = self.gaussians[j]
        mean = gaussian['mean']
        inv = gaussian['inv']
        det = gaussian['det']
        number_of_columns = self.X.shape[1]

        diff = x - mean
        quadratic = np.dot(np.dot(diff.T,inv),diff)
        e = np.exp(-0.5*quadratic[0][0])
        c = 1.0/((2*np.pi)**(number_of_columns/2.0)*det**0.5)
        prob_x_given_z = c*e

        return prob_x_given_z

    def _prob_z(self,j):
        prob_z = self.multinomial[j]

        return prob_z

    def _update_phi(self,weights):
        for j in weights:
            phi_j = 0.0
            for prob_z_given_x,_ in weights[j]:
                phi_j = phi_j + prob_z_given_x

            number_of_rows = self.X.shape[0]
            phi_j = phi_j/number_of_rows
            self.multinomial[j] = phi_j

    def _update_mean(self,weights):
        number_of_columns = self.X.shape[1]
        for j in weights:
            sum_probs = 0.0
            mean_j = np.zeros((number_of_columns,1))
            for prob_z_given_x,i in weights[j]:
                x = self.X[i]
                x.resize((number_of_columns,1))
                mean_j = mean_j + prob_z_given_x*x
                sum_probs = sum_probs + prob_z_given_x

            mean_j = mean_j/sum_probs
            self.gaussians[j]['mean'] = mean_j

    def _update_cov(self,weights):
        number_of_columns = self.X.shape[1]
        for j in weights:
            sum_probs = 0.0
            cov_j = np.zeros((number_of_columns,number_of_columns))
            mean_j = self.gaussians[j]['mean']
            for prob_z_given_x,i in weights[j]:
                x = self.X[i]
                x.resize((number_of_columns,1))
                diff = x - mean_j
                product = prob_z_given_x*diff*diff.T
                cov_j = cov_j + product
                sum_probs = sum_probs + prob_z_given_x

            cov_j = cov_j/sum_probs
            inv_j = np.linalg.inv(cov_j)
            det_j = np.linalg.det(cov_j)
            self.gaussians[j]['cov'] = cov_j
            self.gaussians[j]['inv'] = inv_j
            self.gaussians[j]['det'] = det_j

    def generate_example(self,number_of_clusters=3,sample_size_per_cluster=500,number_of_runs=3):
        # assemble data
        X = np.array([])
        X.resize((0,2))
        for inx in xrange(number_of_clusters):
            x_1 = random.sample(range(0,15),1)[0]
            x_2 = random.sample(range(0,15),1)[0]
            cov = random.uniform(-0.75,0.75)
            mean = np.array([x_1,x_2])
            cov = np.array([[1,cov],[cov,1]])
            res = np.random.multivariate_normal(mean,cov,sample_size_per_cluster)
            X = np.row_stack((X,res))

        # run EM a few times
        likelihoods = []
        for i in xrange(number_of_runs):
            print 'RUN:',i
            self.set_X(X)
            self.set_tolerance()
            self.initialize_EM(number_of_clusters)
            likelihood = self.run_EM()
            s_gaussians = pickle.dumps(self.gaussians)
            pair = (likelihood,s_gaussians)
            likelihoods.append(pair)
            print '---\n'

        # select run with highest likelihood
        likelihoods.sort(reverse=True)
        s_gaussians = likelihoods[0][1]
        gaussians = pickle.loads(s_gaussians)

        # plot the original data, X, and the estimated means from EM.
        plot.scatter(self.X[:,0],self.X[:,1],s=0.5,color='grey')
        for j in gaussians:
            mean_j = gaussians[j]['mean']
            plot.scatter(mean_j[0][0],mean_j[1][0])

        plot.show()

        # plot random samples from the densities estimated by EM and the means estimated by EM.
        colors = ['red','orange','green','purple','grey']
        X_colors = []
        X = np.array([])
        X.resize((0,2))
        for j in gaussians:
            color = colors[int(fmod(j,len(colors)))]
            mean_j = gaussians[j]['mean']
            cov_j = gaussians[j]['cov']
            res = np.random.multivariate_normal(mean_j.T[0],cov_j,sample_size_per_cluster)
            X = np.row_stack((X,res))
            for inx in xrange(sample_size_per_cluster):
                X_colors.append(color)

        X_colors = np.array(X_colors)
        X_colors.resize((number_of_clusters*sample_size_per_cluster,1))

        plot.scatter(X[:,0],X[:,1],color=X_colors[:,0],s=0.5)
        for j in gaussians:
            mean_j = gaussians[j]['mean']
            plot.scatter(mean_j[0][0],mean_j[1][0])

        plot.show()


""" Tuple representation class
"""
import numpy as np

class Tuple:
    """ (x, y, z, w)

        for w == 0, it's a vector
        for w == 1, it's a point
    """

    def __init__(self, x: float, y: float, z: float, w: float, narray=None):
        if narray is not None:
            self._t = narray
        else:
            self._t = np.array((x, y, z, w))

    @property
    def x(self):
        return self._t[0]

    @property
    def y(self):
        return self._t[1]

    @property
    def z(self):
        return self._t[2]

    @property
    def w(self):
        return self._t[3]

    def __eq__(self, other):
        return (self._t == other._t).all()

    def __add__(self, other):
        return Tuple(0, 0, 0, 0, self._t + other._t)

    def __neg__(self):
        return Tuple(0, 0, 0, 0, -self._t)

    def __mul__(self, other):
        return Tuple(0, 0, 0, 0, self._t * other)

    def __truediv__(self, other):
        return Tuple(0, 0, 0, 0, self._t / other)

class Point(Tuple):
    """ a point is a tuple with w == 1
    """

    def __init__(self, x: float, y: float, z: float):
        super().__init__(x, y, z, 1)

    def __sub__(self, other):
        """ Returns a Vector
        """
        return Tuple(0, 0, 0, 0, self._t - other._t)

class Vector(Tuple):
    """ a vector is a tuple with w == 0
    """

    def __init__(self, x: float, y: float, z: float):
        super().__init__(x, y, z, 0)
        self._magnitude = None
        self._norm = None

    @property
    def magnitude(self):
        if not self._magnitude:
            self._magnitude = np.linalg.norm(self._t)
        return self._magnitude

    @property
    def norm(self):
        if not self._norm:
            result = self._t / self.magnitude
            self._norm = Vector(result[0], result[1], result[2])
        return self._norm

    def __sub__(self, other):
        return Tuple(0, 0, 0, 0, self._t - other._t)

def make_tuple(x: float, y: float, z: float, w: float) -> Tuple:
    if w == 0:
        return Vector(x, y, z)
    elif w == 1:
        return Point(x, y, z)

    return Tuple(x, y, z, w)

def dot(a: Tuple, b: Tuple) -> float:
    return np.dot(a._t, b._t)

def cross(a: Vector, b: Vector) -> Vector:
    v = np.cross(a._t[0:3], b._t[0:3])
    print(v[0], v[1], v[2])
    return Vector(v[0], v[1], v[2])


"""
author: Fabian Schaipp

This is Condat's algorithm for the proximal operator of the Fused Lasso regularizer (https://lcondat.github.io/publis/Condat-fast_TV-SPL-2013.pdf)
"""

import numpy as np
from numba import jit
#from tick.prox import ProxTV

@jit(nopython = True)
def condat_method(y,lam):

    N = len(y)

    x = np.zeros(N)
    k = k0 = kplus = kminus = 0

    vmin = y[0] - lam
    vmax = y[0] + lam
    umin = lam
    umax = -lam

    while 0 < 1:

        while k == N-1:
            if umin < 0:
                x[k0:kminus +1] = vmin
                kminus += 1
                k = k0 = kminus
                umin = lam; vmin = y[k]; umax = y[k] + lam - vmax
            elif umax > 0:
                x[k0:kplus+1] = vmax
                kplus += 1
                k  = k0 = kplus
                umax = -lam; vmax = y[k]; umin = y[k] - lam - vmin
            else:
                x[k0:] = vmin + umin/(k-k0+1)
                return x

            if k == N-1:
                x[k] = vmin + umin
                return x

        if y[k+1] + umin - vmin < -lam:
            x[k0:kminus+1] = vmin
            kminus += 1
            k = kplus = k0 = kminus
            vmin = y[k]; vmax = y[k] + 2*lam
            umin = lam; umax = -lam

        elif y[k+1] + umax -vmax > lam:
            x[k0:kplus+1] = vmax
            kplus += 1
            k  = kminus = k0 = kplus
            vmin = y[k] - 2*lam; vmax = y[k]
            umin = lam; umax = -lam

        else:
            k += 1
            umin = umin + y[k] - vmin
            umax = umax + y[k] - vmax
            if umin >= lam:
                vmin += (umin-lam)/(k-k0+1); umin = lam; kminus = k
            if umax <= -lam:
                vmax += (umax+lam)/(k-k0+1); umax = -lam; kplus = k

    return

# for testing

# def objective(x, y, l1):
#     res = l1* abs(x[1:] - x[0:-1]).sum() + .5*np.linalg.norm(x-y)**2

#     return res

#l1 = .1
#
# for i in range(1000):
#     print(i)
#     y = np.random.rand(100)
#     x = condat_method(y, l1)
#     x2 = ProxTV(l1).call(y)

#     print(np.linalg.norm(x-x2))
#     print("Condat function correct: ", objective(x, y, l1) <= objective(x2, y, l1) + 1e-5)
#     assert  np.linalg.norm(x-x2) <= 1e-10