packages feed

brush-strokes-0.1.0.0: src/lib/Math/Linear.hs

{-# LANGUAGE AllowAmbiguousTypes  #-}
{-# LANGUAGE PolyKinds            #-}
{-# LANGUAGE ScopedTypeVariables  #-}
{-# LANGUAGE UndecidableInstances #-}

module Math.Linear
  ( -- * Points and vectors
    Segment(..), Mat22(..)

  -- * Points and vectors (second version)
  , ℝ(..), T(.., V2, V3, V4)
  , Fin(..), MFin(..)
  , universe, coordinates, choose
  , RepDim, RepresentableQ(..)
  , Representable(..), set, injection, projection
  , Vec(..), (!), find, zipIndices

  , swap, rotate

  ) where

-- base
import Prelude hiding ( unzip )
import Control.Applicative
  ( ZipList(..) )
import Data.Coerce
  ( coerce )
import Data.Kind
  ( Type )
import GHC.Exts
  ( proxy# )
import GHC.Generics
  ( Generic, Generic1, Generically(..), Generically1(..) )
import GHC.Stack
  ( HasCallStack )
import GHC.TypeNats
  ( Nat, KnownNat, type (<=)
  , natVal'
  )

-- acts
import Data.Act
  ( Act((•)), Torsor((-->)) )

-- deepseq
import Control.DeepSeq
  ( NFData(..), NFData1 )

-- groups
import Data.Group
  ( Group(..) )

-- groups-generic
import Data.Group.Generics
  ( )

-- brush-strokes
import Math.Linear.Internal
import Math.Ring
  ( Ring )
import qualified Math.Ring as Ring

--------------------------------------------------------------------------------

data Mat22 = Mat22 !Double !Double !Double !Double
data Segment p =
  Segment
    { segmentStart :: !p
    , segmentEnd   :: !p
    }
  deriving stock ( Generic, Generic1, Functor, Foldable, Traversable )
  deriving ( Semigroup, Monoid, Group )
    via Generically ( Segment p )
  deriving Applicative
    via Generically1 Segment
  deriving anyclass ( NFData, NFData1 )

instance Show p => Show ( Segment p ) where
  show ( Segment s e ) = show s ++ " -> " ++ show e

--------------------------------------------------------------------------------

-- | Tangent space to Euclidean space.
type T :: Type -> Type
newtype T e = T { unT :: e }
  deriving stock ( Eq, Functor, Foldable, Traversable )
  deriving newtype NFData

instance Semigroup ( T Double ) where
  (<>) = coerce ( (+) @Double )
instance Monoid ( T Double ) where
  mempty = T 0

instance Group ( T Double ) where
  invert ( T x ) = T ( negate x )

instance Act ( T Double ) Double where
  T u • v = u + v
instance Torsor ( T Double ) Double where
  a --> b = T ( b - a )

instance {-# OVERLAPPING #-} Show ( ℝ n ) => Show ( T ( ℝ n ) ) where
  show ( T p ) = "V" ++ drop 1 ( show p )
instance {-# INCOHERENT #-} Show v => Show ( T v ) where
  showsPrec p ( T v )
    = showParen ( p >= 11 )
    $ showString "T"
    . showsPrec 0 v

instance Applicative T where
  pure = T
  T f <*> T a = T ( f a )

{-# COMPLETE V2 #-}
pattern V2 :: Double -> Double -> T ( ℝ 2 )
pattern V2 x y = T ( ℝ2 x y )

{-# COMPLETE V3 #-}
pattern V3 :: Double -> Double -> Double -> T ( ℝ 3 )
pattern V3 x y z = T ( ℝ3 x y z )

{-# COMPLETE V4 #-}
pattern V4 :: Double -> Double -> Double -> Double -> T ( ℝ 4 )
pattern V4 x y z w = T ( ℝ4 x y z w )

--------------------------------------------------------------------------------

type Vec :: Nat -> Type -> Type
newtype Vec n a = Vec { vecList :: [ a ] }
type role Vec nominal representational

deriving newtype instance Show a => Show ( Vec n a )
deriving newtype instance   Eq a =>   Eq ( Vec n a )
deriving newtype instance  Ord a =>  Ord ( Vec n a )
deriving newtype instance  Functor ( Vec n )
deriving newtype instance Foldable ( Vec n )
deriving via ZipList
  instance Applicative ( Vec n )
instance Traversable ( Vec n ) where
  traverse f ( Vec as ) = Vec <$> traverse f as
instance NFData a => NFData ( Vec n a ) where
  rnf ( Vec as ) = rnf as

universe :: forall n. KnownNat n => Vec n ( Fin n )
universe = Vec [ Fin i | i <- [ 1 .. fromIntegral ( natVal' @n proxy# ) ] ]
{-# INLINEABLE universe #-}

coordinates :: forall r u. ( Representable r u ) => u -> Vec ( RepDim u ) r
coordinates u = fmap ( index u ) $ universe @( RepDim u )
{-# INLINEABLE coordinates #-}

-- | Binomial coefficient: choose all subsets of size @k@ of the given set
-- of size @n@.
choose
  :: forall n k
   . ( KnownNat n, KnownNat k
     , 1 <= n, 1 <= k, k <= n
     )
  => [ Vec k ( Fin n ) ]
choose = coerce $ go ( fromIntegral $ natVal' @n proxy# )
                     ( fromIntegral $ natVal' @k proxy# )
  where
    go :: Word -> Word -> [ [ Word ] ]
    go n k
      | k == 1
      = [ [ i ] | i <- [ 1 .. n ] ]
      | n == k
      = [ [ 1 .. n ] ]
    go n k = go ( n - 1 ) k
          ++ ( map ( ++ [ n ] ) $ go ( n - 1 ) ( k - 1 ) )
{-# INLINEABLE choose #-}

infixl 9 !
(!) :: forall l a. HasCallStack => Vec l a -> Fin l -> a
( Vec l ) ! Fin i = l !! ( fromIntegral i - 1 )

find :: forall l a. ( a -> Bool ) -> Vec l a -> MFin l
find f ( Vec v ) = MFin ( find_ 1 v )
  where
    find_ :: Word -> [ a ] -> Word
    find_ j ( a : as )
      | f a
      = j
      | otherwise
      = find_ ( j + 1 ) as
    find_ _ [] = 0

zipIndices :: forall n a. Vec n a -> [ ( Fin n, a ) ]
zipIndices ( Vec v ) = zipIndices_ 1 v
  where
    zipIndices_ ::  Word -> [ a ] -> [ ( Fin n, a ) ]
    zipIndices_ _ [] = []
    zipIndices_ i (a : as) = ( Fin i, a ) : zipIndices_ ( i + 1 ) as

--------------------------------------------------------------------------------

-- | Rotate a vector by the given angle (counter-clockwise),
-- given the cosine and sine of the angle (in that order).
rotate :: ( Representable r m, RepDim m ~ 2, Ring r )
       => r -- \( \cos \theta \)
       -> r -- \( \sin \theta \)
       -> T m
       -> T m
rotate cosθ sinθ ( T xy ) =
  let x = xy `index` Fin 1
      y = xy `index` Fin 2
  in  T $ tabulate \ case
        Fin 1 -> x Ring.* cosθ Ring.- y Ring.* sinθ
        _     -> y Ring.* cosθ Ring.+ x Ring.* sinθ
{-# INLINEABLE rotate #-}

-- | Swap the two coordinates of a 2D vector.
swap :: ( Representable r m, RepDim m ~ 2, Ring r )
       => T m
       -> T m
swap ( T xy ) =
  let x = xy `index` Fin 1
      y = xy `index` Fin 2
  in  T $ tabulate \ case
        Fin 1 -> y
        _     -> x
{-# INLINEABLE swap #-}