"""
Title: Finding where the function f(x) = x^4 - 7x + 10 has its minimum and determining the minimum value of the function

Solution to Problem Set 1, Problem 4
~ Arsh R. Nadkarni

To run:
python newton_1_4.py

Can change initial guess (x0), tolerance (eps), and number of iterations (n) in the function call on line 37

"""
# import libraries
import numpy as np

# define f(x)
def f(x):
    return x**4-7*x+10
# define the first derivative of f(x)
def derf(x):
    return 4*x**3-7
# construct the newton method function
def newton(x0,eps,n):
    init = 1 # initial counter variable
    inf_init = 1 # initial infinite loop counter variable
    while True:
        xmin = x0 - f(x0)/derf(x0) # newton's method condition
        print('Iteration No-%d: xmin = %0.3f and f(xmin) = %0.3f' % (init, xmin, f(xmin)))
        x0 = xmin # update x0
        init += 1 # iterate the counter variable
        if init > n: # break the loop if it goes to infinite iterations
            inf_init = 0
            break
    print("Minimum:", np.min(xmin)) # print the minimum for the function
    print("The minimum value of f(x):", np.min(f(xmin))) # print the minimum value for the function

newton(3,1e-3,5) # call the function

Problem Sets/Problem Set 1/newton_1_4.py


import io
import base64
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
matplotlib.use('Qt5agg')
from sympy import Function, Symbol, diff
from sympy.parsing.sympy_parser import parse_expr

class Grapher:

    def __init__(self, function, a=-10, b=10, points=100):
        self.f = parse_expr(function)
        self.x = Symbol("x")
        self.a = float(a)
        self.b = float(b)
        self.points = int(points)

    @staticmethod
    def convert_graph_to_b64(fig):
        graph_string = io.BytesIO()
        fig.savefig(graph_string, format="jpg")
        graph_string.seek(0)
        return base64.b64encode(graph_string.read())

    def get_diffs(self):
        return diff(self.f, self.x).simplify()

    def run(self):
        x = np.linspace(self.a, self.b, self.points)
        y = [self.f.subs(self.x, _x) for _x in x]
        fig, ax = plt.subplots()
        ax.plot(x, y)
        ax.set(title=f"{self.f}")
        ax.grid()

        result = {
            "graph_image": self.convert_graph_to_b64(fig).decode(),
            "derivative_function": str(diff(self.f, self.x).simplify()),
        }

        _simplify = str(self.f.simplify())
        result["simplified_function"] = _simplify if str(self.f) != _simplify else ''

        return result

if __name__ == "__main__":
    f = "x**2 - x**3 + x + x + 1"
    G = Grapher(f)
    print(G.run())


import unittest
from decimal import Decimal
from math import pi
import numpy as np

class TestMapsdGeoLibrary(unittest.TestCase):
  # 0. coord to rad point tuple conversion
  def test_coord_to_rad(self):
    points = [
      (0., 360.),
      (Decimal(0.), Decimal(360.)),
      (Decimal(180.), Decimal(540.)),
    ]
    expected = [
      (0., 2 * pi),
      (0., 2 * pi),
      (pi, 3 * pi),
    ]
    rad_tuples = list(map(lambda p: np.radians(p), points))
    self.assertEqual(rad_tuples, expected)

  # Helpers
  def _assertAlmostEqualList(self, a, b, places=0):
      for idx, el_a in enumerate(a):
        self.assertAlmostEqual(el_a, b[idx], places)

  def _assertAlmostEqualListOfTuples(self, a, b, places=0):
    for idx, el_a in enumerate(a):
      for idy, el_el_a in enumerate(el_a):
        self.assertAlmostEqual(el_el_a, b[idx][idy], places)


from numpy import array, append, cross, sign, seterr, around, array_equal
from numpy.linalg import det, solve
seterr(all='raise')

def dist(p,q):
    """ Calculates the distance between the points p and q.

    Parameters
    ---------
    p : Point
        the first point

    q : Point
        the second point

    Returns
    ----------
    Float
        The distance between the points p and q.
    """
    return (sum((p.array()-q.array())**2))**0.5

def orient(*points):
    """ Performs the ccw test to determine the orientation of the points.

    Parameters:
    -------------
    points : Points
        An arbitrary number of Points to determine the orientation of

    Returns
    -------
    Integer
        Returns 1 if the points are ordered in ccw order. Returns -1 if the points are ordered in clockwise order.
        Returns 0 if the points are colinear.
    """
    points = [p.array() for p in points]
    d = det(array(points))
    if d > 0:
        return 1
    elif d < 0:
        return -1
    else:
        return 0

def incircle(a,b,c,d):
    """ Determines if the Point d is inside of the circle formed by the traingle a,b,c.

    Parameters
    ------------
    a : Point
        The first point of the triangle

    b : Point
        the second point on the triangle

    c : Point
        The third point on the triangle

    Returns
    -------------
    Integer
        Returns 0 if the point d is on the circle. Returns -1 if the point d lies outside the circle
        Returns 1 if the point d lies inside the circle.
    """

    zero = Point(0,0)
    _a = [a[0], a[1], dist(a, zero)**2, 1]
    _b = [b[0], b[1], dist(b, zero)**2, 1]
    _c = [c[0], c[1], dist(c, zero)**2, 1]
    _d = [d[0], d[1], dist(d, zero)**2, 1]
    A = array([_a, _b, _c, _d])
    d = around(det(A),decimals=6)
    if d == 0:
      return 0
    return sign(d)*orient(a,b,c)

def circumcenter(a,b,c):
    """Finds the circumcenter of a triangle.

    Parameters
    ------------
    a : Point
        The first point of the triangle

    b : Point
        the second point of the triangle

    c : Point
        the third point of the triangle

    Returns
    -------------
    Point
        The circumcenter of the triangle
    """
    zero = Point(0,0)
    d = 2*((a[0]*(b[1]-c[1])) + (b[0]*(c[1]-a[1])) + (c[0]*(a[1]-b[1])))
    x = ((dist(a, zero)**2)*(b[1]-c[1]) + (dist(b, zero)**2)*(c[1]-a[1]) + (dist(c, zero)**2)*(a[1]-b[1])) / d
    y = ((dist(a, zero)**2)*(c[0]-b[0]) + (dist(b, zero)**2)*(a[0]-c[0]) + (dist(c, zero)**2)*(b[0]-a[0])) / d
    return Point(x,y)

# Slope from a to b
def slope(a,b):
    """ Gets the slope between two points.

    Parameters
    -------------
    a : Point
        The first point in the slope calculation.

    b : Point
        The second point is the slope calculation.

    Returns
    ------------
    Float
        The slope between the two points. If we have the case where we divide by zero, if b[1] > a[1], then
        we return float('inf'). Otherwise, returns float('-inf').
    """
    try:
        return (b[1]-a[1]) / (b[0]-a[0])
    except (ZeroDivisionError, RuntimeWarning, FloatingPointError) as e:
        if (b[1] > a[1]):
            return float('inf')
        else:
            return float('-inf')

class Point:
    """This class represents a point that has been lifted up to some z plane."""

    def __init__(self, *coordinates, z = 1):
        """
        Parameters
        --------------

        coordinates : Integers or Floats
            An arbitrary number of Intergers and Floats that correspond to the coordinates of the point to be created.

        z : Integer
            The z plane for the coordinate to be lifted up to. Defaults to z=1.
        """

        self._p = append(coordinates, z)

    def array(self):
        """Gets an array of the coordinates of the point.

        Returns
        ----------
        numpy.Array
            An array containing the coordinates of the point.
        """
        return self._p

    def __eq__(self, other):
        return array_equal(self._p, other._p)

    def __lt__(self, other):
        if (self._p[0] != other._p[0]):
            return self._p[0] < other._p[0]
        else:
            return self._p[1] < other._p[1]

    def __str__(self):
        return str(self._p[:2])

    def __getitem__(self, index):
        return self._p[index]


import numpy as np

def minmax(x):
	'''
	x m*1
	'''
	min_ = np.min(x)
	max_ = np.max(x)
	x_ = np.zeros_like(x,dtype=float)
	for i in range(x.shape[0]):
		x_[i] = (x[i] - min_) / (max_ - min_)
	return x_

if __name__ == "__main__":
	X = np.array([0, 15, -9, 7, 12, 3, -21])
	print(minmax(X))


'''

    A collection of functions to perform portfolio analysis.

    Max Gosselin, 2019

'''
import numpy as np
import pandas as pd
from scipy import optimize

def portfolio_metrics(weights, avg_xs_returns, covariance_matrix):
    ''' Compute basic portfolio metrics: return, stdv, sharpe ratio '''

    portfolio_return = np.sum(weights * avg_xs_returns)
    portfolio_stdv = np.sqrt(np.dot(weights.T, np.dot(weights, covariance_matrix)))

    portfolio_sharpe = portfolio_return / portfolio_stdv

    tickers = covariance_matrix.columns

    metrics = {
        'return': portfolio_return,
        'stdv': portfolio_stdv,
        'sharpe': portfolio_sharpe,
        'weights': weights
        }
    metrics.update(dict([(ticker, weight) for ticker, weight in zip(tickers, weights)]).items())

    return metrics

def simulate_portfolios(iters, xs_stats, covariance_matrix):
    ''' What we want here is to randomly generate portfolios that will sit
        inside the efficiency frontier for illustrative purposes '''

    # Set up an empty array to store our generated portfolios
    simulations = []

    while iters > 1:

        weights = np.random.random(len(xs_stats.columns))
        weights /= np.sum(weights)

        simulations.append(portfolio_metrics(weights, xs_stats.loc['Avg'], covariance_matrix))

        iters -= 1

    return simulations

def solve_minvar(xs_avg, covariance_matrix):
    ''' Solve for the weights of the minimum variance portfolio

        Constraints:
            sum of weights = 1,
            weights bound by [0, 0.2],

        Returns the weights and the jacobian used to generate the solution.

        '''

    def __minvar(weights, xs_avg, covariance_matrix):
        ''' Anonymous function to compute stdv '''
        return np.sqrt(np.dot(weights.T, np.dot(weights, covariance_matrix)))

    p_size = len(xs_avg)
    args = (xs_avg, covariance_matrix)
    constraints = [{'type': 'eq', 'fun': lambda x: np.sum(x) - 1}]
    bounds = [(0, 0.2)] * p_size

    minimized_weights = optimize.minimize(__minvar, np.zeros(p_size), args=args,
                        method='SLSQP', bounds=bounds, constraints=constraints, options={'maxiter':1000})

    return minimized_weights

def solve_maxsharpe(xs_avg, covariance_matrix):
    ''' Solve for the weights of the maximum Sharpe ratio portfolio

        Constraints:
            sum of weights = 1,
            weights bound by [0, 0.2],

        Returns the weights and the jacobian used to generate the solution.

        '''
    def __max_by_min_sharpe(weights, xs_avg, covariance_matrix):
        ''' Anonymous function to compute sharpe ratio, note that since scipy only minimizes we go negative. '''
        pm = portfolio_metrics(weights, xs_avg, covariance_matrix)
        return -pm['return'] / pm['stdv']

    p_size = len(xs_avg)
    args = (xs_avg, covariance_matrix)
    constraints = [{'type': 'eq', 'fun': lambda x: np.sum(x) - 1}]
    bounds = [(0, 0.2)] * p_size

    minimized_weights = optimize.minimize(__max_by_min_sharpe, ((1/p_size) * np.ones(p_size)), args=args,
                        method='SLSQP', bounds=bounds, constraints=constraints, options={'maxiter':1000})

    return minimized_weights

def solve_for_target_return(xs_avg, covariance_matrix, target):
    ''' Solve for the weights of the minimum variance portfolio which has
        a specific targeted return.

        Constraints:
            sum of weights = 1,
            weights bound by [0, 0.2],
            portfolio return = target return,

        Returns the weights and the jacobian used to generate the solution.

        '''

    def __minvar(weights, xs_avg, covariance_matrix):
        ''' Anonymous function to compute stdv '''
        return np.sqrt(np.dot(weights.T, np.dot(weights, covariance_matrix)))

    def __match_target(weights):
        ''' Anonymous function to check equality with the target return '''
        return np.sum(weights * xs_avg)

    p_size = len(xs_avg)
    args = (xs_avg, covariance_matrix)
    constraints = [
        {'type': 'eq', 'fun': lambda x: np.sum(x) - 1},
        {'type': 'eq', 'fun': lambda x: __match_target(x) - target},
    ]
    bounds = [(0, 0.2)] * p_size

    minimized_weights = optimize.minimize(__minvar, ((1/p_size) * np.ones(p_size)), args=args,
                        method='SLSQP', bounds=bounds, constraints=constraints, options={'maxiter':1000})

    return minimized_weights

def generate_efficient_frontier(targets, xs_avg, covariance_matrix):

    portfolios = []

    for target in targets:

        p_weights = solve_for_target_return(xs_avg, covariance_matrix, target)
        portfolios.append(portfolio_metrics(p_weights['x'], xs_avg, covariance_matrix))

    return portfolios


'''
This file contains all of the PRNG methods used in this experiment.
<notes>
    Listed below are the PRNG implementations we are focusing on (this covers a good amount of them from 1946-1998);

        Done  [Middle-square method (1946)](https://en.wikipedia.org/wiki/Middle-square_method)
        Done  [Linear congruential generator (1958)](https://en.wikipedia.org/wiki/Linear_congruential_generator)
        Done  [Lagged Fibonacci (1965)](https://en.wikipedia.org/wiki/Lagged_Fibonacci_generator)
        Done  [Park-Miller (1988)](https://en.wikipedia.org/wiki/Lehmer_random_number_generator)
        Done  [Mersenne Twister (1998)](https://en.wikipedia.org/wiki/Mersenne_Twister)

    Note that many of the notes in the below comments are mostly indirectly or directly referenced to the wiki pages.

    The call definition of any given PRNG function should be as follows:

    PRNGfunc(seed, n, optionalparam=default, anotherparam=default).

    This is so we can experimentally automate the calling of each PRNG without getting too complex.

    The given PRNG function should return a list of n generated numbers using the generation method.
    All other parameters outside of the seed and n need to have default values.

    For example, if I call PRNG(seed, 10)
    It might return something like
    [3,5,10,1,31,17,2,4,6,7]

    We will control parsing the n-length list and handling seeds externally.
    This plays logically with separation of concerns for our usecase.

    While python generators can be useful iterating over previously generated iterables,
    we did not want to clutter our experimental execution code, so we stuck to a classical
    internal handling of all iterations.

    Please read Core.SeedMethods.py for notes about the seed generation methods used.
</notes>
'''

import sys
from decimal import *
from Core.IsPrime import *
import numpy as np # For Mersenne Twister
from numpy.random import Generator, MT19937, SeedSequence # For Merseene Twister

def Get_Defs():
    return {
        'Middle_Square': '1946',
        'Linear_Congruential':'1958',
        'Lagged_Fibonacci' : '1958',
        'Park_Miller' : '1988',
        'Mersenne_Twister':'1998'
        }

'''
<Middle_Square finished=true/>
<notes>
    Weaknesses:
        For a generator of n-digit numbers, the period can be no longer than 8n.
        If the middle n digits are all zeroes, the generator then outputs zeroes forever.
        If the first half of a number in the sequence is zeroes, the subsequent
        numbers will be decreasing to zero.
        While these runs of zero are easy to detect, they occur too frequently for this
        method to be of practical use.
        The middle-squared method can also get stuck on a number other than zero.

    An already_seen list is NOT included in the original middle square method and will not be used.
</notes>
'''
def Middle_Square(seed, listlength):
    numlist = []
    for i in range(listlength):
        seedlength = len(str(seed))
        # The value of n must be even in order for the method to work ...
        # It is acceptable to pad the seeds with zeros to the left in order to create
        # an even valued n-digit (eg: 540 → 0540).
        if (seedlength % 2 != 0):
            seedlength += 1
            seed = str(int(seed)).zfill(seedlength)

        #fill leading zeros if seed*seed is less than (2*seed) digits long
        seed = str(int(seed) * int(seed)).zfill(2 * seedlength)
        half = int(seedlength / 2)
        seed = seed[(half):(seedlength + half)]
        numlist.append(seed)
    return(numlist)

'''
<Linear_Congruential finished=true/>
<notes>
    The generator is not sensitive to the choice of c,
    as long as it is relatively prime to the modulus
    (e.g. if m is a power of 2, then c must be odd),
    so the value c=1 is commonly chosen.

    If c = 0, the generator is often called a multiplicative
    congruential generator (MCG), or Lehmer RNG. If c ≠ 0, the
    method is called a mixed congruential generator.

    Parameters were chosen based on 2^32 in table 2 of
    https://citeseerx.ist.psu.edu/viewdoc/download;jsessionid=BBA0C7ED3ADAB606642BB8D939774B4F?doi=10.1.1.34.1024&rep=rep1&type=pdf
</notes>
'''
def Linear_Congruential(seed, listlength, modulus=4294967291, a=1588635695, c=1):
    numlist = []
    for i in range(listlength):
        seed = (a * seed + c) % modulus
        numlist.append(seed)
    return numlist

'''
<Lagged_Fibonacci finished=true/>
<notes>
    This is two tap, not three tap...
    It's a "lagged" generator, because "j" and "k" lag behind the generated pseudorandom value.
    Also, this is called a "two-tap" generator, in that you are using 2 values in the sequence
    to generate the pseudorandom number. However, a two-tap generator has some problems with
    randomness tests, such as the Birthday Spacings. Apparently, creating a "three-tap" generator
    addresses this problem.
</notes>
'''
def Lagged_Fibonacci(seed, listlength, j=7 , k=10):

    # 0 < j < k
    if(not(0 < j and j < k)):
        return

    #If addition is used, it is required that at least one of the first k values
    # chosen to initialise the generator be odd
    flag = False
    for i in range(len(str(seed))):
        if(i > k):
            flag = True
            break

        if(int(str(seed)[i]) % 2 != 0):
            flag = True
            break

    numlist = []
    seedlist = []

    for i in range(len(str(seed))):
        seedlist.append(str(seed)[i])

    if(flag):
        for n in range(listlength):
            for i in range(len(seedlist)):
                if i is 0:
                    val = int(seedlist[j-1]) + int(seedlist[k-1]) % 10 #arbitrary mod?
                    numlist.append(val)
                elif 0 < i < len(seedlist) - 1:
                    seedlist[i] = seedlist[i+1] # shift array
                else:
                    seedlist[i] = val
    return numlist

'''
<Park-Miller finished=true/>
<notes>
    The Lehmer generator is an lcg. Park Miller is a specific lcg.

    The Lehmer random number generator (named after D. H. Lehmer),
    sometimes also referred to as the Park–Miller random number generator
    (after Stephen K. Park and Keith W. Miller), is a type of linear congruential generator (LCG).

    Park miller generator, because they essentially the same.
    Park-miller request the use of certain modulos.
    Lehmer is an lcg where c = 0...
    If c = 0, the generator is often called a multiplicative congruential generator (MCG),
    or Lehmer RNG. If c ≠ 0, the method is called a mixed congruential generator.

    While the Lehmer RNG can be viewed as a particular case of the linear congruential generator
    with c=0, it is a special case that implies certain restrictions and properties.
    In particular, for the Lehmer RNG, the initial seed must be coprime to the modulus m that is
    not required for LCGs in general.

    The choice of the modulus m and the multiplier a is also more restrictive for the Lehmer RNG.
    In contrast to LCG, the maximum period of the Lehmer RNG equals m−1 and it is such when m is
    prime and a is a primitive root modulo m.
</notes>
'''
def Park_Miller(seed, listlength):
    #initialize the state to any number greater than zero and less than the modulus.
    if(not(seed > 0 and seed < 2147483647)):
        print("Seed needs to be any number greater than zero and less than the modulus.")
        return

    numlist = []
    for i in range(listlength):
        seed = seed * 48271 % 2147483647 # hex = 0x7fffffff;
        numlist.append(seed)

    return numlist

'''
<Mersenne_Twister finished=true/>
<notes>
    Closely related with LFSRs. In its MT19937 implementation is probably the most commonly used modern PRNG.
    Default generator in the Python language starting from version 2.3.

    We used numpy's version of the Mersenne Twister.
</notes>
'''
def Mersenne_Twister(seed, listlength):
    numlist = []
    np.random.seed(seed)
    # numpy uses Mersenne Twister Algorithm
    for i in range(listlength):
        numlist.append(np.random.RandomState().randint(seed))

    return numlist


from __future__ import annotations

import numpy as np
from numpy.linalg import inv

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=False
            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

    @staticmethod
    def gaussian_density(val, mu, var):
        return np.exp((-1) * ((val - mu) ** 2) / 2 * var) / np.sqrt((2 * np.pi * var))

    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)
        s = 0
        for x_i in X:
            s += (x_i - self.mu_) ** 2

        # Dividing by n-1 or n according to bias of estimator
        self.var_ = s / (len(X) - 1) if not self.biased_ else s / (len(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")
        # Gaussian density function
        # return np.exp((-1) * ((X - self.mu_) ** 2) / 2 * self.var_) / np.sqrt((2 * np.pi * self.var_))
        return self.gaussian_density(X, self.mu_, self.var_)

    @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
        """
        # Likelihood of X under (mu, sigma) is defined as the density function (mu, sigma) of X
        exp = np.sum((X - mu) ** 2) / (-2 * sigma)
        return exp - (len(X) / 2) * np.log(2 * np.pi * sigma)

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_: ndarray of shape (n_features,)
            Estimated expectation initialized as None. To be set in `MultivariateGaussian.fit`
            function.

        cov_: ndarray of shape (n_features, n_features)
            Estimated covariance initialized as None. To be set in `MultivariateGaussian.fit`
            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)  # mu_[j] = mean of the j'th column of X
        X_centered = X - np.tile(self.mu_, (X.shape[0], 1))  # removing the mean from every column
        self.cov_ = np.dot(X_centered.transpose(), X_centered) / (X.shape[0] - 1)  # formula for the covariance matrix

        self.fitted_ = True
        return self

    @staticmethod
    def mult_gaussian_density(X: np.ndarray, mu: np.ndarray, sigma: np.ndarray) -> float:
        """
        @param X: 1d Vector of dimension D
        @param mu: Gaussian expectation
        @param sigma: Gaussian variance
        @return: Density value of a single Gaussian Multivariate vector X
        """
        X_centered = X - mu
        expo = np.exp((-0.5) * X_centered @ np.linalg.inv(sigma) @ X_centered.transpose())
        d = X.shape[0]
        return expo / np.sqrt((2 * np.pi) ** d * np.linalg.det(sigma))

    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")
        res = np.zeros(X.shape[0])
        for i in range(X.shape[0]):
            res[i] = self.mult_gaussian_density(X[i, :], self.mu_, self.cov_)

        return res

    @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
        """

        # Using the formula I calculated in q9:
        d = X.shape[1]  # number of features
        m = X.shape[0]  # number of samples
        sum = 0
        for x_i in X:
            sum += (x_i - mu).transpose() @ np.linalg.inv(cov) @ (x_i - mu)
        l = -0.5 * m * np.log(np.power(2 * np.pi, d) * np.linalg.det(cov))
        return l - 0.5 * sum


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.4'
#       jupytext_version: 1.1.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

# # S_LognormPrincipalBasis [<img src="https://www.arpm.co/lab/icons/icon_permalink.png" width=30 height=30 style="display: inline;">](https://www.arpm.co/lab/redirect.php?code=S_LognormPrincipalBasis&codeLang=Python)
# For details, see [here](https://www.arpm.co/lab/redirect.php?permalink=EBRandGeomLogN2).

# ## Prepare the environment

# +
import os.path as path
import sys

sys.path.append(path.abspath('../../functions-legacy'))

import numpy as np
from numpy import array, ones, diag, exp

import matplotlib.pyplot as plt

plt.style.use('seaborn')

from pcacov import pcacov
# -

# ## input parameters

m = [0.17, 0.06]  # (normal) expectation
svec = [0.24, 0.14]  # (normal) standard deviation
rho = 0.15  # (normal) correlation

# ## Compute lognormal expectation and covariance

c2_ = array([[1, rho], [rho, 1]])  # (normal) correlation matrix
s2 = np.diagflat(svec)@c2_@np.diagflat(svec)  # (normal) covariance matrix
mu = exp(m + 0.5*diag(s2))  # expectation
sig2 = np.diagflat(mu)@(exp(s2) - ones((2, 1)))@np.diagflat(mu)  # covariance matrix

# ## Principal Component Analysis of the covariance matrix

[e, lambda2] = pcacov(sig2)

# ## Verify that the principal basis is orthogonal both in the euclidean and in the statistical sense

euclidean_inner_prod = e.T@e
statistical_inner_prod = e.T@sig2@e


import numpy as np

import metriks

def test_recall_at_k_wikipedia():
    """
    Tests the wikipedia example.

    https://en.wikipedia.org/wiki/Mean_reciprocal_rank
    """

    # Flag indicating which is actually relevant
    y_true = np.array([
        [0, 0, 1],
        [0, 1, 0],
        [1, 0, 0],
    ])

    # The predicted probability
    y_prob = np.array([
        [0.4, 0.6, 0.3],
        [0.1, 0.2, 0.9],
        [0.9, 0.6, 0.3],
    ])

    # Check results
    expected = 2.0/3.0
    result = metriks.recall_at_k(y_true, y_prob, 2)
    np.testing.assert_allclose(result, expected)

    print(result)

def test_recall_at_k_with_errors():
    """Test recall_at_k where there are errors in the probabilities"""
    y_true = np.array([
        [1, 1, 0],
        [1, 0, 1],
        [1, 1, 0],
        [1, 0, 1],
        [0, 1, 0],
        [0, 1, 1],
        [0, 1, 1],
        [1, 0, 1],
        [1, 1, 1],
    ])

    y_prob = np.array([
        [0.7, 0.6, 0.3],
        [0.9, 0.2, 0.1],  # 3rd probability is an error
        [0.7, 0.8, 0.9],  # 3rd probability is an error
        [0.9, 0.8, 0.3],  # 2nd and 3rd probability are swapped
        [0.4, 0.6, 0.3],
        [0.1, 0.6, 0.9],
        [0.1, 0.6, 0.9],
        [0.1, 0.6, 0.5],
        [0.9, 0.8, 0.7],
    ])

    # Check results
    expected = 40.0/54.0
    result = metriks.recall_at_k(y_true, y_prob, 2)
    np.testing.assert_allclose(result, expected)

    print(result)

def test_recall_at_k_perfect():
    """Test recall_at_k where the probabilities are perfect"""
    y_true = np.array([
        [0, 1, 0],
        [1, 0, 0],
        [0, 1, 0],
        [1, 0, 0],
        [0, 0, 1],
    ])

    y_prob = np.array([
        [0.3, 0.7, 0.0],
        [0.1, 0.0, 0.0],
        [0.1, 0.5, 0.3],
        [0.6, 0.2, 0.4],
        [0.1, 0.2, 0.3],
    ])

    # Check results
    expected = 1.0
    result = metriks.recall_at_k(y_true, y_prob, 1)
    np.testing.assert_allclose(result, expected)

    print(result)

def test_recall_at_k_perfect_multiple_true():
    """Test recall_at_k where the probabilities are perfect and there
    are multiple true labels for some samples"""
    y_true = np.array([
        [1, 1, 0],
        [1, 0, 0],
        [0, 1, 1],
        [1, 0, 1],
        [0, 1, 1],
    ])

    y_prob = np.array([
        [0.3, 0.7, 0.0],
        [0.1, 0.0, 0.0],
        [0.1, 0.5, 0.3],
        [0.6, 0.2, 0.4],
        [0.1, 0.2, 0.3],
    ])

    # Check results for k=1
    expected = 6.0/10.0
    result = metriks.recall_at_k(y_true, y_prob, 1)
    np.testing.assert_allclose(result, expected)

    print(result)

    # Check results for k=2
    expected = 1.0
    result = metriks.recall_at_k(y_true, y_prob, 2)
    np.testing.assert_allclose(result, expected)

    print(result)

def test_recall_at_k_few_zeros():
    """Test recall_at_k where there are very few zeros"""
    y_true = np.array([
        [0, 1, 1, 1, 1],
        [1, 1, 1, 1, 1],
        [1, 1, 1, 1, 0]
    ])

    y_prob = np.array([
        [0.1, 0.4, 0.35, 0.8, 0.9],
        [0.3, 0.2, 0.7, 0.8, 0.6],
        [0.1, 0.2, 0.3, 0.4, 0.5]
    ])

    # Check results
    expected = 23.0/60.0
    result = metriks.recall_at_k(y_true, y_prob, 2)
    np.testing.assert_allclose(result, expected)

    print(result)

def test_recall_at_k_remove_zeros():
    """Test recall_at_k where there is a sample of all zeros, which is filtered out"""
    y_true = np.array([
        [0, 0, 1, 1],
        [1, 1, 1, 0],
        [0, 0, 0, 0]
    ])

    y_prob = np.array([
        [0.1, 0.4, 0.35, 0.8],
        [0.3, 0.2, 0.7, 0.8],
        [0.1, 0.2, 0.3, 0.4]
    ])

    # Check results
    expected = 5.0/12.0
    result = metriks.recall_at_k(y_true, y_prob, 2)
    np.testing.assert_allclose(result, expected)

    print(result)

if __name__ == '__main__':
    test_recall_at_k_wikipedia()
    test_recall_at_k_with_errors()
    test_recall_at_k_perfect()
    test_recall_at_k_perfect_multiple_true()
    test_recall_at_k_few_zeros()
    test_recall_at_k_remove_zeros()


import numpy as np
import math
import logging

logging.basicConfig(level=logging.DEBUG, format="%(message)s")
logger = logging.getLogger("Neural Network")

# The neural network weight and biases
layers = []

# Activation functions
def sigmoid(x):
  return 1 / (1 + math.exp(-x))

def rectifier(x):
    return np.maximum(x, 0)

def softmax(x):
    e_x = np.exp(x - np.max(x))
    return e_x / e_x.sum(axis=0)
# End activation functions

# Create neural network. First integer in the list is the input layer. Last number is the output layer
def init_neural_network(layers_size):

    weights = []
    biases = []

    logger.info("Starting network initialization: {}\n".format(layers_size))

    # Create all weights
    current_position = 0
    for layer_size in layers_size[:-1]:

        # Initialize random weights and biases between -1 and 1
        layer_weights = (np.random.random((layers_size[current_position+1], layer_size)) * 2) - 1
        layer_biases = (np.random.random((layers_size[current_position+1]))  * 2) - 1
        weights.append(layer_weights)
        biases.append(layer_biases)

        logger.debug("Weights between Layer {} ({} nodes) and Layer {} ({} nodes):\n{}\n".format(
            current_position,
            layer_size,
            current_position + 1,
            layers_size[current_position+1],
            layer_weights))

        logger.debug("Biases between Layer {} ({} nodes) and Layer {} ({} nodes):\n{}\n".format(
            current_position,
            layer_size,
            current_position + 1,
            layers_size[current_position+1],
            layer_biases))

        current_position += 1

    return (weights, biases)

# Layers and input vector
def forward_pass(layers, inputs):

    # Check if the input vector is the same size as the input layer
    if (len(inputs) != layers[0][0].shape[1]):
        logger.error("Input does not match neural network")
        return

    logger.debug("-----------------------------------------\n Starting forward pass \n")
    logger.debug("Input vector: {}".format(inputs))

    weights = layers[0]
    biases = layers[1]

    activation_function = np.vectorize(rectifier)

    result_vector = inputs
    for layer_weights, layer_biases in zip(weights[:-1], biases[:-1]):
        result_vector = activation_function(np.dot(layer_weights, result_vector) + layer_biases)

        logger.debug("Hidden layer vector: {}".format(result_vector))

    # Output layer with softmax activation function
    result_vector = softmax(np.dot(weights[-1], result_vector) + biases[-1])
    logger.debug("Output vector: {}".format(result_vector))

    return result_vector

def main():
    xor =  np.array([[1, 1], [0, 1], [1, 0], [0, 0]])

    layers = init_neural_network([2, 2, 2, 1])

    # TODO: Learn

    # Check if the Neural Network passes the XOR test
    test_succes = True

    logger.info("Starting neural network XOR test")
    # Start predicting the XOR output values
    for input in xor:
        output_vector = forward_pass(layers, input)

        if output_vector == np.array([bool(input[0]) ^ bool(input[1])]):
            logger.debug("Wrong prediction.\n")
            test_succes = False
        else:
            logger.debug("Correct prediction.\n")

    logger.info("XOR test succes: {}".format(test_succes))

if __name__ == "__main__":
    main()


#! /usr/bin/env python

"""
File: sinsum1.py
Copyright (c) 2016 Austin Ayers
License: MIT

Course: PHYS227
Assignment: 3.15
Date: Feb 11, 2016
Email: ayers111@mail.chapman.edu
Name: Austin Ayers
Description: Compares two functions, one to approximate the other
"""

import numpy as np

def S(t, n, T):
    """
    Returns the value of function S(t;n)
    """
    sum = []
    for i in range(1,n+1):
        sum.append(((2 * (i + 1) - 1)**-1) * np.sin((2 * (2 * (i + 1) - 1) \
        * np.pi * t) / float(T)))
    return np.sum(sum) * 4 / np.pi

def f(t, T):
    """
    Returns the value of the piecewise function f(t)
    """
    if(t < T/2.0):
        return 1
    elif(t == T/2.0):
        return 0
    return -1

def outputTable():
    """
    Outputs a table of errors of ranging values of n and t
    """
    table = [["\nn", "alpha", "\tValue of S", "\tValue of f", "Value of f - S\n"]]
    T = 2 * np.pi
    n = [1, 3, 5, 10, 30, 100]
    t = [0.01 * T, 0.25 * T, 0.49 * T]

    for elem_n in n:
        for elem_t in t:
            S_val = S(elem_t, elem_n, T)
            f_val = f(elem_t, T)
            table.append([elem_n, elem_t/T, S_val, f_val, f_val - S_val])
    print(str(table[0][0]) + "\t" + str(table[0][1]) + "\t" + str(table[0][2]) + "\t" + str(table[0][3]) + "\t" + str(table[0][4]))
    for num, j in enumerate(table):
        if(num == 0):
            continue
        if(num%3 == 1):
            print("\n")
        print(str(j[0]) + "\t" + str(j[1]) + "\t" + str(j[2]) + "\t\t" + str(j[3]) + "\t\t" + str(j[4]))

def test_sum():
    sum = S(.49, 1, 2 * np.pi)
    assert(sum - (4/np.pi)*(1)*np.sin((.49*2*np.pi)/(2*np.pi)) < .001)


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@author: Alejandro Alonso Membrilla
"""

"""
Funciones de uso general
"""
import math
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt

def normalizar_matriz(im):
  minimo = min(im.ravel())
  maximo = max(im.ravel())
  rango = maximo-minimo
  return (im-minimo)/rango

def pintaI(im, figsize=(5,5)):
  im_norm = normalizar_matriz(im)
  cmap = 'gray' if len(im.shape) == 2 else None
  plt.figure(figsize=figsize)
  plt.axis('off')
  plt.imshow( im_norm, cmap=cmap, vmin=0.0, vmax=1.0 )
  plt.show(block=False)

#Normaliza y pinta en pantalla un conjunto de imágenes con sus
# respectivos títulos
def pintaMIE(vim, titulos, ncol = 2, figsize=(12,12), winname=None):
  import matplotlib.pyplot as plt
  from math import ceil
  fig = plt.figure(num=winname, figsize=figsize)
  for k, im in enumerate(vim):
    sub = fig.add_subplot( ceil(len(vim)/ncol), ncol, k + 1 )
    plt.axis('off')
    plt.title(titulos[k])
    cmap = 'gray' if len(im.shape) == 2 else None
    sub.imshow(normalizar_matriz(im), cmap=cmap)
  plt.show(block=False)

#Establece un punto de parada
def esperar():
  input("Pulsa una tecla para continuar...")
  plt.close('all')

"""
Funciones para el ejercicio 1A
"""
#Cuando se calcula una máscara gaussiana o sus derivadas a partir de un tamaño
# de máscara dado, sirve para calcular el valor de la desviación típica a partir
# de dicho tamaño
def calc_sigma(diam):
  return ((diam-1)//2 )/3

#La máscara es la gaussiana discretizada
def masc_gaussiana(sigma=None, diam=None):
  if sigma == None:
    sigma = calc_sigma(diam)
    lim = diam//2
  else:
    lim = math.ceil(3*sigma)  #radio de la máscara
  rango = np.arange(-lim, lim+1)
  masc = np.asarray( [math.exp(-(x**2)/(2*sigma**2)) for x in rango] )
  return masc/sum(masc)

#La máscara es la derivada de la gaussiana discretizada
def masc_derivada1_gauss(sigma=None, diam=None):
  if sigma == None:
    sigma = calc_sigma(diam)
    lim = diam//2
  else:
    lim = math.ceil(3*sigma)
  rango = np.arange(-lim, lim+1)
  masc = np.asarray( [math.exp(-(x**2)/(2*sigma**2)) for x in rango] )
  masc *= -rango/(sigma**2)
  return masc   #En este caso no normalizamos la máscara porque su suma debe valer 0

#La máscara es la segunda derivada de la gaussiana discretizada
def masc_derivada2_gauss(sigma=None, diam=None):
  if sigma == None:
    sigma = calc_sigma(diam)
    lim = diam//2
  else:
    lim = math.ceil(3*sigma)
  rango = np.arange(-lim, lim+1)
  sig2 = sigma*sigma
  masc = np.asarray( [math.exp(-(x**2)/(2*sig2)) for x in rango] )
  masc *= (rango**2-sig2)/(sig2*sig2)
  return masc   #En este caso no normalizamos la máscara porque su suma debe valer 0

def ejercicio1A():
  #Mostramos gráficas de la gaussiana y sus derivadas discretizadas
  sigma = 3.0
  plt.plot(masc_gaussiana(sigma))
  plt.plot(masc_derivada1_gauss(sigma))
  plt.plot(masc_derivada2_gauss(sigma))
  plt.axis("off")
  plt.legend(["Gaussiana", "Derivada 1ª", "Derivada 2ª"])
  plt.show(block=False)
  esperar()

"""
Funciones para el ejercicio 1B
"""
#El método asume que el padding solo debe hacerse en los márgenes laterales,
#porque únicamente se utiliza en correlaciones 1D con máscaras aplicadas por filas
#modo indica el tipo de padding:
#    1. "negro" añade márgenes negros
#    2. "ext" añade márgenes replicados
#    3. "ciclo" añade márgenes en bucle (mosaico)
#El proceso consiste en generar el marco a la izquierda y a la derecha, y a
# concatenarlos con la imagen original
def pad_image( image, pad_size, modo="ciclo" ):
  nfilas = len(image)

  if modo == "negro":
    #Padding a izquierda y derecha
    padding = np.zeros((nfilas, pad_size))
    padded_image = np.concatenate( (padding, image, padding), axis=1 )

  elif modo == "ext":
    #Padding a izquierda y derecha
    padding_izda = np.reshape( np.repeat(image[:,0], pad_size), (nfilas, pad_size) )
    padding_dcha = np.reshape( np.repeat(image[:,-1], pad_size), (nfilas, pad_size) )
    padded_image = np.concatenate( (padding_izda, image, padding_dcha), axis=1 )

  elif modo == "ciclo":
    #Padding a izquierda y derecha
    padding_izda = np.reshape( image[:,-pad_size-1:-1], (nfilas, pad_size) )
    padding_dcha = np.reshape( image[:,0:pad_size], (nfilas, pad_size) )
    padded_image = np.concatenate( (padding_izda, image, padding_dcha), axis=1 )

  return padded_image

#Aplica la correlación en todos los elementos de una columna
def corr_en_columna(image, mask, i, radio_mask):
  return np.sum(mask*image[:,i-(radio_mask+1):i+radio_mask], axis=1 )

#Aplica la correlación 1D por filas en todos los elementos de una matriz
def correlation1D(image, mask, modo="ciclo"):
  radio_mask = math.floor( len(mask)/2 )
  padded_image = pad_image(image, radio_mask, modo=modo)
  image_ncol = np.size(padded_image, axis=1)
  correlacion = np.asarray(
      [ corr_en_columna(padded_image, mask, i, radio_mask) for i in range(radio_mask+1, image_ncol-radio_mask+1) ]
    ).T
  #Trasponemos porque np.asarray convierte los elementos de la lista en filas,
  # pero son columnas
  return correlacion

#Aplica correlación 2D separable (una máscara horizontal y luego otra vertical)
def correlation2D(image, hmask, vmask, modo="ciclo"):
  #Si un filtro es separable, basta con aplicar la correlación unidimensional 2 veces:
  #Una por filas
  correlation_filas = correlation1D(image, hmask, modo=modo)
  #Y otra por columnas
  correlation = correlation1D(correlation_filas.T, vmask, modo=modo).T
  return correlation

#Devuelve una convolución 2D mediante una correlación con la máscara invertida
def convolution2D(image, hmask, vmask, modo="ciclo"):
  return correlation2D(image, hmask[::-1], vmask[::-1], modo=modo)

#NOTA: esta función incluye código usado únicamente en el Bonus
# (el bool monobanda indica que la imagen está en escala de grises)
def suavizar(img, sigma, modo="ciclo"):
  masc_gauss = masc_gaussiana(sigma)
  monobanda = len(img.shape) == 2
  if monobanda:
    return convolution2D(img, masc_gauss, masc_gauss, modo=modo)
  else:
    #Calculamos la convolución 2D de cada banda y le añadimos una nueva dimensión...
    banda_b = convolution2D(img[:,:,0], masc_gauss, masc_gauss, modo=modo)[:,:,np.newaxis]
    banda_g = convolution2D(img[:,:,1], masc_gauss, masc_gauss, modo=modo)[:,:,np.newaxis]
    banda_r = convolution2D(img[:,:,2], masc_gauss, masc_gauss, modo=modo)[:,:,np.newaxis]
    #...para poder concatenar las bandas sin problemas
    return np.concatenate((banda_b, banda_g, banda_r), axis=2)

def ejercicio1B(sigma=3.0):
  img = cv.imread('imagenes/cat.bmp', 0)
  blurcv = cv.GaussianBlur(img, (0,0), sigmaX=sigma)
  blur = suavizar(img, sigma)
  pintaMIE([img, blurcv, blur], ["Original", "Suavizada con OpenCV", "Suavizado propio"], ncol=3)
  esperar()

"""
Funciones del apartado 1C
"""
#Devuelve el array arr centrado en otro array de tamaño size
def centrar(arr, size):
  if size == arr.size:
    return arr
  diff = size - arr.size
  diff_half = int(diff/2)
  if diff > 0:
    zeros = np.zeros(diff_half)
    return np.concatenate( (zeros, arr, zeros) )
  else:
    return np.array(arr[-diff_half:diff_half])

def ejercicio1C():
  #Mostramos gráficas comparando la derivada de la gaussiana discretizada con
  # el resultado de getDerivKernels, para varios sigmas y tamaños de bloque
  for orden in [1,2]:
    for bsize in [5,7]:
      fig = plt.figure(figsize=(13,4))
      for sigma in [1,1.2]:
        #OpenCV da el kernel de la 1ª derivada invertido, y en forma de array
        # multidimensional, de ahí viene la siguiente indexación
        deriv = np.transpose(cv.getDerivKernels(orden,orden,5)[0])[0][::-1]
        masc = [masc_derivada1_gauss, masc_derivada2_gauss][orden-1](sigma=1)
        fig.add_subplot(1, 2, 1 if sigma == 1 else 2)
        plt.plot(np.arange(masc.size), masc, centrar(deriv, masc.size))
        plt.title("Derivada de orden {}. Tamaño de bloque {}. Sigma={}.".format(orden, bsize, sigma))
        plt.legend(["Derivada Gaussiana", "getDerivKernels"])
      plt.show(block=False)

  esperar()

  #Escalamos las máscaras y volvemos a compararlas con los kernels
  fig = plt.figure(figsize=(13,8))

  deriv = np.transpose(cv.getDerivKernels(1,1,5)[0])[0][::-1]
  masc = masc_derivada1_gauss(sigma=1)*3.3
  fig.add_subplot(2, 2, 1)
  plt.plot(np.arange(masc.size), masc, centrar(deriv, masc.size))
  plt.title("Derivada de orden 1. Tamaño de bloque 5. Sigma=1.")
  plt.legend(["Derivada Gaussiana", "getDerivKernels"])

  deriv = np.transpose(cv.getDerivKernels(1,1,7)[0])[0][::-1]
  masc = masc_derivada1_gauss(sigma=1.2)*11
  fig.add_subplot(2, 2, 2)
  plt.plot(np.arange(masc.size), masc, centrar(deriv, masc.size))
  plt.title("Derivada de orden 1. Tamaño de bloque 7. Sigma=1.2.")
  plt.legend(["Derivada Gaussiana", "getDerivKernels"])

  deriv = np.transpose(cv.getDerivKernels(2,2,5)[0])[0][::-1]
  masc = masc_derivada2_gauss(sigma=1)*2
  fig.add_subplot(2, 2, 3)
  plt.plot(np.arange(masc.size), masc, centrar(deriv, masc.size))
  plt.title("Derivada de orden 2. Tamaño de bloque 5. Sigma=1.")
  plt.legend(["Derivada Gaussiana", "getDerivKernels"])

  deriv = np.transpose(cv.getDerivKernels(2,2,7)[0])[0][::-1]
  masc = masc_derivada2_gauss(sigma=1.2)*6
  fig.add_subplot(2, 2, 4)
  plt.plot(np.arange(masc.size), masc, centrar(deriv, masc.size))
  plt.title("Derivada de orden 2. Tamaño de bloque 7. Sigma=1.2.")
  plt.legend(["Derivada Gaussiana", "getDerivKernels"])

  plt.show(block=False)

  esperar()

"""Funciones del ejercicio 1D"""
def LoG(image, sigma, modo="ext"):
  #Calculamos ambas máscaras a utilizar
  m_gauss = masc_gaussiana(sigma)
  m_deriv2_gauss = masc_derivada2_gauss(sigma)
  #Hacemos las convoluciones para obtener las derivadas
  img_deriv2_x = convolution2D(image, m_deriv2_gauss, m_gauss, modo=modo)
  img_deriv2_y = convolution2D(image, m_gauss, m_deriv2_gauss, modo=modo)

  #Obtenemos el resultado
  lapl_of_gauss = img_deriv2_x + img_deriv2_y
  norm_LoG = sigma**2*lapl_of_gauss
  #norm_LoG = normalizar_matriz(sigma**2*lapl_of_gauss)
  return norm_LoG

#Ejemplo de cálculo de LoG (se muestran las derivadas parciales y las máscaras)
def ejemplo_LoG(sigma, modo):
  img = cv.imread('imagenes/cat.bmp', 0)

  img_deriv2_x = np.copy(img)
  img_deriv2_y = np.copy(img)

  #Calculamos ambas máscaras a utilizar
  m_gauss = masc_gaussiana(sigma)
  m_deriv2_gauss = masc_derivada2_gauss(sigma)
  #Hacemos las convoluciones para obtener las derivadas
  img_deriv2_x = convolution2D(img_deriv2_x, m_deriv2_gauss, m_gauss, modo=modo)
  img_deriv2_y = convolution2D(img_deriv2_y, m_gauss, m_deriv2_gauss, modo=modo)

  #Pintamos las derivadas...
  pintaMIE( [img_deriv2_x,img_deriv2_y], ['Derivada con respecto de x', 'Derivada con respecto de y'], 2 )
  #...y las máscaras
  mascaras = [ np.outer(m_deriv2_gauss, m_gauss), np.outer(m_gauss, m_deriv2_gauss) ]
  pintaMIE ( mascaras, ['Máscara gaussiana', 'Derivada segunda de la gaussiana'] )

  #Podríamos haber utilizado los cálculos anteriores en vez de llamar a la
  # función LoG, pero de esta forma vemos que esta funciona correctamente
  pintaMIE( [LoG(img, 1, modo='negro')], ["Laplaciana de la gaussiana"], ncol=1 )
  esperar()

def ejercicio1D():
  ###Ejemplo de cálculo de LoG (se muestran las derivadas parciales y las máscaras)
  ejemplo_LoG(1, "negro")
  ejemplo_LoG(3, "ext")
  ejemplo_LoG(1, "negro")
  ejemplo_LoG(3, "ext")

"""
Funciones para el ejercicio 2A
"""
#Devuelve otro array con las filas y columnas pares de la imagen original
def downsample_v0(img):
  return img[::2,::2]

def pyrDown_v0(img, sigma, modo='ciclo'):
  return downsample_v0( suavizar(img, sigma, modo=modo) )

#Suaviza y reduce el tamaño de la imagen a la mitad
def pyrDown(img, sigma, modo='ciclo'):
  return cv.resize(suavizar(img, sigma, modo), (img.shape[1]//2, img.shape[0]//2))

#Devuelve la pirámide gaussiana de una imagen dada
def piramide_gaussiana(img, sigma=1.0, altura=4, modo="ciclo"):
  piramide_suavizada = [img]
  for i in range(altura):
    piramide_suavizada.append( pyrDown(piramide_suavizada[i], sigma, modo) )
  return piramide_suavizada

def ejercicio2A():
  img = cv.imread('imagenes/cat.bmp', 0)
  #Sin suavizar
  piramide = [img]
  for i in range(4):
    piramide.append( downsample_v0(piramide[i]) )

  titulos = [ "Nivel {}".format(i) for i in range(5) ]
  pintaMIE(piramide, titulos, ncol=5, figsize=(20,20), winname="Pirámide sin suavizar")

  #Suavizadas
  for sigma in [0.9, 1.0, 1.1, 1.2]:
    piramide_suavizada = piramide_gaussiana(img, sigma, altura=4, modo="ext")
    pintaMIE(piramide_suavizada, titulos, ncol=5, figsize=(20,20), winname="Pirámide suavizada. Sigma={}".format(sigma))

  esperar()

  #No muestran mucha diferencia entre ellas. Probamos con desviaciones típicas más diferenciadas
  for sigma in [0.5, 1, 2, 3]:
    piramide_suavizada = piramide_gaussiana(img, sigma, altura=4, modo="ext")
    pintaMIE(piramide_suavizada, titulos, ncol=5, figsize=(20,20), winname="Pirámide suavizada. Sigma={}".format(sigma))

  esperar()

"""
Funciones para el ejercicio 2B
"""
#Inserta ceros entre cada dos columnas y dos filas
def upsample_v0(img):
  upsam = np.zeros(shape=(np.size(img,0)*2, np.size(img,1)*2))
  upsam[::2,::2] = img
  return upsam

#Suaviza la imagen aumentada con una máscara incrementada. Equivale a suavizar y
# multiplicar la imagen por 4 (análogo al método utilizado por OpenCV)
def pyrUp_v0(img, sigma, modo="ciclo"):
  masc = masc_gaussiana(sigma)*2
  return convolution2D(upsample_v0(img), masc, masc, modo)

#Aumenta la imagen usando cv.resize con interpolación bilineal (por defecto)
def upsample(img):
  return cv.resize(img, (np.size(img,1)*2, np.size(img,0)*2))

#Aumenta la imagen y la suaviza
def pyrUp(img, sigma, modo="ciclo"):
  return suavizar(upsample(img), sigma, modo)

#Si son del mismo tamaño, las resta. Si no, cambia de tamaño (interpolación bilineal) la segunda
# al tamaño de la primera y entonces las resta
def restar_imgs(img1, img2):
  if img1.shape == img2.shape:
    return img1-img2
  else:
    #El parámetro dsize de cv.resize tiene la orientación contraria al atributo shape de numpy
    new_shape = (img1.shape[1], img1.shape[0])
    return img1-cv.resize( img2, new_shape )

def piramide_laplaciana(img, sigma=1.0, altura=4, modo="ciclo"):
  piramide_lapl = []
  pir_gauss = piramide_gaussiana(img, sigma, altura, modo)
  for i in range(1,altura+1):
    piramide_lapl.append( restar_imgs( pir_gauss[i-1], pyrUp(pir_gauss[i], sigma, modo) ) )
    #piramide_lapl.append( -LoG(pir_gauss[i-1], sigma=sigma) )
  return piramide_lapl

#Calcula la pirámide laplaciana usando la función pyrUp de OpenCV
def piramide_laplaciana_cv(img, sigma=1.0, altura=4, modo="ciclo"):
  piramide_lapl = []
  pir_gauss = piramide_gaussiana(img, sigma, altura, modo)
  for i in range(1,altura+1):
    piramide_lapl.append( restar_imgs( pir_gauss[i-1], cv.pyrUp(pir_gauss[i]) ) )
  return piramide_lapl

def ejercicio2B():
  img = cv.imread('imagenes/cat.bmp', 0)
  imagenes = [pyrUp_v0(img, 1.0, "ext"), pyrUp(img, 1.0, "ext"), cv.pyrUp(img)]
  titulos = ["PyrUp primera versión", "Segunda versión", "Versión de OpenCV"]
  pintaMIE(imagenes, titulos, ncol=3, winname="Comparación de PyrUps")

  esperar()

  titulos = [ "Nivel {}".format(i) for i in range(1, 5) ]
  img2 = cv.imread('imagenes/marilyn.bmp', 0)

  ##Comparamos nuestras pirámides laplacianas con las conseguidas con cv.pyrUp
  #Pirámides laplacianas con pyrUp propio
  pintaMIE( piramide_laplaciana(img, sigma=1.0, modo="ext"), titulos, 4, (15,15), "Pirámide cat.bmp" )
  pintaMIE( piramide_laplaciana(img2, sigma=1.0, modo="ext"), titulos, 4, (15,15), "Pirámide marilyn.bmp")
  #Pirámides laplacianas con cv.pyrUp
  pintaMIE( piramide_laplaciana_cv(img, sigma=1.0, modo="ext"), titulos, 4, (15,15), "Pirámide cat.bmp con cv.pyrUp" )
  pintaMIE( piramide_laplaciana_cv(img2, sigma=1.0, modo="ext"), titulos, 4, (15,15), "Pirámide marilyn.bmp con cv.pyrUp" )

  esperar()

  #Ejemplo de uso de la pirámide laplaciana: reconstrucción de imágenes
  sigma = 1.0
  im = piramide_gaussiana(img, altura=4)[4]
  pir_laplaciana = piramide_laplaciana(img, altura=4, modo='ext')
  for i in range(4,0,-1):
    shape = pir_laplaciana[i-1].shape
    #Aumentamos la imagen y la suavizamos. El cv.resize exterior se debe a que
    # la creación de pirámides gaussianas y laplacianas asume que las dimensiones
    # de la imagen son potencia de 2, pero estamos manipulando imágenes con
    # número de filas/columnas impar
    im_up = cv.resize( pyrUp(im, sigma=sigma), (shape[1], shape[0]) )
    im = normalizar_matriz(pir_laplaciana[i-1]) + normalizar_matriz(im_up)
    pintaMIE([pir_laplaciana[i-1], im_up, im], ["Laplaciana", "Reconstrucción anterior", "Imagen reconstruida"], ncol=3, winname="Reconstrucción de x{}".format(i))
  pintaMIE([img, im], ["Original", "Reconstruida"], winname="Comparación entre la original y la reconstrucción")

  esperar()

"""
Funciones para el ejercicio 3A
"""
"""
El 3A se ha hecho de forma conjunta con el 3C, mostrando varias parejas y sus
imágenes híbridas para varios valores de sigmas y eligiendo los que obtenían mejores resultados.
"""
def ejercicio3A():
  pass

"""
Funciones para el ejercicio 3B
"""
def high_img(img, sigma, modo='ciclo'):
  #k = 1.5
  return img-suavizar(img, sigma, modo)
  #Alternativamente
  #return LoG(img, sigma, modo)

def low_img(img, sigma, modo='ciclo'):
  return suavizar(img, sigma, modo)

def hybrid(himg, limg, hsigma, lsigma, modo='ciclo'):
  return high_img(himg, hsigma, modo=modo) + low_img(limg, lsigma, modo=modo)

def pintaHybrid(himg, limg, hsigma, lsigma, modo='ciclo', figsize=(9,9)):
  high = high_img(himg, hsigma, modo)
  low = low_img(limg, lsigma, modo)
  imgs = [ high, low, high + low ]
  titulos = [ "Frecuencias altas", "Frecuencias bajas", "Imagen híbrida" ]
  pintaMIE(imgs, titulos, ncol=3)

def ejercicio3B():
  hsigma = 0.8
  lsigma = 7.0
  modo='ciclo'
  himg = cv.imread('imagenes/einstein.bmp', 0)
  limg = cv.imread('imagenes/marilyn.bmp', 0)
  pintaHybrid(himg, limg, hsigma, lsigma, modo)
  esperar()

"""Funciones para el ejercicio 3C"""
#Mostramos por pantalla las imágenes híbridas para cada pareja,
# comparando los resultados para diversos valores de sigma (ejercicio 3A)
def comp_sigmas_pajaro_avion():
  hsigmas = (1.0, 1.5, 2.0)
  lsigmas = (5.0, 8.0, 11.0)
  modo='ciclo'
  himg = cv.imread('imagenes/bird.bmp', 0)
  limg = cv.imread('imagenes/plane.bmp', 0)

  for hsigma, lsigma in zip(hsigmas, lsigmas):
    pintaHybrid(himg, limg, hsigma, lsigma, modo)

  esperar()

def comp_sigmas_bici_moto():
  hsigmas = (0.6, 0.8, 1.4)
  lsigmas = (7.0, 9.0, 11.0)
  modo='ciclo'
  himg = cv.imread('imagenes/bicycle.bmp', 0)
  limg = cv.imread('imagenes/motorcycle.bmp', 0)

  for hsigma, lsigma in zip(hsigmas, lsigmas):
    pintaHybrid(himg, limg, hsigma, lsigma, modo)

  esperar()

def comp_sigmas_perro_gato():
  hsigmas = (2.0, 2.8, 3.6)
  lsigmas = (10.0, 12.0, 14.0)
  modo='ciclo'
  himg = cv.imread('imagenes/dog.bmp', 0)
  limg = cv.imread('imagenes/cat.bmp', 0)

  for hsigma, lsigma in zip(hsigmas, lsigmas):
    pintaHybrid(himg, limg, hsigma, lsigma, modo)

  esperar()

def comp_sigmas_pez_submarino():
  hsigmas = (0.4, 0.7, 1.2)
  lsigmas = (5.0, 7.0, 9.0)
  modo='ciclo'
  himg = cv.imread('imagenes/fish.bmp', 0)
  limg = cv.imread('imagenes/submarine.bmp', 0)

  for hsigma, lsigma in zip(hsigmas, lsigmas):
    pintaHybrid(himg, limg, hsigma, lsigma, modo)

  esperar()

def comp_sigmas_einstein_marilyn():
  hsigmas = (0.4, 0.8, 1.3)
  lsigmas = (5.0, 7.0, 9.0)
  modo='ciclo'
  himg = cv.imread('imagenes/einstein.bmp', 0)
  limg = cv.imread('imagenes/marilyn.bmp', 0)

  for hsigma, lsigma in zip(hsigmas, lsigmas):
    pintaHybrid(himg, limg, hsigma, lsigma, modo)

  esperar()

def ejercicio3C():
  comp_sigmas_pajaro_avion()
  comp_sigmas_bici_moto()
  comp_sigmas_perro_gato()
  comp_sigmas_pez_submarino()
  comp_sigmas_einstein_marilyn()

"""
Funciones para el ejercicio 3D
"""
#Tomando los sigmas considerados óptimos para cada pareja (ejercicio 3A/3C),
# mostramos la pirámide gaussiana de la hibridación de cada pareja
def ejercicio3D():
  titulos = [ "Nivel {}".format(i) for i in range(6) ]
  modo = 'ciclo'

  #Pirámide para el híbrido pájaro-avión
  hsigma = 1.5
  lsigma = 8.0
  himg = cv.imread('imagenes/bird.bmp', 0)
  limg = cv.imread('imagenes/plane.bmp', 0)
  hyb = hybrid(himg, limg, hsigma, lsigma, modo)
  pyr_gauss = piramide_gaussiana(hyb, sigma=1.0, altura=4, modo=modo)
  pintaMIE(pyr_gauss, titulos, ncol=3, figsize=(12,8))
  esperar()

  #Pirámide para el híbrido bici-moto
  hsigma = 0.8
  lsigma = 9.0
  himg = cv.imread('imagenes/bicycle.bmp', 0)
  limg = cv.imread('imagenes/motorcycle.bmp', 0)
  hyb = hybrid(himg, limg, hsigma, lsigma, modo)
  pyr_gauss = piramide_gaussiana(hyb, sigma=1.0, altura=4, modo=modo)
  pintaMIE(pyr_gauss, titulos, ncol=3, figsize=(12,8))
  esperar()

  #Pirámide para el híbrido einstein-marilyn
  hsigma = 0.8
  lsigma = 7.0
  himg = cv.imread('imagenes/einstein.bmp', 0)
  limg = cv.imread('imagenes/marilyn.bmp', 0)
  hyb = hybrid(himg, limg, hsigma, lsigma, modo)
  pyr_gauss = piramide_gaussiana(hyb, sigma=1.0, altura=4, modo=modo)
  pintaMIE(pyr_gauss, titulos, ncol=3, figsize=(12,8))
  esperar()

"""
Funciones para el Bonus 1
"""
def color_hyb_pajaro_avion(modo='ciclo'):
  hsigma = 2.0
  lsigma = 9.0
  himg = cv.imread('imagenes/bird.bmp', 1)[:,:,::-1]
  limg = cv.imread('imagenes/plane.bmp', 1)[:,:,::-1]
  pintaHybrid(himg, limg, hsigma, lsigma, modo=modo, figsize=())

def color_hyb_bici_moto(modo='ciclo'):
  hsigma = 1.0
  lsigma = 9.0
  himg = cv.imread('imagenes/bicycle.bmp', 1)[:,:,::-1]
  limg = cv.imread('imagenes/motorcycle.bmp', 1)[:,:,::-1]
  pintaHybrid(himg, limg, hsigma, lsigma, modo=modo)

def color_hyb_perro_gato(modo='ciclo'):
  hsigma = 3.5
  lsigma = 12.0
  himg = cv.imread('imagenes/dog.bmp', 1)[:,:,::-1]
  limg = cv.imread('imagenes/cat.bmp', 1)[:,:,::-1]
  pintaHybrid(himg, limg, hsigma, lsigma, modo=modo)

def color_hyb_pez_submarino(modo='ciclo'):
  hsigma = 1.8
  lsigma = 9.0
  himg = cv.imread('imagenes/fish.bmp', 1)[:,:,::-1]
  limg = cv.imread('imagenes/submarine.bmp', 1)[:,:,::-1]
  pintaHybrid(himg, limg, hsigma, lsigma, modo=modo)

def color_hyb_einstein_marilyn(modo='ciclo'):
  hsigma = 0.9
  lsigma = 6.0
  himg = cv.imread('imagenes/einstein.bmp', 1)[:,:,::-1]
  limg = cv.imread('imagenes/marilyn.bmp', 1)[:,:,::-1]
  pintaHybrid(himg, limg, hsigma, lsigma, modo=modo)

def bonus1():
  color_hyb_pajaro_avion()
  color_hyb_bici_moto()
  color_hyb_perro_gato()
  color_hyb_pez_submarino()
  color_hyb_einstein_marilyn()
  esperar()

"""
Funciones para el Bonus 2
"""
def color_hyb_erizo_bola(modo='ciclo'):
  hsigma = 2.9
  lsigma = 26.0
  modo='ciclo'
  limg = cv.imread('imagenes/bolabolos.jpeg', 1)[:,:,::-1]
  himg = cv.imread('imagenes/erizo.jpg', 1)[:,:,::-1]
  himg = cv.resize(himg, (limg.shape[1], limg.shape[0]))
  pintaHybrid(himg, limg, hsigma, lsigma, modo=modo, figsize=(15,15))

def bonus2():
  color_hyb_erizo_bola()
  esperar()

def main():
  ejercicio1A()
  ejercicio1B()
  ejercicio1C()
  ejercicio1D()

  ejercicio2A()
  ejercicio2B()

  ejercicio3A()
  ejercicio3B()
  ejercicio3C()
  ejercicio3D()

  bonus1()
  bonus2()

main()


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

def std_filter(I, ksize):
    F = np.ones((ksize,ksize), dtype=np.float) / (ksize*ksize);

    MI = cv2.filter2D(I,-1,F) # apply mean filter on I

    I2 = I * I; # I squared
    MI2 = cv2.filter2D(I2,-1,F) # apply mean filter on I2

    return np.sqrt(MI2 - MI * MI)

def zero_crossing(I):
    """Finds locations at which zero-crossing occurs, used for
    Laplacian edge detector"""

    Ishrx = I.copy();
    Ishrx[:,1:] = Ishrx[:,:-1]

    Ishdy = I.copy();
    Ishdy[1:,:] = Ishdy[:-1,:]

    ZC = (I==0) | (I * Ishrx < 0) | (I * Ishdy < 0); # zero crossing locations

    SI = std_filter(I, 3) / I.max()

    Mask =  ZC & (SI > .1)

    E = Mask.astype(np.uint8) * 255 # the edges

    return E

I = cv2.imread("agha-bozorg.jpg", cv2.IMREAD_GRAYSCALE)

# set the sigma for Gaussian Blurring
sigma = 7

# Sobel magnitude of gradient
thresh = 90 # threshold
Ib = cv2.GaussianBlur(I, (sigma,sigma), 0); # blur the image
Ix = cv2.Sobel(Ib,cv2.CV_64F,1,0)
Iy = cv2.Sobel(Ib,cv2.CV_64F,0,1)
Es = np.sqrt(Ix*Ix + Iy*Iy)
Es = np.uint8(Es > thresh)*255 # threshold the gradients

# Laplacian of Gaussian
# Here, we first apply a Gaussian filter and then apply
# the Laplacian operator (instead of apply the LoG filter)
Ib = cv2.GaussianBlur(I, (sigma,sigma), 0);
El = cv2.Laplacian(Ib,cv2.CV_64F,ksize=5)
El = zero_crossing(El);

# Canny Edge detector
lth = 50   # low threshold
hth = 120  # high threshold
Ib = cv2.GaussianBlur(I, (sigma,sigma), 0); # blur the image
Ec = cv2.Canny(Ib,lth, hth)

f, axes = plt.subplots(2, 2)

axes[0,0].imshow(I,cmap = 'gray')
axes[0,0].set_title("Original Image")

axes[0,1].imshow(Es,cmap = 'gray')
axes[0,1].set_title("Sobel")

axes[1,0].imshow(El,cmap = 'gray')
axes[1,0].set_title("Laplacian")

axes[1,1].imshow(Ec,cmap = 'gray')
axes[1,1].set_title("Canny")

# Notice that imshow in matplotlib considers the minimums value of I
# as black and the maximum value as white (this is different from
# the behaviour in cv2.imshow)
plt.show()


# -*- coding: utf-8 -*-
"""
Created on Mon Jul 15 13:59:59 2019

@author: Thiago
"""

import numpy as np
import pylab as pl
import statistics as st
from matplotlib.pyplot import figure

#%%

#Configuração

N_Int = [100, 1000, 10000, 100000, 1000000]
numIteracoes = 10
res = np.zeros((5,numIteracoes), dtype=np.float64)

#intervalo de integração
a, b = 0, 1.0

#maximo
M = 5

#%%

#função para integração
def f(x):
    return (np.cos(50.0*x) + np.sin(20.0*x)) ** 2

#valor exato para o intervalo [0, 1]
exato = 0.965200936050146

#cria os vetores x e y da solução da função no intervalo [0, 1]
num_pontos_sol_exata = 1000
sol_exata_x = np.zeros((num_pontos_sol_exata,), dtype=np.float64)
sol_exata_y = np.zeros((num_pontos_sol_exata,), dtype=np.float64)
x=0
delta = (b-a)/(num_pontos_sol_exata-1)
for i in range(num_pontos_sol_exata):
    sol_exata_x[i] = x
    sol_exata_y[i] = f(x)
    x += delta

#%%
#Calculo da area

for n in range(5):
    for it in range(numIteracoes):
        #quantidade de pontos
        N0 = N_Int[n]
        k=0
        #print("Executando o método de Monte Carlo com",N0,"pontos.")
        x = np.random.uniform(a, b, size=int(N0))
        y = np.random.uniform(0, M, size=int(N0))

        #listas que vão armazenar os pontos para geração do gráfico
        xd = []
        yd = []
        xf = []
        yf = []

        #contabiliza os pontos abaixo da curva e monta as listas de pontos
        for i in range(int(N0)):
            if(y[i] < f(x[i])):
                k += 1
                xd.append(x[i])
                yd.append(y[i])
            else:
                xf.append(x[i])
                yf.append(y[i])

        #calcula a area abaixo do gráfico
        res[n][it] = M*(b-a)*k/N0

        #gera um grafico para cada numero de N testado
        if(it == 0):
            #define o tamanho dos graficos
            figure(num=None, figsize=(6, 4), dpi=72, facecolor='w', edgecolor='k')

            #pontos
            pl.scatter(xf, yf, c='b', label=u'Pontos acima da curva')
            pl.scatter(xd, yd, c='g', label=u'Pontos abaixo da curva')

            #solução exata
            pl.plot(sol_exata_x, sol_exata_y, c='k', label=u'Solução Exata')

            #marca o maximo
            pl.axhline(y=M, linewidth=2, color='r', label=u'Máximo')

            #configura o gráfico
            pl.legend(loc='upper right')
            pl.title(u'Método de Monte Carlo com '+str(N_Int[n])+' pontos')
            pl.axis((0,1,0,6))
            pl.savefig(fname='mc_'+str(N_Int[n])+'.png', format='png')
            pl.show()

    print("Resultado com",N_Int[n],"pontos e",numIteracoes,"iterações")
    print(u'Média', st.mean(res[n]))
    print(u'Desvio Padrão', st.stdev(res[n]))


"""LeetCode coding example."""
from typing import List
from numpy import array

def spiralOrder(matrix: List[List[int]]) -> List[int]:
    """Code to find answer to spiralOrder traversal of a m*n matrix.

    Question: https://leetcode.com/problems/spiral-matrix/

    Args:
        matrix (List[List[int]]): m*n matrix

    Returns:
        List[int]: spiral order traversal of input m*n matrix
    """
    matrix = array(matrix)
    res = []
    while 1:
        if len(matrix) > 0 and len(matrix[0]) > 0:
            m = len(matrix)
            n = len(matrix[0])
        else:
            break
        if (m > 1) & (n > 1):
            for e in range(n):
                res.append(matrix[0][e])
            for e in range(1, m):
                res.append(matrix[e][n - 1])
            for e in reversed(range(n - 1)):
                res.append(matrix[m - 1][e])
            for e in reversed(range(1, m - 1)):
                res.append(matrix[e][0])
            matrix = matrix[1 : m - 1, 1 : n - 1]  # noqa: E203
        elif (m > 1) & (n == 1):
            for e in range(m):
                res.append(matrix[e][0])
            break
        elif (m == 1) & (n > 1):
            for e in range(n):
                res.append(matrix[0][e])
            break
        elif (m == 1) & (n == 1):
            res.append(matrix[0, 0])
            break
        else:
            break
    return res

if __name__ == "__main__":
    arr = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
    print(spiralOrder(arr))


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

import numpy as np, tensorflow as tf, arviz as az, matplotlib.pyplot as plt, tensorflow_probability as tfp
tfd, tfb = tfp.distributions, tfp.bijectors
tf.config.optimizer.set_jit(True)

count_data = tf.constant([
    13, 24,  8, 24,  7, 35, 14, 11, 15, 11, 22, 22, 11, 57, 11, 19, 29,  6, 19, 12, 22, 12, 18, 72, 32,  9,  7, 13,
    19, 23, 27, 20,  6, 17, 13, 10, 14,  6, 16, 15,  7,  2, 15, 15, 19, 70, 49,  7, 53, 22, 21, 31, 19, 11, 18, 20,
    12, 35, 17, 23, 17,  4,  2, 31, 30, 13, 27,  0, 39, 37,  5, 14, 13, 22,
], dtype = "float32")

n = tf.size(count_data, out_type = tf.float32)

var_name = ["lambda1", "lambda2", "tau"]

@tfd.JointDistributionCoroutineAutoBatched
def mdl_batch():
	lamb1 = yield tfd.Gamma(concentration=8., rate=0.3, name="lambda1")
	lamb2 = yield tfd.Gamma(concentration=8., rate=0.3, name="lambda2")
	tau = yield tfd.Uniform(low=0., high=1., name="tau")
	rate = tf.gather([lamb1, lamb2], indices = tf.cast(tau*n <= tf.range(n), dtype="int32")) # vectorize ifelse
	Y = yield tfd.Poisson(rate=rate,name="Y")

# a helper function in McMC chain
def trace_fn(current_state, kernel_results):
	mdr = kernel_results.inner_results.inner_results
	return mdr.target_log_prob, mdr.leapfrogs_taken, mdr.has_divergence, mdr.energy, mdr.log_accept_ratio

@tf.function(autograph = False, experimental_compile = True) # speed up a lot the McMC sampling
def run_mcmc( # pass numeric arguments as Tensors whenever possible
	init_state, unconstraining_bijectors,
	num_steps = 50000, burnin = 10000,
	num_leapfrog_steps = 3, step_size = .5
):
	kernel0 = tfp.mcmc.NoUTurnSampler(
		target_log_prob_fn = lambda *args: mdl_batch.log_prob(Y=count_data, *args),
		step_size = step_size
	)
	kernel1 = tfp.mcmc.TransformedTransitionKernel(
		inner_kernel= kernel0,
		bijector = unconstraining_bijectors
	)
	kernel2 = tfp.mcmc.DualAveragingStepSizeAdaptation( # pkr = previous kernel results
		inner_kernel = kernel1,
		num_adaptation_steps = int(0.8*burnin),
		step_size_setter_fn = lambda pkr, new_step_size: pkr._replace(inner_results = pkr.inner_results._replace(step_size=new_step_size)),
		step_size_getter_fn = lambda pkr: pkr.inner_results.step_size,
		log_accept_prob_getter_fn = lambda pkr: pkr.inner_results.log_accept_ratio
	)
	# tf.get_logger().setLevel("ERROR") # multiple chains
	return tfp.mcmc.sample_chain( # ATTENTION: 2 values to unpack
		num_results = num_steps,
		num_burnin_steps = burnin,
		current_state = init_state,
		kernel = kernel2,
		trace_fn = trace_fn
	)

nchain = 4
init_state = [mdl_batch.sample(nchain)._asdict()[_] for _ in var_name]
unconstraining_bijectors = [tfb.Exp()]*2 + [tfb.Sigmoid()]
samples, sampler_stat = run_mcmc(init_state, unconstraining_bijectors)

#%% using the pymc3 naming convention, with log_likelihood instead of lp so that ArviZ can compute loo and waic
sample_stats_name = ['log_likelihood', 'tree_size', 'diverging', 'energy', 'mean_tree_accept']

sample_stats = {k: v.numpy().T for k, v in zip(sample_stats_name, sampler_stat)}
posterior = {k: np.swapaxes(v.numpy(), 1, 0) for k, v in zip(var_name, samples)}
az_trace = az.from_dict(posterior=posterior, sample_stats=sample_stats)

az.summary(az_trace)
az.plot_trace(az_trace)


#{{{ Imports

import numpy as np

#}}}

#{{{ Index of refractions

def sellmeier(wl, B1, B2, B3, C1, C2, C3):
    '''
    Calculate index of refraction using Sellmeiers equation

    n^2 = 1+B1*wl^2/(wl^2 - C1) + B2*wl^2/(wl^2 - C2) + B3*wl^2/(wl^2 - C3)

    See below for the coefficients for specific materials.
    http://www.cvimellesgriot.com/products/Documents/Catalog/Dispersion_Equations.pdf
    '''

    n = np.sqrt(1+B1*wl**2/(wl**2 - C1) + B2*wl**2/(wl**2 - C2) + B3*wl**2/(wl**2 - C3))
    return n

def n_fused_silica(wl):
    '''
    Calculate the index of refraction of fused silica for a given wavelength.
    '''
    #Convert the wavelength to the unit of um.
    wl = wl*1e6

    B1 = 6.961663e-1
    B2 = 4.079426e-1
    B3 = 8.974794e-1
    C1 = 4.67914826e-3
    C2 = 1.35120631e-2
    C3 = 9.79340025e1

    return sellmeier(wl, B1, B2, B3, C1, C2, C3)

def n_sapphire_ordinary(wl):
    '''
    Calculate the index of refraction of Sapphire ordinary axis for a given wavelength.
    '''

    #Convert the wavelength to the unit of um.
    wl = wl*1e6

    B1 = 1.4313493
    B2 = 6.5054713e-1
    B3 = 5.3414021
    C1 = 5.2799261e-3
    C2 = 1.42382647e-2
    C3 = 3.25017834e2

    return sellmeier(wl, B1, B2, B3, C1, C2, C3)

def n_sapphire_extraordinary(wl):
    '''
    Calculate the index of refraction of Sapphire extraordinary axis for a given wavelength.
    '''

    #Convert the wavelength to the unit of um.
    wl = wl*1e6

    B1 = 1.5039759
    B2 = 5.5069141e-1
    B3 = 6.5927379
    C1 = 5.48041129e-3
    C2 = 1.47994281e-2
    C3 = 4.0289514e2

    return sellmeier(wl, B1, B2, B3, C1, C2, C3)

n_fused_silica_532nm = 1.46071
n_fused_silica_1064nm = 1.45
n_fused_silica_1550nm = 1.444

no_sapphire_532nm = 1.77170
ne_sapphire_532nm = 1.76355

no_sapphire_1064nm = 1.75449
ne_sapphire_1064nm = 1.74663

no_sapphire_1550nm = 1.74618
ne_sapphire_1550nm = 1.73838

#}}}


import numpy as np
import numpy_financial as npf
import pandas as pd

from random import randrange

import aiof.config as config

from aiof.data.car import CarLoanResponse

# Configs
_settings = config.get_settings()
_round_dig = _settings.DefaultRoundingDigit

def loan_calc(
    car_loan: float = None,
    interest: float = None,
    years: int = None,
    data_as_json: bool = False) -> CarLoanResponse:
    """
    Calculate car loan payments and details

    Parameters
    ----------
    `car_loan` : float or None.
        car loan amount. defaults to `35,000`\n
    `interest` : float or None.
        interest. defaults to `7`\n
    `years` : int or None.
        years for the loan. defaults to `5`\n
    `data_as_json` : bool or False.
        return data (DataFrame) result as JSON. defaults to `False`
    """
    car_loan = car_loan if car_loan is not None else 35000
    interest = interest if interest is not None else 7
    years = years if years is not None else 5

    interest = interest / 100

    car_payments = npf.pmt(
        rate=interest,
        nper=years,
        pv=-car_loan,
        fv=0,
        when="end")
    car_payments_monthly = npf.pmt(
        rate=interest / 12,
        nper=years * 12,
        pv=-car_loan,
        fv=0,
        when="end")

    loan_df = np.zeros((years, 6))
    loan_df = pd.DataFrame(loan_df)
    loan_df.columns = ["year", "startingBalance", "payments", "interestPaid", "principalPaid", "endingBalance"]

    loan_df.iloc[0, 0] = 1
    loan_df.iloc[0, 1] = car_loan
    loan_df.iloc[0, 2] = car_payments
    loan_df.iloc[0, 3] = car_loan * interest
    loan_df.iloc[0, 4] = car_payments - (car_loan * interest)
    loan_df.iloc[0, 5] = car_loan - (car_payments - (car_loan * interest))
    for i in range(1, years):
        loan_df.iloc[i, 0] = i + 1
        loan_df.iloc[i, 1] = loan_df.iloc[(i - 1), 5]
        loan_df.iloc[i, 2] = car_payments
        loan_df.iloc[i, 3] = loan_df.iloc[i, 1] * interest
        loan_df.iloc[i, 4] = car_payments - (loan_df.iloc[i, 1] * interest)
        loan_df.iloc[i, 5] = loan_df.iloc[i, 1] - (car_payments - (loan_df.iloc[i, 1] * interest))

    loan_df = loan_df.round(_round_dig)

    resp = CarLoanResponse(
        carLoan = car_loan,
        interest = interest,
        years = years,
        monthlyPayment = round(car_payments_monthly, _round_dig),
        data = loan_df if not data_as_json else loan_df.to_dict(orient="records"))

    return resp

def value_depreciation_calc(
    initial_value: float  = None,
    years: int            = None,
    as_json: bool         = False):
    """
    Calculates how much your car will depreciate over the years.
    The assumptions are that your car's value decreases around 20% to 30% by the end of the first year.
    From years two to six, depreciation ranges from 15% to 18% per year.
    As a rule of thumb, in five years, cars lose 60% or more of their initial value

    Parameters
    ----------
    `initial_value`: float.
        the initial value of the car. defaults to `35,000`\n
    `years`: int.
        the number of years to calculate the depreciation for. defaults to `5`\n
    `as_json`: bool.
        whether to return the response as JSON. defaults to `False`
    """
    initial_value = initial_value if initial_value is not None else 35000
    years = years if years is not None else 5

    value = initial_value
    years_list = list(range(1, years + 1))

    first_year_avg_int = randrange(20, 30)
    two_to_six_years_avg_int = randrange(15, 18)
    remaining_years_int = 60 - (first_year_avg_int + two_to_six_years_avg_int)

    first_year_avg_int = first_year_avg_int / 100
    two_to_six_years_avg_int = two_to_six_years_avg_int / 100
    remaining_years_int = remaining_years_int / 100

    value = initial_value - (-npf.fv(
        rate=first_year_avg_int,
        nper=1,
        pmt=0,
        pv=initial_value,
        when="end") - initial_value)

    value_df = pd.DataFrame(index=years_list, columns=["year", "depreciationPercentage", "value"], dtype="float")
    value_df["year"] = years_list

    value_df.iloc[0, 1] = first_year_avg_int
    value_df.iloc[0, 2] = value

    for year in range(1, years):
        interest = two_to_six_years_avg_int
        if year >= 2 and year <= 6:
            interest = two_to_six_years_avg_int
        elif year > 6:
            interest = remaining_years_int

        year_value = value_df.iloc[year - 1, 2] - (-npf.fv(
            rate=interest,
            nper=1,
            pmt=0,
            pv=value_df.iloc[year - 1, 2],
            when="end") - value_df.iloc[year - 1, 2])

        value_df.iloc[year, 1] = interest
        value_df.iloc[year, 2] = year_value

    value_df = value_df.round(_round_dig)
    return value_df if not as_json else value_df.to_dict(orient="records")


import numpy as np

def expectedPortfolioRet(returns, weight):
    return np.sum(returns.mean() * weight) * 252

def expectedPortfolioStd(returns, weight):
    return np.sqrt(np.dot(weight, np.dot(returns.cov() * 252, weight)))

def expectedSharpeRatio(returns, weight, rf=0):
    return (expectedPortfolioRet(returns, weight) - rf) / (
        expectedPortfolioStd(returns, weight)
    )

def sharpe(returns, std, rf=0):
    return (returns - rf) / std

def randomWeight(length):
    w = np.random.random(length)
    w /= np.sum(w)
    return w


import numpy as np

def tanh(x):
    return np.tanh(x)

def tanh_prime(x):
    return 1 - np.tanh(x)**2


from math import sqrt
from scipy.stats import t

class TTests(object):

    @classmethod
    def t_statistic(cls, population_mean, sample_mean, sample_std_deviation, sample_length):
        """Gives the t_statistics

        :param population_mean: (float)
        :param sample_mean: (float)
        :param sample_std_deviation: (float)
        :param sample_length: (int)
        :return: (int) t_statistic
        """

        # Numerator is difference of sample mean and population mean
        t_num = float(sample_mean) - float(population_mean)

        # Denominator is sample's standard deviation divided by square root of sample length
        t_denom = float(sample_std_deviation)/sqrt(float(sample_length))

        return t_num/t_denom

    @classmethod
    def t_distribution(cls, sample_length, alpha):
        """

        :param sample_length: (int)
        :param alpha: (float)
        :return: (float, float) r_min, r_max
        """
        df = sample_length - 1
        half_alpha = alpha/float(2)

        # T-distribution minimum value
        r_min = t.ppf(half_alpha, df)

        # T-distribution maximum value
        r_max = t.ppf(1-half_alpha, df)

        return r_min, r_max

class Statistics(object):

    def __init__(self, data_list):

        self.data_list = data_list

    @classmethod
    def sum(cls, data_list):
        return sum(data_list)

    @classmethod
    def mean(cls, summation, list_length):
        """

        :return: (float) mean value
        """
        return summation/float(list_length)

    @classmethod
    def variance(cls, data_list, mean):
        """

        :return: (float) variance value
        """

        square_diff = [(x-mean)**2 for x in data_list]
        return sum(square_diff)/float(len(data_list))

    @classmethod
    def standard_dev(cls, variance):
        """

        :return: (float) standard deviation
        """
        return sqrt(variance)

    def compute_all(self):
        self.data_list_len_f = len(self.data_list)
        self.data_list_sum = self.sum(self.data_list)
        self.data_list_mean = self.mean(self.data_list_sum, self.data_list_len_f)
        self.data_list_variance = self.variance(self.data_list, self.data_list_mean)
        self.data_list_standard_dev = self.standard_dev(self.data_list_variance)


import numpy as np
from sklearn.metrics import accuracy_score

y_pred = [0, 2, 1, 3]
y_true = [0, 1, 2, 3]
print("accuracy_score: ",accuracy_score(y_true, y_pred))

from sklearn.metrics import confusion_matrix
y_true = [2, 0, 2, 2, 0, 1]
y_pred = [0, 0, 2, 2, 0, 2]
print("confusion_matrix: ")
print(confusion_matrix(y_true, y_pred))
#    0 1 2
# 0[[2 0 0]
# 1[0 0 1]
# 2[1 0 2]]

y_true = [0, 0, 0, 1, 1, 1, 1, 1]
y_pred = [0, 1, 0, 1, 0, 1, 0, 1]
tn, fp, fn, tp = confusion_matrix(y_true, y_pred).ravel()
print(tn, fp, fn, tp) #(2, 1, 2, 3)

# auc
from sklearn.metrics import roc_auc_score

def calcAUC(labels, probs):
    N = 0
    P = 0
    neg_prob = []
    pos_prob = []
    for index, label in enumerate(labels):
        if (label == 1):
            P += 1
            pos_prob.append(probs[index])
        else:
            N += 1
            neg_prob.append(probs[index])

    # 公式需要满足：预测得到正样本的概率大于负样本的概率 0,1  1:0.8 > 0:0.1  p(y=1|x)=0.9 y=0
    # 一共有N*P对样本（一对样本即，一个正样本与一个负样本）。
    # 统计这N*P对样本里，正样本的预测概率大于负样本的预测概率的个数
    number = 0
    for pos in pos_prob:
        for neg in neg_prob:
            # 正样本概率>负样本 1
            if (pos > neg):
                number += 1
            # 正样本和负样本概率一样 0.5
            elif (pos == neg):
                number += 0.5
            # 还有没写的，当负样本概率>正样本 0，就不需要加
    return number / (N * P)

y = np.array([1, 0, 0, 0, 1, 0, 1, 0,])
pred = np.array([0.9, 0.8, 0.3, 0.1, 0.4, 0.9, 0.66, 0.7])
print('auc=', calcAUC(y, pred))
print('roc_auc=', roc_auc_score(y, pred))

'''
A  0  0.1
C  1  0.3
B  0  0.4
D  1  0.8
E  1  0.9
C,D,E

2+4+5 => A+C+A+B+C+D+A+B+C+D+E => 1+2+3=>C,C,D,C,D,E (A, A,B, A,B)   1+2+3+4=>4*(4+1)/2=10
两层for循环，找规律变成一次循环 正样本个数P
(（2+4）-（p（p+1）/2）)/n*p= 正样本>负样本的个数 n(n+1)/2

'''


import pandas as pd
import scipy.stats

def pearson_correlation_coefficient(col1, col2):

	# col1 and col2 are numeric variables

	correlation_value = round(scipy.stats.pearsonr(col1, col2)[0], 2)

	print('\n Pearson\'s Correlation Coefficient =', correlation_value)

"""Reading data"""

data = pd.read_csv("Sample 1.csv")

col1 = data['PetalLength']
col2 = data['SepalLength']

"""Applying Pearson's Correlation Coefficient"""

pearson_correlation_coefficient(col1,col2)


# # from scipy.stats.stats import pearsonr
# import numpy as np
#
import math
physics_scores = [int(x) for x in input().strip().split(' ')]
history_scores = [int(x) for x in input().strip().split(' ')]
# print(physics_scores, history_scores)
# print(len(physics_scores), len(history_scores))
# # print("%.3f" %pearsonr(physics_scores,history_scores))
# print(np.corrcoef(physics_scores, history_scores))

def average(l):
    n = len(l)
    total = sum(l)
    return total//n

def pearson_corr(l1,l2):
    n = len(l1)# should be equal to len(l2)
    x = average(l1)
    y = average(l2)
    xdiff = 0
    ydiff = 0
    diffprod = 0
    for i in range(n):
        xdiff_temp = l1[i] - x
        ydiff_temp = l2[i] - y
        diffprod += xdiff_temp * ydiff_temp
        xdiff += xdiff_temp ** 2
        ydiff += ydiff_temp ** 2
    # print(diffprod)
    # print(xdiff, ydiff)
    return diffprod / math.sqrt(xdiff * ydiff)

print(pearson_corr(physics_scores, history_scores))


'''
area_curves.py

Find the area enclosed by two curves between two points
'''

from sympy import Integral, Symbol, SympifyError, sympify

def find_area(f1x, f2x, var, a, b):
    a = Integral(f1x-f2x, (var, a, b)).doit()
    return a

if __name__ == '__main__':
    f1x = input('Enter the upper function in one variable: ')
    f2x = input('Enter the lower upper function in one variable: ')
    var = input('Enter the variable: ')
    l = float(input('Enter the lower bound of the enclosed region: '))
    u = float(input('Enter the upper bound of the enclosed region: '))

    try:
        f1x = sympify(f1x)
        f2x = sympify(f2x)
    except SympifyError:
        print('One of the functions entered is invalid')
    else:
        var = Symbol(var)
        print('Area enclosed by {0} and {1} is: {2} '.format(f1x, f2x, find_area(f1x, f2x, var, l, u)))


import numpy as np
# from __future__ import annotations

# softmax = exp(a) / sum(exp(a))

def softmax(vector):
    exp_vec = np.exp(vector)
    exp_sum_vec = np.sum(exp_vec)
    return exp_vec / exp_sum_vec

if __name__ == "__main__":
    # return max or 0 depending on value
    print(softmax(np.array([5, 1])))


# Code made for : Sergio Andrés Díaz Ariza
# Specialization--> Statistics in Python Coursera
# 19 Abril, 2021
# License MIT

# Replicate Cartwheel example

import numpy as np
import statsmodels.api as sm
import pandas as pd

t_star = 1.96  # Number of t from tabbles
p = .85  # Proportion of population
n = 659  # Number of observations

se = np.sqrt((p * (1 - p)) / n)  # Standard Error
print("\nStandard Error:\t{}".format(se))

# Confidence Interval
lcb = p - t_star * se
ucb = p + t_star * se
print("\nConfidence Interval:\t({}, {})".format(lcb, ucb))

# Library that calculate the intervals

ci = sm.stats.proportion_confint(n * p,
                                 n)  # Here we calculate the confidence interval, with the proportion and population
print("\nConfidence interval with stats library: {}\n".format(ci))

# With pandas and data basis
df = pd.read_csv(r"C:\Users\HP\PycharmProjects\Python_Statistics\Understanding_and_Visualizing_Data_With_Python"
                 r"\Univariate Data\Python for Analysis of Univariate Data\Cartwheeldata.csv")

print(df.head())

mean = df["CWDistance"].mean()
sd = df["CWDistance"].std()
n = len(df)

tstar = 2.064
se = sd / np.sqrt(n)

print("\nStandard Error of population:\t {}".format(se))

lcb2 = mean - tstar * se
ucb2 = mean + tstar * se
print("\nConfidence Interval Cartwheel:\t({}, {})".format(lcb2, ucb2))

# Using the library stats

ci2 = sm.stats.DescrStatsW(df["CWDistance"]).zconfint_mean()
print("\nConfidence interval with stats library: {}\n".format(ci2))

Inferential Satatistics Analysis/Introduction Confidence Intervals/Introduction.py


import numpy as np
import pandas as pd
from pandas_datareader import data as wb
import matplotlib as mpl
import matplotlib.pyplot as plt
from scipy.stats import norm

def get_simulation(ticker):
    data = pd.DataFrame()
    data[ticker] = wb.DataReader(ticker, data_source='yahoo', start='2020-1-1')['Adj Close']

    log_returns = np.log(1 + data.pct_change())

    u = log_returns.mean()

    var = log_returns.var()

    drift = u - (0.5 * var)

    stdev = log_returns.std()

    t_intervals = 6
    iterations = 10000

    daily_returns = np.exp(drift.values + stdev.values * norm.ppf(np.random.rand(t_intervals, iterations)))
    x = data.iloc[-1]

    price_list = np.zeros_like(daily_returns)
    price_list[0] = x

    for t in range(1, t_intervals):
        price_list[t] = price_list[t - 1] * daily_returns[t]
    plt.figure(figsize=(10, 6))
    plt.title(ticker)
    plt.ylabel("Price (P)")
    plt.xlabel("Time (Days)")
    plt.plot(price_list)
    plt.savefig('tempplot.png')
    plt.show()
    price_list = pd.DataFrame(price_list)
    c = int(95 / 100 * iterations)
    finprice = price_list.loc[5]
    finprice = np.array(finprice)
    dprice = np.sort(finprice)
    var = dprice[:c]
    var2 = min(var)
    print("VaR = ", var2)

get_simulation('AMZN')


import numpy as np

def to_numbers(file_name):
    with open(file_name) as f:
        file_lines = f.readlines()
    numbers_str = file_lines[0].split(",")
    return list(map(lambda x: int(x.strip()), numbers_str))

input = to_numbers("day7.txt")
#input = [16,1,2,0,4,2,7,1,2,14]

# part 1
numbers = np.array(input)
mean = np.median(numbers)
sum = 0
for nr in input:
    sum += abs(nr - mean)

print("part 1", sum)

# part 2

min_sum = -1
for m in range(min(numbers), max(numbers)):
    sum = 0
    for nr in input:
        dif = abs(nr - m)
        sum += (dif * (dif + 1)) / 2
    if (min_sum == -1) | (sum < min_sum):
        min_sum = sum

print("part 2", min_sum)


import numpy as np

def compute_cost(X, y, theta):
    # Initialize some useful values
    m = y.size
    cost = 0

    # ===================== Your Code Here =====================
    # Instructions : Compute the cost of a particular choice of theta.
    #                You should set the variable "cost" to the correct value.

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

    # cost of linear regression
    # J = (X*theta-y)'*(x*theta-y)/(2*m)
    # or J = (x*theta-y)**2/(2*m)

    #cost = sum((X.dot(theta)-y)**2)/(2*m)
    #cost = (X.dot(theta)-y).transpose().dot(X.dot(theta)-y)/(2*m)
    diff = (np.dot(X, theta)-y)
    cost = np.dot(diff, diff.T)/(2*m)
    return cost


from math import sqrt, log10, pow
import json, os, yaml
import numpy as np

class FallVelocity_solver(object):
    """
    FallVelocity

    Python version of Gary Parker's 1D Sediment Transport Morphodynamics e-book,
    originally in Visual Basic and converted to C by Andrew Leman (2009)

    MC Perignon
    Oct 2015

    ----------------------------------------------

    Computes the settling velocity, vs, of a particle with the formulation of Dietrich (1982).

    Only valid for Reynold's numbers less than or equal to 2.5 x 10**6. If Re is greater than
    this upper limit, warns and exits.

    Dietrich, E. W., 1982, Settling velocity of natural particles,
    Water Resources Research, 18(6), 1626-1982.

    ----------------------------------------------
    Input:
    D: Sediment particle size (mm)
    nu: Kinematic viscosity of the liquid (m**2/s)
    g: Acceleration due to gravity (m/s**2)
    rho_w: Density of liquid (Kg/m**3)
    rho_s: Density of sediment(Kg/m**3)

    Output:
    vs: Particle settling velocity (m/s)
    Re: Particle Reynolds number
    Rf: Dimensionless fall velocity of the particle

    Equations:
    R = (rho_s - rho_w) / rho_w

    Re = sqrt(R*g*D)*D / nu

    Rf = vs / sqrt(R*g*D)
    """

    def __init__(self, params = None):

        if params is None:

            filename='input_files/FallVelocity.yaml'

            with open(filename, 'r') as file_obj:
                params = yaml.load(file_obj)

            default_params = {
                  'particle__diameter': 0.1,
                  'fluid__kinematic_viscosity': 1e-6,
                  'gravitational_acceleration': 9.81,
                  'fluid__density': 1000.,
                  'particle__density': 2650.,
                  'output_filename': 'output/FallVelocity_output.json'}

            # sets missing parameters to default values
            for key,value in default_params.items():
                params[key] = params.get(key, default_params[key])

            for key,value in params.items():
                if (value is None) or (value == 'None'):
                    params[key] = default_params[key]

        self._grain_size = np.array([float(params['particle_diameter'])]) # mm
        self._nu = np.array([float(params['fluid__kinematic_viscosity'])])
        self._g = np.array([float(params['gravitational_acceleration'])])
        self._rho_w = np.array([float(params['fluid__density'])])
        self._rho_s = np.array([float(params['particle__density'])])
        self._output_filename = str(params['output_filename'])

        # Outputs
        self._vs = np.empty((1,))
        self._Re = np.empty((1,))
        self._Rf = np.empty((1,))

    @property
    def grain_size(self):
        """Grain size in mm."""

        return self._grain_size

    @grain_size.setter
    def grain_size(self, new_D):
        """Set the grain size.

        Parameters
        ----------
        new_D : float
            New grain size in mm.
        """
        self._grain_size[:] = new_D

    @property
    def kinematic_viscosity(self):
        """Kinematic viscosity of the fluid in m2/s"""
        return self._nu

    @kinematic_viscosity.setter
    def kinematic_viscosity(self, new_nu):
        """Set the kinematic viscosity.

        Parameters
        ----------
        new_nu : float
            New kinematic viscosity in m2/s.
        """

        self._nu[:] = new_nu

    @property
    def gravitational_acceleration(self):
        """Gravitational acceleration in m/s2"""
        return self._g

    @gravitational_acceleration.setter
    def gravitational_acceleration(self, new_g):
        """Set the gravitational acceleration.

        Parameters
        ----------
        new_g : float
            New gravitational acceleration in m/s2.
        """

        self._g[:] = new_g

    @property
    def density_of_fluid(self):
        """Density of the fluid in Kg/m3"""
        return self._rho_w

    @density_of_fluid.setter
    def density_of_fluid(self, new_rhow):
        """Set the density of the fluid.

        Parameters
        ----------
        new_rhow : float
            New density of the fluid in Kg/m3.
        """

        self._rho_w[:] = new_rhow

    @property
    def density_of_particle(self):
        """Density of the particle in Kg/m3"""
        return self._rho_s

    @density_of_particle.setter
    def density_of_particle(self, new_rhos):
        """Set the density of the particle.

        Parameters
        ----------
        new_rhos : float
            New density of the particle in Kg/m3.
        """

        self._rho_s[:] = new_rhos

    @property
    def settling_velocity(self):
        """Settling velocity in m/s"""

        return self._vs

    @property
    def Reynolds_number(self):
        """Reynolds number of the particle"""

        return self._Re

    @property
    def dimensionless_fall_velocity(self):
        """Dimensionless fall velocity of the particle"""

        return self._Rf

    def run(self):

        self._R = (self._rho_s - self._rho_w) / self._rho_w

        self._Re[:] = self.calculateReynoldsnumber()
        self._Rf[:] = self.calculate_dimensionless_fall_velocity()
        self._vs[:] = self.calculate_settling_velocity()

    def calculateReynoldsnumber(self):

        _Re = sqrt(self._R * self._g * (self._grain_size/1000.)) * \
                    (self._grain_size/1000.) / self._nu

        failMessage  = "This equation is only valid for Reynolds numbers "\
                        "below 2.6e+06. The calculated Reynolds number for "\
                        "this particle is %.2g" % _Re
        assert _Re <= 2.6e6, failMessage

        return _Re

    def calculate_dimensionless_fall_velocity(self):

        x = log10(self._Re**2)
        y = (-3.76715) + (1.92944*x) - (0.09815*x*x) - \
            (0.00575*x*x*x) + (0.00056*x*x*x*x)

        _Rf = pow(pow(10,y) / self._Re, 1./3)

        return _Rf

    def calculate_settling_velocity(self):

        _vs = self._Rf * sqrt(self._R * self._g * (self._grain_size/1000.))

        return _vs

    def _assure_path_exists(self, path):
        """Helper function to create output directories"""

        dir = os.path.dirname(path)
        if not os.path.exists(dir):
                os.makedirs(dir)

    def finalize(self):

        self._assure_path_exists(self._output_filename)

        output_dict = {
            'Grain_size_mm' : list(self._grain_size),
            'Kinematic_viscosity' : list(self._nu),
            'Gravitational_acceleration' : list(self._g),
            'Density_of_fluid' : list(self._rho_w),
            'Density_of_sediment' : list(self._rho_s),
            'Reynolds_number' : list(self._Re),
            'Dimensionless_fall_velocity' : list(self._Rf),
            'Settling_velocity' : list(self._vs)
        }

        with open(self._output_filename, 'w') as f:
            json.dump(output_dict, f, indent=4)


from hsi_toolkit.util import img_det
import numpy as np

def spsmf_detector(hsi_img, tgt_sig, mask = None, mu = None, sig_inv = None):
	"""
	Subpixel Spectral Matched Filter
	 matched filter derived from a subpixel mixing model
	 H0: x = b
	 H1: x = alpha*s + beta*b
	 Ref: formulation from Eismann's book, pp 664

	Inputs:
	 hsi_image - n_row x n_col x n_band hyperspectral image
	 tgt_sig - target signature (n_band x 1 - column vector)
	 mask - binary image limiting detector operation to pixels where mask is true
	        if not present or empty, no mask restrictions are used
	 mu - background mean (n_band x 1 column vector)
	 siginv - background inverse covariance (n_band x n_band matrix)

	Outputs:
	 spsmf_out - detector image

	8/8/2012 - Taylor C. Glenn
	6/2/2018 - Edited by Alina Zare
	12/2018 - Python Implementation by Yutai Zhou
	"""
	if tgt_sig.ndim == 1:
		tgt_sig = tgt_sig[:, np.newaxis]

	spsmf_out, kwargsout = img_det(spsmf_helper, hsi_img, tgt_sig, mask, mu = mu, sig_inv = sig_inv)

	return spsmf_out

def spsmf_helper(hsi_data, tgt_sig, kwargs):
	mu = np.mean(hsi_data, axis = 1) if kwargs['mu'] is None else kwargs['mu']
	mu = mu[:, np.newaxis]
	sig_inv = np.linalg.pinv(np.cov(hsi_data.T, rowvar = False)) if kwargs['sig_inv'] is None else kwargs['sig_inv']

	n_band, n_pixel = hsi_data.shape
	s = tgt_sig # 72 x 1
	st_sig_inv = s.T @ sig_inv # 1 x 72
	st_sig_inv_s = s.T @ sig_inv @ s # 1 x 1
	K = n_band

	spsmf_data = np.zeros(n_pixel)

	for i in range(n_pixel):
		x = hsi_data[:,i][:, np.newaxis] # 72x1
		st_sig_inv_x = st_sig_inv @ x # 1 x 1
		a0 = (x.T @ sig_inv @ x) * st_sig_inv_s - st_sig_inv_x ** 2
		a1 = st_sig_inv_x * (s.T @ sig_inv @ mu) - st_sig_inv_s * (mu.T @ sig_inv @ x)
		a2 = -K * st_sig_inv_s

		beta = (-a1 + np.sqrt(a1 ** 2 - 4 * a2 * a0)) / (2 * a2)
		alpha = st_sig_inv @ (x - beta * mu) / st_sig_inv_s
		z1 = x - mu
		z2 = x - alpha * s - beta * mu

		spsmf_data[i] = z1.T @ sig_inv @ z1 - (z2.T @ sig_inv @ z2) / (beta ** 2) - 2 * K * np.log(np.abs(beta))

	return spsmf_data, {}