from __future__ import absolute_import, division
from __future__ import print_function, unicode_literals
from past.builtins import range


from clifford import Cl, randomMV, Frame, get_mult_function, \
    conformalize, grade_obj, val_get_right_gmt_matrix, MultiVector, MVArray


import numpy as np
from numpy import exp, float64, testing
import unittest
from functools import reduce

from nose.plugins.skip import SkipTest


class CliffordTests(unittest.TestCase):

    def setUp(self):
        self.algebras = [Cl(i) for i in [3, 4, 5]] # we should make this a dict, so we know what dimension it is

    def test_inverse(self):
        for layout, blades in self.algebras:
            a = 1. + blades['e1']
            self.assertRaises(ValueError, lambda x: 1/x, a)
            for i in range(10):
                a = randomMV(layout, grades=[0, 1])
                denominator = float(a(1)**2-a(0)**2)
                if abs(denominator) > 1.e-5:
                    a_inv = (-a(0)/denominator) + ((1./denominator) * a(1))
                    self.assert_(abs((a * a_inv)-1.) < 1.e-11)
                    self.assert_(abs((a_inv * a)-1.) < 1.e-11)
                    self.assert_(abs(a_inv - 1./a) < 1.e-11)

    def test_grade_masks(self):
        for alg in self.algebras:
            layout, blades = alg
            A = layout.randomMV()
            for i in range(layout.dims + 1):
                np.testing.assert_almost_equal(A(i).value,A.value*layout.grade_mask(i))

    def test_rotor_mask(self):
        for alg in self.algebras:
            layout, blades = alg
            rotor_m = layout.rotor_mask
            rotor_m_t = np.zeros(layout.gaDims)
            for _ in range(10):
                rotor_m_t += 100*np.abs(layout.randomRotor().value)
            np.testing.assert_almost_equal(rotor_m_t > 0, rotor_m)

    def test_exp(self):
        layout, blades = self.algebras[0]
        e12 = blades['e12']
        theta = np.linspace(0, 100 * np.pi, 101)
        a_list = [np.e**(t * e12) for t in theta]
        for a in a_list:
            np.testing.assert_almost_equal(abs(a), 1.0, 5)
    
    def test_exp_g4(self):
        '''
        a numerical test for the exponential of a bivector. truth was
        generated by results of clifford v0.82
        '''
        layout, blades = self.algebras[1] # g4

        valB = np.array([-0.                 ,  0.                 ,  0.                 ,
               -0.                 , -0.                 , -1.9546896043012914 ,
                0.7069828848351363 , -0.22839793693302957,  1.0226966962560002 ,
                1.8673816483342143 , -1.7694566455296474 , -0.                 ,
               -0.                 ,  0.                 , -0.                 ,
               -0.                 ])
        valexpB = np.array([-0.8154675764311629  ,  0.                  ,
                0.                  ,  0.                  ,
                0.                  ,  0.3393508714682218  ,
                0.22959588097548828 , -0.1331099867581965  ,
               -0.01536404898029994 ,  0.012688721722814184,
                0.35678394795928464 ,  0.                  ,
                0.                  ,  0.                  ,
                0.                  , -0.14740840378445502 ])

        
        B = MultiVector(layout=layout,value=valB)
        expB =MultiVector(layout=layout,value=valexpB)
        np.testing.assert_almost_equal(exp(B)[0].value,expB.value)
    
    def test_inv_g4(self):
        '''
        a numerical test for the inverse of a MV. truth was
        generated by results of clifford v0.82
        '''
        layout, blades = self.algebras[1] # g4
        valA = np.array([-0.3184271488037198 , -0.8751064635010213 , -1.5011710376191947 ,
                1.7946332649746224 , -0.8899576254164621 , -0.3297631748225678 ,
                0.04310366054166925,  1.3970365638677635 , -1.545423393858595  ,
                1.7790215501876614 ,  0.4785341530609175 , -1.32279679741638   ,
                0.5874769077573831 , -1.0227287710873676 ,  1.779673249468527  ,
               -1.5415648119743852 ])

        valAinv= np.array([ 0.06673424072253006 , -0.005709960252678998,
               -0.10758540037163118 ,  0.1805895938775471  ,
                0.13919236400967427 ,  0.04123255613093294 ,
               -0.015395162562329407, -0.1388977308136247  ,
               -0.1462160646855434  , -0.1183453106997158  ,
               -0.06961956152268277 ,  0.1396713851886765  ,
               -0.02572904638749348 ,  0.02079613649197489 ,
               -0.06933660606043765 , -0.05436077710009021 ])
        
        A = MultiVector(layout=layout,value=valA)
        Ainv = MultiVector(layout=layout,value=valAinv)
        

        np.testing.assert_almost_equal(A.inv().value, Ainv.value)
        
    def test_indexing(self):
        layout, blades = self.algebras[0]
        e12 = blades['e12']
        e1 = blades['e1']
        e2 = blades['e2']
        e3 = blades['e3']
        self.assertAlmostEqual(e12[e12],1)
        self.assertAlmostEqual(e12[e3], 0)
        self.assertAlmostEqual(e12[(2,1)], -1)


    def test_add_float64(self):
        '''
        test array_wrap method to take control addition from numpy array
        '''
        layout, blades = self.algebras[0]
        e1 = blades['e1']

        float64(1) + e1
        self.assertEqual(1 + e1, float64(1) + e1)

        self.assertEqual(1 + e1, e1 + float64(1))

    def test_array_control(self):
        '''
        test methods to take control addition from numpy arrays
        '''
        layout, blades = self.algebras[0]
        e1 = blades['e1']
        e3 = blades['e3']
        e12 = blades['e12']

        for i in range(100):

            number_array = np.random.rand(4)

            output = e12+(e1*number_array)
            output2 = MVArray([e12+(e1*n) for n in number_array])
            np.testing.assert_almost_equal(output, output2)

            output = e12 + (e1 * number_array)
            output2 = MVArray([e12 + (e1 * n) for n in number_array])
            np.testing.assert_almost_equal(output, output2)

            output = (number_array*e1) + e12
            output2 = MVArray([(n*e1) + e12 for n in number_array])
            np.testing.assert_almost_equal(output, output2)

            output = number_array/ e12
            output2 = MVArray([n/ e12 for n in number_array])
            np.testing.assert_almost_equal(output, output2)

            output = (e1 / number_array)
            output2 = MVArray([(e1/n) for n in number_array])
            np.testing.assert_almost_equal(output, output2)

            output = ((e1 / number_array)*e3)/e12
            output2 = MVArray([((e1 / n)*e3)/e12 for n in number_array])
            np.testing.assert_almost_equal(output, output2)

    def test_array_overload(self):
        '''
        test overload operations
        '''
        for alg in self.algebras:
            layout, blades = alg
            test_array = MVArray([layout.randomMV() for i in range(100)])

            normed_array = test_array.normal()
            other_array = np.array([t.normal().value for t in test_array])
            np.testing.assert_almost_equal(normed_array.value, other_array)

            dual_array = test_array.dual()
            other_array_2 = np.array([t.dual().value for t in test_array])
            np.testing.assert_almost_equal(dual_array.value, other_array_2)



class BasicConformal41Tests(unittest.TestCase):
    def test_metric(self):
        layout = Cl(4, 1)[0]
        e1 = layout.blades['e1']
        e2 = layout.blades['e2']
        e3 = layout.blades['e3']
        e4 = layout.blades['e4']
        e5 = layout.blades['e5']

        self.assertAlmostEqual((e1 * e1)[0], 1)
        self.assertAlmostEqual((e2 * e2)[0], 1)
        self.assertAlmostEqual((e3 * e3)[0], 1)
        self.assertAlmostEqual((e4 * e4)[0], 1)
        self.assertAlmostEqual((e5 * e5)[0], -1)


    def test_factorise(self):
        layout_a = Cl(3)[0]
        layout,blades,stuff = conformalize(layout_a)
        e1 = layout.blades['e1']
        e2 = layout.blades['e2']
        e3 = layout.blades['e3']
        e4 = layout.blades['e4']
        e5 = layout.blades['e5']

        up = stuff['up']

        blade = up(e1 + 3*e2 + 4*e3)^up(5*e1 + 3.3*e2 + 10*e3)^up(-13.1*e1)

        basis, scale = blade.factorise()
        new_blade = (reduce(lambda a, b: a^b, basis)*scale)
        print(new_blade)
        print(blade)
        np.testing.assert_almost_equal(new_blade.value, blade.value, 5)


    def test_gp_op_ip(self):
        layout = Cl(4, 1)[0]
        e1 = layout.blades['e1']
        e2 = layout.blades['e2']
        e3 = layout.blades['e3']
        e4 = layout.blades['e4']
        e5 = layout.blades['e5']

        e123 = layout.blades['e123']
        np.testing.assert_almost_equal(e123.value, (e1 ^ e2 ^ e3).value)
        np.testing.assert_almost_equal(e123.value, (e1 * e2 * e3).value)

        e12345 = layout.blades['e12345']
        np.testing.assert_almost_equal(e12345.value, (e1 ^ e2 ^ e3 ^ e4 ^ e5).value)
        np.testing.assert_almost_equal(e12345.value, (e1 * e2 * e3 * e4 * e5).value)

        e12 = layout.blades['e12']
        np.testing.assert_almost_equal(-e12.value, (e2 ^ e1).value)

        t = np.zeros(32)
        t[0] = -1
        np.testing.assert_almost_equal(t, (e12*e12).value)


class BasicSpaceTimeTests(unittest.TestCase):
    def test_initialise(self):

        # Dirac Algebra  `D`
        D, D_blades = Cl(1, 3, names='d', firstIdx=0)

        # Pauli Algebra  `P`
        P, P_blades = Cl(3, names='p')

        # put elements of each in namespace
        locals().update(D_blades)
        locals().update(P_blades)


class BasicAlgebraTests(unittest.TestCase):

    def test_gp_op_ip(self):
        layout = Cl(3)[0]
        e1 = layout.blades['e1']
        e2 = layout.blades['e2']
        e3 = layout.blades['e3']

        print('outer product')
        e123 = layout.blades['e123']
        np.testing.assert_almost_equal(e123.value, (e1 ^ e2 ^ e3).value)
        np.testing.assert_almost_equal(e123.value, (e1 * e2 * e3).value)

        print('outer product ordering')
        e12 = layout.blades['e12']
        np.testing.assert_almost_equal(-e12.value, (e2 ^ e1).value)

        print('outer product zeros')
        np.testing.assert_almost_equal(0, (e1 ^ e1).value)
        np.testing.assert_almost_equal(0, (e2 ^ e2).value)
        np.testing.assert_almost_equal(0, (e3 ^ e3).value)

        print('scalar outer product')
        np.testing.assert_almost_equal(((1 + 0 * e1) ^ (1 + 0 * e1)).value, (1 + 0 * e1).value)

        print('scalar inner product')
        np.testing.assert_almost_equal(((1 + 0 * e1) | (1 + 0 * e1)).value, 0)

    def test_grade_obj(self):
        algebras = [Cl(i) for i in [3, 4]] + [conformalize(Cl(3)[0])]
        for alg in algebras:
            layout = alg[0]
            for i in range(len(layout.sig)+1):
                mv = layout.randomMV()(i)
                assert i == grade_obj(mv)

    def test_left_multiplication_matrix(self):
        algebras = [Cl(i) for i in [3, 4]] + [conformalize(Cl(3)[0])]
        for alg in algebras:
            layout = alg[0]
            for i in range(1000):
                mv = layout.randomMV()
                mv2 = layout.randomMV()
                np.testing.assert_almost_equal(np.matmul(layout.get_left_gmt_matrix(mv),mv2.value), (mv*mv2).value)


    def test_right_multiplication_matrix(self):
        algebras = [Cl(i) for i in [3, 4]] + [conformalize(Cl(3)[0])]
        for alg in algebras:
            layout = alg[0]
            for i in range(1000):
                a = layout.randomMV()
                b = layout.randomMV()
                b_right = val_get_right_gmt_matrix(b.value, layout.k_list, layout.l_list, layout.m_list,
                                                   layout.mult_table_vals, layout.gaDims)
                res = a*b
                res2 = layout.MultiVector(value=b_right@a.value)
                testing.assert_almost_equal(res.value, res2.value)


class FrameTests(unittest.TestCase):

    def check_inv(self, A):
        Ainv= None
        for k in range(3):
            try:
                Ainv = A.inv
            except(ValueError):
                pass
        if Ainv ==None:
            return True        
        for m, a in enumerate(A):
            for n, b in enumerate(A.inv):
                if m == n:
                    assert(a | b == 1)
                else:
                    assert(a | b == 0)

    def test_frame_inv(self):
        for p, q in [(2, 0), (3, 0), (4, 0)]:
            layout, blades = Cl(p, q)
            A = Frame(layout.randomV(p + q))
            self.check_inv(A)

    def test_innermorphic(self):
        for p, q in [(2, 0), (3, 0), (4, 0)]:
            layout, blades = Cl(p, q)

            A = Frame(layout.randomV(p+q))
            R = layout.randomRotor()
            B = Frame([R*a*~R for a in A])
            self.assertTrue(A.is_innermorphic_to(B))



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