# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Defines quadratic loss functions."""

import jax
import jax.numpy as jnp

HIGHEST = jax.lax.Precision.HIGHEST

def quadform(hess, x, precision):
  """Computes a quadratic form (x^T @ H @ x)."""
  u = jnp.dot(hess, x, precision=precision)  # u = Hx
  return jnp.inner(x, u, precision=precision)

def loguniform(n, lambda_min, lambda_max, precision=HIGHEST):
  """Quadratic loss function with loguniform eigenvalues.

  The loss is: f(x) = (1/2) x^T H x + x^T v + b.

  The eigenvalues of the Hessian (H) are sampled uniformly on a
  logarithmic grid from lambda_min to lambda_max.

  Args:
    n: int, Problem dimension (number of parameters).
    lambda_min: float, Minimum eigenvalue of the Hessian.
    lambda_max: float, Maximum eigenvalue of the Hessian.
    precision: Which lax precision to use (default: HIGHEST).

  Returns:
    problem_fun: Function that takes a jax PRNGkey and a precision argument
      and returns an (initial_params, loss_fun) tuple.
  """

  def problem_fun(key):
    """Builds a quadratic loss problem."""
    pkey, ekey, qkey, vkey = jax.random.split(key, 4)

    # Sample eigenvalues.
    log_eigenvalues = jax.random.uniform(ekey,
                                         shape=(n,),
                                         minval=lambda_min,
                                         maxval=lambda_max)
    eigenvalues = 10**log_eigenvalues

    # Build orthonormal basis.
    basis = jax.nn.initializers.orthogonal()(qkey, shape=(n, n))

    # Define hessian.
    hess = jnp.dot(jnp.dot(basis, jnp.diag(eigenvalues), precision=precision),
                   basis.T,
                   precision=precision)

    # Random vector for the linear term in the loss.
    v = jax.random.normal(vkey, shape=(n,))

    # Compute an offset such that the global minimum has a loss of zero.
    xstar = jnp.linalg.solve(hess, -v)
    offset = -0.5 * quadform(hess, xstar, precision=precision) - jnp.inner(v, xstar, precision=precision)  # pylint: disable=line-too-long

    def loss_fun(x, _):
      return 0.5 * quadform(hess, x, precision=precision) + jnp.inner(v, x, precision=precision) + offset  # pylint: disable=line-too-long

    x_init = jax.random.normal(pkey, shape=(n,))
    return x_init, loss_fun

  return problem_fun


#!/usr/bin/env
# -*- coding: utf-8 -*-
# Copyright (C) Victor M. Mendiola Lau - All Rights Reserved
# Unauthorized copying of this file, via any medium is strictly prohibited
# Proprietary and confidential
# Written by Victor M. Mendiola Lau <ryuzakyl@gmail.com>, May 2017

from math import log2

import numpy as np

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

def calc_ent(dm):
    """Computes the entropy of a gallery

    Args:
        dm (ndarray): The similarity matrix of the gallery.

    Returns:
        The entropy value.

    Examples:
        >>> dm = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
        >>> calc_ent(dm)
        -31.546487678572873
        >>> dm = np.array([[1.0, 0.8, 0.7], [0.8, 1.0, 0.7], [0.7, 0.8, 1.0]])
        >>> calc_ent(dm)
        0.58462931767437532

    """

    # validating the received matrix
    if not isinstance(dm, np.ndarray):
        raise ValueError('NumPy ndarray expected.')

    # making a copy of the data matrix
    dm_cpy = dm.copy()

    # computing the amount of positive (>0) values
    dm_pos_mask = (dm_cpy > 0.0).astype(int)
    n = np.sum(dm_pos_mask)

    # validating that the count of positive similarity values
    if n <= 1:
        return 0.0

    # log2 of matrix values (except zero values)
    dm_cpy[dm_cpy > 0] = np.log2(dm_cpy[dm_cpy > 0])

    # normalizing dm values by log2(n)
    log_dm = dm_cpy / log2(n)

    # multiplying each number by it's log2 value (excluding non-positive similarity values)
    t = dm_pos_mask * (-dm) * log_dm

    # returning the sum of all values
    return np.sum(t)


import sys
import os
sys.path.insert(1, os.path.join(sys.path[0], '..'))

import unittest
import numpy as np

from base.least_square_estimator import LeastSquareEstimator

class TestLeastSquareEstimator(unittest.TestCase):
    '''
    NOTE: not testing the forgetting factor now.
    '''
    VALUE_EQUAL_PLACE = 2
    def setUp(self):
        pass

    def test_straight_line_no_noise(self):
        '''
        2D points are generated according to y = k * x + b, without noise.
        '''
        k = 1.0
        b = 10.0
        x_sample = np.arange(0, 100, 1)
        y_sample = k * x_sample + b

        initial_value = np.array([0.0, 0.0]) # deliberately made off
        initial_covar = np.eye(2) * 1e3
        lse = LeastSquareEstimator(initial_value, initial_covar)

        for (x, y) in zip(x_sample, y_sample):
            phi = np.array([x, 1])
            lse.update(phi, y)

        mean = lse.get_estimate_mean()

        self.assertAlmostEqual(mean[0], k, TestLeastSquareEstimator.VALUE_EQUAL_PLACE)
        self.assertAlmostEqual(mean[1], b, TestLeastSquareEstimator.VALUE_EQUAL_PLACE)

    def test_straight_line_symmetric(self):
        '''
        2D points are symmetric about y = 0.
        '''
        x_sample = np.arange(-50, 50, 1)
        y_sample = 2 * (np.mod(x_sample, 2) - 0.5)

        initial_value = np.array([1.0, -1.0]) # deliberately made off
        initial_covar = np.eye(2) * 1e3
        lse = LeastSquareEstimator(initial_value, initial_covar)

        for (x, y) in zip(x_sample, y_sample):
            phi = np.array([x, 1])
            lse.update(phi, y)

        mean = lse.get_estimate_mean()

        self.assertAlmostEqual(mean[0], 0.0, TestLeastSquareEstimator.VALUE_EQUAL_PLACE)
        self.assertAlmostEqual(mean[1], 0.0, TestLeastSquareEstimator.VALUE_EQUAL_PLACE)

    def test_strait_line(self):
        '''
        2D points are generated according to y = k * x + b, with moderate noise
        '''
        k = 1.0
        b = 10.0
        x_sample = np.arange(0, 100, 1)
        y_sample = k * x_sample + b + np.random.normal(0.0, 0.01, x_sample.shape)

        initial_value = np.array([0.0, 0.0]) # deliberately made off
        initial_covar = np.eye(2) * 1e3
        lse = LeastSquareEstimator(initial_value, initial_covar)

        for (x, y) in zip(x_sample, y_sample):
            phi = np.array([x, 1])
            lse.update(phi, y)

        mean = lse.get_estimate_mean()

        self.assertAlmostEqual(mean[0], k, TestLeastSquareEstimator.VALUE_EQUAL_PLACE)
        self.assertAlmostEqual(mean[1], b, TestLeastSquareEstimator.VALUE_EQUAL_PLACE)

if (__name__ == "__main__"):
    unittest.main()


#!/usr/bin/python
#
# Solution to exercise 1.2 from the book _Essential Algorithms_.

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

# Orders of growth. For some reason, matplotlib is happy when these are defined
# as functions, but not happy if I try to give it lambdas?
def inv_log(s):
    return 2 ** s

def inv_sqrt(s):
    return s ** 2

def inv_linear(s):
    return s

def inv_quad(s):
    return math.sqrt(s)

def inv_exp(s):
    return math.log(s, 2)

def inv_fact(s):
    n = 1
    while s > math.factorial(n):
        n += 1
    return n

def make_table():
    # number of algorithm 'steps' per second
    rate = 1E6

    # run-times in seconds: 1 sec, 1 min, 1 hour, 1 day, 1 week, 1 year
    periods = [1, 60, 3600, 86400, 604800, 31536000]

    # orders of growth: log n, sqrt n, n, n^2, 2^n, n!
    def inv_fact(s):
        n = 1
        while s > math.factorial(n):
            n += 1
        return n

    fns = [
        ['log n',  lambda s: 2 ** s],         # log
        ['sqrt n', lambda s: s ** 2],         # sqrt
        ['n',      lambda s: s],              # linear
        ['n^2',    lambda s: math.sqrt(s)],   # quadratic
        ['2^n',    lambda s: math.log(s, 2)], # exponential
        ['n!',     inv_fact],                 # factorial
    ]

    table = [[''] + periods]
    for label, fn in fns:
        row = [label]
        for t in periods:
            try:
                row.append(fn(t * rate))
            except OverflowError:
                row.append('  <overflow>')
        table.append(row)
    return table

def print_table_text(table):
    for row in table:
        for col in row:
            if type(col) == int:
                print '{0:12d}'.format(col),
            elif type(col) == float:
                print '{0:12g}'.format(col),
            else:
                print '{0:12s}'.format(col),
        print
    print

def print_table_html(table):
    print '
'
    print '  '

    # assume the first row is a header row
    for col in table[0]:
        print '    {0}	'.format(col)
    print '  	'

    # now we print the data
    for row in table[1:]:
        print '
'
        for col in row:
            print '    {0}	'.format(col)
        print '
'
    print '
'

# Show the growth in the problem size, for each function, as a function of time
# in seconds.
def plot_results():
    t = np.arange(1.0, 100.0) # 100 s
    fns = [
        ['log n',  inv_log],
        ['sqrt n', inv_sqrt],
        ['n',      inv_linear],
        ['n^2',    inv_quad],
        ['2^n',    inv_exp],
        ['n!',     inv_fact],
    ]
    for label, fn in fns:
        plt.plot(t, [fn(x) for x in t], label=label)
    plt.yscale('log')
    plt.legend(loc='upper left')
    plt.show()

if __name__ == '__main__':
    print_table_html(make_table())


from __future__ import annotations

import math

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

class UnivariateGaussian:
    """
    Class for univariate Gaussian Distribution Estimator
    """

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

        Parameters
        ----------
        biased_var : bool, default=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

    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`
        """
        if self.biased_:
            m = X.shape[0]
        else:
            m = X.shape[0] - 1
        self.mu_ = np.mean(X)
        self.var_ = np.sum(np.power(X - self.mu_, 2)) / m
        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")
        args = [self.mu_, self.var_]
        B = np.apply_along_axis(self.normal_dist_density, 0, [X], *args).reshape(X.shape[0], )
        return B

    @staticmethod
    def normal_dist_density(x: float, mu: float, sigma: float) -> float:
        power = -1 * (x - mu) ** 2 / 2 * sigma
        return 1 / (2 * math.pi * sigma) ** 0.5 * math.exp(power)

    @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
        """
        # args = (mu, sigma)
        # A = np.apply_along_axis(UnivariateGaussian.normal_dist_density, 0, [X], *args).reshape(X.shape[0], )
        # return math.log(np.product(A))
        sum = np.sum((X - mu) ** 2)
        m = X.shape[0]
        return math.log(1 / ((2 * math.pi * math.sqrt(sigma)) ** (m / 2))) - 1 / (2 * sigma) * sum

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, 0)
        # # self.cov_ = np.matmul(np.transpose(X - self.mu_), X - self.mu_) * 1 / (X.shape[0] - 1)
        self.cov_ = np.cov(np.transpose(X))
        self.fitted_ = True
        return self

    @staticmethod
    def mult_gaus_density(X: np.ndarray, sigma: np.ndarray, meu: np.ndarray):
        d = meu.shape[0]
        power = np.linalg.multi_dot([np.transpose(X - meu), np.linalg.inv(sigma), X - meu]) * (-0.5)
        return 1 / ((2 * math.pi) ** d * np.linalg.det(sigma)[1]) * math.exp(power)

    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")
        args = (self.cov_, self.mu_)
        B = np.apply_along_axis(MultivariateGaussian.mult_gaus_density, 1, X, *args)
        return B

    @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
        """
        d = X.shape[1]
        n = X.shape[0]
        sum = np.sum(np.multiply(np.transpose(X - mu), np.matmul(np.linalg.inv(cov), np.transpose(X - mu))))
        return n / 2 * (math.log(1) - math.log((math.pi * 2) ** d) + np.linalg.slogdet(cov)[1]) - 0.5 * sum
     #closed expression to calculate log likelihood
# from __future__ import annotations
# import numpy as np
#
#
# 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
#
#     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_ = UnivariateGaussian._calc_mu(X)
#         if self.biased_:
#             self.var_ = UnivariateGaussian._calc_unbiased_var(self.mu_, X)
#         else:
#             self.var_ = UnivariateGaussian._calc_biased_var(self.mu_, 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")
#         return UnivariateGaussian._calc_pdf(self.mu_, self.var_, X)
#
#     @staticmethod
#     def _calc_pdf(mu: float, var: float, X: np.ndarray) -> np.ndarray:
#         """
#         Calculate PDF of observations under Gaussian model if random variable X~N(mu, var)
#         Parameters
#         ----------
#         mu : float
#             Expectation of Gaussian
#         var : float
#             Variance of Gaussian
#         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_)
#         """
#         scalar1 = 1 / np.sqrt(2 * np.pi * var)
#         scalar2 = -2 * var
#         return scalar1 * np.exp((np.power((X - mu), 2)) / scalar2)
#
#     @staticmethod
#     def _calc_mu(X: np.ndarray) -> float:
#         """
#         Calculate mean estimator of observations under Gaussian model
#
#         Parameters
#         ----------
#         X: ndarray of shape (n_samples, )
#             Samples to calculate mean for
#
#         Returns
#         -------
#         mean: float
#            mean calculated
#         """
#         if len(X) == 0:  # Don't divide by 0, if the array is empty return mean = 0
#             return 0
#         return X.sum() / len(X)
#
#     @staticmethod
#     def _calc_biased_var(mu: float, X: np.ndarray) -> float:
#         """
#         Calculate variance estimator of observations under Gaussian model for biased estimator
#
#         Parameters
#         ----------
#         mu : float
#             Expectation of Gaussian
#         X: ndarray of shape (n_samples, )
#             Samples to calculate mean for
#
#         Returns
#         -------
#         variance: float
#            variance calculated
#         """
#         if len(X) == 0:  # Don't divide by 0, if the array is empty return var = 0
#             return 0
#         scalar = 1 / len(X)
#         return scalar * np.sum(np.power((X - mu), 2))
#
#     @staticmethod
#     def _calc_unbiased_var(mu: float, X: np.ndarray) -> float:
#         """
#         Calculate variance estimator of observations under Gaussian model for unbiased estimator
#
#         Parameters
#         ----------
#         mu : float
#             Expectation of Gaussian
#         X: ndarray of shape (n_samples, )
#             Samples to calculate mean for
#
#         Returns
#         -------
#         variance: float
#            variance calculated
#         """
#         if len(X) <= 1:  # Don't divide by 0, if the array is empty or single sample return var = 0
#             return 0
#         scalar = 1 / (len(X) - 1)
#         return scalar * np.sum(np.power((X - mu), 2))
#
#     @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
#         """
#         return np.sum(np.log(UnivariateGaussian._calc_pdf(mu, sigma, X)))
#
#
# 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_ = MultivariateGaussian._calc_mu(X)
#         self.cov_ = MultivariateGaussian._calc_unbiased_cov(self.mu_, X)
#         self.fitted_ = True
#         return self
#
#     def pdf(self, X: np.ndarray):
#         """
#         Calculate PDF of observations under Gaussian model with fitted estimators
#
#         Parameters
#         ----------
#         X: ndarray of shape (n_samples, n_features)
#             Samples to calculate PDF for
#
#         Returns
#         -------
#         pdfs: ndarray of shape (n_samples, )
#             Calculated values of given samples for PDF function of N(mu_, cov_)
#
#         Raises
#         ------
#         ValueError: In case function was called prior fitting the model
#         """
#         if not self.fitted_:
#             raise ValueError("Estimator must first be fitted before calling `pdf` function")
#         # from scipy.stats import multivariate_normal
#         # var = multivariate_normal(self.mu_, self.cov_)
#         # real_pdf = var.pdf(X)
#
#         # m = X - self.mu_
#         # scalar = 1 / (np.sqrt(np.power(2 * np.pi, len(self.mu_)) * np.linalg.det(self.cov_)))
#         # pdf = np.zeros(len(X))
#         # for i in range(len(pdf)):
#         #     pdf[i] = scalar * np.exp((-1 / 2) * (m[i].T @ np.linalg.inv(self.cov_) @ m[i]))
#         return MultivariateGaussian._calc_pdf(self.mu_, self.cov_, X)
#
#     @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
#         """
#
#         return np.float(np.sum(np.log(MultivariateGaussian._calc_pdf(mu, cov, X))))
        # loglikelihood = -0.5 * (
        #         np.log(np.linalg.det(cov))
        #         + np.einsum('...j,jk,...k', X, np.linalg.inv(cov), X)
        #         + len(mu) * np.log(2 * np.pi)
        # )
        # loglikelihood = np.sum(loglikelihood)

        # if a == loglikelihood:
        #     print(a)

        # from scipy.stats import multivariate_normal
        # y = multivariate_normal.pdf(X, mean=mu, cov=cov)
        # y_log = np.log(y)
        # from scipy.stats import norm
        # t, s = norm.fit(X)
        # log_likelihood_2 = np.sum(np.log(norm.pdf(X, t, s)))

        # def calc_loglikelihood(residuals):
        #     return -0.5 * (np.log(np.linalg.det(cov)) + residuals.T.dot(np.linalg.inv(cov)).dot(residuals) + 2 * np.log(
        #         2 * np.pi))
        #
        # m = X - mu
        # b = np.apply_along_axis(calc_loglikelihood, 1, m)
        # c = b.sum()
        # if a == c:
        #     print(a)

        # vals, vecs = np.linalg.eigh(cov)
        # logdet = np.sum(np.log(vals))
        # valsinv = np.array([1. / v for v in vals])
        # # `vecs` is R times D while `vals` is a R-vector where R is the matrix
        # # rank. The asterisk performs element-wise multiplication.
        # U = vecs * np.sqrt(valsinv)
        # rank = len(vals)
        # dev = X - mu
        # # "maha" for "Mahalanobis distance".
        # maha = np.square(np.dot(dev, U)).sum()
        # log2pi = np.log(2 * np.pi)
        # log_l = -0.5 * (rank * log2pi + maha + logdet)
        # if a == log_l:
        #     print(a)
        # return c

    # @staticmethod
    # def _calc_mu(X: np.ndarray) -> np.ndarray:
    #     """
    #     Calculate multivariate sample mean estimator under Gaussian model
    #
    #     Parameters
    #     ----------
    #     X: ndarray of shape (n_samples, n_features)
    #         Training data
    #
    #     Returns
    #     -------
    #     mean: ndarray of shape (n_features,)
    #        mean calculated
    #     """
    #     if len(X) == 0:  # Don't divide by 0, if the array is empty return mean = 0
    #         raise ValueError("Non valid samples were given to fit function, samples size must be > 1")
    #     return np.mean(X, axis=0)
    #
    # @staticmethod
    # def _calc_unbiased_cov(mu: np.ndarray, X: np.ndarray) -> np.ndarray:
    #     """
    #     Calculate covariance multivariate estimator of observations under Gaussian model for unbiased estimator
    #
    #     Parameters
    #     ----------
    #     mu : ndarray of shape (n_features,)
    #         Expectation of Gaussian
    #     X: ndarray of shape (n_samples, n_features)
    #         Training data
    #
    #     Returns
    #     -------
    #     covariance: ndarray of shape (n_features,)
    #        covariance calculated
    #     """
    #     if len(X) <= 1:  # Don't divide by 0, if the array is empty or single sample return var = 0
    #         raise ValueError("Non valid samples were given to fit function, samples size must be > 1")
    #     scalar = 1 / (len(X) - 1)
    #     m = X - mu
    #     try:
    #         cov = np.cov(X.T)  # Reminder: come back here and decide which implementation to choose
    #         # cov = scalar * m.T @ m
    #     except:
    #         try:
    #             cov = scalar * m @ m.T
    #         except:
    #             raise ValueError("Non valid samples were given to pdf")
    #     return cov
    #
    # @staticmethod
    # def _calc_pdf(mu: np.ndarray, cov: np.ndarray, X: np.ndarray) -> np.ndarray:
    #     """
    #     Calculate PDF of observations under Gaussian model if random variable X~N(mu, var)
    #     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)
    #         Training data
    #     Returns
    #     -------
    #     pdfs: ndarray of shape (n_samples, )
    #         Calculated values of given samples for PDF function of N(mu_, var_)
    #     """
    #     m = X - mu
    #     scalar = 1 / (np.sqrt(np.power(2 * np.pi, len(mu)) * np.linalg.det(cov)))
    #     pdf = np.zeros(len(X))
    #     for i in range(len(pdf)):
    #         pdf[i] = scalar * np.exp((-0.5) * (m[i].T @ np.linalg.inv(cov) @ m[i]))
    #     return pdf
    #
    #


"""
Problem 9:

A Pythagorean triplet is a set of three natural numbers, a < b < c, for which,

a^2 + b^2 = c^2

For example, 3^2 + 4^2 = 9 + 16 = 25 = 5^2.

There exists exactly one Pythagorean triplet for which a + b + c = 1000.
Find the product abc.
"""

import numpy as np

# Brute force: start computing triplets until the correct one has been found
def find_triplet():
    for a in range(1, 500):
        for b in range(1, 500):

            c = np.sqrt(a**2 + b**2)

            if a + b + c == 1000:
                print(f"Triplet found!")
                print(f"a: {a}, b: {b}, c: {c}")
                print(f"a + b + c = {a + b + c}")
                print(f"Product abc = {a*b*c}")
                return

find_triplet()


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

η = 0.001  # learning rate

train_df = pd.read_csv("train_data.csv")
x_train, y_train = train_df['x_train'], train_df['y_train']

# wired initial weight:
# β0, β1=[
#     (-2.8878, -0.7567),
#     (-1.4769, -0.7439),
#     (-0.265, -1.3396)
# ]

def MSE(β0, β1, x_data, y_data):
    # y = β0 + β1 * x
    # error = 0
    # for x, y in zip(x_data, y_data):
    #     error += (β0 + β1*x - y) ** 2
    return sum((β0 + β1*x_data - y_data) ** 2) / x_data.size

def MAE(β0, β1, x_data, y_data):
    # y = β0 + β1 * x
    # error = 0
    # for x, y in zip(x_data, y_data):
    #     error += abs(β0 + β1*x - y)
    return sum(abs(β0 + β1*x_data - y_data)) / x_data.size

def f(β0, β1, X):
    return β0 + β1*X

def plot_line(β0, β1, X, Y):
    x_min = X.min() - 1
    x_max = X.max() + 1
    # x_dist = int(x_max - x_min) + 1
    # dots = np.linspace(int(x_min)-1, int(x_max)+1, x_dist*3)

    plt.plot(X, Y, '.')
    plt.plot([x_min, x_max], f(β0, β1, np.array([x_min, x_max])))

def MSE_train(x_train, y_train):
    # IN:   [x_train], [y_train]
    # OUT:  [iter_loss], β0, β1
    n = x_train.shape[0]
    β0, β1 = np.random.normal(0, 1, 2)  # μ, σ, shape
    loss = MSE(β0, β1, x_train, y_train)
    loss_record = [loss]
    pre_loss = loss + 1
    # 1st derivation
    # β0 2/n ∑(β0+β1*X_i-Y_i)
    # β1 2/n ∑(β0+β1*X_i-Y_i)*β1
    while(abs(loss - pre_loss) > 0.000001):
        # print("η:", η)
        sum_β0_β1_X_Y = sum(β0 + β1 * x_train - y_train)
        β0 -= η * 2 * sum_β0_β1_X_Y / n
        β1 -= η * 2 * sum_β0_β1_X_Y * β1 / n
        pre_loss = loss
        loss = MSE(β0, β1, x_train, y_train)
        loss_record += [loss]
        print(loss, β0, β1)
    return loss_record, β0, β1

def MAE_train(x_train, y_train):
    # IN:   [x_train], [y_train]
    # OUT:  [iter_loss], β0, β1
    n = x_train.shape[0]
    β0, β1 = np.random.normal(0, 1, 2)  # μ, σ, shape
    loss = MAE(β0, β1, x_train, y_train)
    loss_record = [loss]
    pre_loss = loss + 1
    # 1st derivation
    # β0 2/n ∑(+1 if β0+β1*X_i-Y_i > 0
    #           0 if β0+β1*X_i-Y_i ==0
    #          -1 if β0+β1*X_i-Y_i < 0)
    # β1 2/n ∑(+1 if β0+β1*X_i-Y_i > 0
    #           0 if β0+β1*X_i-Y_i ==0
    #          -1 if β0+β1*X_i-Y_i < 0) * β1
    while(abs(loss - pre_loss) > 0.000001):
        sum_β0_β1_X_Y = sum(
            1 if e > 0 else -1
            for e in (β0 + β1 * x_train - y_train)
        )  # 假裝 β0 + β1 * x_train - y_train == 0 沒發生
        β0 -= η * 2 * sum_β0_β1_X_Y / n
        β1 -= η * 2 * sum_β0_β1_X_Y * β1 / n

        pre_loss = loss
        loss = MAE(β0, β1, x_train, y_train)
        loss_record += [loss]
        print(loss, β0, β1)
    return loss_record, β0, β1

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

loss, β0, β1 = MSE_train(x_train, y_train)
# print("loss:", loss[-1])
# print("β0, β1: ", β0, β1)
# plot_line(β0, β1, x_train, y_train)
b = plt.figure(2)
plt.title('MSE')
plt.plot(range(len(loss)), loss)

loss, β0, β1 = MAE_train(x_train, y_train)
c = plt.figure(3)
plt.title('MAE')
plt.plot(range(len(loss)), loss)
plt.show()

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

# TEST
test_data = pd.read_csv("test_data.csv")
x_test, y_test = test_data['x_test'], test_data['y_test']

# print("test data error:", MSE(β0, β1, x_test, y_test))
# print("test data error:", MAE(β0, β1, x_test, y_test))


import numpy as np
from matplotlib import pyplot as plt
from sklearn import linear_model

###plot 5 - cubic fit
def housing_cubic():
    plt.plot(dataset[:,1]+2000,dataset[:,0],'o')
    plt.ylim([0,600000])
    plt.title('California Median House Price - With Quadratic Approximation')
    X=np.ones((42,4))
    X[:,1]=dataset[:,1]
    X[:,2]=dataset[:,1]**2
    X[:,3]=dataset[:,1]**3
    Y=dataset[:,0]
    betahat = np.linalg.inv(X.T.dot(X)).dot(X.T.dot(Y))
    xseq=np.arange(0,12,.1)
    plt.plot(xseq+2000,betahat[0]+betahat[1]*xseq+betahat[2]*xseq**2+betahat[3]*xseq**3)
    plt.show()

###plot 6 - quartic fit
def housing_quartic():
    plt.plot(dataset[:,1]+2000, dataset[:,0], 'o')
    plt.ylim([0, 600000])
    plt.title('California Median House Price - With Quadratic Approximation')
    X = np.ones((42, 5))
    X[:,1] = dataset[:,1]
    X[:,2] = dataset[:,1]**2
    X[:,3] = dataset[:,1]**3
    X[:,4] = dataset[:,1]**4
    Y = dataset[:,0]
    betahat = np.linalg.inv(X.T.dot(X)).dot(X.T.dot(Y))
    xseq = np.arange(0, 12, .1)
    plt.plot(xseq+2000, betahat[0]+betahat[1]*xseq+betahat[2]*xseq**2+betahat[3]*xseq**3+betahat[4]*xseq**4)
    plt.show()

##logistic regression
def challenger_logistic():
    dat = np.load('challenger.npy')

    ###Logreg plot #2
    plt.plot(dat[:,0],dat[:,1],'o')
    plt.xlim(30,100)
    plt.xlabel('Ambient Temperature (F)')
    plt.ylim(-0.5,1.5)
    plt.ylabel('O-ring Damage Present')
    plt.title('Potential for Shuttle Damage - With Logistic Regression Prediction')
    #X=np.ones((dat.shape[0],2))
    #X[:,1]=dat[:,0]
    X=dat[:,0].reshape([23,1])
    Y=dat[:,1]#.reshape([23,1])
    logreg = linear_model.LogisticRegression(C=1000000,penalty="l2")
    logreg.fit(X,Y)
    coef=logreg.coef_[0]
    xseq=np.arange(30,100,.5)
    #xseqmat=np.ones((len(xseq),2))
    #xseqmat[:,1]=xseq
    xB=logreg.intercept_[0]+logreg.coef_[0][0]*xseq
    #plt.plot(xseq,1/(np.exp(-xB)+1))
    plt.plot(xseq,logreg.predict_proba(xseq2)[:,1])
    plt.show()

def challenger_disaster(model):
    xB = logreg.coef_*31 + logreg.intercept_
    1/(np.exp(-xB)+1)


'''
==============
3D quiver plot
==============

Demonstrates plotting directional arrows at points on a 3d meshgrid.

YOUR MISSIONS, if you choose to accept them:

JALAPENO. Change the size / dimensions of the vector positions
TABASCO. Make a tornado shape
SRIRACHA. Make a tornado shape with higher velocity at higher altitude
PERI-PERI. Try adding more vectors, and measure the time it takes to add more
			- specifically, how many seconds per vector does it take?

'''

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

fig = plt.figure()
ax = fig.gca(projection='3d')  # tell matplotlib we're doing a 3d plot

# Make the grid of x y and z points
x, y, z = np.meshgrid(np.arange(-0.8, 1, 0.2),
					  np.arange(-0.8, 1, 0.2),
					  np.arange(0, .1, 0.01))

# Make the direction data for each arrows at each x, y, z point
# These are crazy equations. Try messing them up and see what happens!! :)

# U is the x amount for EVERY vector at each X, Y, Z
u = np.sin(np.pi * x) * np.cos(np.pi * y) * np.cos(np.pi * z)

# V is the y component of EVERY vector at each X, Y, Z
v = -np.cos(np.pi * x) * np.sin(np.pi * y) * np.cos(np.pi * z)

# W is the z component of EVERY vector at each X, Y, Z
w = (np.sqrt(2.0 / 3.0) * np.cos(np.pi * x) * np.cos(np.pi * y) *
	 np.sin(np.pi * z))

# Do the "quiver" plot in 3d, given all of the data
ax.quiver(x, y, z, u, v, w,

            length=0.1,  # set the max length of vector
            normalize=False, # make all vectors same length?

        )

plt.show()


"""
Last night you partied a little too hard. Now there's a black and
white photo of you that's about to go viral! You can't let this ruin
your reputation, so you want to apply the box blur algorithm to the
photo to hide its content.

The pixels in the input image are represented as integers. The algorithm
distorts the input image in the following way: Every pixel x in the output
image has a value equal to the average value of the pixel values from the
3 x 3 square that has its center at x, including x itself. All the pixels
on the border od x are then removed.

Return the blurred image as an integer, with the fractions rounded down.

Example

For

image = [[1, 1, 1],
         [1, 7, 1],
         [1, 1, 1]]
the output should be boxBlur(image) = [[1]].

To get the value of the middle pixel in the input 3x3 square:
(1 + 1 + 1 + 1 + 7 + 1 + 1 + 1 + 1) = 15 / 9 = 1.666 = 1
The border pixels are cropped from the final result.
"""

import numpy as np
import math

def boxBlur(image):

    np_image = np.array(image)
    lx = len(image[0]) - 2
    ly = len(image) - 2

    new_img = np.zeros((ly,lx))

    x = 0
    y = 3
    for i in range(ly):
        for j in range(lx):
            t = np_image[x + i: y + i, x + j: y + j]
            t.tolist()
            flat_list = [k for sublist in t for k in sublist]
            s = math.floor(sum(flat_list)/9)
            new_img[i][j] = int(s)

    return(new_img.tolist())

image = [[1,1,1],
         [1,7,1],
         [1,1,1]]

print(boxBlur(image))  # [[1]]


import numpy as np
import pandas as pd
import time, gc
pi = np.pi
cos = np.cos
sin = np.sin
acos = np.arccos
degrees = np.degrees
radians = np.radians

def angularDistance(row):
    '''
    Computes the angular distance (degrees) between the reference Right-Ascension (RA)
    & Declination (Dec) value, and the corresponding RA - Dec value of the stars in
    the dataframe - <OB_CATALOGUE>

    <Formula> - http://spiff.rit.edu/classes/phys373/lectures/radec/radec.html

    Parameters
    ----------
    row : pd.Dataframe - series
        Input RA/Dec in degrees from the <IMG_DF> dataframe

    Returns
    -------
    y : pd.Dataframe - series
        The corresponding angular distance in degree value.
    '''

    # Units of right-ascension is in (hours) format
    alpha1, alpha2 = radians(15*row['RA_1']), radians(15*row['RA_2'])

    # Units of declination is in (degrees) format
    delta1, delta2 = radians(row['Dec_1']), radians(row['Dec_2'])

    temp = cos(pi/2 - delta1)*cos(pi/2 - delta2) + sin(pi/2 - delta1)*sin(pi/2 - delta2)*cos(alpha1 - alpha2)

    return np.degrees(acos(temp))

def genRefCatalogue(mag_limit, no_iter = -1, gen_csv = True):
    '''
    Generates the reference star catalogue for Geometric Voting Algorithm where each row
    of the table has two unique stars and the corresponding angular distance in degrees,
    for all pairs of stars with a specified upper magnitude limit

    Parameters
    ----------
    mag_limit : floating-point number
        The upper magnitude limit of stars that are required in the reference catalogue

    no_iter : integer, default = -1
        Specifies the number of iterations, thereby allowing it to be reduced
        Default value = -1, allows for the completion of the entire catalogue

    gen_csv : boolean, default = True
          If True generates csv files of the reference catalogues

    Returns
    -------
    OB_CATALOGUE : pd.Dataframe
        The corresponding angular distance in degree value.
    '''

    # Start clock-1
    start1 = time.time()

    # Generate restricted catalogue based on upper magnitude limit
    temp0 = CATALOGUE[CATALOGUE.Mag <= mag_limit]

    # Number of rows in the resticted catalogue
    rows = temp0.shape[0]
    # Resets the index of <temp0>
    temp0.index = list(range(rows))

    # Prints total number of stars in <temp0> and the (n)C(2) - combinations
    print('Number of stars - ', rows)
    print('Number of unique combinations = ', (rows-1)*rows/2)

    # Initialize the number of iterations to take place
    no_iter = (rows-1) if no_iter == -1 else no_iter

    for i in range(no_iter):
        # Throws error if an iteration runs beyond number of available rows in <temp0>
        assert i<(rows-1), 'iterating beyond available number of rows'

        # The final iteration is reduntant, as <temp2> will be zero rows
        '''
        if (rows-1-i)==0:
            continue
        '''

        # Generates <temp1> dataframe which has the (i - th) star of <temp0>
        # repetated (rows-1-i) times
        temp1 = pd.DataFrame(columns = ['Star_ID1','RA_1', 'Dec_1', 'Mag_1'])
        s1, ra, dec, mag = temp0.iloc[i]
        temp1.loc[0] = [s1] + [ra] + [dec] + [mag]
        temp1 = pd.concat([temp1]*(rows-1-i), ignore_index=True)

        # Generates <temp2> dataframe by copying values of <temp0> and dropping the first
        # (i + 1) number of stars
        temp2 = temp0
        temp2 = temp2.drop(list(range(i+1)), axis = 0)
        # Resets the index
        temp2.index = list(range(0, rows-1-i))

        # Concatenates <temp1> & <temp2> side-by-side such that resulting <temp3> has (8) columns altogether
        temp3 = pd.concat([temp1, temp2], axis=1)

        # Initializes <temp4> in the first iteratation
        if i == 0:
            temp4 = temp3

        # Append subsequent <temp4> with <temp3> after first iteration
        else:
            temp4 = pd.concat([temp4, temp3], axis = 0, ignore_index=True)

        # Releases memory back to OS
        if i%40 == 0:
            gc.collect()

    gc.collect()
    # Rename columns
    temp4.columns = ['Star_ID1','RA_1', 'Dec_1', 'Mag_1', 'Star_ID2', 'RA_2', 'Dec_2', 'Mag_2']

    if gen_csv == True:
        #Generates CSV of <temp4>
        temp4.to_csv('Processed_Catalogue1.csv', index = False)

    # Stop clock-1
    end1 = time.time() - start1

    # Print time taken
    print('Process 1 - ', end1)

    # Start clock-2
    start2 = time.time()

    # Initialize <OB_CATALOGUE>
    OB_CATALOGUE = temp4

    # Calculate angular distance between the two stars present in every row
    OB_CATALOGUE['Ang_Distance'] = OB_CATALOGUE.apply(angularDistance, axis = 1)

    if gen_csv == True:
        # Generates CSV of <OB_CATALOGUE>
        OB_CATALOGUE.to_csv('Processed_Catalogue2.csv', index = False)

    # Stop clock-2
    end2 = time.time() - start2

    # Print time taken
    print('Process 2 - ', end2)
    print('Total Process ', end1+ end2)

    return OB_CATALOGUE

CATALOGUE = pd.read_csv("Modified Star Catalogue.csv")
# StarID: The database primary key from a larger "master database" of stars
# Mag: The star's apparent visual magnitude
# RA, Dec: The star's right ascension and declination, for epoch 2000.0 (Unit: RA - hrs; Dec - degrees)

# Sorts <CATALOGUE>
CATALOGUE.sort_values('Mag', inplace=True)

# Run function
REF_DF = genRefCatalogue(mag_limit=4, no_iter=-1, gen_csv=False)

# Sort <REF_DF>
REF_DF.sort_values('Ang_Distance', inplace=True)
# Generates CSV of <REF_DF>
REF_DF.to_csv('Processed_Catalogue3.csv', index = False)


import matplotlib.pyplot as plt
import numpy as np
import plotly
import plotly.tools as tls
import plotly.graph_objs as go
# import random
import jsonReader

# import ipywidgets as widgets
# values is the input that DCK will input from the JSON file

def mean(vals):
    sum = 0
    for i in range(len(vals)):
        sum += vals[i]
    return sum / len(vals)

"""
What follows is a ckassical linear approximation of a good trendline, using the method
of least squares as presentedin the textbook Uncertainities and their Measurements
by Ifan Hugh and Thomas Hase, page 74
"""

def delta(vals):
    return len(vals) * sum(mult_tup(vals, vals)) - (sum(vals)) ** 2

def intercept(valx, valy):
    return (sum(mult_tup(valx, valx)) * sum(valy) - sum(valx) * sum(mult_tup(valx, valy))) / delta(valx)

def mult_tup(x, y):
    m = np.zeros(len(x))
    for i in range(len(x)):
        m[i] = x[i] * y[i]
    return m

def gradient(valx, valy):
    """print(valx)
    print(valy)
    print(summ(valx*valy))
    print(delta)"""
    return (len(valx) * sum(mult_tup(valx, valy)) - sum(valx) * sum(valy)) / delta(valx)

def fin_func(varXName, varYName):
    # make values by calling
    values = jsonReader.getDataCollection(varXName, varYName)

    xuser = (0, 0)  # varXName
    yuser = (0, 0)  # varYName
    x, y = zip(*values)  # splits the tuples in values into two lists, x and y

    trial_array = np.linspace(min(x), max(x))
    super_array = np.linspace(min(x), max(x))
    for i in range(len(trial_array)):
        super_array[i] = trial_array[i] * gradient(x, y) + intercept(x, y)

    fig, ax = plt.subplots()
    ax.plot(trial_array, super_array)
    ax.scatter(x, y)
    ax.scatter(xuser, yuser)

    layout = go.Layout(
        title="Plot of:\n" + str(varXName) + " against \n" + str(varYName),
        xaxis=dict(
            title=str(varXName),
            titlefont=dict(
                family='Oswald, monospace',
                size=18
            ),
        ),
        yaxis=dict(
            title=str(varYName),
            titlefont=dict(
                family='Oswald, monospace',
                size=18
            )
        )

    )
    trace1 = go.Scatter(x=x, y=y, mode="markers", name=str(varXName) + " and " + str(varYName))
    # trace2 = go.Scatter(x=xuser, y=yuser, mode = "markers")
    trace3 = go.Scatter(x=trial_array, y=super_array, name="Linear Correlation Line")
    data = [trace1, trace3]  # trace3]
    plotly_fig = go.Figure(data=data, layout=layout)
    plotly.offline.plot(plotly_fig, show_link=False, filename='basic-scatter-plot.html', )
    with open('basic-scatter-plot.html', 'r') as myfile:
        html_words = myfile.read().replace("\n", '')
    return html_words


from __future__ import division
import numpy as np

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

def tanh_deriv(x):
    return 1.0 - np.tanh(x)**2

def logistic(x):
    return 1/(1 + np.exp(-x))

def logistic_derivative(x):
    return logistic(x)*(1-logistic(x))

def _scale_to_binary(e, minV, maxV):
    result = ((e-minV)/(maxV-minV))*(1-0)+0
    return result

def rescale_from_binary(e, minV, maxV):
    result = e*(maxV-minV) + minV
    return result

class NeuralNetwork:
    def __init__(self, layers, activation='tanh'):
        """
        :param layers: A list containing the number of units in each layer.
        Should be at least two values
        :param activation: The activation function to be used. Can be
        "logistic" or "tanh"
        """
        np.random.seed(0)
        if activation == 'logistic':
            self.activation = logistic
            self.activation_deriv = logistic_derivative
        elif activation == 'tanh':
            self.activation = tanh
            self.activation_deriv = tanh_deriv

        self.weights = []
        for i in range(1, len(layers) - 1):
            self.weights.append((2*np.random.random((layers[i - 1] + 1, layers[i]
                                + 1))-1)*2.0)

        self.weights.append((2*np.random.random((layers[i] + 1, layers[i +
                            1]))-1)*2.0)

        #print self.weights

    def fit(self, X, y, learning_rate=2, epochs=50000):
        #Set initial temperature to be 27
        X = np.atleast_2d(X)
        #temp = np.full([X.shape[0], X.shape[1]+1],27)#Initial temperature is 27
        temp = np.ones([X.shape[0], X.shape[1]+1])
        temp[:, 0:-1] = X  # adding the bias unit to the input layer
        X = temp #Create a new X but with an extra bias item
        y = np.array(y)

        for k in range(epochs):
            i = np.random.randint(X.shape[0])
            a = [X[i]]

            for l in range(len(self.weights)):
                a.append(self.activation(np.dot(a[l], self.weights[l])))
            error = y[i] - a[-1]
            deltas = [error * self.activation_deriv(a[-1])]

            for l in range(len(a) - 2, 0, -1): # we need to begin at the second to last layer
                deltas.append(deltas[-1].dot(self.weights[l].T)*self.activation_deriv(a[l]))
            deltas.reverse()
            for i in range(len(self.weights)):
                layer = np.atleast_2d(a[i])
                delta = np.atleast_2d(deltas[i])
                self.weights[i] += learning_rate * layer.T.dot(delta)

    def predict(self, x):
        x = np.array(x)
        #temp = np.full([x.shape[0]+1],27)
        temp = np.ones(x.shape[0]+1)
        temp[0:-1] = x
        a = temp
        #print a
        for l in range(0, len(self.weights)):
            #print a
            a = self.activation(np.dot(a, self.weights[l]))
        return a


from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
import numpy as np
import pandas as pd

data = np.array([[2.5, 2.4], [0.5, 0.7], [2.2, 2.9], [1.9, 2.2], [3.1, 3.0], [2.3, 2.7], [2, 1.6], [1, 1.1], [1.5, 1.6], [1.1, 0.9]])
# data = StandardScaler().fit_transform(data)
pca = PCA(n_components=2)
# pca.fit_transform(data)
principalComponents_x = pca.fit_transform(data)
# print(data)
print (principalComponents_x)

output2 = {}
output2["graph"] = []
for principalComponents in principalComponents_x:
    data = {}
    data["principalComponent1"] = principalComponents[0]
    data["principalComponent2"] = principalComponents[1]
    output2["graph"].append(data)
print (output2)

# principal_component_Df = pd.DataFrame(data = principalComponents_x, columns = ['principal component 1', 'principal component 2'])
# print ("principal component values for all samples")
# print (principal_component_Df)

# print ("information or variance each principal component holds after projecting the data to a lower dimensional subspace - Explanied Varience Ratio")
# print (pca.explained_variance_ratio_)

# print ("eigen vectors - Componenets")
# print(pca.components_)

# print ("eigen values - Explanied Varience")
# print(pca.explained_variance_)

# print ("Loading")
# print (pca.components_.T * np.sqrt(pca.explained_variance_))


import numpy as np

class LinearRegressionScratch:
    def __init__(self):
        return None

    def fit(self, X, y, method, learning_rate=0.01, iterations=500, batch_size=32):
        """inputs :
            X : Ndarray : the data we want to train on
            y : Nadarray : the labeled data for regression
            method : string : ["ols","sgd"]. ols refers to Ordinary Least Square Estimators and sgd to Stochastic Gradient Descent
            learning : float : parameter for the sdg method
            iterations : integer : parameter for the sdg
            batch_size : integer : parameter for the sdg method

        """
        # x devient une matrice avec des 1 en bas avec la taille de y
        y = np.reshape(y, (len(y),1), order='C')
        X = np.concatenate([X, np.ones_like(y)], axis=1)
        # we gather X shape
        rows, cols = X.shape
        if method == 'ols':
            # if more raws than columns, we gather the rank of the matrix
            if rows >= cols == np.linalg.matrix_rank(X):
                # We use the famous formula for the OLS
                self.weights = np.matmul(
                    np.matmul(
                        np.linalg.inv(
                            np.matmul(
                                X.transpose(),
                                X)),
                        X.transpose()),
                    y)
            # if less raws than columns => impossible
            else:
                print('X has not full column rank. method=\'solve\' cannot be used.')

        # METHOD Stochastic gradient descent
        elif method == 'sgd':
            self.weights = np.random.normal(scale=1/cols, size=(cols, 1))
            for i in range(iterations):
                Xy = np.concatenate([X, y], axis=1)
                np.random.shuffle(Xy)
                X, y = Xy[:, :-1], Xy[:, -1:]
                for j in range(int(np.ceil(rows/batch_size))):
                    start, end = batch_size*j, np.min([batch_size*(j+1), rows])
                    Xb, yb = X[start:end], y[start:end]
                    gradient = 2*np.matmul(
                        Xb.transpose(),
                        (np.matmul(Xb,
                                   self.weights)
                         - yb))
                    self.weights -= learning_rate*gradient

        else:
            print('unknown method')

        return self

    def predict(self, X):
        """ function to predict the result with a test set based on what have learned the fit method
            X : NdArray: test set
        """

        # If no weights => no fit
        if not hasattr(self, 'weights'):
            print('Cannot predict. You should call the .fit() method first.')
            return

        X = np.concatenate([X, np.ones((X.shape[0], 1))], axis=1)

        if X.shape[1] != self.weights.shape[0]:
            #print(f'Shapes do not match. {X.shape[1]} != {self.weights.shape[0]}')
            return

        return np.matmul(X, self.weights)

    def rmse(self, X, y):
        # we use the fit method from bellow
        y_hat = self.predict(X).ravel()

        # if not prediction we quit
        if y_hat is None:
            return

        # otherwise we apply the formula
        return np.sqrt((np.mean((y_hat - y)**2)))

    def get_weights(self):
        return self.weights[:-1]


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

class quad_rand:
    def __init__(self, n, seed=135, max_iters=200):
        """
        intitialize random coefficients with given dimention
        """
        np.random.seed(seed)
        self.n = n
        self.b = np.matrix(2 * np.random.rand(n, 1) - 1)
        U = np.matrix(ortho_group.rvs(dim=n))
        D = np.matrix(np.diagflat(np.random.rand(n)))
        self.A = U.T * D * U
        self.max_iters = max_iters

    def f(self, x):
        """
        calculate f(x) with input x
        """
        return np.asscalar(1 / 2 * x.T * self.A * x + self.b.T * x)

    def df(self, x):

        """
        calculate f'(x) with input x
        """
        return self.A * x + self.b

    def grad_desc_const(self, alpha):
        """
        gradient descent with constant step size alpha
        """
        x = np.matrix(np.zeros((self.n, 1)))
        self.fun_values_const = [self.f(x)]
        for i in range(self.max_iters-1):
            # calculate derivative
            d = self.df(x)
            # update x
            x -= alpha * d
            # get new function value
            self.fun_values_const.append(self.f(x))

    def grad_desc_exact(self):
        """
        gradient descent with exact line minimization
        """
        x = np.matrix(np.zeros((self.n, 1)))
        self.fun_values_exact = [self.f(x)]
        for i in range(self.max_iters-1):
            # calculate derivative
            d = self.df(x)
            # calculate step size
            alpha = np.asscalar(d.T * d / (d.T * self.A * d))
            # update x
            x -= alpha * d
            # get new function value
            self.fun_values_exact.append(self.f(x))

    def grad_desc_armijo(self, alpha, beta=0.5, sigma=0.9):
        """
        gradient descent with Armijo step size rule
        """
        x = np.matrix(np.zeros((self.n, 1)))
        self.fun_values_armijo = [self.f(x)]
        for i in range(self.max_iters-1):
            # calculate derivative
            d = self.df(x)
            # backtracking line search
            cur_alpha = alpha
            cur_value = self.f(x - cur_alpha * d)
            while cur_value <= self.fun_values_exact[-1] + sigma * cur_alpha * d.T * d:
                cur_alpha *= beta
                cur_value = self.f(x - cur_alpha * d)
            # update x
            x -= cur_alpha * d
            # get new function value
            self.fun_values_armijo.append(cur_value)

# create my random function
my_fun = quad_rand(100)
# run graident descent
my_fun.grad_desc_const(alpha=0.3)
my_fun.grad_desc_exact()
my_fun.grad_desc_armijo(alpha=1)
# plot
plt.plot(range(200), my_fun.fun_values_const, label='Constant')
plt.plot(range(200), my_fun.fun_values_exact, label='Exact Min')
plt.plot(range(200), my_fun.fun_values_armijo, label='Armijo')
plt.legend(loc="best")
plt.xlabel("Iterations")
plt.ylabel("Function Value")
plt.show()


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

description = """ This one is a bit different, and explains how to use
   odds ratios, and the beta distribution, to understand the difference
   between a case and control sample

               Cancer   Control
   Smokers     83       72
   Nonsmokers  3        14

   Here we have 86 individuals in both case and control samples.  83
   smokers obtained cancer, while 3 nonsmokers obtained cancer.  72
   smokers did not obtain cancer, while 3 nonsmokers did not obtain
   cancer.  Ask the question: is there a significant difference
   between the smoking habits in the two groups?

   pL: population proportion of lung-cancer patients who smoke = 83/86
   pC: population proportion of control who smoke  = 72/86

   The question may be be answered by examining the posterior
   distribution of pL - pC.  Or the odds ratio

      pL/(1-pL)  /  pC/(1-pC)

   The log odds ratio lambda is less skewed, so ask the question
   p(lambda>0 | data)

   Overall likelihood is :

     L(pL, pC) = pL^83(1 - pL)^3 pC^72(1 - pC)^14

   pL has a Beta distribution, specifically Beta(83,3)
   pC has Beta(72,14)

   Draw 1000 pL' from scipy.stats.beta(83, 3)
   Draw 1000 pC' from scipy.stats.beta(72, 14)
   Calculate the number of times the odds ratio is > 0!
   """

betaL   = stats.beta(83, 3)
betaC   = stats.beta(72, 14)

ndraws  = 10000
pLp     = betaL.rvs(size=ndraws)
pCp     = betaC.rvs(size=ndraws)

numer   = pLp / (1 - pLp)
denom   = pCp / (1 - pCp)
logodds = np.log(numer/denom)

print "Expectation value of logodds  : %.4f" % (np.mean(logodds))
print "Standard deviation of logodds : %.4f" % (np.std(logodds))
print "P(logodds > 0 | data)         : %.4f" % (1.0*len(np.where(logodds>0)[0])*1.0/ndraws)


import numpy as np

def lines_closest_pts(a, b, c, d):
    """
    Returns two closest pints on the lines AB and CD,
    which detetermines the distance between the lines.
    a, b, c, d: [3] numpy vectors
    return: n, m: [3] numpy vectors of the closest points
    """
    A = np.array([[(b-a).dot(b-a), -(b-a).dot(d-c)],
                  [(d-c).dot(b-a), -(d-c).dot(d-c)]])
    B = -np.array([(b-a).dot(a-c), (d-c).dot(a-c)])
    x = np.linalg.inv(A).dot(B)
    n = a + x[0] * (b-a)
    m = c + x[1] * (d-c)
    return n, m

def lines_angle(a, b, c, d):
    """
    Returns the angle between the lines AB and CD,
    a, b, c, d: [3] numpy vectors
    return: alpha: [3] numpy vector
    """
    A = (a-b) / np.linalg.norm(a-b)
    B = (c-d) / np.linalg.norm(c-d)
    alpha = np.arccos(np.dot(A, B))
    return alpha

if __name__ == "__main__":
    a = np.array([ 1, 2, -2])
    b = np.array([ 2, 3, -2])
    c = np.array([ 0,-1, 5])
    d = np.array([ 0, 1, 5])
    n, m = lines_closest_pts(a, b, c, d)
    assert np.allclose(n, np.array([ 0.,  1., -2.]))
    assert np.allclose(m, np.array([ 0.,  1.,  5.]))

    a = np.array([ 0, 0, -3])
    b = np.array([ 1, 1, -3])
    c = np.array([ 0, 0, 5])
    d = np.array([ 1, 0, 5])
    alpha = lines_angle(a, b, c, d)
    assert np.allclose(alpha, np.pi / 4)


#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
:Purpose:   This module contains the statistical calculations for the project.

:Platform:  Linux/Windows | Python 3.6+
:Developer: J Berendt
:Email:     development@s3dev.uk

:Comments:  n/a

:Example:

    Calculate a linear regression line on an X/Y dataset.:

        >>> from lib.stats import LinearRegression

        >>> linreg = LinearRegression(x, y)
        >>> linreg.calculate()

        >>> # View the intercept value.
        >>> linreg.intercept
        5.6

        >>> # View the slope value.
        >>> linreg.slope
        0.25678

        >>> # Obtain the regression line array.
        >>> y_ = linreg.regression_line

"""
# pylint: disable=line-too-long

import numpy as np

class LinearRegression:
    """Calculate the linear regression of a dataset.

    Args:
        x (np.array): Array of X-values.
        y (np.array): Array of Y-values.

    :Slope Calculation:
        The calculation for the slope itself is borrowed from the
        :func:`scipy.stats.linregress` function. Whose `source code`_ was
        obtained on GitHub.

    .. _source code: https://github.com/scipy/scipy/blob/v1.8.0/scipy/stats/_stats_mstats_common.py#L16-L203

    """

    def __init__(self, x: np.array, y: np.array):
        """LinearRegression class initialiser."""
        self._x = x
        self._y = y
        self._xbar = 0.0
        self._ybar = 0.0
        self._c = 0.0
        self._m = 0.0
        self._line = np.array(())

    @property
    def slope(self):
        """Accessor to the slope value."""
        return self._m

    @property
    def intercept(self):
        """Accessor to the slope's y-intercept."""
        return self._c

    @property
    def regression_line(self):
        """Accessor to the calculated regression line, as y-values."""
        return self._line

    def calculate(self):
        """Calculate the linear regression for the X/Y data arrays.

        The result of the calculation is accessible via the
        :attr:`regression_line` property.

        """
        self._calc_means()
        self._calc_slope()
        self._calc_intercept()
        self._calc_regression_line()

    def _calc_intercept(self):
        """Calculate the intercept as: ybar - m * xbar."""
        self._c = self._ybar - self._m * self._xbar

    def _calc_means(self) -> float:
        """Calculate the mean of the X and Y arrays."""
        self._xbar = self._x.mean()
        self._ybar = self._y.mean()

    def _calc_regression_line(self):
        """Calculate the regression line as: y = mx + c."""
        self._line = self._m * self._x + self._c

    def _calc_slope(self):
        """Calculate the slope value as: R * ( std(y) / std(x) ).

        Per the ``scipy`` source code comments::

            # Average sums of square differences from the mean
            #   ssxm = mean( (x-mean(x))^2 )
            #   ssxym = mean( (x-mean(x)) * (y-mean(y)) )

            ...

            slope = ssxym / ssxm

        """
        ssxm = np.mean( (self._x - self._xbar)**2 )
        ssxym = np.mean( (self._x - self._xbar) * (self._y - self._ybar) )
        self._m = ssxym / ssxm

    @staticmethod
    def _calc_std(data: np.array, ddof: int=1) -> float:  # pragma nocover
        """Calculate the standard deviation.

        Args:
            data (np.array): Array of values.
            ddof (int): Degrees of freedom. Defaults to 1.

        Returns:
            float: Standard deviation of the given values.

        """
        return np.std(data, ddof=ddof)


from sympy.core import (Function, Pow, sympify, Expr)
from sympy.core.relational import Relational
from sympy.polys import Poly, decompose
from sympy.utilities.misc import func_name

def decompogen(f, symbol):
    """
    Computes General functional decomposition of ``f``.
    Given an expression ``f``, returns a list ``[f_1, f_2, ..., f_n]``,
    where::
              f = f_1 o f_2 o ... f_n = f_1(f_2(... f_n))

    Note: This is a General decomposition function. It also decomposes
    Polynomials. For only Polynomial decomposition see ``decompose`` in polys.

    Examples
    ========

    >>> from sympy.solvers.decompogen import decompogen
    >>> from sympy.abc import x
    >>> from sympy import sqrt, sin, cos
    >>> decompogen(sin(cos(x)), x)
    [sin(x), cos(x)]
    >>> decompogen(sin(x)**2 + sin(x) + 1, x)
    [x**2 + x + 1, sin(x)]
    >>> decompogen(sqrt(6*x**2 - 5), x)
    [sqrt(x), 6*x**2 - 5]
    >>> decompogen(sin(sqrt(cos(x**2 + 1))), x)
    [sin(x), sqrt(x), cos(x), x**2 + 1]
    >>> decompogen(x**4 + 2*x**3 - x - 1, x)
    [x**2 - x - 1, x**2 + x]

    """
    f = sympify(f)
    if not isinstance(f, Expr) or isinstance(f, Relational):
        raise TypeError('expecting Expr but got: `%s`' % func_name(f))
    if symbol not in f.free_symbols:
        return [f]

    result = []

    # ===== Simple Functions ===== #
    if isinstance(f, (Function, Pow)):
        from sympy import S
        if f.is_Pow and f.base == S.Exp1:
            arg = f.exp
        else:
            arg = f.args[0]
        if arg == symbol:
            return [f]
        result += [f.subs(arg, symbol)] + decompogen(arg, symbol)
        return result

    # ===== Convert to Polynomial ===== #
    fp = Poly(f)
    gens = list(filter(lambda x: symbol in x.free_symbols , fp.gens))

    if len(gens) == 1 and gens[0] != symbol:
        f1 = f.subs(gens[0], symbol)
        f2 = gens[0]
        result += [f1] + decompogen(f2, symbol)
        return result

    # ===== Polynomial decompose() ====== #
    try:
        result += decompose(f)
        return result
    except ValueError:
        return [f]

def compogen(g_s, symbol):
    """
    Returns the composition of functions.
    Given a list of functions ``g_s``, returns their composition ``f``,
    where:
        f = g_1 o g_2 o .. o g_n

    Note: This is a General composition function. It also composes Polynomials.
    For only Polynomial composition see ``compose`` in polys.

    Examples
    ========

    >>> from sympy.solvers.decompogen import compogen
    >>> from sympy.abc import x
    >>> from sympy import sqrt, sin, cos
    >>> compogen([sin(x), cos(x)], x)
    sin(cos(x))
    >>> compogen([x**2 + x + 1, sin(x)], x)
    sin(x)**2 + sin(x) + 1
    >>> compogen([sqrt(x), 6*x**2 - 5], x)
    sqrt(6*x**2 - 5)
    >>> compogen([sin(x), sqrt(x), cos(x), x**2 + 1], x)
    sin(sqrt(cos(x**2 + 1)))
    >>> compogen([x**2 - x - 1, x**2 + x], x)
    -x**2 - x + (x**2 + x)**2 - 1
    """
    if len(g_s) == 1:
        return g_s[0]

    foo = g_s[0].subs(symbol, g_s[1])

    if len(g_s) == 2:
        return foo

    return compogen([foo] + g_s[2:], symbol)


import json

from pydantic import BaseModel

import asyncio
import aiohttp
import logging

# import F1.Bot as Bot
import F2.APIParams as APIParams
import F2.Event as Event
import F2.Message as Message
import utils.utils as utils

import sympy
import math

async def isprime(regular_message: Message.Message, event:Event.MessageEvent, *, bot) -> Message.Message:
    """
        判断一个数是否是质数。

        格式：isprime arg1
            arg1: 要判断的数
    """
    n = int(regular_message[1].data['text'])

    return Message.Message.init_with_segments(
        Message.MessageSegment.text("isprime: True" if sympy.isprime(n) else "isprime: False")
    )

async def gcd(regular_message: Message.Message, event:Event.MessageEvent, *, bot) -> Message.Message:
    """
        最大公因数。

        格式：gcd x y
            x, y: 要求的数
    """
    x = int(regular_message[1].data['text'])
    y = int(regular_message[2].data['text'])
    ans = math.gcd(x, y)

    return Message.Message.init_with_segments(
        Message.MessageSegment.text(str(ans))
    )

async def lcm(regular_message: Message.Message, event:Event.MessageEvent, *, bot) -> Message.Message:
    """
        最小公倍数。

        格式：lcm x y
            x, y: 要求的数
    """
    x = int(regular_message[1].data['text'])
    y = int(regular_message[2].data['text'])
    ans = sympy.lcm(x, y)

    return Message.Message.init_with_segments(
        Message.MessageSegment.text(str(ans))
    )

async def log(regular_message: Message.Message, event:Event.MessageEvent, *, bot) -> Message.Message:
    """
        对数。

        格式：log b x  (or) log x
            b: 基
            x: 要求的数
    """
    x = float(regular_message[1].data['text'])
    if len(regular_message) == 3:
        y = float(regular_message[2].data['text'])
        ans = math.log(y, x)
    else:
        ans = math.log(x)

    return Message.Message.init_with_segments(
        Message.MessageSegment.text(str(ans))
    )

async def sqrt(regular_message: Message.Message, event:Event.MessageEvent, *, bot) -> Message.Message:
    """
        开方。

        格式：sqrt x
            x: 要求的数
    """
    x = float(regular_message[1].data['text'])
    ans = math.sqrt(x)

    return Message.Message.init_with_segments(
        Message.MessageSegment.text(str(ans))
    )

async def sin(regular_message: Message.Message, event:Event.MessageEvent, *, bot) -> Message.Message:
    """
        三角函数sin。

        格式：sin x
            x: 要求的数
    """
    x = float(regular_message[1].data['text'])
    ans = math.sin(x)

    return Message.Message.init_with_segments(
        Message.MessageSegment.text(str(ans))
    )

async def cos(regular_message: Message.Message, event:Event.MessageEvent, *, bot) -> Message.Message:
    """
        三角函数cos。

        格式：cos x
            x: 要求的数
    """
    x = float(regular_message[1].data['text'])
    ans = math.cos(x)

    return Message.Message.init_with_segments(
        Message.MessageSegment.text(str(ans))
    )

async def tan(regular_message: Message.Message, event:Event.MessageEvent, *, bot) -> Message.Message:
    """
        三角函数tan。

        格式：tan x
            x: 要求的数
    """
    x = float(regular_message[1].data['text'])
    ans = math.tan(x)

    return Message.Message.init_with_segments(
        Message.MessageSegment.text(str(ans))
    )


import numpy
import torch
from torch import einsum

# 'expression-with-axis-ids -> output-ids'
# Transpose
from torch.nn.functional import softmax

a = torch.arange(6).view(2, 3)
# print(a)
# print(einsum("ij->ji", a))

# Sum Rows
# print(einsum("ij->i", a))

# Sum Columns
# print(einsum("ij->j", a))

# Sum
# print(einsum("ij->", a))

# element-wise multiplication
# print(einsum("ij,ij->ij", a, a))

# Dot product
x = torch.arange(3)
y = torch.arange(3, 6)
# print(einsum("i, i->", x, y))

# Outer Product
# print(einsum("i,j->ij", x, y))

# Matrix Multiplication
x = torch.arange(6).view(2, 3)
y = torch.arange(12).view(3, 4)
# print(einsum("ij,jk->ik", x, y))

# Batch Matrix multiplication
x = torch.arange(6).view(1, 2, 3)
y = torch.arange(12).view(2, 3, 2)
# print(einsum("ijk,ikl->ijl", x, y))

# 4-D matrix multiplication
x = torch.arange(64).view(2, 4, 2, 4)
y = torch.arange(96).view(2, 4, 4, 3)
# print(einsum("bhmd,bhdn->bhmn", x, y))

# Dot Product of 4-D tensors with axis rotation
# q = torch.arange(24).view(2, 2, 2, 3)
# k = torch.arange(24, 48).view(2, 2, 2, 3)
b = 1
h = 2
d = 3
q = torch.tensor([[[0., 1., 1., 0., 1., 1.],
                   [4., 6., 0., 4., 6., 0.],
                   [2., 3., 1., 2., 3., 1.]]]).view(b, -1, h, d)
k = torch.tensor([[[1., 1., 1., 1., 1., 1.],
                   [4., 2., 2., 4., 2., 2.],
                   [3., 1., 2., 3., 1., 2.]]]).view(b, -1, h, d)
v = torch.tensor([[[1., 1., 2., 1., 1., 2.],
                   [2., 4., 4., 2., 4., 4.],
                   [2., 2., 3., 2., 2., 3.]]]).view(b, -1, h, d)
print('Query')
print(q)
print('Key')
print(k)
attn_score = einsum("bmhd,bnhd->bhmn", q, k).type(torch.float32)
print(attn_score)
attn_score_probability = softmax(attn_score, dim=-1)
print(numpy.round(attn_score_probability.detach(), decimals=2))
print(v)
output = einsum("bhmn,bnhd->bmhd", attn_score_probability, v)
print(output)
print(output.reshape(b, -1, h*d))
# In the above example, we have m, and n varying size and sum is over axis d (last axis)


import aerosandbox.numpy as np
from scipy.special import comb
from aerosandbox.geometry.polygon import stack_coordinates
import re

_default_n_points_per_side = 200

def get_NACA_coordinates(
        name: str = 'naca2412',
        n_points_per_side: int = _default_n_points_per_side
) -> np.ndarray:
    """
    Returns the coordinates of a specified 4-digit NACA airfoil.
    Args:
        name: Name of the NACA airfoil.
        n_points_per_side: Number of points per side of the airfoil (top/bottom).

    Returns: The coordinates of the airfoil as a Nx2 ndarray [x, y]

    """
    name = name.lower().strip()

    if not "naca" in name:
        raise ValueError("Not a NACA airfoil!")

    nacanumber = name.split("naca")[1]
    if not nacanumber.isdigit():
        raise ValueError("Couldn't parse the number of the NACA airfoil!")

    if not len(nacanumber) == 4:
        raise NotImplementedError("Only 4-digit NACA airfoils are currently supported!")

    # Parse
    max_camber = int(nacanumber[0]) * 0.01
    camber_loc = int(nacanumber[1]) * 0.1
    thickness = int(nacanumber[2:]) * 0.01

    # Referencing https://en.wikipedia.org/wiki/NACA_airfoil#Equation_for_a_cambered_4-digit_NACA_airfoil
    # from here on out

    # Make uncambered coordinates
    x_t = np.cosspace(0, 1, n_points_per_side)  # Generate some cosine-spaced points
    y_t = 5 * thickness * (
            + 0.2969 * x_t ** 0.5
            - 0.1260 * x_t
            - 0.3516 * x_t ** 2
            + 0.2843 * x_t ** 3
            - 0.1015 * x_t ** 4  # 0.1015 is original, #0.1036 for sharp TE
    )

    if camber_loc == 0:
        camber_loc = 0.5  # prevents divide by zero errors for things like naca0012's.

    # Get camber
    y_c = np.where(
        x_t <= camber_loc,
        max_camber / camber_loc ** 2 * (2 * camber_loc * x_t - x_t ** 2),
        max_camber / (1 - camber_loc) ** 2 * ((1 - 2 * camber_loc) + 2 * camber_loc * x_t - x_t ** 2)
    )

    # Get camber slope
    dycdx = np.where(
        x_t <= camber_loc,
        2 * max_camber / camber_loc ** 2 * (camber_loc - x_t),
        2 * max_camber / (1 - camber_loc) ** 2 * (camber_loc - x_t)
    )
    theta = np.arctan(dycdx)

    # Combine everything
    x_U = x_t - y_t * np.sin(theta)
    x_L = x_t + y_t * np.sin(theta)
    y_U = y_c + y_t * np.cos(theta)
    y_L = y_c - y_t * np.cos(theta)

    # Flip upper surface so it's back to front
    x_U, y_U = x_U[::-1], y_U[::-1]

    # Trim 1 point from lower surface so there's no overlap
    x_L, y_L = x_L[1:], y_L[1:]

    x = np.hstack((x_U, x_L))
    y = np.hstack((y_U, y_L))

    return stack_coordinates(x, y)

def get_kulfan_coordinates(
        lower_weights=-0.2 * np.ones(5),  # type: np.ndarray
        upper_weights=0.2 * np.ones(5),  # type: np.ndarray
        enforce_continuous_LE_radius=True,
        TE_thickness=0.005,  # type: float
        n_points_per_side=_default_n_points_per_side,  # type: int
        N1=0.5,  # type: float
        N2=1.0,  # type: float
) -> np.ndarray:
    """
    Calculates the coordinates of a Kulfan (CST) airfoil.
    To make a Kulfan (CST) airfoil, use the following syntax:

    asb.Airfoil("My Airfoil Name", coordinates = asb.kulfan_coordinates(*args))

    More on Kulfan (CST) airfoils: http://brendakulfan.com/docs/CST2.pdf
    Notes on N1, N2 (shape factor) combinations:
        * 0.5, 1: Conventional airfoil
        * 0.5, 0.5: Elliptic airfoil
        * 1, 1: Biconvex airfoil
        * 0.75, 0.75: Sears-Haack body (radius distribution)
        * 0.75, 0.25: Low-drag projectile
        * 1, 0.001: Cone or wedge airfoil
        * 0.001, 0.001: Rectangle, circular duct, or circular rod.
    :param lower_weights:
    :param upper_weights:
    :param enforce_continuous_LE_radius: Enforces a continous leading-edge radius by throwing out the first lower weight.
    :param TE_thickness:
    :param n_points_per_side:
    :param N1: LE shape factor
    :param N2: TE shape factor
    :return:
    """

    if enforce_continuous_LE_radius:
        lower_weights[0] = -1 * upper_weights[0]

    x_lower = cosspace(0, 1, n_points_per_side)
    x_upper = x_lower[::-1]

    def shape(w, x):
        # Class function
        C = x ** N1 * (1 - x) ** N2

        # Shape function (Bernstein polynomials)
        n = len(w) - 1  # Order of Bernstein polynomials

        K = comb(n, np.arange(n + 1))  # Bernstein polynomial coefficients

        S_matrix = (
                w * K * np.expand_dims(x, 1) ** np.arange(n + 1) *
                np.expand_dims(1 - x, 1) ** (n - np.arange(n + 1))
        )  # Polynomial coefficient * weight matrix
        S = np.sum(S_matrix, axis=1)

        # Calculate y output
        y = C * S
        return y

    y_lower = shape(lower_weights, x_lower)
    y_upper = shape(upper_weights, x_upper)

    # TE thickness
    y_lower -= x_lower * TE_thickness / 2
    y_upper += x_upper * TE_thickness / 2

    x = np.concatenate([x_upper, x_lower])
    y = np.concatenate([y_upper, y_lower])
    coordinates = np.vstack((x, y)).T

    return coordinates

def get_coordinates_from_raw_dat(raw_text) -> np.ndarray:
    """
    Returns a Nx2 ndarray of airfoil coordinates from the raw text of a airfoil *.dat file.
    Args:
        raw_text: The raw text of the *.dat file, as read by file.readlines()

    Returns: A Nx2 ndarray of airfoil coordinates [x, y].

    """
    raw_coordinates = []

    def is_number(s):  # determines whether a string is representable as a float
        try:
            float(s)
        except ValueError:
            return False
        return True

    for line in raw_text:
        try:
            line_split = re.split(r'[; |, |\*|\n]', line)
            line_items = [s for s in line_split
                          if s != "" and is_number(s)
                          ]
            if len(line_items) == 2:
                raw_coordinates.append(line_items)
        except:
            pass

    if len(raw_coordinates) == 0:
        raise ValueError("File was found, but could not read any coordinates!")

    coordinates = np.array(raw_coordinates, dtype=float)

    return coordinates

def get_file_coordinates(
        filepath
):
    try:
        with open(filepath, "r") as f:
            raw_text = f.readlines()
    except FileNotFoundError as e:
        try:
            with open(f"{filepath}.dat", "r") as f:
                raw_text = f.readlines()
        except FileNotFoundError as e:
            raise FileNotFoundError(
                f" Neither '{filepath}' nor '{filepath}.dat' were found."
            ) from e

    return get_coordinates_from_raw_dat(raw_text)

def get_UIUC_coordinates(
        name: str = 'dae11'
) -> np.ndarray:
    """
    Returns the coordinates of a specified airfoil in the UIUC airfoil database.
    Args:
        name: Name of the airfoil to retrieve from the UIUC database.

    Returns: The coordinates of the airfoil as a Nx2 ndarray [x, y]
    """

    name = name.lower().strip()

    import importlib.resources
    from aerosandbox.geometry.airfoil import airfoil_database

    try:
        with importlib.resources.open_text(airfoil_database, name) as f:
            raw_text = f.readlines()
    except FileNotFoundError as e:
        try:
            with importlib.resources.open_text(airfoil_database, name + '.dat') as f:
                raw_text = f.readlines()
        except FileNotFoundError as e:
            raise FileNotFoundError(
                f"Neither '{name}' nor '{name}.dat' were found in the UIUC airfoil database."
            ) from e

    return get_coordinates_from_raw_dat(raw_text)


import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
from scipy.fftpack import fft, fft2, ifft
import scipy.io as sio
import copy

## general simulation parameters

fs = 1024
npnts = fs*5 # 5 seconds# parameters
freq = 4 # peak frequency

# centered time vector
timevec = np.arange(0,npnts)/fs
timevec = timevec - np.mean(timevec)

# for power spectrum
hz = np.linspace(0,fs/2,int(np.floor(npnts/2)+1))

## Difference of Gaussians (DoG)
# (approximation of Laplacian of Gaussian)

# define sigmas
sPos = .1
sNeg = .5

# create the two GAussians
gaus1 = np.exp( (-timevec**2) / (2*sPos**2) ) / (sPos*np.sqrt(2*np.pi))
gaus2 = np.exp( (-timevec**2) / (2*sNeg**2) ) / (sNeg*np.sqrt(2*np.pi))

# their difference is the DoG
DoG = gaus1 - gaus2

# amplitude spectrum
DoGPow = np.abs(fft(DoG)/npnts)

# time-domain plotting
plt.subplot(211)
plt.plot(timevec,DoG,'k')
plt.xlabel('Time (sec.)')
plt.title('DoG wavelet in time domain')

# frequency-domain plotting
plt.subplot(212)
plt.plot(hz,DoGPow[:len(hz)],'k')
plt.xlim([0,freq*3])
plt.xlabel('Frequency (Hz)')
plt.title('DoG wavelet in frequency domain')
plt.show()


import numpy as np

class SimpleIteration(object):
    """求解线性方程组的迭代法:
        A @ x = b

    调用 SimpleIteration 子类实例得到解, e.g. JacobiIteration(A, b)()

    """

    def __init__(self, A, b):
        """
        A:  np_array_like, 系数矩阵
        b:  np_array_like, 右端常数
        """
        self.A = np.array(A)
        self.b = np.array(b)

        n, m = self.A.shape
        if n != m or n != len(self.b):
            raise ValueError(f"Not match: A({n, m}) and b({len(b)},)")

    @staticmethod
    def _dlu(A):
        """分裂 A:
            A = D - L - U

        Args:
            A: np.array

        Returns:
            (D, L, U)
        """
        D = np.diag(np.diag(A))
        L = - np.tril(A, -1)
        U = - np.triu(A, 1)

        return D, L, U

    def _B_f(self):
        """计算迭代 x = B @ x + f 的 B 和 f
        """
        raise NotImplementedError('_B_f')

    def __call__(self, x0=None, eps=1e-5, max_steps=5000, verbose=False):
        """线性方程组「简单迭代法」的迭代过程
            x = B @ x + f

        其中 B, f 调用 self._B_f() 得到

        Args:
            x0: np_array_like, 迭代初值
                default x0=None means using random values.
            eps: float, 精度要求
            max_steps: int, 最大迭代次数
            verbose: bool, 如果计算成功，打印出结果及迭代次数

        Returns:
            x: 方程组的解

        Raises:
            ValueError: x0 形状不符合问题
            Expection:  达到最大迭代次数，仍不满足精度
        """
        if not x0:
            x0 = np.random.random(self.A.shape[0])

        x0_shape = np.shape(x0)
        if x0_shape[0] != self.A.shape[0]:
            raise ValueError(
                f"Not match: A({self.A.shape}) and x0({x0_shape})")

        B, f = self._B_f()

        x = x0
        i = 0
        for i in range(int(max_steps)):
            x_prev = np.array(x)  # deep copy

            x = B @ x + f

            if np.all(np.abs(x - x_prev) <= eps):  # 达到精度要求
                break
        else:
            raise Exception(
                f"{self.method_name()} cannot reach eps ({eps}) after max_steps ({max_steps}). The last result: x = {x}")

        if verbose:
            print(f"{self.method_name()} get result x = {x} after {i} iterations.")

    def method_name(self):
        return self.__class__.__name__

class JacobiIteration(SimpleIteration):
    """雅可比（Jacobi）迭代法求解线性方程组:
        A @ x = b
    """

    def _B_f(self):
        D, L, U = self._dlu(self.A)

        inv_D = np.linalg.pinv(D)

        B = inv_D @ (L + U)
        f = inv_D @ self.b

        return B, f

class GaussSeidel(SimpleIteration):
    """高斯-赛德尔迭代（Gauss–Seidel method）求解线性方程组:
        A @ x = b
    """

    def _B_f(self):
        D, L, U = self._dlu(self.A)

        inv_DsL = np.linalg.pinv(D - L)

        B = inv_DsL @ U
        f = inv_DsL @ self.b

        return B, f

class SOR(SimpleIteration):
    """逐次超松驰法（Successive over-relaxation, SOR）求解线性方程组:
        A @ x = b
    """

    def __init__(self, A, b, w=1):
        """
        w:  float, 松弛因子 w > 0
            default w=1，即 Gauss–Seidel 迭代
        """
        super().__init__(A, b)
        self.w = w

    def _B_f(self):
        D, L, U = self._dlu(self.A)
        w = self.w

        _inv_DsWL = np.linalg.pinv(D - w * L)

        B = _inv_DsWL @ ((1-w) * D + w * U)
        f = w * _inv_DsWL @ self.b

        return B, f

    def method_name(self):
        return super().method_name() + f' (w={self.w})'


"""
inner

The inner tool returns the inner product of two arrays.

import numpy

A = numpy.array([0, 1])
B = numpy.array([3, 4])

print numpy.inner(A, B)     #Output : 4

outer

The outer tool returns the outer product of two arrays.

import numpy

A = numpy.array([0, 1])
B = numpy.array([3, 4])

print numpy.outer(A, B)     #Output : [[0 0]
                            #          [3 4]]

Task

You are given two arrays:
and

.
Your task is to compute their inner and outer product.

Input Format

The first line contains the space separated elements of array
.
The second line contains the space separated elements of array

.

Output Format

First, print the inner product.
Second, print the outer product.

Sample Input

0 1
2 3

Sample Output

3
[[0 0]
 [2 3]]
"""

import numpy as np
A = np.array(input().split(), int)
B = np.array(input().split(), int)
print(np.inner(A,B), np.outer(A,B), sep='\n')

# or like this
import numpy

A, B = [numpy.array(input().split(), int) for _ in range(2)]
print('\n'.join(str(op(A, B)) for op in (numpy.inner, numpy.outer)))


# https://en.wikipedia.org/wiki/Test_functions_for_optimization

import numpy as np

# Rastrigin

def rastrigin(x):
    x = np.array(x)
    return 10 * len(x) + np.sum(x ** 2 - 10 * np.cos(2 * np.pi * x), axis=0)

def rastrigin_bounds(d):
    """

    Parameters
    ----------
    d : int
        dimension

    Returns
    -------
    list of 2-tuples
    """
    return [(-5.12, 5.12) for _ in range(d)]

def rastrigin_ySol(d):
    return 0

def rastrigin_xSol(d):
    """

    Parameters
    ----------
    d : int
        dimension

    Returns
    -------

    """
    return np.zeros((d,))

# Sphere

def sphere(x):
    x = np.array(x)
    return np.sum(x ** 2)

def sphere_bounds(d):
    """

    Parameters
    ----------
    d : int
        dimension

    Returns
    -------

    """
    return [(-10., 10.) for _ in range(d)]

def sphere_ySol(d):
    return 0

def sphere_xSol(d):
    """

    Parameters
    ----------
    d : int
        dimension

    Returns
    -------

    """
    return np.zeros((d,))

# Rosenbrock

def rosenbrock(x):
    x = np.array(x)
    summation = 0
    for i in range(len(x)-1):
        summation += 100*((x[i+1] - x[i]**2)**2) + (1 - x[i])**2

    return summation

def rosenbrock_bounds(d):
    """

    Parameters
    ----------
    d : int
        dimension

    Returns
    -------

    """
    return [(-10., 10.) for i in range(d)]

def rosenbrock_ySol(d):
    return 0

def rosenbrock_xSol(d):
    return np.ones((d,))

# Styblinski

def styblinski(x):
    x = np.array(x)
    return np.sum(x ** 4 - 16 * x ** 2 + 5 * x) / 2

def styblinski_bounds(d):
    """

    Parameters
    ----------
    d : int
        dimension

    Returns
    -------

    """
    return [(-5., 5.) for _ in range(d)]

def styblinski_ySol(d):
    return -39.16616 * d

def styblinski_xSol(d):
    return np.ones((d,)) * -2.903534

all_functions_string = ['rastrigin', 'sphere', 'rosenbrock', 'styblinski']

all_functions = [eval(function_string) for function_string in all_functions_string]

all_bounds = [eval(function_string+'_bounds') for function_string in all_functions_string]

all_xSol = [eval(function_string+'_xSol') for function_string in all_functions_string]

all_ySol = [eval(function_string+'_ySol') for function_string in all_functions_string]


from Statistics.Mean import mean
from Calculator.Multiplication import multiplication
from Calculator.Subtraction import subtraction
from Calculator.Square import square
from Calculator.Division import division
from Calculator.SquareRoot import square_root
from pprint import pprint
import numpy as np

def population_correlation(data, data1):
    try:
        mean1 =mean(data)
        mean2 =mean(data1)
        #pprint(mean1)
        #pprint(mean2)
        List1=[]
        List2=[]

        for num in data:
            a = subtraction(int(round(mean1,0)),num)
            List1.append(a)

        for num in data1:
            b = subtraction(mean2,num)
            List2.append(b)
        c = np.multiply(List1, List2)
        cc=0

        for num in c:
            cc= cc+num

        d=0
        e=0

        for num in List1:
            d = d+ square(num)
        for num in List2:
            e = e +square(num)

        f = multiplication(int(d), e)
        g = square_root(int(f))
        h = division(int(g),cc)

        nCorrelation = round(h,9)
        pprint(nCorrelation)
        return nCorrelation
    except ZeroDivisionError:
        print("Error - Cannot divide by 0")
    except ValueError:
        print("Error - Invalid data inputs")

#https://www.youtube.com/watch?v=eIZD1BFfw8E population vs sample
#https://www.statisticshowto.datasciencecentral.com/probability-and-statistics/correlation-coefficient-formula/


import numpy as np
import numpy.polynomial.polynomial as poly
from matplotlib import pyplot as plt

class Predictor:

    def __init__(self, days, data):
        data = data.transpose()
        self.x = data[:, 0:1].copy()
        self.y = data[:, 1:2].copy()
        self.days = days

    def polynomial_regression(self, n):
        # get regression
        first_col = np.ones((self.x.shape[0], 1))
        a = np.append(first_col, self.x, axis=1)

        if n >= 2:
            for i in range(2, n+1):
                a = np.append(a, self.x**i, axis=1)

        b = self.y
        a2 = np.matmul(a.transpose(), a)
        b2 = np.matmul(a.transpose(), b)
        c = np.matmul(np.linalg.inv(a2), b2)
        c = c.transpose()[0]
        val = np.arange(1, self.days+1, dtype=float)
        val += self.x[-1]
        val = val.reshape((1, val.shape[0]))
        val = np.append(self.x.transpose(), val, axis=1)

        return np.array([val[0], poly.polyval(val[0], c)])

    def exponential_regression(self):
        # get regression
        first_col = np.ones((self.x.shape[0], 1))
        a = np.append(first_col, self.x, axis=1)

        temp = self.y.copy()

        for i in range(self.y.shape[0]):
            if self.y[i, 0] <= 0:
                self.y[i, 0] = 0.001

        b = np.log(self.y)
        a2 = np.matmul(a.transpose(), a)
        b2 = np.matmul(a.transpose(), b)
        c = np.matmul(np.linalg.inv(a2), b2)
        c = np.exp(c.transpose()[0])

        val = np.arange(1, self.days+1, dtype=float)
        val += self.x[-1]
        val = val.reshape((1, val.shape[0]))
        val = np.append(self.x.transpose(), val, axis=1)

        self.y = temp

        return np.array([val[0], c[0]*np.power(c[1], val[0])])

    def derivative(self):
        der = np.array([])

        n = self.y.shape[0]

        for i in range(n):
            if i == 0:
                der = np.append(der, self.y[i, 0])
            else:
                der = np.append(der, self.y[i, 0] - self.y[i-1, 0])

        return der

    def gaussian(self):
        """Adjusting a Gaussian function to the daily increment
        f(x) = A * exp((-(x-B)^2)/(2*C^2))

        :return:
        """

        d = np.expand_dims(self.derivative(), axis=1)
        der_x = self.x.copy()

        # data preprocessing
        while d[0] == 0:
            # remove zeros at start
            d = np.delete(d, 0, axis=0)
            der_x = np.delete(der_x, 0, axis=0)

            if d.size == 0:
                break

        for i in reversed(range(d.shape[0])):
            if d[i] <= 0:
                d = np.delete(d, i, axis=0)
                der_x = np.delete(der_x, i, axis=0)

        if der_x.shape[0] == 0:
            return np.array([[0], [0]])

        der_y = np.log(d)

        # get regression
        first_col = np.ones((der_x.shape[0], 1))
        a = np.append(first_col, der_x, axis=1)

        for i in range(2, 3):
            a = np.append(a, der_x**i, axis=1)

        b = der_y
        a2 = np.matmul(a.transpose(), a)
        b2 = np.matmul(a.transpose(), b)

        c = np.matmul(np.linalg.pinv(a2), b2)

        if c[2] == 0:
            c[2] = 0.001

        # square of c2
        c2_2 = -1/(2*c[2])

        c1 = c[1]*(-1/(2*c[2]))
        c0 = np.exp(c[0]+0.5*(c1**2/c2_2))

        val = np.arange(1, self.days+1, dtype=float)
        val += self.x[-1, :]
        val = val.reshape((val.shape[0], 1))
        val = np.append(self.x, val, axis=0)

        y2 = c0*np.exp((-(val - c1)**2)/(2*c2_2))
        plt.plot(val, y2, 'r')

        return np.array([val.transpose()[0], y2.transpose()[0]])

if __name__ == "__main__":
    p = Predictor(30, np.array([[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], [1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144]]))
    # print(p.polynomial_regression(2))
    # print(p.exponential_regression())
    print(p.gaussian())


import pathlib

import numpy as np
import pandas as pd
import scipy.stats as stats

src_dir = pathlib.Path(__file__).resolve().parent.parent
dat_path = src_dir.joinpath("data", "clouds.dat")

df = pd.read_table(dat_path, sep="\s+", skiprows=29)
unseeded_rainfall, seeded_rainfall = df["Unseeded_Clouds"], df["Seeded_Clouds"]

alpha = 0.05
z = stats.norm.ppf(1 - alpha / 2)

unseeded_mean = unseeded_rainfall.mean()
unseeded_mean_se = unseeded_rainfall.std() / np.sqrt(unseeded_rainfall.size)
seeded_mean = seeded_rainfall.mean()
seeded_mean_se = seeded_rainfall.std() / np.sqrt(seeded_rainfall.size)

diff = seeded_mean - unseeded_mean
diff_se = np.sqrt(unseeded_mean_se ** 2 + seeded_mean_se ** 2)
diff_lower_bound = diff - z * diff_se
diff_upper_bound = diff + z * diff_se

print(f"The mean waiting time is approximately {diff:.4}.")
print(
    "A 95% CI for this value is given by",
    f"[{diff_lower_bound:.4}, {diff_upper_bound:.4}].",
)


import numpy as np
import numba

@numba.jit(nopython=True, parallel=True)
def tridiagonal_invers(alpha, beta, gamma, b):
    # solution to Ax=b, where A is [n x n] tridiagonal matrix with values alpha, beta, gamma
    # alpha: [n x 1] numpy vector  of main diagonal of A
    # beta: [(n-1) x 1] numpy vector of diagonal above main diagonal (+1)
    # gamma: [(n-1) x 1] numpy vector of diagonal below main diagonal (-1)
    # b: [n x 1] numpy vector

    # returns x: [n x 1] numpy vector (solution to Ax=b)
    # ------------------------------------------------------------------------------------------------------------------
    n = len(alpha)
    alpha_hat = np.zeros(n, dtype=np.float64)
    b_hat = np.zeros(n, dtype=np.float64)

    alpha_hat[0] = alpha[0]
    b_hat[0] = b[0]

    for i in range(1, n):
        alpha_hat[i] = alpha[i] - gamma[i - 1] * beta[i - 1] / alpha_hat[i - 1]
        b_hat[i] = b[i] - gamma[i - 1] * b_hat[i - 1] / alpha_hat[i - 1]

    x = np.ones(n, dtype=np.float64)
    x[-1] = b_hat[-1] / alpha_hat[-1]

    for i in range(n - 2, -1, -1):
        x[i] = (b_hat[i] - beta[i] * x[i + 1]) / alpha_hat[i]
    return x

@numba.jit(nopython=True, parallel=True)
def brennon_schwartz_algorithm(alpha, beta, gamma, bs, g_nui):
    # Solution to Ax-b >= 0 ; x >= g and (Ax-b)'(x-g)=0 ; such that solution x satisfies x_i = g_i for i=1,...,k and x_i > g_i for i = k-1,...,n
    #  where A is tridiagonal with values alpha, beta, gamma
    # alpha: [n x 1] numpy vector  of main diagonal of A
    # beta: [(n-1) x 1] numpy vector of diagonal above main diagonal (+1)
    # gamma: [(n-1) x 1] numpy vector of diagonal below main diagonal (-1)
    # b: [n x 1] numpy vector

    # returns solution x: [n x 1] numpy vector
    # ------------------------------------------------------------------------------------------------------------------
    n = len(alpha)
    alpha_hat = np.zeros(n, dtype=np.float64)
    b_hat = np.zeros(n, dtype=np.float64)

    alpha_hat[-1] = alpha[-1]
    b_hat[-1] = bs[-1]

    for i in range(n - 2, -1, -1):
        alpha_hat[i] = alpha[i] - beta[i] * gamma[i] / alpha_hat[i + 1]
        b_hat[i] = bs[i] - beta[i] * b_hat[i + 1] / alpha_hat[i + 1]

    x = np.zeros(n, dtype=np.float64)
    x[0] = np.maximum(b_hat[0] / alpha_hat[0], g_nui[0])
    for i in range(1, n):
        x[i] = np.maximum((b_hat[i] - gamma[i - 1] * x[i - 1]) / alpha_hat[i], g_nui[i])
    return x


import numpy as np

def sigmoid(A):
    return 1 / (1 + np.exp(-A))

def dsigmoid(A):
    B = sigmoid(A)
    return B * (1 - B)

def tanh(A):
    return 2 * sigmoid(2 * A) - 1

def dtanh(A):
    return 4 * dsigmoid(2 * A)

def relu(A):
    return np.maximum(A, 0)

def drelu(A):
    return np.sign(relu(A + 0.00001))

if __name__ == "__main__":
    assert np.equal(sigmoid(0), 0.5)
    assert np.allclose(sigmoid(np.array([0, np.log(3), -np.log(3)])), np.array([ 0.5, 0.75, 0.25]))
    assert np.allclose(dsigmoid(np.array([0, 100, -100])), np.array([ 0.25, 0, 0]))
    assert np.equal(tanh(0), 0)
    assert np.isclose(dtanh(1), 1 - tanh(1)**2)
    assert np.equal(relu(-1), 0)
    assert np.equal(relu(1), 1)
    assert np.array_equal(drelu(np.array([-1, 2])), np.array([0, 1]))