linearmap-category-0.6.0.0: Math/VectorSpace/DimensionAware.hs
-- |
-- Module : Math.VectorSpace.DimensionAware
-- Copyright : (c) Justus Sagemüller 2022
-- License : GPL v3
--
-- Maintainer : (@) jsag $ hvl.no
-- Stability : experimental
-- Portability : portable
--
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE UnicodeSyntax #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE CPP #-}
module Math.VectorSpace.DimensionAware where
import Data.VectorSpace
import Data.Singletons (SingI, sing, Sing)
#if MIN_VERSION_singletons(3,0,0)
import Prelude.Singletons (SNum(..))
import Data.Maybe.Singletons
import GHC.TypeLits.Singletons (withKnownNat)
#else
import Data.Singletons.Prelude.Num (SNum(..))
import Data.Singletons.Prelude.Maybe (SMaybe(..))
import Data.Singletons.TypeLits (withKnownNat)
#endif
import qualified Data.Vector.Generic as GArr
import qualified Data.Vector.Generic.Mutable as GMArr
import Control.Monad.ST (ST)
import Control.Monad
import GHC.TypeLits
import GHC.Exts (Constraint)
import Data.Proxy (Proxy(..))
import Data.Ratio
import qualified Math.VectorSpace.DimensionAware.Theorems.MaybeNat as Maybe
-- | Low-level case distinction between spaces with a dimension that is both fixed
-- and low enough that it makes sense to treat it this way, and more general
-- spaces where this is not feasible.
--
-- Use this type only when defining instances of 'DimensionAware'. When making
-- decisions based on dimensionality, 'DimensionalityCases' is more convenient.
data DimensionalityWitness v where
IsStaticDimensional :: (n`Dimensional`v) => DimensionalityWitness v
IsFlexibleDimensional :: StaticDimension v ~ 'Nothing => DimensionalityWitness v
-- | This class does not really pose any restrictions on a vector space type, but
-- allows it to express its dimension.
-- This is for optimisation purposes only, specifically to allow low-dimensional vectors
-- to be represented efficiently in unboxed arrays / matrices.
class VectorSpace v => DimensionAware v where
-- | If this is `Nothing`,
-- it can mean the dimension is infinite, or just big, or simply unknown / not
-- considered in the implementation.
type StaticDimension v :: Maybe Nat
type StaticDimension v = 'Nothing
dimensionalityWitness :: DimensionalityWitness v
instance DimensionAware Float where
type StaticDimension Float = 'Just 1
dimensionalityWitness = IsStaticDimensional
instance DimensionAware Double where
type StaticDimension Double = 'Just 1
dimensionalityWitness = IsStaticDimensional
instance DimensionAware Int where
type StaticDimension Int = 'Just 1
dimensionalityWitness = IsStaticDimensional
instance DimensionAware Integer where
type StaticDimension Integer = 'Just 1
dimensionalityWitness = IsStaticDimensional
instance Integral n => DimensionAware (Ratio n) where
type StaticDimension (Ratio n) = 'Just 1
dimensionalityWitness = IsStaticDimensional
instance ∀ u v . (DimensionAware u, DimensionAware v, Scalar u ~ Scalar v)
=> DimensionAware (u,v) where
type StaticDimension (u,v) = Maybe.ZipWithPlus (StaticDimension u) (StaticDimension v)
dimensionalityWitness = case (dimensionalityWitness @u, dimensionalityWitness @v) of
(IsStaticDimensional, IsStaticDimensional)
-> withKnownNat (dimensionalitySing @u %+ dimensionalitySing @v)
IsStaticDimensional
(IsFlexibleDimensional, _) -> IsFlexibleDimensional
(_, IsFlexibleDimensional) -> IsFlexibleDimensional
class (DimensionAware v, StaticDimension v ~ 'Just n)
=> n`Dimensional`v | v -> n where
knownDimensionalitySing :: Sing n
{-# INLINE knownDimensionalitySing #-}
default knownDimensionalitySing :: KnownNat n => Sing n
knownDimensionalitySing = sing
-- | Read basis expansion from an array, starting at the specified offset.
-- The array must have at least length @n + offset@, else the behaviour is undefined.
unsafeFromArrayWithOffset :: GArr.Vector α (Scalar v) => Int -> α (Scalar v) -> v
unsafeWriteArrayWithOffset :: GArr.Vector α (Scalar v)
=> GArr.Mutable α σ (Scalar v) -> Int -> v -> ST σ ()
-- | Batteries-included version of 'DimensionalityWitness'.
data DimensionalityCases v where
StaticDimensionalCase :: (KnownNat n, n`Dimensional`v) => DimensionalityCases v
FlexibleDimensionalCase :: StaticDimension v ~ 'Nothing => DimensionalityCases v
#if !MIN_VERSION_singletons(3,0,0)
type family FromJust (a :: Maybe k) :: k where
FromJust ('Just v) = v
#endif
type Dimension v = FromJust (StaticDimension v)
#if !MIN_VERSION_singletons(3,0,0)
type family IsJust (a :: Maybe k) :: Bool where
IsJust ('Just _) = 'True
IsJust _ = 'False
#endif
class DimensionAware v => StaticDimensional v where
dimensionIsStatic :: ∀ r . (∀ n . (KnownNat n, n`Dimensional`v) => r) -> r
{-# INLINE dimensionalitySing #-}
dimensionalitySing :: ∀ v n . n`Dimensional`v => Sing n
dimensionalitySing = knownDimensionalitySing @n @v
instance ( DimensionAware v, IsJust (StaticDimension v) ~ 'True )
=> StaticDimensional v where
dimensionIsStatic = case dimensionalityWitness @v of
IsStaticDimensional -> \φ -> withKnownNat (dimensionalitySing @v) φ
dimensionality :: ∀ v . DimensionAware v => DimensionalityCases v
dimensionality = case dimensionalityWitness @v of
IsStaticDimensional -> withKnownNat (dimensionalitySing @v) StaticDimensionalCase
IsFlexibleDimensional -> FlexibleDimensionalCase
{-# INLINE dimension #-}
dimension :: ∀ v n a . (n`Dimensional`v, Integral a) => a
dimension = withKnownNat (dimensionalitySing @v) (fromIntegral $ natVal @n Proxy)
-- | Convenience function. The result does never depend on the runtime input, only
-- on its type.
dimensionOf :: ∀ v n a . (n`Dimensional`v, Integral a) => v -> a
dimensionOf _ = dimension @v
{-# INLINE unsafeFromArray #-}
-- | Read basis expansion from an array. The array must have length @n@, else the
-- behaviour is undefined.
unsafeFromArray :: ∀ v n α . (n`Dimensional`v, GArr.Vector α (Scalar v))
=> α (Scalar v) -> v
unsafeFromArray = unsafeFromArrayWithOffset 0
-- | Read basis expansion from an array, if the size equals the dimension.
fromArray :: ∀ v n α . (n`Dimensional`v, GArr.Vector α (Scalar v))
=> α (Scalar v) -> Maybe v
fromArray ar
| GArr.length ar == dimension @v = Just $ unsafeFromArray ar
| otherwise = Nothing
{-# INLINE toArray #-}
-- | Write out basis expansion to an array, whose length will always be @n@.
toArray :: ∀ v n α . (n`Dimensional`v, GArr.Vector α (Scalar v))
=> v -> α (Scalar v)
toArray v = GArr.create (do
ar <- GMArr.new $ dimension @v
unsafeWriteArrayWithOffset ar 0 v
return ar
)
{-# INLINE staticDimensionSing #-}
staticDimensionSing :: ∀ v . DimensionAware v => Sing (StaticDimension v)
staticDimensionSing = case dimensionalityWitness @v of
IsStaticDimensional -> SJust (dimensionalitySing @v)
IsFlexibleDimensional -> sing
{-# INLINE scalarUnsafeFromArrayWithOffset #-}
scalarUnsafeFromArrayWithOffset :: (v ~ Scalar v, GArr.Vector α v)
=> Int -> α v -> v
scalarUnsafeFromArrayWithOffset i = (`GArr.unsafeIndex`i)
{-# INLINE scalarUnsafeWriteArrayWithOffset #-}
scalarUnsafeWriteArrayWithOffset :: (v ~ Scalar v, GArr.Vector α v)
=> GArr.Mutable α σ v -> Int -> v -> ST σ ()
scalarUnsafeWriteArrayWithOffset ar i = GMArr.unsafeWrite ar i
{-# INLINE unsafeFromArrayWithOffsetViaList #-}
unsafeFromArrayWithOffsetViaList
:: ∀ v n α . (n`Dimensional`v, GArr.Vector α (Scalar v))
=> ([Scalar v] -> v) -> Int -> α (Scalar v) -> v
unsafeFromArrayWithOffsetViaList l2v i
= l2v . GArr.toList . GArr.unsafeSlice i (dimension @v)
{-# INLINE unsafeWriteArrayWithOffsetViaList #-}
unsafeWriteArrayWithOffsetViaList
:: ∀ v n α σ . (n`Dimensional`v, GArr.Vector α (Scalar v))
=> (v -> [Scalar v]) -> GArr.Mutable α σ (Scalar v)
-> Int -> v -> ST σ ()
unsafeWriteArrayWithOffsetViaList v2l ar i
= GMArr.unsafeCopy (GMArr.unsafeSlice i (dimension @v) ar)
<=< GArr.unsafeThaw @(ST σ) @α . GArr.fromList . v2l
instance 1`Dimensional`Float where
{-# INLINE unsafeFromArrayWithOffset #-}
unsafeFromArrayWithOffset = scalarUnsafeFromArrayWithOffset
{-# INLINE unsafeWriteArrayWithOffset #-}
unsafeWriteArrayWithOffset = scalarUnsafeWriteArrayWithOffset
instance 1`Dimensional`Double where
{-# INLINE unsafeFromArrayWithOffset #-}
unsafeFromArrayWithOffset = scalarUnsafeFromArrayWithOffset
{-# INLINE unsafeWriteArrayWithOffset #-}
unsafeWriteArrayWithOffset = scalarUnsafeWriteArrayWithOffset
instance 1`Dimensional`Int where
{-# INLINE unsafeFromArrayWithOffset #-}
unsafeFromArrayWithOffset = scalarUnsafeFromArrayWithOffset
{-# INLINE unsafeWriteArrayWithOffset #-}
unsafeWriteArrayWithOffset = scalarUnsafeWriteArrayWithOffset
instance 1`Dimensional`Integer where
{-# INLINE unsafeFromArrayWithOffset #-}
unsafeFromArrayWithOffset = scalarUnsafeFromArrayWithOffset
{-# INLINE unsafeWriteArrayWithOffset #-}
unsafeWriteArrayWithOffset = scalarUnsafeWriteArrayWithOffset
instance Integral n => 1`Dimensional`Ratio n where
{-# INLINE unsafeFromArrayWithOffset #-}
unsafeFromArrayWithOffset = scalarUnsafeFromArrayWithOffset
{-# INLINE unsafeWriteArrayWithOffset #-}
unsafeWriteArrayWithOffset = scalarUnsafeWriteArrayWithOffset
instance ∀ n u m v nm . ( n`Dimensional`u, m`Dimensional`v
, Scalar u ~ Scalar v
, nm ~ (n+m) )
=> nm`Dimensional`(u,v) where
{-# INLINE knownDimensionalitySing #-}
knownDimensionalitySing = dimensionalitySing @u %+ dimensionalitySing @v
{-# INLINE unsafeFromArrayWithOffset #-}
unsafeFromArrayWithOffset i arr
= ( unsafeFromArrayWithOffset i arr
, unsafeFromArrayWithOffset (i + dimension @u) arr )
{-# INLINE unsafeWriteArrayWithOffset #-}
unsafeWriteArrayWithOffset arr i (x,y) = do
unsafeWriteArrayWithOffset arr i x
unsafeWriteArrayWithOffset arr (i + dimension @u) y
notStaticDimensionalContradiction :: ∀ v n r
. (n`Dimensional`v, StaticDimension v ~ 'Nothing) => r
notStaticDimensionalContradiction = undefined