# -*- coding: utf-8 -*-
"""
Created on Fri Jul  2 12:09:14 2021

@author: vohuynhq
"""

import numpy as np
import pandas as pd
from sympy import symbols, init_printing, pi, sqrt, diff, sin, cos, exp

def example(A):
    A_true = np.array([[3, 4, 5], [3, 4, 5]])
    np.testing.assert_equal(A, A_true)
    return None

def derivative(eval_1,eval_2,eval_3,eval_4,*args):
    init_printing(use_unicode= True)
    x = symbols('x')
    
    f_1 = x **(3/2) + pi * x ** 2 + sqrt(2)
    diff_1 = diff(f_1,x)
    eval_1_true = diff_1.subs(x,2)
    
    f_2 = sin(x)*exp(cos(x))
    diff_2 = diff(f_2,x)
    eval_2_true= diff_2.subs(x,pi)

    f_3 = exp((x + 1) ** 2)
    diff_3 = diff(f_3,x)
    eval_3_true = diff_3.subs(x,1)

    f_4 = x ** 2 * (cos(x) ** 3)
    diff_4 = diff(f_4,x)
    eval_4_true = diff_4.subs(x,pi)
    
    assert eval_1 == eval_1_true, "Wrong answer!"
    assert eval_2 == eval_2_true, "Wrong answer!"
    assert eval_3 == eval_3_true, "Wrong answer!"
    assert eval_4 == eval_4_true, "Wrong answer!"
    
    return None

def jacobians_hessians(eval_1,eval_2,eval_3,eval_4):
    init_printing(use_unicode= True)
    x, y, z = symbols('x y z')
    
    f_1 = x ** 2 * cos(y) + exp(z)*sin(y)
    J_1 = [diff(f_1,x), diff(f_1,y), diff(f_1,z)] 
    eval_1_true = [J_1[0].subs([(x,pi), (y,pi),(z,1)]), 
              J_1[1].subs([(x,pi), (y,pi),(z,1)]),
              J_1[2].subs([(x,pi), (y,pi),(z,1)])]
    
    u = x ** 2 *y - cos(x) * sin(y)
    v = exp(x + y)
    J_u = [diff(u,x), diff(u,y)]
    J_v = [diff(v,x), diff(v,y)]
    eval_u = [J_u[0].subs([(x,0),(y,pi)]), 
              J_u[1].subs([(x,0),(y,pi)])]
    eval_v = [J_v[0].subs([(x,0),(y,pi)]), 
              J_v[1].subs([(x,0),(y,pi)])]
    eval_2_true = [eval_u,eval_v]
    
    f_3 = x ** 3 * cos(y) - x * sin(y)
    H_3 = [[diff(diff(f_3,x),x), diff(diff(f_3,x),y)],
           [diff(diff(f_3,y),x), diff(diff(f_3,y),y)]]
    eval_3_true = [[H_3[0][0].subs([(x,pi), (y,pi)]), H_3[0][1].subs([(x,pi),(y,pi)])],
                   [H_3[1][0].subs([(x,pi), (y,pi)]), H_3[1][1].subs([(x,pi),(y,pi)])]]
    
    f_4 = x * y * cos(z) - sin(x) * exp(y) * z**3
    H_4 = [[diff(diff(f_4,x),x), diff(diff(f_4,x),y), diff(diff(f_4,x),z)],
           [diff(diff(f_4,y),x), diff(diff(f_4,y),y), diff(diff(f_4,y),z)],
           [diff(diff(f_4,z),x), diff(diff(f_4,z),y), diff(diff(f_4,z),z)]]
       
    eval_4_true = [
        [H_4[0][0].subs([(x,pi),(y,pi),(z,pi)]), H_4[0][1].subs([(x,pi),(y,pi),(z,pi)]), H_4[0][2].subs([(x,pi),(y,pi),(z,pi)])],
        [H_4[1][0].subs([(x,pi),(y,pi),(z,pi)]), H_4[1][1].subs([(x,pi),(y,pi),(z,pi)]), H_4[1][2].subs([(x,pi),(y,pi),(z,pi)])],
        [H_4[2][0].subs([(x,pi),(y,pi),(z,pi)]), H_4[2][1].subs([(x,pi),(y,pi),(z,pi)]), H_4[2][2].subs([(x,pi),(y,pi),(z,pi)])]
        ]

    
    assert set(eval_1) == set(eval_1_true), 'Wrong answer!'
    assert set(eval_2) == set(eval_2_true), 'Wrong answer!'
    assert set(eval_3) == set(eval_3_true), 'Wrong answer!'
    assert set(eval_4) == set(eval_4_true), 'Wrong answer!'
    
    return None


def question1(m,b,X,y,*args):
    ##
    # Load dataset:
    #
    data = pd.read_csv("./dataset/Xy_dataset.csv")
    X = np.array(data['X'])
    y = np.array(data['y'])
    X = X.reshape(X.shape[0],1)
    y = y.reshape(y.shape[0],1)

    ##
    # Step 1: Initialize m and b:
    #
    m_true = 0
    b_true = 0
    
    for i in range(50):   
    
        ##
        # Step 2: Find y_pred = mx + b:
        #
        y_pred = m*X + b
        
        ##
        # Step 3: Update m and b using the Gradient Descent algorithm:
        #
        dm = np.mean((y_pred - y) * X)
        db = np.mean(y_pred - y)
        m_true = m_true - 0.1*dm
        b_true = b_true - 0.1*db
       
    np.testing.assert_equal(m,m_true)
    np.testing.assert_equal(b,b_true)
    return None

def question2(m,b,X,y,*args):
    
    ##
    # Step 1: Initialize m and b:
    #
    m_true = 0
    b_true = 0
    
    for i in range(50):   
    
        ##
        # Step 2: Find y_pred = mx + b:
        #
        y_pred = m*X + b
        
        ##
        # Step 3: Update m and b using the Gradient Descent algorithm:
        #
        dm = np.mean((y_pred - y) * X)
        db = np.mean(y_pred - y)
        m_true = m_true - 0.1*dm
        b_true = b_true - 0.1*db
       
    np.testing.assert_equal(m,m_true)
    np.testing.assert_equal(b,b_true)
    return None

def question3(m,b,X,y,*args):
        
    ##
    # Step 1: Initialize m and b:
    #
    m_true = 0
    b_true = 0
    
    for i in range(50):   
        ##
        # Step 2: Find y_pred = mx + b:
        #
        y_pred = m*X + b
        
        ##
        # Step 3: Update m and b using the Gradient Descent algorithm:
        #
        dm = np.mean((y_pred - y) * X)
        db = np.mean(y_pred - y)
        m_true = m_true - 0.2*dm
        b_true = b_true - 0.2*db
       
    np.testing.assert_equal(m,m_true)
    np.testing.assert_equal(b,b_true)

    return None

def question4(m,b,X,y,*args):
        
    ##
    # Step 1: Initialize m and b:
    #
    m_true = 0
    b_true = 0
    
    for i in range(100):   
        ##
        # Step 2: Find y_pred = mx + b:
        #
        y_pred = m*X + b
        
        ##
        # Step 3: Update m and b using the Gradient Descent algorithm:
        #
        dm = np.mean((y_pred - y) * X)
        db = np.mean(y_pred - y)
        m_true = m_true - 0.1*dm
        b_true = b_true - 0.1*db
       
    np.testing.assert_equal(m,m_true)
    np.testing.assert_equal(b,b_true)
    
    return None