# encoding=utf8

import os
import random
import operator
import itertools
# import collections
import numpy as np
import FukuML.Utility as utility
import FukuML.MLBase as ml
import FukuML.LinearRegression as linear_regression
# np.set_printoptions(threshold=np.nan)

random.seed(1)

class LogisticRegression(ml.Learner):

    def __init__(self):

        """init"""

        self.status = 'empty'
        self.train_X = []
        self.train_Y = []
        self.W = []
        self.data_num = 0
        self.data_demension = 0
        self.test_X = []
        self.test_Y = []
        self.feature_transform_mode = ''
        self.feature_transform_degree = 1

        self.feed_mode = 'batch'
        self.step_eta = 0.126
        self.updates = 2000

    def load_train_data(self, input_data_file=''):

        '''
        Load train data
        Please check dataset/logistic_regression_train.dat to understand the data format
        Each feature of data x separated with spaces
        And the ground truth y put in the end of line separated by a space
        '''

        self.status = 'load_train_data'

        if (input_data_file == ''):
            input_data_file = os.path.normpath(os.path.join(os.path.join(os.getcwd(), os.path.dirname(__file__)), "dataset/logistic_regression_train.dat"))
        else:
            if (os.path.isfile(input_data_file) is not True):
                print("Please make sure input_data_file path is correct.")
                return self.train_X, self.train_Y

        self.train_X, self.train_Y = utility.DatasetLoader.load(input_data_file)

        return self.train_X, self.train_Y

    def load_test_data(self, input_data_file=''):

        '''
        Load test data
        Please check dataset/logistic_regression_test.dat to understand the data format
        Each feature of data x separated with spaces
        And the ground truth y put in the end of line separated by a space
        '''

        if (input_data_file == ''):
            input_data_file = os.path.normpath(os.path.join(os.path.join(os.getcwd(), os.path.dirname(__file__)), "dataset/logistic_regression_test.dat"))
        else:
            if (os.path.isfile(input_data_file) is not True):
                print("Please make sure input_data_file path is correct.")
                return self.test_X, self.test_Y

        self.test_X, self.test_Y = utility.DatasetLoader.load(input_data_file)

        if (self.feature_transform_mode == 'polynomial') or (self.feature_transform_mode == 'legendre'):
            self.test_X = self.test_X[:, 1:]

            self.test_X = utility.DatasetLoader.feature_transform(
                self.test_X,
                self.feature_transform_mode,
                self.feature_transform_degree
            )

        return self.test_X, self.test_Y

    def set_param(self, feed_mode='batch', step_eta=0.126, updates=2000):

        self.feed_mode = feed_mode
        self.step_eta = step_eta
        self.updates = updates

        return self.feed_mode, self.step_eta, self.updates

    def init_W(self, mode='normal'):

        '''
        Init the W
        Simple way is init W all zeros
        '''

        if (self.status != 'load_train_data') and (self.status != 'train'):
            print("Please load train data first.")
            return self.W

        self.status = 'init'

        self.data_num = len(self.train_Y)
        self.data_demension = len(self.train_X[0])
        self.W = np.zeros(self.data_demension)

        if mode == 'linear_regression_accelerator':
            accelerator = linear_regression.Accelerator()
            self.W = accelerator.init_W(self)

        return self.W

    def theta(self, s):

        '''
        Theta sigmoid function
        '''

        s = np.where(s < -709, -709, s)

        return 1 / (1 + np.exp((-1) * s))

    def score_function(self, x, W):
        # need refector

        '''
        Score function to calculate score
        '''

        score = self.theta(np.inner(x, W))

        return score

    def error_function(self, x, y, W):
        # need refector

        '''
        Error function to calculate error: cross entropy error
        '''

        error = np.log(1 + np.exp((-1) * y * np.inner(x, W)))

        return error

    def calculate_gradient(self, X, Y, W):

        if type(Y) is np.ndarray:
            data_num = len(Y)
        else:
            data_num = 1

        gradient_average = np.dot(self.theta((-1) * Y * np.dot(W, X.transpose())) * ((-1) * Y), X) / data_num

        return gradient_average

    def calculate_avg_error(self, X, Y, W):

        '''
        Calculate avg error from X, Y, W
        '''

        data_num = len(Y)
        error_num = 0

        for i in range(data_num):
            error_num = error_num + self.error_function(X[i], Y[i], W)

        avg_error = error_num / float(data_num)

        return avg_error

    def calculate_test_data_avg_error(self):

        '''
        Calculate test data avg error
        '''

        avg_error = self.calculate_avg_error(self.test_X, self.test_Y, self.W)

        return avg_error

    def train(self):

        '''
        Train Linear Regression Algorithm
        From f(x) = WX
        Find best h(x) = WX similar to f(x)
        Output W

        mode: batch / stochastic
        '''

        if (self.status != 'init'):
            print("Please load train data and init W first.")
            return self.W

        self.status = 'train'

        for i in range(0, self.updates):
            if self.feed_mode == 'stochastic':
                stochastic_i = random.randint(0, self.data_num - 1)
                x = self.train_X[stochastic_i]
                y = self.train_Y[stochastic_i]
                gradient = self.calculate_gradient(x, y, self.W)
            else:
                gradient = self.calculate_gradient(self.train_X, self.train_Y, self.W)
            if np.linalg.norm(gradient) == 0:
                return self.W
            self.W = self.W - self.step_eta * gradient

        return self.W

    def prediction(self, input_data='', mode='test_data'):

        return super(LogisticRegression, self).prediction(input_data, mode)

class BinaryClassifier(LogisticRegression):

    def __init__(self):

        """init"""

        self.status = 'empty'
        self.train_X = []
        self.train_Y = []
        self.W = []
        self.data_num = 0
        self.data_demension = 0
        self.test_X = []
        self.test_Y = []
        self.feature_transform_mode = ''
        self.feature_transform_degree = 1

        self.feed_mode = 'batch'
        self.step_eta = 0.126
        self.updates = 2000

    def load_train_data(self, input_data_file=''):

        return super(BinaryClassifier, self).load_train_data(input_data_file)

    def load_test_data(self, input_data_file=''):

        return super(BinaryClassifier, self).load_test_data(input_data_file)

    def set_param(self, feed_mode='batch', step_eta=0.126, updates=2000):

        return super(BinaryClassifier, self).set_param(feed_mode, step_eta, updates)

    def init_W(self, mode='normal'):

        return super(BinaryClassifier, self).init_W(mode)

    def theta(self, s):

        return super(BinaryClassifier, self).theta(s)

    def score_function(self, x, W):

        '''
        Score function to calculate score
        '''

        score = super(BinaryClassifier, self).score_function(x, W)
        if score >= 0.5:
            score = 1.0
        else:
            score = -1.0

        return score

    def error_function(self, y_prediction, y_truth):
        # need refector

        '''
        Error function to calculate error
        '''

        if y_prediction != y_truth:
            return 1
        else:
            return 0

    def calculate_gradient(self, X, Y, W):

        return super(BinaryClassifier, self).calculate_gradient(X, Y, W)

    def calculate_avg_error(self, X, Y, W):

        data_num = len(Y)
        error_num = 0

        for i in range(data_num):
            error_num = error_num + self.error_function(self.score_function(X[i], W), Y[i])

        avg_error = error_num / float(data_num)

        return avg_error

    def calculate_test_data_avg_error(self):

        return super(BinaryClassifier, self).calculate_test_data_avg_error()

    def train(self):

        return super(BinaryClassifier, self).train()

    def prediction(self, input_data='', mode='test_data'):

        return super(BinaryClassifier, self).prediction(input_data, mode)

class MultiClassifier(LogisticRegression):

    class_list = []
    temp_train_X = []
    temp_train_Y = []
    temp_W = {}
    temp_data_num = 0
    decomposition = 'ova'

    def __init__(self):

        """init"""

        self.status = 'empty'
        self.train_X = []
        self.train_Y = []
        self.W = {}
        self.data_num = 0
        self.data_demension = 0
        self.test_X = []
        self.test_Y = []
        self.feature_transform_mode = ''
        self.feature_transform_degree = 1

        self.feed_mode = 'batch'
        self.step_eta = 0.126
        self.updates = 2000
        self.class_list = []
        self.temp_train_X = []
        self.temp_train_Y = []
        self.temp_W = {}
        self.temp_data_num = 0
        self.decomposition = 'ova'

    def load_train_data(self, input_data_file=''):

        self.status = 'load_train_data'

        if (input_data_file == ''):
            input_data_file = os.path.normpath(os.path.join(os.path.join(os.getcwd(), os.path.dirname(__file__)), "dataset/digits_multiclass_train.dat"))
        else:
            if (os.path.isfile(input_data_file) is not True):
                print("Please make sure input_data_file path is correct.")
                return self.train_X, self.train_Y

        self.train_X, self.train_Y = utility.DatasetLoader.load(input_data_file)

        return self.train_X, self.train_Y

    def load_test_data(self, input_data_file=''):

        if (input_data_file == ''):
            input_data_file = os.path.normpath(os.path.join(os.path.join(os.getcwd(), os.path.dirname(__file__)), "dataset/digits_multiclass_test.dat"))
        else:
            if (os.path.isfile(input_data_file) is not True):
                print("Please make sure input_data_file path is correct.")
                return self.test_X, self.test_Y

        self.test_X, self.test_Y = utility.DatasetLoader.load(input_data_file)

        if (self.feature_transform_mode == 'polynomial') or (self.feature_transform_mode == 'legendre'):
            self.test_X = self.test_X[:, 1:]

            self.test_X = utility.DatasetLoader.feature_transform(
                self.test_X,
                self.feature_transform_mode,
                self.feature_transform_degree
            )

        return self.test_X, self.test_Y

    def set_param(self, feed_mode='batch', step_eta=0.126, updates=2000):

        return super(MultiClassifier, self).set_param(feed_mode, step_eta, updates)

    def init_W(self, mode='normal', decomposition='ova'):

        self.W = {}

        if (self.status != 'load_train_data') and (self.status != 'train'):
            print("Please load train data first.")
            return self.W

        self.status = 'init'

        self.data_num = len(self.train_Y)
        self.data_demension = len(self.train_X[0])
        self.decomposition = decomposition

        if self.decomposition == 'ovo':
            self.class_list = list(itertools.combinations(np.unique(self.train_Y), 2))
        elif self.decomposition == 'ova':
            self.class_list = np.unique(self.train_Y)

        for class_item in self.class_list:
            self.W[class_item] = np.zeros(self.data_demension)

        if mode == 'linear_regression_accelerator':
            accelerator = linear_regression.Accelerator()
            for class_item in self.class_list:
                if self.decomposition == 'ovo':
                    modify_X, modify_Y = utility.DatasetLoader.modify_XY(self.train_X, self.train_Y, class_item)
                    self.temp_train_X = self.train_X
                    self.temp_train_Y = self.train_Y
                    self.train_X = modify_X
                    self.train_Y = modify_Y
                    self.temp_data_num = self.data_num
                    self.data_num = len(self.train_Y)
                    self.temp_W = self.W
                    self.W = self.temp_W[class_item]
                    self.temp_W[class_item] = accelerator.init_W(self)
                    self.train_X = self.temp_train_X
                    self.train_Y = self.temp_train_Y
                    self.temp_train_X = []
                    self.temp_train_Y = []
                    self.data_num = self.temp_data_num
                    self.temp_data_num = 0
                    self.W = self.temp_W
                    self.temp_W = {}
                elif self.decomposition == 'ova':
                    modify_Y = utility.DatasetLoader.modify_Y(self.train_Y, class_item)
                    self.temp_train_Y = self.train_Y
                    self.train_Y = modify_Y
                    self.temp_W = self.W
                    self.W = self.temp_W[class_item]
                    self.temp_W[class_item] = accelerator.init_W(self)
                    self.train_Y = self.temp_train_Y
                    self.temp_train_Y = []
                    self.W = self.temp_W
                    self.temp_W = {}

        return self.W

    def theta(self, s):

        return super(MultiClassifier, self).theta(s)

    def score_function(self, x, W):

        return super(MultiClassifier, self).score_function(x, W)

    def score_function_all_class(self, x, W):

        score_list = {}
        ovo_vote = []

        for class_item in self.class_list:
            score = self.score_function(x, W[class_item])
            if self.decomposition == 'ovo':
                if score >= 0.5:
                    score_list[class_item] = class_item[0]
                else:
                    score_list[class_item] = class_item[1]
                ovo_vote.append(score_list[class_item])
            elif self.decomposition == 'ova':
                score_list[class_item] = score

        if self.decomposition == 'ovo':
            return max(set(ovo_vote), key=ovo_vote.count)
        elif self.decomposition == 'ova':
            return max(score_list.items(), key=operator.itemgetter(1))[0]

    def error_function(self, y_prediction, y_truth):

        if y_prediction != y_truth:
            return 1
        else:
            return 0

    def calculate_gradient(self, X, Y, W):

        return super(MultiClassifier, self).calculate_gradient(X, Y, W)

    def calculate_avg_error(self, X, Y, W):

        return super(MultiClassifier, self).calculate_avg_error(X, Y, W)

    def calculate_avg_error_all_class(self, X, Y, W):

        data_num = len(Y)
        error_num = 0

        for i in range(data_num):
            error_num = error_num + self.error_function(self.score_function_all_class(X[i], W), Y[i])

        avg_error = error_num / float(data_num)

        return avg_error

    def calculate_test_data_avg_error(self):

        return super(MultiClassifier, self).calculate_test_data_avg_error()

    def train(self):

        if (self.status != 'init'):
            print("Please load train data and init W first.")
            return self.W

        for class_item in self.class_list:
            self.status = 'init'
            if self.decomposition == 'ovo':
                modify_X, modify_Y = utility.DatasetLoader.modify_XY(self.train_X, self.train_Y, class_item)
                self.temp_train_X = self.train_X
                self.temp_train_Y = self.train_Y
                self.train_X = modify_X
                self.train_Y = modify_Y
                self.temp_data_num = self.data_num
                self.data_num = len(self.train_Y)
                self.temp_W = self.W
                self.W = self.temp_W[class_item]
                self.temp_W[class_item] = super(MultiClassifier, self).train()
                self.train_X = self.temp_train_X
                self.train_Y = self.temp_train_Y
                self.temp_train_X = []
                self.temp_train_Y = []
                self.data_num = self.temp_data_num
                self.temp_data_num = 0
                self.W = self.temp_W
                self.temp_W = {}
                print("class %d to %d learned." % (class_item[0], class_item[1]))
            elif self.decomposition == 'ova':
                modify_Y = utility.DatasetLoader.modify_Y(self.train_Y, class_item)
                self.temp_train_Y = self.train_Y
                self.train_Y = modify_Y
                self.temp_W = self.W
                self.W = self.temp_W[class_item]
                self.temp_W[class_item] = super(MultiClassifier, self).train()
                self.train_Y = self.temp_train_Y
                self.temp_train_Y = []
                self.W = self.temp_W
                self.temp_W = {}
                print("class %d learned." % class_item)

        self.status = 'train'

        return self.W

    def prediction(self, input_data='', mode='test_data'):

        prediction = {}
        prediction_list = {}
        prediction_return = 0.0
        ovo_vote = []

        for class_item in self.class_list:
            self.temp_W = self.W
            self.W = self.temp_W[class_item]
            prediction = super(MultiClassifier, self).prediction(input_data, mode)
            if self.decomposition == 'ovo':
                if prediction['prediction'] >= 0.5:
                    prediction_list[class_item] = class_item[0]
                else:
                    prediction_list[class_item] = class_item[1]
                ovo_vote.append(prediction_list[class_item])
            elif self.decomposition == 'ova':
                prediction_list[class_item] = prediction['prediction']
            self.W = self.temp_W
            self.temp_W = {}

        if self.decomposition == 'ovo':
            # counter = collections.Counter(ovo_vote)
            prediction_return = max(set(ovo_vote), key=ovo_vote.count)
        elif self.decomposition == 'ova':
            prediction_return = max(prediction_list.items(), key=operator.itemgetter(1))[0]

        return {
            "input_data_x": prediction['input_data_x'],
            "input_data_y": prediction['input_data_y'],
            "prediction": prediction_return,
            "prediction_list": prediction_list,
        }


from scipy.special import gamma
import math

class Weibull:
    '''
w = Weibull(shape:beta,scale:theta,position:lambda)

    '''
    def __init__(self,k,lmbda,delta):
        self.k = k
        self.lmbda = lmbda
        self.delta = delta
        self.x = (np.random.weibull(k,1000)*lmbda) + delta
        _,self.bins  = np.histogram(self.x,100,density=True)

    def pdf(self):
        x = self.bins
        k = self.k
        lmbda = self.lmbda
        delta =  self.delta
        return ((k/lmbda)*((x-delta)/lmbda)**(k-1))*(np.exp(-((x-delta)/lmbda)**k))

    def cdf(self):
        x = self.bins
        k = self.k
        lmbda = self.lmbda
        delta = self.delta
        return 1- np.exp(-((x-delta)/lmbda)**k)

    def failure_rate(self):
        x = self.x
        k = self.k
        lmbda = self.lmbda
        return (k/lmbda)*((x/lmbda)**(k-1))

    def E_x(self):
        k = self.k
        lmbda = self.lmbda
        return lmbda*(gamma(1+1/k))

    def var_x(self):
        k = self.k
        lmbda = self.lmbda
        return (lmbda**2)*(gamma(1+(2/k))-((gamma(1+(1/k)))**2))

    def plot_pdf(self):
        plt.plot(self.bins,self.pdf())

    def plot_cdf(self):
        plt.plot(self.bins,self.cdf())

    def plot_fr(self):
        plt.plot(self.bins,self.failure_rate())

    def plot_hist(self):
        plt.hist(self.x)

    def find_x(self,F_x):
        return (self.lmbda)*((-math.log(1-F_x))**(1/self.k)) + self.delta
w = Weibull(25,12.887,25.5146)

X = w.find_x(0.5)
print('X:(the one with 0.5 CDF)',X)


import numpy as np
from numba import njit

from ..containers import StatisticsContainer

@njit()
def skewness(data, mean=None, std=None):
    """Calculate skewnewss (normalized third central moment)
    with allowing precomputed mean and std.

    With precomputed mean and std, this is ~10x faster than scipy.stats.skew
    for our use case (1D arrays with ~100-1000 elements)

    njit provides ~10% improvement over the non-jitted function.

    Parameters
    ----------
    data: ndarray
        Data for which skewness is calculated
    mean: float or None
        pre-computed mean, if not given, mean is computed
    std: float or None
        pre-computed std, if not given, std is computed

    Returns
    -------
    skewness: float
        computed skewness
    """
    if mean is None:
        mean = np.mean(data)

    if std is None:
        std = np.std(data)

    return np.mean(((data - mean) / std) ** 3)

@njit()
def kurtosis(data, mean=None, std=None, fisher=True):
    """Calculate kurtosis (normalized fourth central moment)
    with allowing precomputed mean and std.

    With precomputed mean and std, this is ~10x faster than scipy.stats.skew
    for our use case (1D arrays with ~100-1000 elements)

    njit provides ~10% improvement over the non-jitted function.

    Parameters
    ----------
    data: ndarray
        Data for which skewness is calculated
    mean: float or None
        pre-computed mean, if not given, mean is computed
    std: float or None
        pre-computed std, if not given, std is computed
    fisher: bool
        If True, Fisher’s definition is used (normal ==> 0.0).
        If False, Pearson’s definition is used (normal ==> 3.0).

    Returns
    -------
    kurtosis: float
        kurtosis
    """
    if mean is None:
        mean = np.mean(data)

    if std is None:
        std = np.std(data)

    kurt = np.mean(((data - mean) / std) ** 4)
    if fisher is True:
        kurt -= 3.0
    return kurt

def descriptive_statistics(
    values, container_class=StatisticsContainer
) -> StatisticsContainer:
    """ compute intensity statistics of an image  """
    mean = values.mean()
    std = values.std()
    return container_class(
        max=values.max(),
        min=values.min(),
        mean=mean,
        std=std,
        skewness=skewness(values, mean=mean, std=std),
        kurtosis=kurtosis(values, mean=mean, std=std),
    )


import numpy as np

def add_polynomial(dataframe, input_col, output_col, power=2):
    """Creates polynomial feature extension that adds newly created features to a DataFrame.
    Example implementation of do feature engineering only creating the powers

    Parameters
    ----------
    dataframe : pandas.DataFrame
    input_col : String
        Name of the column to be mutated.
    output_col : String
        New column name with
    power : int
        the power of the new column

    Returns
    -------
    dataframe : pandas.DataFrame
        DataFrame with newly created column

    """
    dataframe[output_col] = np.power(dataframe[input_col].values, power)
    return dataframe


""" Example showing how to create a Coons NURBS surface """

# -------------------------------------------------------------------------------------------------------------------- #
# Importing packages
# -------------------------------------------------------------------------------------------------------------------- #
import numpy as np
import nurbspy as nrb
import matplotlib.pyplot as plt

# -------------------------------------------------------------------------------------------------------------------- #
# Coons surface example
# -------------------------------------------------------------------------------------------------------------------- #

# Define the south boundary (rational Bézier curve)
P = np.zeros((3, 4))
P[:, 0] = [0.00, 0.00, 0.00]
P[:, 1] = [0.33, 0.00, -0.40]
P[:, 2] = [0.66, 0.10, 0.60]
P[:, 3] = [1.00, 0.20, 0.40]
W = np.asarray([1, 2, 2, 1])
nurbsCurve_south = nrb.NurbsCurve(control_points=P, weights=W)

# Define the north boundary (rational Bézier curve)
P = np.zeros((3, 4))
P[:, 0] = [0.05, 1.00, 0.00]
P[:, 1] = [0.33, 1.15, 0.40]
P[:, 2] = [0.66, 1.15, 0.00]
P[:, 3] = [1.05, 1.25, 0.40]
W = np.asarray([1, 2, 2, 1])
nurbsCurve_north = nrb.NurbsCurve(control_points=P, weights=W)

# Define the west boundary (rational Bézier curve)
P = np.zeros((3, 3))
P[:, 0] = nurbsCurve_south.P[:, 0]
P[:, 1] = [-0.20, 0.50, -0.40]
P[:, 2] = nurbsCurve_north.P[:, 0]
W = np.asarray([nurbsCurve_south.W[0], 1, nurbsCurve_north.W[0]])
nurbsCurve_west = nrb.NurbsCurve(control_points=P, weights=W)

# Define the east boundary (rational Bézier curve)
P = np.zeros((3, 3))
P[:, 0] = nurbsCurve_south.P[:, -1]
P[:, 1] = [1.15, 0.50, 0.30]
P[:, 2] = nurbsCurve_north.P[:, -1]
W = np.asarray([nurbsCurve_south.W[-1], 1, nurbsCurve_north.W[-1]])
nurbsCurve_east = nrb.NurbsCurve(control_points=P, weights=W)

# Create and plot the Coons NURBS surface
coonsNurbsSurface = nrb.NurbsSurfaceCoons(nurbsCurve_south, nurbsCurve_north, nurbsCurve_west, nurbsCurve_east).NurbsSurface
fig, ax = coonsNurbsSurface.plot(surface=True, surface_color='blue', control_points=False)

# Plot isoparametric curves
coonsNurbsSurface.plot_isocurve_u(fig, ax, np.linspace(0, 1, 5))
coonsNurbsSurface.plot_isocurve_v(fig, ax, np.linspace(0, 1, 5))

# Plot the boundary NURBS curves
nurbsCurve_south.plot_curve(fig, ax, color='b', linewidth=2.5)
nurbsCurve_north.plot_curve(fig, ax, color='b', linewidth=2.5)
nurbsCurve_west.plot_curve(fig, ax, color='b', linewidth=2.5)
nurbsCurve_east.plot_curve(fig, ax, color='b', linewidth=2.5)
plt.show()


# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT license.

import math
import numpy as np
import scipy.special as sp
from scipy import misc
import functools
import itertools

# Compute the Sharot coefficients a_{j,k,n,r}.
def sharot(k,n,r):
    p = float(r)/float(n)
    res = np.zeros(k+1)
    for j in xrange(0,k+1):
        c = sp.binom(k,j)*((1.0-j*p)**k)
        if (j % 2) == 1:
            c *= -1
        res[j] = c / ((p**k)*math.factorial(k))
    return res

def jvi_size(n,k):
    return sum([int(sp.binom(n,j)) for j in xrange(0,k+1)])

# Compute the JVI weighting vector and weighting matrix.
#
# Return A,B, where:
#   A: (M,) vector of weights, one for each of M sets.
#   B: (M,n) matrix; each row corresponds to a subset of n samples,
#      withing a row, the weights correspond to the weighted sum over samples.
def jvi_matrix(n,k):
    if n <= k:
        raise ValueError("JVI order must be smaller than number of samples.")

    sc = jvi_size(n,k)
    B = np.zeros((sc,n))
    A = np.zeros(sc)
    SH = sharot(k,n,1)

    j = 0
    for setsize in xrange(0,k+1):
        for index_set in itertools.combinations(range(n), n-setsize):
            B[j,index_set] = 1.0/float(n-setsize)
            A[j] = SH[setsize] / sp.binom(n, n-setsize)
            j += 1

    return A, B


'''
    在这里将用代码实现一些数学中常用的概念，方便理解
    1、均方误差
'''
import numpy as np
import matplotlib.pyplot as plt

# 生成一组随机数
x1 = np.random.rand(10)
x2 = np.random.randn(100) # 标准正态分布
x3 = np.random.randint(1, 10, 10) # 1-10的整数
x4 = np.random.normal(size=10)

# 生成一组真实值和预测值
target = [1, 2, 3, 4, 5, 6]
prediction = [1, 1, 2, 4, 4, 7]

## 对单组数据而言
# 计算均值
# mean = np.mean(target)
# print('均值:', mean)

# # 计算方差
# var = np.var(target)
# print('方差：', var)

## 对两组数据而言
# 计算误差
error = []
for i in range(len(target)):
    error.append(target[i] - prediction[i])

squaredError = []
for val in error:
    squaredError.append(val^2)

print('MSE:', sum(squaredError)/len(squaredError))

# 计算标准差
std = np.std(target)
print(std)
# 画出随机数的图像
# plt.figure(1)
# plt.plot(x4)
# # plt.figure(2)
# # plt.hist(x4, 100)
# plt.show()

# 1、均方误差(MSE)
# print("MSE = ", sum(squaredError) / len(squaredError))


import numpy as np
import numpy.matlib as npmat
import math
from scipy.interpolate import interp2d
from scipy.integrate import simps
from skimage.transform import rotate

def imrotate_new(img, theta):
    """
    Rotation function.

    Parameters
    ----------
    img     : image
    theta   : angle of rotation

    Returns
    -------
    Ra      : rotated image

    """
    n    = img.shape[0]
    th   = np.deg2rad(theta)
    x_1n = np.arange(n)
    y_1n = x_1n.copy()
    mid  = (n+1)/2
    X, Y = np.meshgrid(x_1n, y_1n)
    X = X - mid
    Y = Y - mid
    xi = mid + X*np.cos(th) - Y*np.sin(th)
    yi = mid + X*np.sin(th) + Y*np.cos(th)

    f  = interp2d(X,Y,img)

    Ra = f(xi,yi)
    Ra[np.where(Ra == np.isnan)[0]] = 0.0

    return Ra

def my_radon(M, theta):
    """
    Radon Transform.

    Parameters
    ----------
    M        : image
    thetaval : list of angles

    Returns
    -------
    Rt       : Radon transform
    """
    m,n = M.shape
    theta_len = len(theta)
    R = np.zeros((m,theta_len))

    x, y = np.meshgrid(np.linspace(0,1,m), np.linspace(0,1,n))

    for i in range(theta_len):
        R_theta = rotate(M, 90 - theta[i])
        #R[ :, i] = np.sum(R_theta, axis = 1)
        R[ :,i] = simps( y = R_theta, x = x, axis = 1)
    Rt = R[::-1,:]
    return Rt


from numpy import *
import matplotlib.pyplot as plt
import timeit
def best_worst_fij(a, b):
    f = zeros((b.shape[0], 2))
    for i in range(b.shape[0]):
        if b[i] == 'max':
            f[i, 0] = a.max(0)[i]
            f[i, 1] = a.min(0)[i]
        elif b[i] == 'min':
            f[i, 0] = a.min(0)[i]
            f[i, 1] = a.max(0)[i]
    return f
def SR(a, b, c):
    s = zeros(a.shape[0])
    r = zeros(a.shape[0])
    for i in range(a.shape[0]):
        k = 0
        o = 0
        for j in range(a.shape[1]):
            k = k + c[j] * (b[j, 0] - a[i, j]) / (b[j, 0] - b[j, 1])
            u = c[j] * (b[j, 0] - a[i, j]) / (b[j, 0] - b[j, 1])
            if u > o:
                o = u
                r[i] = round(o, 3)
            else:
                r[i] = round(o, 3)
        s[i] = round(k, 3)
    return s, r
def Q(s, r, n):
    q = zeros(s.shape[0])
    for i in range(s.shape[0]):
        q[i] = round((((n + 1) / (2 * n)) *(s[i] - min(s)) / (max(s) - min(s)) +(1 - (n + 1) / (2 * n)) *(r[i] - min(r)) / (max(r) - min(r))), 3)
    return q
def vikor(a, b, c, pl):
    s, r = SR(a, best_worst_fij(a, b), c)
    q = Q(s, r, len(c))
    if pl == 'y':
        e = [i + 1 for i in range(a.shape[0])]
        plt.plot(e, s, 'p--', color = 'red',markeredgewidth = 2, markersize = 8)

        plt.plot(e, r, '*--', color = 'blue',markeredgewidth = 2, markersize = 8)
        plt.plot(e, q, 'o--', color = 'green',markeredgewidth = 2, markersize = 8)
        plt.legend(['S', 'R', 'Q'])
        plt.xticks(range(a.shape[0] + 2))
        plt.axis([0, a.shape[0] + 1, 0,
        max(maximum(maximum(s, r), q)) + 0.3])
        plt.title("VIKOR results")
        plt.xlabel("Alternatives")
        plt.legend()
        plt.grid(True)
        plt.show()
    return s, r, q
x = array([[8, 7, 2, 1], [5, 3, 7, 5], [7, 5, 6, 4],[9, 9, 7, 3], [11, 10, 3, 7], [6, 9, 5, 4]])
w = array([0.4, 0.3, 0.1, 0.2])
crit_max_min = array(['max', 'max', 'max', 'max'])
start = timeit.default_timer()
vikor(x, crit_max_min, w, 'n')
stop = timeit.default_timer()
print(stop - start)
s, r, q = vikor(x, crit_max_min, w, 'y')
print("S = ", s)
print("R = ", r)
print("Q = ", q)


import numpy as np
import tensorflow.compat.v1 as tf
from .. import SupervisedClassifier
from .. import ModelNotTrainedError

def fit_linear_regression(trainX, trainY,
                          learning_rate=0.01,
                          ridge_alpha=0.0,
                          lasso_alpha=0.0,
                          max_iter=1000,
                          display_step=50,
                          converged_tol=1e-8,
                          to_print=False):
    # disable eager execution
    tf.disable_eager_execution()

    # check dimensions
    assert trainX.shape[0] == trainY.shape[0]   # number of training data the same
    nbtrain = trainX.shape[0]
    nbfeatures = trainX.shape[1]

    # placeholder
    X = tf.placeholder(tf.float32, shape=(None, nbfeatures), name='X')
    Y = tf.placeholder(tf.float32, shape=(None, 1), name='Y')

    # fitting parameters
    theta = tf.Variable(np.random.uniform(size=(nbfeatures, 1)), name='theta', dtype='float')
    b = tf.Variable(np.random.uniform(), name='b', dtype='float')

    # fitting function
    pred_Y = tf.matmul(X, theta) + b

    # cost function
    cost = tf.reduce_mean(tf.square(pred_Y - Y))
    # regularization
    if ridge_alpha is not None and ridge_alpha!=0:
        cost += 0.5 * ridge_alpha * (tf.reduce_sum(tf.square(theta)) + tf.square(b))
    if lasso_alpha is not None and lasso_alpha!=0:
        cost += lasso_alpha * (tf.reduce_sum(tf.abs(theta)) + tf.abs(b))

    # training the machine
    optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost)
    initializer = tf.global_variables_initializer()

    sess = tf.Session()
    sess.run(initializer)

    old_cost = sess.run(cost, feed_dict={X: trainX, Y: trainY})

    # Fit all training data
    for epoch in range(max_iter):
        sess.run(optimizer, feed_dict={X: trainX, Y: trainY})

        if to_print:
            # Display logs per epoch step
            if (epoch + 1) % display_step == 0:
                c = sess.run(cost, feed_dict={X: trainX, Y: trainY})
                print("Epoch:", '%04d' % (epoch + 1), "cost=", "{:.9f}".format(c), \
                      "theta=", sess.run(theta), "b=", sess.run(b))

        if converged_tol is not None:
            new_cost = sess.run(cost, feed_dict={X: trainX, Y: trainY})
            if abs(new_cost - old_cost) < converged_tol:
                break
            else:
                old_cost = new_cost

    if to_print:
        print("Optimization Finished!")

    # extract value
    training_cost = sess.run(cost, feed_dict={X: trainX, Y: trainY})
    trained_theta = sess.run(theta)
    trained_b = sess.run(b)

    fitted_params = {'theta': trained_theta,
                     'b': trained_b,
                     'cost': training_cost,
                     'nbepoch': epoch,
                     'nbfeatures': nbfeatures,
                     'nbtrain': nbtrain}
    tf_sess = {'session': sess, 'inputs': X, 'outputs': pred_Y}

    return fitted_params, tf_sess

class TFLinearRegression(SupervisedClassifier):
    def __init__(self, learning_rate=0.01,
                 ridge_alpha=0.0,
                 lasso_alpha=0.0,
                 max_iter=1000,
                 converged_tol=1e-8):
        self.learning_rate = learning_rate
        self.ridge_alpha = ridge_alpha
        self.lasso_alpha = lasso_alpha
        self.max_iter = max_iter
        self.convered_tol = converged_tol
        self.trained = False

    def train(self, trainX, trainY, to_print=False, display_step=50):
        fitted_param, tf_sess = fit_linear_regression(trainX, trainY,
                                                      learning_rate=self.learning_rate,
                                                      ridge_alpha=self.ridge_alpha,
                                                      lasso_alpha=self.lasso_alpha,
                                                      max_iter=self.max_iter,
                                                      display_step=display_step,
                                                      converged_tol=self.convered_tol,
                                                      to_print=to_print
                                                      )

        self.fitted_param = fitted_param
        self.tf_sess = tf_sess
        self.trained = True

    def predict(self, testX):
        if not self.trained:
            raise ModelNotTrainedError()
        sess = self.tf_sess['session']
        X = self.tf_sess['inputs']
        Y = self.tf_sess['outputs']
        return sess.run(Y, feed_dict={X: testX})


import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']    # 替换sans-serif字体
plt.rcParams['axes.unicode_minus'] = False      # 解决坐标轴负数的负号显示问题
import seaborn as sns

DAYS_PER_YEAR = 252
WEEKS_PER_YEAR = 52
MONTHS_PER_YEAR = 12

READ_FOLDER = 'problems'
WRITE_FOLDER = 'solutions'

DATA_FILE_PATH = '{}/sample_data_gj_fund.xlsx'.format(READ_FOLDER)
COL_DATETIME = '日期'
RENAME_COL_DICT = {'基金A': 'fundA', '基金B': 'fundB', '基金A基准': 'fundA_base'}

RISK_FREE_RATE = 0

def load_data(data_file_path, col_datetime, rename_col_dict=None, *args, **kwargs):
	# read data
	df = pd.read_excel(data_file_path, *args, **kwargs)

	# set period index for resampling
	df.set_index(col_datetime, inplace=True)
	df.index = pd.PeriodIndex(df.index, freq='D')

	# rename columns to English for the dot autocomplement
	if rename_col_dict:
		df = df.rename(columns=rename_col_dict)

	return np.log(df).diff().iloc[1:]

def _get_multiplier_from_freq(freq=None, standardize=False):
	if freq is None:
		value = DAYS_PER_YEAR
	elif freq.lower() in ['d', 'day', 'days', '天', '日']:
		value = DAYS_PER_YEAR
	elif freq.lower() in ['w', 'week', 'weeks', '周', '星期']:
		value = WEEKS_PER_YEAR
	elif freq.lower() in ['m', 'month', 'months', '月']:
		value = MONTHS_PER_YEAR
	else:
		raise Exception('Please Input A Valid Freq Slogan!')
	if standardize:
		return np.sqrt(value)
	else:
		return value

def get_alpha_sequence(data, base_data):
	return data - base_data

def get_year_return(data, freq=None):
	return np.mean(data) * _get_multiplier_from_freq(freq, standardize=False)

def get_year_volatility(data, freq=None):
	return np.std(data) * _get_multiplier_from_freq(freq, standardize=True)

def get_year_downside_volatility(data, freq=None):
	data = np.array(data)
	data = np.where(data > 0, 0, data)
	return np.sqrt(sum(data ** 2) / (len(data) - 1)) * _get_multiplier_from_freq(freq=freq, standardize=True)

def get_interval_max_dropdown(data):
	cum_data = np.cumsum(data) + 1   # 从每日对数收益转为累积对数收益，一定要一个起始值 1
	back_data = 1 - cum_data / np.maximum.accumulate(cum_data)   # 从累积对数收益序列转为回撤序列
	# 因为净值有时候是不变的，即收益率变化为0，因此直接求最大回撤可能会出现nan，所以将nan替换为0
	# 此处使用np.nanmax也不好，因为要考虑所有净值不变的情况，这个时候使用np.nanmax就会报错
	return max(back_data.fillna(0))

def get_sharpeRatio(data):
	data_year_return = get_year_return(data)
	data_year_volatility = get_year_volatility(data)
	return (data_year_return - RISK_FREE_RATE) / data_year_volatility

def get_sortinoRatio(data):
	data_year_return = get_year_return(data)
	data_year_downside_volatility = get_year_downside_volatility(data)
	return (data_year_return - RISK_FREE_RATE) / data_year_downside_volatility

def get_calmarRatio(data, freq=None):
	"""
	Carmar比率的计算，是求最近三年的收益与最大回撤的比值
	为保证 TotalReturn 与 MaxDropdown 之间的比例与 CalmarRatio 吻合，因此此处不做时间区间截断处理
	:param data:
	:param freq:
	:return:
	"""
	# multiplier = _get_multiplier_from_freq(freq)
	# if len(data) > 3 * multiplier:
	# 	data = data[: 3*multiplier]
	data_year_return = get_year_return(data)
	data_maxDropdown = get_interval_max_dropdown(data)
	return data_year_return / data_maxDropdown

def gen_portfolio_trend(data, col_A, col_B, PCT_A, PCT_B):
	start_asset = 1
	result = []

	for year in data.index.year.unique():
		asset_A = start_asset * PCT_A
		asset_B = start_asset * PCT_B
		for now_date, row in data[data.index.year == year].iterrows():
			asset_A *= (1 + row[col_A])
			asset_B *= (1 + row[col_B])
			now_asset = asset_A + asset_B
			asset_return = now_asset / start_asset - 1
			result.append({
				'now_date': now_date,
				'asset_A': asset_A,
				'asset_B': asset_B,
				'asset_sum': now_asset,
				'asset_return': asset_return
			})
			start_asset = now_asset
	return pd.DataFrame(result)

def gen_basic_info(fundA_series, fundB_series, gen_excel_file=True):
	fundA_info_dict = dict()
	fundB_info_dict = dict()
	for year in [2013, 2014, 2015, 2016, 2017, 'ALL']:
		if year != 'ALL':
			fundA = fundA_series[fundA_series.index.year == year]
			fundA_base = fundA_base_series[fundA_base_series.index.year == year]
			fundB = fundB_series[fundB_series.index.year == year]
		else:
			fundA = fundA_series
			fundA_base = fundA_base_series
			fundB = fundB_series

		fundA_return = get_year_return(fundA)
		fundA_base_return = get_year_return(fundA_base)
		fundA_volatility = get_year_volatility(fundA)
		fundA_alpha = get_alpha_sequence(fundA, fundA_base)
		funA_alpha_return = get_year_return(fundA_alpha)
		fundA_alpha_volatility = get_year_volatility(fundA_alpha)
		fundA_maxDropdown = get_interval_max_dropdown(fundA)
		fundA_sharpeRatio = get_sharpeRatio(fundA)
		fundA_sortinoRatio = get_sortinoRatio(fundA)

		fundA_year_info = {
			'Return': fundA_return,
			'BaseReturn': fundA_base_return,
			'AlphaReturn': funA_alpha_return,
			'Volatility': fundA_volatility,
			'AlphaVolatility': fundA_alpha_volatility,
			'MaxDropdown': fundA_maxDropdown,
			'SharpeRatio': fundA_sharpeRatio,
			'SortinoRatio': fundA_sortinoRatio,
		}
		fundA_info_dict[year] = fundA_year_info

		fundB_return = get_year_return(fundB)
		fundB_volatility = get_year_volatility(fundB)
		fundB_maxDropdown = get_interval_max_dropdown(fundB)
		fundB_sharpeRatio = get_sharpeRatio(fundB)
		fundB_calmarRatio = get_calmarRatio(fundB)

		fundB_yar_info = {
			'Return': fundB_return,
			'Volatility': fundB_volatility,
			'MaxDropdown': fundB_maxDropdown,
			'SharpeRatio': fundB_sharpeRatio,
			'CalmarRatio': fundB_calmarRatio,
		}
		fundB_info_dict[year] = fundB_yar_info

	if gen_excel_file:
		pd.DataFrame(fundA_info_dict).to_excel(f'{WRITE_FOLDER}/solution_1/result_fund_A.xlsx')
		pd.DataFrame(fundB_info_dict).to_excel(f'{WRITE_FOLDER}/solution_1/result_fund_B.xlsx')

	return fundA_info_dict, fundB_info_dict

def gen_distplot_of_return(fund_series, suptitle, filename=None):
	fig, ax = plt.subplots()
	fig.suptitle(suptitle)
	sns.distplot(fund_series, ax=ax)
	if not filename:
		filename = suptitle
	fig.savefig(f'{WRITE_FOLDER}/solution_2/{filename}.png')
	return fig, ax

def gen_portfolio_trend_img(df_portfolio, pct_A, pct_B):
	col_asset_return = 'asset_return'
	portfolio_annual_return = get_year_return(df_portfolio[col_asset_return])
	portfolio_annual_volatility = get_year_volatility(df_portfolio[col_asset_return])
	portfolio_annual_maxDropdown = get_interval_max_dropdown(df_portfolio[col_asset_return])

	fig, ax = plt.subplots()
	df_portfolio.asset_sum.plot(ax=ax, x=df_portfolio.index)
	annotate_text = '\n'.join([
		'  组合配比: {:<2d}/{:>2d} '.format(int(pct_A * 10), int(pct_B * 10)),
		'年化收益率: {:5.2f}%'.format(portfolio_annual_return * 100),
		'年化波动率: {:5.2f}%'.format(portfolio_annual_volatility * 100),
		'  最大回撤: {:5.2f}%'.format(portfolio_annual_maxDropdown * 100)
	])
	plt.figtext(0.85, 0.15, annotate_text, horizontalalignment='right', fontsize=16)
	fig.savefig(f'{WRITE_FOLDER}/solution_3/Portfolio Trend of ({pct_A}, {pct_B}).png')
	return fig

if __name__ == '__main__':
	df = load_data(data_file_path=DATA_FILE_PATH, col_datetime=COL_DATETIME, rename_col_dict=RENAME_COL_DICT, skiprows=1)
	fundA_series = df.fundA
	fundB_series = df.fundB
	fundA_base_series = df.fundA_base

	# 第一题，生成表格
	gen_basic_info(fundA_series, fundB_series)

	# 第二题，生成日收益分布图
	gen_distplot_of_return(fundA_series, suptitle='Distplot of Return of Fund A')
	gen_distplot_of_return(fundB_series, suptitle='Distplot of Return of Fund B')

	# 第三题，计算投资组合
	for pct_group in [(.2, .8), (.5, .5)]:
		df_portfolio = gen_portfolio_trend(df, 'fundA', 'fundB', *pct_group)
		gen_portfolio_trend_img(df_portfolio, *pct_group)

	# 第四题，过了面试再解
	# ...


from pylab import *
import numpy

Lx = 100.0
Ly = 50.0
NX = 200
NY = 100

B0 = 0.1
me = 1.0
mi = me*25.0
qe = -1.0
qi = 1.0
dlambda = 1.0
n0 = 1.0
ninf = 0.2*n0
psi0 = B0

dx = Lx/NX
dy = Ly/NY
X = linspace(0.5*dx, Lx-0.5*dx, NX)
Y = linspace(0.5*dy, Ly-0.5*dy, NY)
XX, YY = meshgrid(X, Y)

Bx = numpy.zeros((NX, NY), numpy.float)
n = numpy.zeros((NX, NY), numpy.float)
dBx1 = numpy.zeros((NX, NY), numpy.float)
dBy1 = numpy.zeros((NX, NY), numpy.float)
dBx2 = numpy.zeros((NX, NY), numpy.float)
dBy2 = numpy.zeros((NX, NY), numpy.float)

for i in range(NX):
    for j in range(NY):
        Bx[i,j] = B0*(-1+tanh((Y[j]-Ly/4)/dlambda)-tanh((Y[j]-3*Ly/4)/dlambda))
        n[i,j] = n0/cosh((Y[j]-Ly/4)/dlambda)**2+n0/cosh((Y[j]-3*Ly/4)/dlambda)**2+ninf

        dBx1[i,j] = -psi0*(pi/Ly)*cos(2*pi*(X[i]-Lx/4)/Lx)*sin(pi*(Y[j]-Ly/4)/Ly)
        dBy1[i,j] = psi0*(2*pi/Lx)*sin(2*pi*(X[i]-Lx/4)/Lx)*cos(pi*(Y[j]-Ly/4)/Ly)

        dBx2[i,j] = -psi0*(pi/Ly)*cos(2*pi*(X[i]+Lx/4)/Lx)*sin(pi*(Y[j]+Ly/4)/Ly)
        dBy2[i,j] = psi0*(2*pi/Lx)*sin(2*pi*(X[i]+Lx/4)/Lx)*cos(pi*(Y[j]+Ly/4)/Ly)

figure(1)
pcolormesh(XX, YY, transpose(Bx))
title('Bx(x,y)')
colorbar()

figure(2)
pcolormesh(XX, YY, transpose(n))
title('n(x,y)')
colorbar()

figure(3)
plot(Y, Bx[NX/2,:], 'r-')
xlabel('Y')
ylabel('Bx')
title('Bx(y)')

figure(4)
plot(Y, n[NX/2,:], 'r-')
xlabel('Y')
ylabel('n')
title('n(y)')

figure(7)
Bxt = Bx+dBx1+dBx2
Byt = dBy1+dBy2
Btot = sqrt(Bxt**2+Byt**2)
#contour(XX, YY, transpose(Btot))
streamplot(X, Y, transpose(Bxt), transpose(Byt), density=2)

show()


import numpy as np

class System():
    def __init__(self, matrix: np.ndarray, rhs: np.ndarray = None):
        assert matrix.shape[0] == matrix.shape[1], "matrix is not square"
        if rhs is None:
            rhs = np.zeros(matrix.shape[0])
        else:
            assert rhs.shape[0] == matrix.shape[0], "right-hand side does not combine with the matrix"
        self.matrix = matrix
        self.rhs = rhs

    def solve_gauss(self):
        A = self.matrix.copy()
        b = self.rhs.copy()
        nrows, ncols = A.shape

#       Forward pass
        for i in range(nrows):
            sorted_indices = i + np.abs(A[i:, i]).argsort()[::-1]
            A[i:] = A[sorted_indices]
            b[i:] = b[sorted_indices]
            diag_elem = A[i][i]
            for j in range(i + 1, nrows):
                if A[j][i] != 0:
                    b[j] = b[j] * diag_elem - b[i] * A[j][i]
                    A[j] = A[j] * diag_elem - A[i] * A[j][i]

#       Backward pass
        for i in reversed(range(nrows)):
            diag_elem = A[i][i]
            for j in range(i):
                if A[j][i] != 0:
                    b[j] = b[j] * diag_elem - b[i] * A[j][i]
                    A[j] = A[j] * diag_elem - A[i] * A[j][i]

        return 1/np.diag(A) * b

    def solve_ortogonalization(self):
        A = self.matrix.copy()
        b = self.rhs.copy()
        nrows, ncols = A.shape

        C = np.zeros_like(A, dtype = np.longdouble)
        d = np.zeros_like(b, dtype = np.longdouble)
        C[0] = A[0]/np.sqrt(np.dot(A[0], A[0]))
        d[0] = b[0]/np.sqrt(np.dot(A[0], A[0]))

        for k in range(1, nrows):
            ort_ccomp = 0
            ort_dcomp = 0
            for m in range(k):
                ort_ccomp += np.dot(A[k], C[m]) * C[m]
                ort_dcomp += np.dot(A[k], C[m]) * d[m]

            c_ = A[k] - ort_ccomp
            norm = np.sqrt(np.dot(c_, c_))

            C[k] = c_/norm
            d[k] = (b[k] - ort_dcomp)/norm

        return C.T @ d

    def solve_seidel(self, x_0, iter_num):
        A = self.matrix.copy()
        b = self.rhs.copy()
        nrows, ncols = A.shape

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

        x = x_0
        for i in range(iter_num):
            x = -np.linalg.inv(L + D) @ (U @ x - b)
        return x


import numpy as np

def get_monthly_rate(rate) -> float:
    """
    computes the monthy interest rate based on the yearly interest rate

    :param float rate: the yearly interest rate
    :return: the monthly interest rate

    This computation uses the 12th root on the growth factor
    """

    growth_year = rate + 1
    growth_month = np.power(growth_year, 1./12)
    rate_month = growth_month - 1
    return rate_month


# -*- encoding: utf-8 -*-
'''
@File    :   Task5.py.py
@Modify Time      @Author       @Desciption
------------      -------       -----------
2021/6/6 18:01   Jonas           None
'''

import math

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

training_data = pd.read_csv("gmm.txt", sep="  ")
k = 4
n_iter = 30

def main():
    # init
    covar = np.array(
        [np.identity(training_data.shape[1]) for _ in range(k)])
    mu = np.random.uniform(training_data.min().min(), training_data.max().max(),
                           size=(k, training_data.shape[1]))
    pi = np.random.uniform(size=(k,))

    log_likelihood = np.empty((n_iter,))

    for i in range(n_iter):
        alpha = e(x=training_data.values, mu=mu, covar=covar, pi=pi)
        mu, covar, pi = m(x=training_data.values, alpha=alpha)

        # plot at given steps
        if i + 1 in [1, 3, 5, 10, 30]:
            plt.figure(i)
            visualize(mu, covar, training_data.values, i)
        log_likelihood[i] = calculate_log_likelihood(training_data.values, mu, covar, pi)

        print("Finished iteration {}".format(i))

    plt.figure(n_iter + 1)
    plt.plot(log_likelihood)
    plt.xlabel("Iterations")
    plt.ylabel("Log-Likelihood")
    plt.title("Log-Likelihood Progress")
    plt.show()

def e(x, mu, covar, pi):
    alpha = np.empty((k, x.shape[0]))
    for i in range(k):
        alpha[i] = pi[i] * multivariate_gaussian(x, mu[i], covar[i])

    # sum over all models per data point
    denominator = np.sum(alpha, axis=0)

    return alpha / denominator

def m(x, alpha):
    N = np.sum(alpha, axis=1)  # sum over all data points per model

    mu = np.zeros((k, x.shape[1]))
    covar = np.zeros((k, x.shape[1], x.shape[1]))

    for i in range(k):
        # update mu
        for j, val in enumerate(x):
            mu[i] += (alpha[i, j] * val)

        mu[i] /= N[i]

        # update covariance
        for j, val in enumerate(x):
            diff = val - mu[i]
            covar[i] += alpha[i, j] * np.outer(diff, diff.T)

        covar[i] /= N[i]

    # update pi
    pi = N / x.shape[0]

    return mu, covar, pi

def multivariate_gaussian(data, mu, covar):

    out = np.empty(data.shape[0])
    denominator = np.sqrt((2 * math.pi) * np.linalg.det(covar))
    covar_inv = np.linalg.inv(covar)

    # compute for each datapoint
    for i, x in enumerate(data):
        diff = x - mu
        out[i] = np.exp(-0.5 * diff.T.dot(covar_inv).dot(diff)) / denominator

    return out

def visualize(mu, covar, data, iteration):

    steps = 100

    x_data = data[:, 0]
    y_data = data[:, 1]

    x_min = x_data.min()
    x_max = x_data.max()
    y_min = y_data.min()
    y_max = y_data.max()

    x = np.arange(x_min - 1, x_max + 1, (x_max - x_min + 2) / steps)
    y = np.arange(y_min - 1, y_max + 1, (y_max - y_min + 2) / steps)

    Y, X = np.meshgrid(y, x)
    Z = np.empty((steps, steps))

    for i in range(k):
        for j in range(steps):
            # construct vector with same x and all possible y to cover the plot space
            points = np.append(X[j], Y[j]).reshape(2, x.shape[0]).T
            Z[j] = multivariate_gaussian(points, mu[i], covar[i])
        plt.contour(X, Y, Z, 1)

    # plot the samples
    plt.plot(x_data, y_data, 'co', zorder=1)
    plt.xlabel('x')
    plt.ylabel('y')
    plt.title("Mixtures after {} steps".format(iteration + 1))

def calculate_log_likelihood(x, mu, covar, pi):
    likelihood = np.empty((k, x.shape[0]))
    for i in range(k):
        likelihood[i] = pi[i] * multivariate_gaussian(x, mu[i], covar[i])

    return np.sum(np.log(np.sum(likelihood, axis=0)))

if __name__ == '__main__':
    main()


# -*- coding: utf-8 -*-
"""
Created on Wed May 10 18:00:55 2017

@author: Dipanjan
"""

## vectors

x = [1, 2, 3, 4, 5]
x

# using numpy
import numpy as np
x = np.array([1, 2, 3, 4, 5])

print(x)
print(type(x))

## matrices

m = np.array([[1, 5, 2],
              [4, 7, 4],
              [2, 0, 9]])

# view matrix
print(m)

# view dimensions
print(m.shape)

# matrix transpose
print('Matrix Transpose:\n', m.transpose(), '\n')

# matrix determinant
print ('Matrix Determinant:', np.linalg.det(m), '\n')

# matrix inverse
m_inv = np.linalg.inv(m)
print ('Matrix inverse:\n', m_inv, '\n')

# identity matrix (result of matrix x matrix_inverse)
iden_m =  np.dot(m, m_inv)
iden_m = np.round(np.abs(iden_m), 0)
print ('Product of matrix and its inverse:\n', iden_m)

# eigendecomposition
m = np.array([[1, 5, 2],
              [4, 7, 4],
              [2, 0, 9]])

eigen_vals, eigen_vecs = np.linalg.eig(m)

print('Eigen Values:', eigen_vals, '\n')
print('Eigen Vectors:\n', eigen_vecs)

# SVD
m = np.array([[1, 5, 2],
              [4, 7, 4],
              [2, 0, 9]])

U, S, VT = np.linalg.svd(m)

print ('Getting SVD outputs:-\n')
print('U:\n', U, '\n')
print('S:\n', S, '\n')
print('VT:\n', VT, '\n')

# descriptive statistics
import scipy as sp
import numpy as np

# get data
nums = np.random.randint(1,20, size=(1,15))[0]
print('Data: ', nums)

# get descriptive stats
print ('Mean:', sp.mean(nums))
print ('Median:', sp.median(nums))
print ('Mode:', sp.stats.mode(nums))
print ('Standard Deviation:', sp.std(nums))
print ('Variance:', sp.var(nums))
print ('Skew:', sp.stats.skew(nums))
print ('Kurtosis:', sp.stats.kurtosis(nums))


import numpy
import sympy
import matplotlib.pyplot as plt

from sympy import init_printing
from sympy.utilities.lambdify import lambdify
init_printing(use_latex=True)

x, nu, t = sympy.symbols('x nu t')
phi = sympy.exp(-(x-4*t)**2/(4*nu*(t+1))) + sympy.exp(-(x-4*t-2*numpy.pi)**2/(4*nu*(t+1)))
print phi

phiprime = phi.diff(x)
print phiprime

u = -2*nu*(phiprime/phi)+4
print u

ufunc = lambdify((t, x, nu), u)
print ufunc(1,4,3)

###variable declarations
nx = 101
nt = 100
dx = 2*numpy.pi/(nx-1)
nu = .07
dt = dx*nu

x = numpy.linspace(0, 2*numpy.pi, nx)
#u = numpy.empty(nx)
un = numpy.empty(nx)
t = 0

u = numpy.asarray([ufunc(t, x0, nu) for x0 in x])

plt.figure(figsize=(11,7), dpi=100)
plt.plot(x,u, marker='o', lw=2)
plt.xlim([0,2*numpy.pi])
plt.ylim([0,10]);

for n in range(nt):
    un = u.copy()
    for i in range(nx-1):
        u[i] = un[i] - un[i] * dt/dx *(un[i] - un[i-1]) + nu*dt/dx**2*\
                (un[i+1]-2*un[i]+un[i-1])
    u[-1] = un[-1] - un[-1] * dt/dx * (un[-1] - un[-2]) + nu*dt/dx**2*\
                (un[0]-2*un[-1]+un[-2])

u_analytical = numpy.asarray([ufunc(nt*dt, xi, nu) for xi in x])

plt.figure(figsize=(11,7), dpi=100)
plt.plot(x,u, marker='o', lw=2, label='Computational')
plt.plot(x, u_analytical, label='Analytical')
plt.xlim([0,2*numpy.pi])
plt.ylim([0,10])
plt.legend()
plt.show()


"""
File containing a suite of statistics functions, some of which simply wrap the scipy library.

I use the 'formula_prompt' package to access and evaluate these functions in the terminal.
The @register_formula decorator is used to register my formulas with the package.
More information about this package can be found at https://pypi.org/project/formula-prompt/.

Note that the formula names are indicated in @register_formula(..., name=<>).
"""

from formula_prompt import *

import math
from scipy import stats
from scipy.special import gamma, gammainc

@register_formula(ListInput(), name="sample.mean")
def mean(x):
    """Find the mean of a sample by summing the values and dividing by the size of the sample."""
    return sum(x) / len(x)

@register_formula(ListInput(), name="sample.median")
def median(x):
    """Find the median of a sample by sorting the sample and then returning the middle element."""
    x = sorted(x)  # Sort the sample
    n = len(x)
    middle = n // 2
    if n % 2 == 0:
        # If there's an even number of elements, the median is an average of the two middle values
        return 0.5 * (x[middle - 1] + x[middle])
    else:
        # If there's an odd number of elements, the median is the middle element
        return x[middle]

@register_formula(ListInput(), name="sample.variance")
def sample_variance(x):
    """Find the variance of a sample by summing (x-mean)^2 over each element x, then dividing by (n-1)"""
    n = len(x)
    m = mean(x)
    s = 0  # s is our sum
    for xi in x:  # For every element xi in sample x
        s += (xi - m) ** 2  # Add (xi - m)**2 to our sum s
    return s / (n - 1)  # Then divide the sum by n-1

@register_formula(ListInput(), name="sample.std")
def sample_std(x):
    """Find the sample standard deviation by taking the square root of the sample variance."""
    return math.sqrt(sample_variance(x))

@register_formula(ListInput(), name="sort")
def sort(x):
    """Return the sample x in increasing order."""
    return sorted(x)

@register_formula([
    IntInput("x"),
    IntInput("n"),
    NumInput("p")
],
    name="distributions.binomial"
)
def binomial_distribution(x, n, p):
    """
    Evaluate the binomial distribution b(x; n, p) as defined in the textbook.

    Equivalent to finding the probability of getting x heads when flipping
    a coin n times and the likeliness of getting heads on any one flip is p.

    The equation used is nCx * p ^ x * (1-p) ^ (n-x).
    """
    return math.comb(n, x) * (p ** x) * ((1 - p) ** (n - x))

@register_formula([
    IntInput("x"),
    IntInput("n"),
    NumInput("p")
],
    name="distributions.binomial.cumulative"
)
def binomial_distribution_cumulative(x, n, p):
    """
    Evaluate the cumulative binomial distribution B(x; n, p).

    Equivalent to finding the probability of getting between 0 and x heads
    when flipping a coin n times and the likeliness of getting heads on any one flip is p.

    Found by summing the binomial distribution from 0 up to x.
    """
    return sum([binomial_distribution(i, n, p) for i in range(0, x + 1)])

@register_formula([
    IntInput("x"),
    IntInput("N"),
    IntInput("n"),
    IntInput("k")
], name="distributions.hypergeometric")
def hypergeometric_dist(x, N, n, k):
    """
    Evaluate the hypergeometric distribution h(x; N, n, k) as defined in the textbook.

    Equivalent to the probability of getting x blue socks when picking n socks from a total
    of N socks where a total of k socks are blue.

    Found with the equation kCx * (N-k)C(n-x) / NCn
    """
    return math.comb(k, x) * math.comb(N - k, n - x) / math.comb(N, n)

@register_formula([
    IntInput("x"),
    IntInput("k"),
    NumInput("p")
], name="distributions.negative_binomial")
def negative_binomial(x, k, p):
    """
    Evaluate the negative binomial distribution b*(x; k, p) as defined in the textbook.

    Equivalent to finding the probability of coin flip x to be the k-th head when the
    probability of getting a head on any one coin flip is p.

    Found with the equation (x-1)C(k-1) * p^k * (1 - p)^(x - k)
    """
    return math.comb(x - 1, k - 1) * (p ** k) * ((1 - p) ** (x - k))

@register_formula([
    IntInput("x"),
    IntInput("k"),
    NumInput("p")
], name="distributions.negative_binomial.cumulative")
def negative_binomial_cumulative(x, k, p):
    """
    Evaluate the cumulative negative binomial distribution by summing
    the negative binomial distribution from k to x (inclusive).
    """
    return sum([negative_binomial(xi, k, p) for xi in range(k, x + 1)])

@register_formula([
    IntInput("x"),
    NumInput("mu"),
], name="distributions.poisson")
def poisson_dist(x, m):
    """
    Evaluate the poisson distribution p(x; m) where x is the number
    of outcomes that occur in a given interval and m is the mean
    of the distribution (which equals the rate of occurrence times
    the size of the interval).
    """
    return math.exp(-m) * (m ** x) / math.factorial(x)

@register_formula([
    IntInput("x"),
    NumInput("mu"),
], name="distributions.poisson.cumulative")
def poisson_dist_cumulative(x, m):
    """
    Evaluate the cumulative poisson distribution P(x; m) by summing the
    poisson distribution (defined above) from 0 up to x (inclusive).
    """
    return sum([poisson_dist(i, m) for i in range(0, x + 1)])

@register_formula([
    NumInput("lower bound", optional=True),
    NumInput("upper bound", optional=True)
], name="distributions.normal.cumulative")
def std_normal_dist_cumulative(lower, upper):
    """
    Find the area under the standard normal distribution between
    the lower and upper bounds.

    Bounds that aren't specified are taken at infinity.
    """
    return find_distribution_area(stats.norm, lower, upper)

@register_formula([NumInput("alpha")], name="distributions.normal.inverse")
def normal_dist_inverse(a):
    """
    Given an area 'a', return the z-value that if taken as a lower bound
    results in the standard normal distribution having 'a' area above it.
    """
    return stats.norm.ppf(1 - a)

@register_formula([
    NumInput("alpha"),
    NumInput("upper bound", optional=True)
], name="gamma_function")
def gamma_func(a, x):
    """
    Evaluate the incomplete gamma function with parameter alpha (a) up to x.
    If x is not specified, evaluate the complete gamma function (up to infinity).
    """
    if x is None:
        return gamma(a)
    else:
        # gammainc is regularized (i.e. divided by gamma(a)) which is why we must multiply
        # by gamma(a) to get the true value of the integral.
        return gammainc(a, x) * gamma(a)

@register_formula([
    NumInput("x"),
    NumInput("alpha"),
    NumInput("beta")
], name="distributions.gamma")
def gamma_dist(x, a, b):
    """
    Evaluate the gamma distribution with parameters alpha and beta as defined in the textbook.
    """
    return stats.gamma.pdf(x, a, scale=b)

@register_formula([
    NumInput("lower bound", optional=True),
    NumInput("upper bound", optional=True),
    NumInput("alpha"),
    NumInput("beta")
], name="distributions.gamma.cumulative")
def gamma_dist(lower, upper, a, b):
    return find_distribution_area(stats.gamma, lower, upper, a, scale=b)

@register_formula([
    IntInput("v"),
    NumInput("alpha")
], name="distributions.chi2.inverse")
def inv_chi2_distribution(v, a):
    """
    Given an area 'a', return the value that if taken as a lower bound
    results in the chi-square distribution with v degrees of freedom having 'a' area above it.
    """
    return stats.chi2.ppf(1 - a, v)

@register_formula([
    IntInput("v"),
    NumInput("lower_bound", optional=True),
    NumInput("upper_bound", optional=True)
], name="distributions.chi2.cumulative")
def chi2_cuml(v, lower, upper):
    """
    Find the area between the lower and upper bounds and
    under the chi-squared distribution with v degrees of freedom.

    Bounds that aren't specified are taken at infinity.
    """
    return find_distribution_area(stats.chi2, lower, upper, v)

@register_formula([
    NumInput("v"),
    NumInput("alpha")
], name="distributions.t.inverse")
def inverse_t_dist(v, a):
    """
    Given an area 'a', return the value that if taken as a lower bound
    results in the student t distribution with v degrees of freedom having 'a' area above it.
    """
    return stats.t.ppf(1 - a, v)

@register_formula([
    IntInput("v"),
    NumInput("lower_bound", optional=True),
    NumInput("upper_bound", optional=True)
], name="distributions.t.cumulative")
def t_dist_cuml(v, lower, upper):
    """
    Find the area between the lower and upper bounds and
    under the t-distribution with v degrees of freedom.

    Bounds that aren't specified are taken at infinity.
    """
    return find_distribution_area(stats.t, lower, upper, v)

@register_formula([
    IntInput("v1"),
    IntInput("v2"),
    NumInput("lower_bound", optional=True),
    NumInput("upper_bound", optional=True)
], name="distributions.f.cumulative")
def f_dist_cuml(v1, v2, lower, upper):
    """
    Find the area under the F-distribution between
    the lower and upper bounds given that the
    F-distribution is comparing two chi-squared
    random variables with v1 and v2 degrees of freedom.

    Bounds that aren't specified are taken at infinity.
    """
    return find_distribution_area(stats.f, lower, upper, v1, v2)

@register_formula([
    IntInput("v1"),
    IntInput("v2"),
    NumInput("alpha")
], name="distributions.f.inverse")
def f_dist_inverse(v1, v2, a):
    """
    Given an area 'a', return the value that if taken as a lower bound
    results in the F-distribution with parameters v1 and v2 having 'a' area above it.
    """
    return stats.f.ppf(1 - a, v1, v2)

@register_formula(ListInput("x"), name="sample.sums.x")
def sum_of_x(x):
    """Sums all the values in the list x"""
    return sum(x)

@register_formula(ListInput("x"), name="sample.sums.x ** 2")
def sum_of_squares(x):
    """Sums all the squares of x"""
    return sum(map(lambda xi: xi ** 2, x))

@register_formula((
        ListInput("x"),
        ListInput("y")
), name="sample.sums.(xi - mu_x)(yi - mu_y)")
def unnormalized_covariance(x, y):
    """
    Given two sets x and y of equal lengths, return
    the covariance assuming that x and y only occur in matching
    pairs (one-to-one relationship).
    """
    if len(x) != len(y):
        raise UserInputError("x and y must be one-to-one.")

    mean_x = mean(x)
    mean_y = mean(y)
    # Find of (xi - mean_x) * (yi - mean_y) for all pairs (xi, yi)
    return sum([(x[i] - mean_x) * (y[i] - mean_y) for i in range(len(x))])

@register_formula((
        ListInput("x")
), name="sample.sums.(x - mu_x)^2")
def sum_of_distances_squares(x):
    """
    Given two sets x and y of equal lengths, return
    the covariance assuming that x and y only occur in matching
    pairs (one-to-one relationship).
    """
    mean_x = mean(x)
    # Find the sum of (xi - mean_x) ^2 for all xi
    return sum([(xi - mean_x) ** 2 for xi in x])

@register_formula((
        ListInput("x"),
        ListInput("y")
), name="regression.simple linear regression")
def simple_linear_regression(x, y):
    """
    Estimates the parameters b0 and b1 which represent
    the y-intercept and slope for the line of best fit
    through the (x, y) pairs of data.
    """
    b1 = unnormalized_covariance(x, y) / sum_of_distances_squares(x)
    return {"b0": mean(y) - b1 * mean(x), "b1": b1}

def find_distribution_area(distribution: stats.rv_continuous, lower, upper, *args, **kwargs):
    """
    Find the area between the lower and upper bounds of a scipy.stats continuous random variable distribution.
    """
    # Verify that either lower or upper is specified
    if lower is None and upper is None:
        print("Lower and upper bounds can't both be None")
        raise UserInputError
    # If lower is not specified we want to find the area below upper
    if lower is None:
        return distribution.cdf(upper, *args, **kwargs)
    # If upper is not specified we want to find the are above lower
    if upper is None:
        return 1 - distribution.cdf(lower, *args, **kwargs)
    # Otherwise we find the area between the lower and upper bounds
    return distribution.cdf(upper, *args, **kwargs) - distribution.cdf(lower, *args, **kwargs)

if __name__ == "__main__":
    # Start the prompt used to evaluate the functions.
    launch_prompt()


# -*- coding: utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt

# Funcao de ativacao sigmoide
def sigmoid(soma):
    return 1 / (1 + np.exp(-soma))

# Derivada da funcao de ativaçao sigmoide
def sigmoidDerivada(sig):
    return sig * (1 - sig)

def plotErrors(error_array):
    n_erros = error_array.__len__()
    x = np.linspace(1, n_erros, n_erros)
    y = [error for error in error_array]
    plt.plot(x, y)

    plt.title('Erro médio x época')
    plt.xlabel('Época')
    plt.ylabel('Erro médio')
    plt.show()
    plt.savefig('ErroMedio')

def train_backpropagation(epocas, entradas, pesos0, saidas, pesos1, taxaAprendizagem, momento):

    error_array = []
    camadaSaida = np.zeros([epocas])

    for epoca in range(epocas):

        """
        Feetfoward
        """
        camadaEntrada = entradas
        somaSinapse0 = np.dot(camadaEntrada, pesos0) + 1
        camadaOculta = sigmoid(somaSinapse0)

        somaSinapse1 = np.dot(camadaOculta, pesos1) + 1
        camadaSaida = sigmoid(somaSinapse1)

        """
        Backpropagation
        """

        erroCamadaSaida = (saidas - camadaSaida)
        mediaAbsoluta = np.mean(np.abs(erroCamadaSaida))
        error_array.append(mediaAbsoluta)

        print("Epoca: {}\tErro{}: ".format(epoca, mediaAbsoluta))

        derivadaSaida = sigmoidDerivada(camadaSaida)
        deltaSaida = erroCamadaSaida * derivadaSaida

        pesos1Transposta = pesos1.T
        deltaSaidaXPeso = deltaSaida.dot(pesos1Transposta)
        deltaCamadaOculta = deltaSaidaXPeso * sigmoidDerivada(camadaOculta)

        camadaOcultaTransposta = camadaOculta.T
        pesosNovo1 = camadaOcultaTransposta.dot(deltaSaida)
        pesos1 = (pesos1 * momento) + (pesosNovo1 * taxaAprendizagem)

        camadaEntradaTransposta = camadaEntrada.T
        pesosNovo0 = camadaEntradaTransposta.dot(deltaCamadaOculta)
        pesos0 = (pesos0 * momento) + (pesosNovo0 * taxaAprendizagem)

    plotErrors(error_array)

    return pesos0, pesos1, camadaSaida, error_array

def apply_backpropagation(entradas, pesos0, pesos1, error_array):

    camadaEntrada = entradas
    somaSinapse0 = np.dot(camadaEntrada, pesos0) + 1
    camadaOculta = sigmoid(somaSinapse0)

    somaSinapse1 = np.dot(camadaOculta, pesos1) + 1
    camadaSaida = sigmoid(somaSinapse1)
    print('\nRESULTADO\n', '-' * 30)
    print(camadaSaida)

    for elem in camadaSaida:
        print(elem, np.round(elem))

    plotErrors(error_array)

# n_neuronios_camadaOculta = 3
# epocas =10000
# taxaAprendizagem = 0.5
# momento = 1
#
# entradas = np.array([[0, 0],
#                      [0, 1],
#                      [1, 0],
#                      [1, 1]])
# n_entradas = entradas[0].__len__()
#
# saidas = np.array([[0], [1], [1], [0]])
# n_saidas = saidas[0].__len__()
#
# # camadaSaida = np.zeros([epocas])
#
# pesos0 = 2 * np.random.random((n_entradas, n_neuronios_camadaOculta)) - 1
# pesos1 = 2 * np.random.random((n_neuronios_camadaOculta, n_saidas)) - 1
#
# pesos0, pesos1, camadaSaida, error_array = train_backpropagation(epocas=epocas,
#                                                           entradas=entradas, pesos0=pesos0,
#                                                           saidas=saidas, pesos1=pesos1,
#                                                           taxaAprendizagem=taxaAprendizagem, momento=momento)
#
# apply_backpropagation(entradas=entradas, pesos0=pesos0,
#                       pesos1=pesos1,
#                       error_array=error_array)


# Simple Numerical Laplace Equation Solution using Finite Difference Method
import numpy as np
import matplotlib.pyplot as plt

# Set maximum iteration
maxIter = 500

# Set Dimension and delta
lenX = lenY = 20 #we set it rectangular
delta = 1

# Boundary condition
Ttop = 100
Tbottom = 0
Tleft = 0
Tright = 30

# Initial guess of interior grid
Tguess = 30

# Set colour interpolation and colour map
colorinterpolation = 50
colourMap = plt.cm.jet #you can try: colourMap = plt.cm.coolwarm

# Set meshgrid
X, Y = np.meshgrid(np.arange(0, lenX), np.arange(0, lenY))

# Set array size and set the interior value with Tguess
T = np.empty((lenX, lenY))
T.fill(Tguess)

# Set Boundary condition
T[(lenY-1):, :] = Ttop
T[:1, :] = Tbottom
T[:, (lenX-1):] = Tright
T[:, :1] = Tleft

# Iteration (We assume that the iteration is convergence in maxIter = 500)
print("Please wait for a moment")
for iteration in range(0, maxIter):
    for i in range(1, lenX-1, delta):
        for j in range(1, lenY-1, delta):
            T[i, j] = 0.25 * (T[i+1][j] + T[i-1][j] + T[i][j+1] + T[i][j-1])

print("Iteration finished")

# Configure the contour
plt.title("Contour of Temperature")
plt.contourf(X, Y, T, colorinterpolation, cmap=colourMap)

# Set Colorbar
plt.colorbar()

# Show the result in the plot window
plt.show()

print("")


import numpy as np

w1 = np.random.randn(1,3)
w2 = np.random.randn(3,1)

w1g = np.zeros((1,3))
w2g = np.zeros((3,1))

def sig(x, forward=True):
    if forward:
        return 1 / (1 + np.exp(-x))
    else: # this is the derivative of sigmoid (for backprop)
        return x * (1. - x)

def nn_forward(inpt, w1, w2):
    # LAYER 1 ################
    l1 = np.matmul(inpt, w1)
    l1_sig = sig(l1)
    # LAYER 2 ################
    l2 = np.matmul(l1_sig, w2)

    return l2, l1_sig, l1

def loss(pred, truth):
    return .5 * np.square(pred - truth)

def nn_backward(inpt, l1, l1_sig, l2, target, learning_rate):
    do = np.zeros(l2.shape[0])
    dh = np.zeros(l1.shape[0])

    # OUTPUT LAYER ##############################
        # calculate error signal for output layer
    do = (l2 - target)

        # update gradients for output layer
    for j in range(w2g.shape[0]):
        w2g[j] = do * l1_sig[j]

        # update weights for output layer
    for j in range(w2g.shape[0]):
        w2[j] -= learning_rate * w2g[j]

    # HIDDEN LAYER ##############################
        # calculate error signal for hidden layer
    for j in range(dh.shape[0]):
        dh[j] = sig(l1_sig[j], forward=False) * do * w2[j]

        # update gradients for hidden layer
    for j in range(w1g.shape[1]):
        w1g[0,j] = dh[j] * inpt

        # update weights for hidden layer
    for j in range(w1g.shape[1]):
        w1[0,j] -= learning_rate * w1g[0,j]

''''''
sample = np.array([1])

l2, l1_sig, l1 = nn_forward(sample, w1, w2)
print(l2)
loss(nn_forward(sample, w1, w2)[0], sample * sample)

print(w1)
print(w2.transpose())

nn_backward(sample, l1, l1_sig, l2, 2, 0.1)

print(w1)
print(w2.transpose())

''''''

num_epochs = 100
learning_rate = 0.1

for i in range(num_epochs):
    sample = np.array([1])
    target = np.array([2])

    l2, l1_sig, l1 = nn_forward(sample, w1, w2)

    nn_backward(sample, l1, l1_sig, l2, target, learning_rate)

    print('prediction = ', l2)
    print('loss: ', loss(l2, target))
    print('w1 = ', w1)
    print('w2 = ', w2.transpose(), '\n')


"""
Copyright (c) 2019 ground0state. All rights reserved.
License: MIT License
"""
import numpy as np
# from scipy.stats import f

class HotelingT2():
    """Hotelling's t-squared statistic.
    """

    def __init__(self):
        self.mean_val = None
        self.cov_val_inv = None
        self.M = None
        self.N = None

    def fit(self, X):
        """Fit the Hotelling's t-squared model according to the given train data.

        Parameters
        ----------
        X : array-like, shape (n_samples, )
            Normal measured vectors, where n_samples is the number of samples.

        Returns
        -------
        self : object
        """
        self.N, self.M = X.shape
        self.mean_val = X.mean(axis=0)
        if self.M > 1:
            self.cov_val_inv = np.linalg.inv(np.cov(X, rowvar=0, bias=1))
        elif self.M == 1:
            self.cov_val_inv = 1/np.var(X)
        else:
            raise ValueError("Input shape is incorrect")

        return self

    def score(self, X):
        """Calculate anomaly score according to the given test data.

        Parameters
        ----------
        X : array-like, shape (n_samples,)
            Error measured vectors, where n_samples is the number of samples.

        Returns
        -------
        anomaly_score : array-like, shape (n_samples,)
            Anomaly score.
        """
        pred = []
        for x in X:
            if self.M > 1:
                a = (x-self.mean_val)@self.cov_val_inv@(x-self.mean_val)
            elif self.M == 1:
                a = (x-self.mean_val)**2*self.cov_val_inv
            # T2 = (self.N - self.M)/((self.N + 1) * self.M) * a
            # prob = f.pdf(T2, self.M, self.N-self.M)
            pred.append(a)

        return np.asarray(pred)

# slowly
# class HotelingT2():
#     def __init__(self):
#         self.mean_val = None
#         self.cov_val = None
#         self.M = None
#         self.N = None

#     def fit(self, X):
#         self.N, self.M = X.shape
#         self.mean_val = X.mean(axis=0)
#         self.cov_val = np.cov(X, rowvar=0, bias=1)

#     def score(self, X):
#         pred = []
#         for x in X:
#             b = np.linalg.solve(self.cov_val, x-self.mean_val)
#             a = (x-self.mean_val)@b
#             # T2 = (self.N - self.M)/((self.N + 1) * self.M) * a
#             # prob = f.pdf(T2, self.M, self.N-self.M)
#             pred.append(a)

#         return np.asarray(pred)

if __name__ == '__main__':

    normal_data = np.loadtxt("../input/normal_data.csv", delimiter=",")
    error_data = np.loadtxt("../input/error_data.csv", delimiter=",")

    model = HotelingT2()
    model.fit(normal_data)
    pred = model.score(error_data)

    import matplotlib.pyplot as plt
    plt.plot(pred)
    plt.show()


import numpy as np
from scipy.optimize import minimize
from numba import njit
##############################

nn = 9
T = np.random.rand(3*nn+1)
c_sample = 128
x = np.linspace(start=-10,stop=10,num=c_sample).reshape(-1,1) # vectorized
y_real = np.sin(x)/x
y_sample = np.sin(x)/x + (np.random.rand(c_sample,1)*0.1-0.05)

X = x
Y = y_sample
H = np.array([nn])

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

PROD = np.dot
EXP = np.exp
MEAN = np.mean

@njit
def A(x): return 1/(1+EXP(-x))
MSE = lambda Y,P: MEAN((Y-P)**2)

@njit
def UPK(T,H):
    NN = H[0]
    W1= T[:NN].reshape(1, NN)
    W2= T[NN:NN*2].reshape(NN, 1)
    B1= T[NN*2:-1].reshape(-1, NN)
    B2= T[-1]
    return W1,W2,B1,B2

@njit
def NNOP(T,X,H):
    W1,W2,B1,B2 = UPK(T,H)
    return PROD(A(PROD(X,W1)-B1),W2)-B2

LOSS_NN = lambda T,X,H,Y: MSE(NNOP(T,X,H),Y)

SOLVE = lambda T,X,Y,H:minimize(LOSS_NN,T,(X,H,Y)).x

RS = SOLVE(T,X,Y,H)

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

print('MODEL TO SAVE:\n',UPK(RS,H))

import matplotlib.pyplot as plt
x_test = np.sort(np.random.rand(100,1)*10-5,axis=0)
y_test_real = np.sin(x_test)/x_test
y_test_pred = NNOP(RS, x_test, H)
plt.plot(x_test, y_test_real, 'oy')
plt.plot(x_test, y_test_pred, '-b')
plt.legend(['y_test_real', 'y_test_pred'])
#plt.title('mse={0}'.format(MSE(y_test_real,y_test_pred)))
plt.show()


import pandas as pd
from pandas import Series
import theano.tensor as tt
import numpy as np
import pymc3 as pm

N = 100
X = 35

with pm.Model() as model:
    p = pm.Uniform('freq_cheating', 0, 1)

with model:
    # does testval matter? i don't think so
    true_answers = pm.Bernoulli('truths', p, shape=N, testval=np.random.binomial(1, 0.5, N))

with model:
    first_coin_flips = pm.Bernoulli('first_flips', 0.5, shape=N, testval=np.random.binomial(1, 0.5, N))
    second_coin_flips = pm.Bernoulli('second_flips', 0.5, shape=N, testval=np.random.binomial(1, 0.5, N))

with model:
    val = first_coin_flips*true_answers + (1 - first_coin_flips)*second_coin_flips

    # how to know we have to do float(N) here?
    obs_prop = pm.Deterministic('obs_prop', tt.sum(val)/float(N))

with model:
    obs = pm.Binomial('obs', N, obs_prop, observed=X)

with model:
    # why are we specifying vars here and not in ab example
    step = pm.Metropolis(vars=[p])
    trace = pm.sample(2000, step=step)
    burned_trace = trace[1000:]

ps = Series(burned_trace['freq_cheating'])
(ps < 0.35).mean()


from nntool.abc.layerabc import ActivationFunctionLayer

import numpy as np
class TanhLayer(ActivationFunctionLayer):
    """双曲正弦函数,值域在[-1,1]的激活函数,形状与sigmoid函数类似,一般用于做挤压
    """
    def forward(self,x):
        """计算模型的正向计算结果
        :math:`\\tanh(x)=\\frac {1-e^{-2x}} {1+e^{-2x}}`"""
        self.x = x
        self._size = len(self.x)

        self.y = np.tanh(x)
        return self.y

    def backward(self,djdys,eta):
        self.djdys = djdys
        self.djdxs = self.d_x()

        return self.djdxs,eta

    @property
    def size(self):
        return self._size

    def d_x(self):
        """对x的偏导
        :math:`\\frac{\\partial y}{\\partial x} = 1-y^2`
        """
        djdx = self.djdys*(1-self.y**2)
        return djdx

    def d(self,y):
        pass


import numpy as np
# using sympy because it can correctly work with values of trigonometric functions
# from box, when numpy gives us only closer number, so here used the same function like in numpy
import sympy as sp
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d
from mpl_toolkits.mplot3d import Axes3D

def x_rot(q):
    return np.array([[1, 0, 0, 0],
                     [0, sp.N(sp.cos(q)), sp.N(sp.sin(q)), 0],
                     [0, -sp.N(sp.sin(q)), sp.N(sp.cos(q)), 0],
                     [0, 0, 0, 1]])

def y_rot(q):
    return np.array([[sp.N(sp.cos(q)), 0, sp.N(sp.sin(q)), 0],
                     [0, 1, 0, 0],
                     [-sp.N(sp.sin(q)), 0, sp.N(sp.cos(q)), 0],
                     [0, 0, 0, 1]])

def z_rot(q):
    return np.array([[sp.N(sp.cos(q)), sp.N(sp.sin(q)), 0, 0],
                     [-sp.N(sp.sin(q)), sp.N(sp.cos(q)), 0, 0],
                     [0, 0, 1, 0],
                     [0, 0, 0, 1]])

def x_trans(l):
    t = np.array([[1, 0, 0, 0],
                  [0, 1, 0, 0],
                  [0, 0, 1, 0],
                  [0, 0, 0, 1]])
    t[0][3] = l
    return t

def y_trans(l):
    t = np.array([[1, 0, 0, 0],
                  [0, 1, 0, 0],
                  [0, 0, 1, 0],
                  [0, 0, 0, 1]])
    t[1][3] = l
    return t

def z_trans(l):
    t = np.array([[1, 0, 0, 0],
                  [0, 1, 0, 0],
                  [0, 0, 1, 0],
                  [0, 0, 0, 1]])
    t[2][3] = l
    return t

def forward_kinematics(q):
    t = z_trans(q[1][0]).dot(z_rot(q[0][0])).dot(y_trans(q[1][1])).dot(x_rot(q[0][1])).dot(z_trans(q[1][2])).dot(
        x_rot(q[0][2])).dot(z_trans(q[1][3])).dot(
        y_trans(q[1][4])).dot(y_rot(q[0][3])).dot(x_rot(q[0][4])).dot(y_trans(q[1][5])).dot(y_rot(q[0][5]))
    return t

def inverse_kinematics(t, v):
    a1 = sp.atan2(t[0][3], -t[1][3]) + sp.atan2(sp.sqrt(t[0][3] ** 2 + t[1][3] ** 2), v[1][5])
    a2 = sp.atan2(sp.cos(a1) * t[0][3] + sp.sin(a1) * t[1][3], -t[2][3] + v[1][0])
    a3 = sp.atan2(sp.sin(a2) * (a1 * t[0][3] + a1 * t[1][3]), sp.cos(a2) * (t[2][3] - v[1][0]))
    a4 = sp.atan2((t[0][2] * sp.sin(a1) - t[1][2] * sp.cos(a1)) / sp.sin(a3), - (
            t[2][2] * sp.sin(a2) + t[0][2] * sp.cos(a1) * sp.cos(a2) + t[1][2] * sp.cos(a2) * sp.sin(a1)) / sp.sin(
        a3))
    a5 = sp.atan2(sp.sqrt(
        1 - (t[2][2] * sp.cos(a2) - t[0][2] * sp.cos(a1) * sp.sin(a2) - t[1][2] * sp.sin(a1) * sp.sin(a2)) ** 2),
        t[2][2] * sp.cos(a2) - t[0][2] * sp.cos(a1) * sp.sin(a2) - t[1][1] * sp.sin(a1) * sp.sin(a2))
    a6 = sp.atan2(sp.sqrt(t[0][3] ** 2 + t[1][3] ** 2 + t[2][3] ** 2), t[2][3])
    return a1, a2, a3, a4, a5, a6

def calculate_rotation_matrix(alpha, beta, theta):
    return x_rot(alpha).dot(y_rot(beta)).dot(x_rot(theta))

def rot2eul(rotation_matrix):
    # print(rotation_matrix)
    a = sp.atan2(rotation_matrix[1][0], rotation_matrix[2][0])
    b = sp.atan2(sp.sqrt(rotation_matrix[1][0] ** 2 + rotation_matrix[2][0] ** 2), rotation_matrix[0][0])
    c = sp.atan2(rotation_matrix[0][1], rotation_matrix[0][2])
    return a, b, c

if __name__ == '__main__':
    l1 = 670
    l2 = 312
    l3 = 1075
    l4 = 225
    l5 = 1280
    l6 = 215
    # ---------
    q1 = 0
    q2 = 0
    q3 = 0
    q4 = 0
    q5 = 0
    q6 = 0
    # configuration vector for zero position
    vector = [[q1, q2, q3, q4, q5, q6],
              [l1, l2, l3, l4, l5, l6]]
    # --------------------------------------
    # print euler angels in radians from rotation matrix
    rot_matrix = calculate_rotation_matrix(sp.pi / 2, sp.pi / 2, -sp.pi / 2)
    print(rot2eul(rot_matrix))
    # print homogeneous transformation matrix
    T = forward_kinematics(vector)
    print(T)
    print("Angle: {0}".format(sum((vector[0][:]))))
    # print inverse kinematics
    print(inverse_kinematics(T, vector))

    """ Visualization on develop process
    fig = plt.figure()
    ax = plt.axes(projection="3d")
    ax.plot3D(T[0:3][3])
    plt.show()"""


#****************
# TSP DJIBOUTI  *
#****************
print("TSP38")

# load librarys
import random
import numpy as np
from TSP_util import *
from matplotlib import pyplot as plt
import time

# reproducibility
random.seed(1203)
# random.seed(1815)

# load raw data
djib = np.loadtxt( "http://www.math.uwaterloo.ca/tsp/world/dj38.tsp",
                  delimiter=" ", skiprows=10 )
djib = djib[ :, 1:3 ]
n = len( djib )

# create model
myTSP = TSP2(djib)

# compute distances and visualize distances distribution
D = myTSP.matrix_of_distances
myDist = np.sort( D, axis = None)[ n::2 ]

myHist = plt.hist( myDist, density=True)
plt.title(label = 'Distribution of city distances TSP 38')
plt.show()

#******************************************************************
#**************************GENETIC ALGORITHM***********************
#******************************************************************

print("\n Genetic Algorithm")

print("\n - using Hui's parameters with inverse proportional density for selection")
# first try with Hui parameters except population because of computation limitation
tryTSP(myTSP, 1000, 1000,
       PermutationSwapMutation(0.01, randMut=1),
       PMXCrossover(0.3),
       RouletteWheelSelection(beta = 0),
       StoppingByEvaluations(max_evaluations=20000))

print("\n - with exponential density for selection")
# beta = 0.001 => exponentional density for selection
tryTSP(myTSP, 1000, 1000,
       PermutationSwapMutation(0.01, randMut=1),
       PMXCrossover(0.3),
       RouletteWheelSelection(beta = 0.001),
       StoppingByEvaluations(max_evaluations=20000))

print("\n - with optimal mutation and inverse proportional density for selection")
# randMut = 2 => optimal mutation
tryTSP(myTSP, 1000, 1000,
       PermutationSwapMutation(0.01, randMut=2, D=D, n=n, first=False),
       PMXCrossover(0.3),
       RouletteWheelSelection(beta = 0),
       StoppingByEvaluations(max_evaluations=20000))

print("\n - with optimal mutation and inverse exponential density for selection")
# beta=0.001 => exponentional density for selection
tryTSP(myTSP, 1000, 1000,
       PermutationSwapMutation(0.01, randMut=2, D=D, n=n, first=False),
       PMXCrossover(0.3),
       RouletteWheelSelection(beta = 0),
       StoppingByEvaluations(max_evaluations=20000))

print("\n - reduce population and increase iterations")
tryTSP(myTSP, 100, 100,
       PermutationSwapMutation(0.01, randMut=2, D=D, n=n, first=False),
       PMXCrossover(0.3),
       RouletteWheelSelection(beta = 0),
       StoppingByEvaluations(max_evaluations=100000))

print("\n - increase mutation probability")
tryTSP(myTSP, 100, 100,
       PermutationSwapMutation(0.5, randMut=2, D=D, n=n, first=False),
       PMXCrossover(0.3),
       RouletteWheelSelection(beta = 0),
       StoppingByEvaluations(max_evaluations=100000))

# Iterate the elected method

print("\n The next computation is lengthy, (100 implementations of the previous one)")
YN = input("Do you want to implement it ?")

if YN == "Y" :
    fitnesses = np.zeros(100)
    for i in range(100):
        r = tryTSP(myTSP, 100, 100,
                    PermutationSwapMutation(0.5, randMut=2, D=D, n=n, first=False),
                    PMXCrossover(0.3),
                    RouletteWheelSelection(beta = 0),
                    StoppingByEvaluations(max_evaluations=100000))
        fitnesses[i] = r.objectives[0]

    # get minimum from sample
    print("\n Best fitness = ", np.min(fitnesses))
else :
    print("\n Understood, we pursue")

#******************************************************************
#*******************CLOSEST NEIGHBOUR PATH*************************
#******************************************************************

print("\n Closest neighbour algorithm")

print("\n Find optimal path w.r.t. closest neighbour")
(totDist, optPath, arrDist) = findGLobalOptPath(D, n)
bestPath = np.argmin(totDist)
print("path length = ", totDist[bestPath])

print("\n Find optimal path w.r.t. closest neighbour and optimize with best mutation")
(totDist2, optPath2, arrDist1) = findGLobalOptPath(D, n, optimize=True, maxLoop=30)
bestPath2 = np.argmin(totDist2)
print("path length = ", totDist2[bestPath2])

input("Press enter to exit")


import scipy.special as special
import numpy as np
import math
import torch

def torch_log_factorial(n, base=2.0):
    n = n.float()
    return torch.lgamma(n+1)/math.log(base)

def torch_log_binom(n, k, base=2.0):
    n = n.float()
    k = k.float()
    mask = n.detach() >= k.detach()
    n = mask * n
    k = mask * k
    a = (torch.lgamma(n + 1) - torch.lgamma((n - k) + 1) - torch.lgamma(k + 1))/math.log(base)
    return a * mask

def torch_log_arrangement(n, k, base=2.0):
    n = n.float()
    k = k.float()
    mask = n.detach() >= k.detach()
    n = mask * n
    k = mask * k
    a = (torch.lgamma(n + 1) - torch.lgamma((n - k) + 1))/math.log(base)
    return a * mask

def torch_factorial(n):
    n = n.double()
    return torch.exp(torch.lgamma(n+1))

def torch_binom(n, k):
    # double precision to avoid rounding errors
    n = n.double()
    k = k.double()
    mask = n.detach() >= k.detach()
    n = mask * n
    k = mask * k
    a = torch.lgamma(n + 1) - torch.lgamma((n - k) + 1) - torch.lgamma(k + 1)
    return torch.exp(a) * mask

# modified from: https://stackoverflow.com/questions/14455634/find-the-index-of-a-given-combination-of-natural-numbers-among-those-returned

def combinations_index(comb, n, torch_enabled=True):
    """Return the index of combination (length == k)

    The combination argument should be a sorted sequence (i | i∈{0…n-1})"""

    k= len(comb)
    index= 0
    for offset, item in enumerate(comb):
        item += 1
        index+= special.comb(int(n-item), int(k-offset)) if not torch_enabled \
                else torch.round(torch_binom(n-item, torch.tensor(k-offset, device=n.device)))

    result = int(np.ceil(special.comb(int(n), int(k))-int(index)-1)) if not torch_enabled\
                else torch.ceil(torch.round(torch_binom(n, torch.tensor(k, device=n.device)))-index-1).long()

    return result

def combinations_comb(index, n , k, torch_enabled=True):
    """Select the 'index'th combination of k over n
    Result is a tuple (i | i∈{0…n-1}) of length k

    Note that if index ≥ binomial_coefficient(n,k)
    then the result is almost always invalid"""
    result= []
    for item, n_temp in enumerate(range(int(n), -1, -1)):
        n_temp = torch.tensor(n_temp, device=n.device) if torch_enabled else n_temp
        pivot= special.comb(int(n_temp-1), int(k-1)) if not torch_enabled\
                else torch.round(torch_binom(n_temp - 1, k - 1))
        if index < pivot:
            result.append(item)
            k-= 1
            if k <= 0: break
        else:
            index-= pivot

    return result

def array_pos(index, cols):

    row = index//cols
    col = index - cols*row

    return row, col


import numpy as np

A = np.array([
    [1, 2, 3],
    [4, 5, 6]
])

B = np.array([
    [2, -1],
    [2, 8],
    [7, 5]
])

print("(AB)_1,1")
print(A[0,:], B[:,0], " = ", np.dot(A[0,:], B[:,0]))

print("(AB)_1,2")
print(A[0,:], B[:,1], " = ", np.dot(A[0,:], B[:,1]))

print("(AB)_2,1")
print(A[1,:], B[:,0], " = ", np.dot(A[1,:], B[:,0]))

print("(AB)_2,2")
print(A[1,:], B[:,1], " = ", np.dot(A[1,:], B[:,1]))

print()
print("AB")
print(np.dot(A, B))


import numpy as np
from graphik.utils.geometry import angle_to_se2, rot_axis, trans_axis
from liegroups.numpy import SO2, SO3, SE2, SE3

##################################################
# Planar joints
##################################################
def fk_2d(a: list, theta: list, q: list) -> SE2:
    """Get forward kinematics from an array of link lengths and angles
    :param a: displacement along x (link length)
    :param theta: rotation offset of each link
    :param q: active angle variable
    :returns: SE2 matrix
    :rtype: lie.SE2Matrix
    """
    if len(a) > 1:
        return angle_to_se2(a[0], theta[0] + q[0]).dot(fk_2d(a[1:], theta[1:], q[1:]))
    return angle_to_se2(a[0], theta[0] + q[0])

def fk_tree_2d(a: list, theta: list, q: list, path_indices: list) -> SE2:
    """Get forward kinematics from an array of link lengths and angles
    :param a: displacement along x (link length)
    :param theta: rotation offset of each link
    :param q: active angle variable
    :param path_indices: path through the tree to traverse
    :returns: SE2 matrix
    :rtype: lie.SE2Matrix
    """
    idx = path_indices[0]
    if len(path_indices) > 1:
        return angle_to_se2(a[idx], theta[idx] + q[idx]).dot(
            fk_tree_2d(a, theta, q, path_indices[1:])
        )
    return angle_to_se2(a[idx], theta[idx] + q[idx])

##################################################
# 3D single-axis of rotation
##################################################

def dh_to_se3(a: float, alpha: float, d: float, theta: float) -> SE3:
    """Transform a single set of DH parameters into an SE3 matrix
    :param a: displacement along x
    :param alpha: rotation about x
    :param d: translation along new z
    :param theta: rotation around new z
    :returns: SE3 matrix
    :rtype: lie.SE3Matrix
    """

    TransX = trans_axis(a,"x")
    RotX = rot_axis(alpha,"x")
    TransZ = trans_axis(d,"z")
    RotZ = rot_axis(theta,"z")

    return TransZ.dot(RotZ.dot(TransX.dot(RotX)))

def modified_dh_to_se3(a: float, alpha: float, d: float, theta: float) -> SE3:
    """Transform a single set of modified DH parameters into an SE3 matrix
    :param a: displacement along x
    :param alpha: rotation about x
    :param d: translation along new z
    :param theta: rotation around new z
    :returns: SE3 matrix
    :rtype: lie.SE3Matrix
    """

    # TransX = SE3(SO3.identity(), np.array([a, 0, 0]))
    # RX = SO3(rotx(alpha))
    # RotX = SE3(RX, np.zeros(3))
    # TransZ = SE3(SO3.identity(), np.array([0, 0, d]))
    # RZ = SO3(rotz(theta))
    # RotZ = SE3(RZ, np.zeros(3))

    TransX = trans_axis(a,"x")
    RotX = rot_axis(alpha,"x")
    TransZ = trans_axis(d,"z")
    RotZ = rot_axis(theta,"z")

    return TransX.dot(RotX.dot(TransZ.dot(RotZ)))

def fk_3d(a: list, alpha: list, d: list, theta: list) -> SE3:
    """Get forward kinematics from an array of dh parameters
    :param a: displacement along x
    :param alpha: rotation about x
    :param d: translation along new z
    :param theta: rotation around new z
    :returns: SE3 matrix
    :rtype: lie.SE3Matrix
    """
    if len(a) > 1:
        return dh_to_se3(a[0], alpha[0], d[0], theta[0]).dot(
            fk_3d(a[1:], alpha[1:], d[1:], theta[1:])
        )
    return dh_to_se3(a[0], alpha[0], d[0], theta[0])

def fk_tree_3d(a: list, alpha: list, d: list, theta: list, path_indices: list) -> SE3:
    """Get forward kinematics from an array of link lengths and angles
    :param a: displacement along x (link length)
    :param theta: rotation offset of each link
    :param q: active angle variable
    :param path_indices: path through the tree to traverse
    :returns: SE2 matrix
    :rtype: lie.SE2Matrix
    """
    idx = path_indices[0]
    if len(path_indices) > 1:
        return dh_to_se3(a[idx], alpha[idx], d[idx], theta[idx]).dot(
            fk_tree_3d(a[1:], alpha[1:], d[1:], theta[1:], path_indices[1:])
        )
    return dh_to_se3(a[idx], alpha[idx], d[idx], theta[idx])

def modified_fk_3d(a: list, alpha: list, d: list, theta: list) -> SE3:
    """Get forward kinematics from an array of  modified dh parameters
    :param a: displacement along x
    :param alpha: rotation about x
    :param d: translation along new z
    :param theta: rotation around new z
    :returns: SE3 matrix
    :rtype: lie.SE3Matrix
    """
    if len(a) > 1:
        return modified_dh_to_se3(a[0], alpha[0], d[0], theta[0]).dot(
            modified_fk_3d(a[1:], alpha[1:], d[1:], theta[1:])
        )
    return modified_dh_to_se3(a[0], alpha[0], d[0], theta[0])