packages feed

linear-massiv-0.1.0.0: src/Numeric/LinearAlgebra/Massiv/Types.hs

{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE AllowAmbiguousTypes #-}

-- |
-- Module      : Numeric.LinearAlgebra.Massiv.Types
-- Copyright   : (c) Nadia Chambers 2026
-- License     : BSD-3-Clause
-- Maintainer  : nadia.chambers@iohk.io
-- Stability   : experimental
--
-- Core type definitions for type-safe dimensioned matrices and vectors
-- backed by <https://hackage.haskell.org/package/massiv massiv> arrays.
--
-- The central types are 'Matrix' and 'Vector', which wrap massiv arrays
-- with phantom type-level natural number parameters encoding their dimensions.
-- This ensures that dimensionally incorrect operations (e.g., adding matrices
-- of different sizes, or multiplying matrices with incompatible inner dimensions)
-- are caught at compile time by GHC's type checker.
--
-- = Type-level dimension encoding
--
-- Dimensions are encoded as GHC @DataKinds@ promoted @'GHC.TypeNats.Nat'@
-- values. The constraint @'GHC.TypeNats.KnownNat' n@ provides access to the
-- runtime value via @'GHC.TypeNats.natVal'@.
--
-- @
-- -- A 3x4 matrix of Doubles using Primitive representation
-- type MyMatrix = Matrix 3 4 P Double
--
-- -- A 5-element vector of Doubles using Unboxed representation
-- type MyVector = Vector 5 U Double
-- @
--
-- = Existential wrappers
--
-- For situations where dimensions are not known until runtime (e.g., reading
-- a matrix from a file), use 'SomeMatrix' and 'SomeVector'. These existentially
-- quantify the dimension parameters while retaining 'KnownNat' evidence.
--
-- See "Numeric.LinearAlgebra.Massiv.Internal" for construction helpers.
module Numeric.LinearAlgebra.Massiv.Types
  ( -- * Dimensioned matrix type
    Matrix(..)
    -- * Dimensioned vector type
  , Vector(..)
    -- * Smart constructors
  , matrix
  , vector
    -- * Existential wrappers
  , SomeMatrix(..)
  , SomeVector(..)
  , someMatrix
  , someVector
    -- * Dimension queries
  , rows
  , cols
  , size
    -- * Type-level helpers
  , type KnownDims
  ) where

import Data.Massiv.Array (Array, Ix2(..), Sz(..), Ix1, Comp(..))
import qualified Data.Massiv.Array as M
import GHC.TypeNats (Nat, KnownNat, natVal, SomeNat(..), someNatVal)
import Data.Proxy (Proxy(..))
import Control.DeepSeq (NFData(..))

-- | Constraint synonym for two known dimensions.
--
-- @KnownDims m n@ is equivalent to @(KnownNat m, KnownNat n)@.
type KnownDims m n = (KnownNat m, KnownNat n)

-- | A matrix with compile-time known dimensions \(m\) (rows) \(\times\) \(n\) (cols).
--
-- Wraps a massiv @'Data.Massiv.Array.Array' r 'Data.Massiv.Array.Ix2' e@.
-- The phantom type parameters \(m\) and \(n\) enforce dimensional conformance
-- at compile time. For example, matrix multiplication via 'matMul' requires
-- the inner dimensions to unify:
--
-- @
-- 'matMul' :: Matrix m __k__ r e -> Matrix __k__ n r e -> Matrix m n r e
-- @
--
-- The representation parameter @r@ selects the massiv array backend:
--
-- * @'Data.Massiv.Array.P'@ — Primitive (best for 'Double', 'Int'; pinned memory)
-- * @'Data.Massiv.Array.U'@ — Unboxed (via @Data.Vector.Unboxed@)
-- * @'Data.Massiv.Array.S'@ — Storable (via @Foreign.ForeignPtr@; useful for FFI)
-- * @'Data.Massiv.Array.B'@ — Boxed (polymorphic but slower; GC overhead)
newtype Matrix (m :: Nat) (n :: Nat) r e = MkMatrix { unMatrix :: Array r Ix2 e }

deriving instance Show (Array r Ix2 e) => Show (Matrix m n r e)
deriving instance Eq (Array r Ix2 e) => Eq (Matrix m n r e)

instance NFData (Array r Ix2 e) => NFData (Matrix m n r e) where
  rnf (MkMatrix arr) = rnf arr

-- | A vector with compile-time known dimension \(n\).
--
-- Wraps a massiv @'Data.Massiv.Array.Array' r 'Data.Massiv.Array.Ix1' e@.
-- The phantom parameter \(n\) ensures that vector operations (e.g., 'dot',
-- 'axpy') are only applied to vectors of matching dimension.
newtype Vector (n :: Nat) r e = MkVector { unVector :: Array r Ix1 e }

deriving instance Show (Array r Ix1 e) => Show (Vector n r e)
deriving instance Eq (Array r Ix1 e) => Eq (Vector n r e)

instance NFData (Array r Ix1 e) => NFData (Vector n r e) where
  rnf (MkVector arr) = rnf arr

-- | Smart constructor for matrices. Checks at runtime that the array
-- dimensions match the type-level dimensions \(m\) and \(n\).
--
-- Returns 'Nothing' if the dimensions do not match.
--
-- @
-- let arr = M.makeArray Seq (Sz2 3 4) (\\(i :. j) -> fromIntegral (i + j))
-- matrix \@3 \@4 arr  -- Just (MkMatrix arr)
-- matrix \@2 \@4 arr  -- Nothing
-- @
matrix :: forall m n r e. (KnownDims m n, M.Size r)
       => Array r Ix2 e -> Maybe (Matrix m n r e)
matrix arr
  | M.Sz2 r c <- M.size arr
  , r == fromIntegral (natVal (Proxy @m))
  , c == fromIntegral (natVal (Proxy @n))
  = Just (MkMatrix arr)
  | otherwise = Nothing

-- | Smart constructor for vectors. Checks at runtime that the array
-- size matches the type-level dimension \(n\).
--
-- Returns 'Nothing' if the size does not match.
vector :: forall n r e. (KnownNat n, M.Size r)
       => Array r Ix1 e -> Maybe (Vector n r e)
vector arr
  | M.Sz1 n <- M.size arr
  , n == fromIntegral (natVal (Proxy @n))
  = Just (MkVector arr)
  | otherwise = Nothing

-- | Get the number of rows at the value level. \(O(1)\).
rows :: forall m n r e. KnownNat m => Matrix m n r e -> Int
rows _ = fromIntegral (natVal (Proxy @m))

-- | Get the number of columns at the value level. \(O(1)\).
cols :: forall m n r e. KnownNat n => Matrix m n r e -> Int
cols _ = fromIntegral (natVal (Proxy @n))

-- | Get the size of a vector at the value level. \(O(1)\).
size :: forall n r e. KnownNat n => Vector n r e -> Int
size _ = fromIntegral (natVal (Proxy @n))

-- | Existentially quantified matrix with runtime-determined dimensions.
--
-- Use 'someMatrix' to wrap a massiv array whose dimensions are not known
-- at compile time. Pattern matching on 'SomeMatrix' brings 'KnownNat'
-- evidence into scope:
--
-- @
-- case someMatrix arr of
--   SomeMatrix (mat :: Matrix m n r e) -> ...
--   -- m and n are now in scope as KnownNat
-- @
data SomeMatrix r e where
  SomeMatrix :: (KnownNat m, KnownNat n) => Matrix m n r e -> SomeMatrix r e

-- | Existentially quantified vector with runtime-determined dimensions.
data SomeVector r e where
  SomeVector :: KnownNat n => Vector n r e -> SomeVector r e

-- | Wrap a massiv 2D array into an existentially typed matrix.
someMatrix :: M.Size r => Array r Ix2 e -> SomeMatrix r e
someMatrix arr =
  let M.Sz2 r c = M.size arr
  in case someNatVal (fromIntegral r) of
    SomeNat (_ :: Proxy m) -> case someNatVal (fromIntegral c) of
      SomeNat (_ :: Proxy n) -> SomeMatrix @m @n (MkMatrix arr)

-- | Wrap a massiv 1D array into an existentially typed vector.
someVector :: M.Size r => Array r Ix1 e -> SomeVector r e
someVector arr =
  let M.Sz1 n = M.size arr
  in case someNatVal (fromIntegral n) of
    SomeNat (_ :: Proxy n) -> SomeVector @n (MkVector arr)