{-# OPTIONS -fno-implicit-prelude -fglasgow-exts #-}
{- |
Maintainer  :  numericprelude@henning-thielemann.de
Stability   :  provisional
Portability :  requires multi-parameter type classes

Abstraction of bases of finite dimensional modules
-}

module Algebra.ModuleBasis where

import Number.Ratio (Rational)

import qualified Algebra.Module   as Module
import qualified Algebra.Additive as Additive
import Algebra.Ring     (one, fromInteger)
import Algebra.Additive ((+), zero)

import Data.List (map, length, (++))

import Prelude(Eq, (==), Bool, Int, Integer, Float, Double, asTypeOf, )
-- import qualified Prelude as P

{- |
It must hold:

>   Module.linearComb (flatten v `asTypeOf` [a]) (basis a) == v
>   dimension a v == length (flatten v `asTypeOf` [a])
-}
class (Module.C a v) => C a v where
{- | basis of the module with respect to the scalar type,
the result must be independent of argument, 'Prelude.undefined' should suffice. -}
basis :: a -> [v]
-- | scale a vector by a scalar
flatten :: v -> [a]
{- | the size of the basis, should also work for undefined argument,
the result must be independent of argument, 'Prelude.undefined' should suffice. -}
dimension :: a -> v -> Int

{-* Instances for atomic types -}

instance C Float Float where
basis _ = [one]
flatten = (:[])
dimension _ _ = 1

instance C Double Double where
basis _ = [one]
flatten = (:[])
dimension _ _ = 1

instance C Int Int where
basis _ = [one]
flatten = (:[])
dimension _ _ = 1

instance C Integer Integer where
basis _ = [one]
flatten = (:[])
dimension _ _ = 1

instance C Rational Rational where
basis _ = [one]
flatten = (:[])
dimension _ _ = 1

{-* Instances for composed types -}

instance (C a v0, C a v1) => C a (v0, v1) where
basis s = map (\v -> (v,zero)) (basis s) ++
map (\v -> (zero,v)) (basis s)
flatten (x0,x1) = flatten x0 ++ flatten x1
dimension s ~(x0,x1) = dimension s x0 + dimension s x1

instance (C a v0, C a v1, C a v2) => C a (v0, v1, v2) where
basis s = map (\v -> (v,zero,zero)) (basis s) ++
map (\v -> (zero,v,zero)) (basis s) ++
map (\v -> (zero,zero,v)) (basis s)
flatten (x0,x1,x2) = flatten x0 ++ flatten x1 ++ flatten x2
dimension s ~(x0,x1,x2) = dimension s x0 + dimension s x1 + dimension s x2

{- * Properties -}

propFlatten :: (Eq v, C a v) => a -> v -> Bool
propFlatten a v  =  Module.linearComb (flatten v `asTypeOf` [a]) (basis a) == v

propDimension :: (C a v) => a -> v -> Bool
propDimension a v  =  dimension a v == length (flatten v `asTypeOf` [a])