vector-space-0.2.0: src/Data/LinearMap.hs
{-# LANGUAGE TypeOperators, TypeFamilies, UndecidableInstances
, MultiParamTypeClasses, FlexibleInstances
, FunctionalDependencies
#-}
{-# OPTIONS_GHC -Wall -fno-warn-orphans -fglasgow-exts #-}
----------------------------------------------------------------------
-- |
-- Module : Data.LinearMap
-- Copyright : (c) Conal Elliott 2008
-- License : BSD3
--
-- Maintainer : conal@conal.net
-- Stability : experimental
--
-- Linear maps
----------------------------------------------------------------------
module Data.LinearMap
(
LMapDom(..), inL, inL2, inL3
, linearK, (.*)
, pureL --, (<*>*)
, fmapL, (<$>*), liftL2, liftL3, idL
) where
-- -fglasgow-exts above enables the RULES pragma
import Control.Applicative
import Data.Function
import Data.VectorSpace
-- Temporary
import Graphics.Rendering.OpenGL.GL.CoordTrans
(Vector2(..),Vector3(..))
infixr 9 :-*
infixr 9 .*
infixl 9 `lapply`
infixl 4 <$>* -- , <*>*
-- | Domain of a linear map.
class VectorSpace a s => LMapDom a s | a -> s where
-- | Linear map type
data (:-*) a :: * -> *
-- | Linear map as function
lapply :: VectorSpace b s => (a :-* b) -> (a -> b)
-- | Function (assumed linear) as linear map.
linear :: (a -> b) -> (a :-* b)
-- Neither 'VectorSpace' nor even 'AdditiveGroup' is really required as a
-- 'LMapDom' superclass. Instead, we could have additional constraints in
-- some 'LMapDom' instances and related functions.
{-# RULES
"linear.lapply" forall m. linear (lapply m) = m
"lapply.linear" forall f. lapply (linear f) = f
#-}
-- | Transform a linear map by transforming a linear function.
inL :: (LMapDom c s, VectorSpace b s', LMapDom a s') =>
((a -> b) -> (c -> d)) -> ((a :-* b) -> (c :-* d))
{-# INLINE inL #-}
inL h = linear . h . lapply
-- | Transform a linear maps by transforming linear functions.
inL2 :: ( LMapDom c s, VectorSpace b s', LMapDom a s'
, LMapDom e s, VectorSpace d s ) =>
((a -> b) -> (c -> d) -> (e -> f))
-> ((a :-* b) -> (c :-* d) -> (e :-* f))
{-# INLINE inL2 #-}
inL2 h = inL . h . lapply
-- inL2 h m n
-- = (inL . h . lapply) m n
-- = inL (h (lapply m)) n
-- = (linear . (h (lapply m)) . lapply) n
-- = linear (h (lapply m) (lapply n)
-- = linear (h (lapply m) (lapply n))
-- | Transform a linear maps by transforming linear functions.
inL3 :: ( LMapDom a s, VectorSpace b s
, VectorSpace f s , LMapDom p s, LMapDom c s'
, VectorSpace d s', LMapDom e s ) =>
((a -> b) -> (c -> d) -> (e -> f) -> (p -> q))
-> ((a :-* b) -> (c :-* d) -> (e :-* f) -> (p :-* q))
{-# INLINE inL3 #-}
inL3 h = inL2 . h . lapply
-- TODO: go through this whole module and relax constraints on scalar
-- fields. See if it helps with the scalar dilemma in Mac2
-- | Constant value as a linear map
pureL :: LMapDom a s => b -> (a :-* b)
pureL b = linear (const b)
-- -- | Like '(<*>)' for linear maps.
-- (<*>*) :: (LMapDom a s, VectorSpace b s, VectorSpace c s) =>
-- (a :-* (b -> c)) -> (a :-* b) -> (a :-* c)
-- (<*>*) = inL2 (<*>)
-- | Map a /linear/ function over a linear map.
fmapL, (<$>*) :: (LMapDom a s, VectorSpace b s) =>
(b -> c) -> (a :-* b) -> (a :-* c)
{-# INLINE fmapL #-}
fmapL = inL . fmap
-- fmapL f
-- = inL (f .)
-- = linear . (f .) . lapply
-- = \ m -> linear (fmap f (lapply m))
(<$>*) = fmapL
{-# RULES
"fmapL.fmapL" forall f g m. fmapL f (fmapL g m) = fmapL (f.g) m
#-}
-- | Apply a /linear/ binary function over linear maps.
liftL2 :: ( LMapDom a s, VectorSpace b s
, VectorSpace c s, VectorSpace d s) =>
(b -> c -> d) -> (a :-* b) -> (a :-* c) -> (a :-* d)
liftL2 = inL2 . liftA2
-- liftL2 f a b
-- = inL2 (liftA2 f) a b
-- = linear (liftA2 f (lapply a) (lapply b))
-- I expected the following definition to be equivalent, thanks to rewriting:
--
-- liftL2 f b c = fmapL f b <*>* c
-- = linear (f . lapply b) <*>* c
-- = linear (lapply (linear (f . lapply b)) <*> lapply c)
-- = linear ((f . lapply b) <*> lapply c)
-- = linear (liftA2 f (lapply b) (lapply c))
-- = (inL2.liftA2) f b c
-- The rewrite isn't happening, however. And the '(<*>*)' definition
-- yields an incredibly slow implementation.
--
-- Here's that derivation again, in slo-mo:
-- liftL2 f b c
-- = fmapL f b <*>* c -- inline liftL2
-- = inL (f .) b <*>* c -- inline fmapL
-- = (linear . (f .) lapply) b <*>* c -- inline inL
-- = linear (f . lapply b) <*>* c -- inline (.)
-- = inL2 (<*>) (linear (f . lapply b)) c -- inline (<*>*)
-- = (inL . (<*>) . lapply) (linear (f . lapply b)) c -- inline inL2
-- = inL ((<*>) (lapply (linear (f . lapply b)))) c -- inline (.)
-- = inL ((<*>) (f . lapply b)) c -- RULE lapply.linear
-- = (linear . ((<*>) (f . lapply b)) .lapply) c -- inline inL
-- = linear ((<*>) (f . lapply b) (lapply c)) -- inline (.)
-- = linear ((f . lapply b) <*> (lapply c)) -- infix <*>
-- = linear ((f <$> lapply b) <*> (lapply c)) -- <$> on (a ->)
-- = linear (liftA2 f (lapply b) (lapply c)) -- uninline liftA2
-- When I compile this module, I don't get any firings of lapply.linear
-- | Apply a /linear/ ternary function over linear maps.
liftL3 :: ( LMapDom a s, VectorSpace b s, VectorSpace c s
, VectorSpace d s, VectorSpace e s) =>
(b -> c -> d -> e)
-> (a :-* b) -> (a :-* c) -> (a :-* d) -> (a :-* e)
liftL3 = inL3 . liftA3
-- TODO: Get clear about the linearity requirements of 'apL', 'liftL2',
-- and 'liftL3'.
-- | Identity linear map
idL :: LMapDom a s => a :-* a
idL = linear id
-- | Compose linear maps
(.*) :: (VectorSpace c s, LMapDom b s, LMapDom a s) =>
(b :-* c) -> (a :-* b) -> (a :-* c)
(.*) = inL2 (.)
instance (VectorSpace v s, LMapDom u s) => AdditiveGroup (u :-* v) where
zeroV = pureL zeroV
(^+^) = liftL2 (^+^)
negateV = fmapL negateV
instance (VectorSpace v s, LMapDom u s) => VectorSpace (u :-* v) s where
(*^) s = fmapL ((*^) s)
-- Or possibly the following non-standard definition:
-- instance (VectorSpace v s, VectorSpace s s, LMapDom u s)
-- => VectorSpace (u :-* v) (u :-* s) where
-- zeroV = pureL zeroV
-- (*^) = liftL2 (*^)
-- (^+^) = liftL2 (^+^)
-- negateV = fmapL negateV
-- Alternatively, add some methods to 'LMapDom' and use them as follows.
-- May be more efficient.
-- -- Linear maps form a vector space
-- instance (VectorSpace o s, LinearMap a o s) => VectorSpace (a :--> o) s where
-- zeroV = zeroL
-- (^+^) = addL
-- (*^) = scaleL
-- negateV = negateL
--- Instances of LMapDom
instance LMapDom Float Float where
data Float :-* o = FloatL o
lapply (FloatL o) = (*^ o)
linear f = FloatL (f 1)
instance LMapDom Double Double where
data Double :-* o = DoubleL o
lapply (DoubleL o) = (*^ o)
linear f = DoubleL (f 1)
-- | Convenience function for 'linear' definitions. Both functions are
-- assumed linear.
linearK :: (LMapDom a s) => (a -> b) -> (b -> c) -> a :-* c
linearK k f = linear (f . k)
-- instance LMapDom (Double,Double) Double where
-- data (Double,Double) :-* o = PairD o o
-- PairD ao bo `lapply` (a,b) = a *^ ao ^+^ b *^ bo
-- linear f = PairD (f (0,1)) (f (1,0))
instance (LMapDom a s, LMapDom b s) => LMapDom (a,b) s where
data (a,b) :-* o = PairL (a :-* o) (b :-* o)
PairL ao bo `lapply` (a,b) = ao `lapply` a ^+^ bo `lapply` b
linear f = PairL (linear (\ a -> f (a,zeroV)))
(linear (\ b -> f (zeroV,b)))
-- linear = liftA2 PairL
-- (linearK (\ a -> (a,zeroV)))
-- (linearK (\ b -> (zeroV,b)))
instance (LMapDom a s, LMapDom b s, LMapDom c s) => LMapDom (a,b,c) s where
data (a,b,c) :-* o = TripleL (a :-* o) (b :-* o) (c :-* o)
TripleL ao bo co `lapply` (a,b,c) =
ao `lapply` a ^+^ bo `lapply` b ^+^ co `lapply` c
linear = liftA3 TripleL
(linearK (\ a -> (a,zeroV,zeroV)))
(linearK (\ b -> (zeroV,b,zeroV)))
(linearK (\ c -> (zeroV,zeroV,c)))
-- TODO: unfst, unsnd, pair, unpair
---- OpenGL stuff.
-- I'd rather this code be in a different package. It's here as a
-- temporary bug workaround. In ghc-6.8.2, I get the following error
-- message if the 'LMapDom' instance (below) is compiled in a separate
-- module.
--
-- Type indexes must match class instance head
-- Found o but expected Vector2 u
-- In the associated type instance for `:-*'
-- In the instance declaration for `LMapDom (Vector2 u) s'
-- TODO: is UndecidableInstances still necessary?
instance AdditiveGroup u => AdditiveGroup (Vector2 u) where
zeroV = Vector2 zeroV zeroV
Vector2 u v ^+^ Vector2 u' v' = Vector2 (u^+^u') (v^+^v')
negateV (Vector2 u v) = Vector2 (negateV u) (negateV v)
instance (VectorSpace u s) => VectorSpace (Vector2 u) s where
s *^ Vector2 u v = Vector2 (s*^u) (s*^v)
instance (InnerSpace u s, AdditiveGroup s)
=> InnerSpace (Vector2 u) s where
Vector2 u v <.> Vector2 u' v' = u<.>u' ^+^ v<.>v'
instance LMapDom u s => LMapDom (Vector2 u) s where
data Vector2 u :-* o = VecL (u :-* o) (u :-* o)
VecL ao bo `lapply` Vector2 a b = ao `lapply` a ^+^ bo `lapply` b
linear = liftA2 VecL
(linearK (\ a -> Vector2 a zeroV))
(linearK (\ b -> Vector2 zeroV b))
instance AdditiveGroup u => AdditiveGroup (Vector3 u) where
zeroV = Vector3 zeroV zeroV zeroV
Vector3 u v w ^+^ Vector3 u' v' w'
= Vector3 (u^+^u') (v^+^v') (w^+^w')
negateV (Vector3 u v w) = Vector3 (negateV u) (negateV v) (negateV w)
instance VectorSpace u s => VectorSpace (Vector3 u) s where
s *^ Vector3 u v w = Vector3 (s*^u) (s*^v) (s*^w)
instance (InnerSpace u s, AdditiveGroup s)
=> InnerSpace (Vector3 u) s where
Vector3 u v w <.> Vector3 u' v' w' = u<.>u' ^+^ v<.>v' ^+^ w<.>w'