packages feed

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

{-# LANGUAGE AllowAmbiguousTypes #-}

-- |
-- Module      : Numeric.LinearAlgebra.Massiv.Internal
-- Copyright   : (c) Nadia Chambers 2026
-- License     : BSD-3-Clause
-- Maintainer  : nadia.chambers@iohk.io
-- Stability   : experimental
--
-- Internal module providing unsafe constructors, dimension reification helpers,
-- and array creation utilities. This module is re-exported by
-- "Numeric.LinearAlgebra.Massiv" for convenience, but the unsafe constructors
-- ('unsafeMatrix', 'unsafeVector') bypass the dimension checks provided by the
-- smart constructors in "Numeric.LinearAlgebra.Massiv.Types".
--
-- = Array creation patterns
--
-- Two primary patterns are provided for constructing dimensioned arrays:
--
-- 1. __Pure indexed construction__ via 'makeMatrix' and 'makeVector': supply a
--    pure function @(Int -> Int -> e)@ or @(Int -> e)@ that computes each element
--    from its indices. These use massiv's delayed (@D@) intermediate representation
--    and then @'Data.Massiv.Array.compute'@ to materialise the result.
--
-- 2. __Mutable ST construction__ via 'createMatrix' and 'createVector': supply an
--    @ST@ action operating on a mutable array. This is essential for algorithms
--    that require in-place updates (e.g., LU factorization, Cholesky).
--
-- Both patterns produce arrays with the sequential ('Data.Massiv.Array.Seq')
-- computation strategy by default. Use the @Comp@ variants ('makeMatrixComp',
-- 'makeVectorComp') for parallel construction.
module Numeric.LinearAlgebra.Massiv.Internal
  ( -- * Unsafe constructors
    unsafeMatrix
  , unsafeVector
    -- * Dimension reification
  , dimVal
  , dimVal2
  , reifyDim
  , reifyDim2
    -- * Array creation helpers (pure, sequential)
  , makeMatrix
  , makeVector
    -- * Array creation helpers (pure, with Comp)
  , makeMatrixComp
  , makeVectorComp
    -- * Array creation helpers (mutable ST)
  , createMatrix
  , createVector
  , createMatrixComp
  , createVectorComp
    -- * Mutable modification helpers
  , withMutableMatrix
  , withMutableVector
  , withMutableMatrix_
  , withMutableVector_
    -- * Indexing helpers
  , (!)
  , (!.)
    -- * Identity and zero
  , identityMatrix
  , zeroMatrix
  , zeroVector
  ) where

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

import Numeric.LinearAlgebra.Massiv.Types

-- | Unsafe matrix constructor — wraps a massiv array with /no/ dimension check.
--
-- __Precondition__: the array must have exactly \(m\) rows and \(n\) columns.
-- Violating this precondition leads to index-out-of-bounds errors at runtime.
--
-- Prefer the safe 'matrix' constructor from "Numeric.LinearAlgebra.Massiv.Types"
-- unless you can guarantee correctness (e.g., the array was just constructed
-- with the correct dimensions).
unsafeMatrix :: Array r Ix2 e -> Matrix m n r e
unsafeMatrix = MkMatrix

-- | Unsafe vector constructor — wraps a massiv array with /no/ size check.
--
-- __Precondition__: the array must have exactly \(n\) elements.
unsafeVector :: Array r Ix1 e -> Vector n r e
unsafeVector = MkVector

-- | Get the runtime value of a type-level dimension.
--
-- @
-- dimVal \@3  ==  3
-- dimVal \@100  ==  100
-- @
dimVal :: forall n. KnownNat n => Int
dimVal = fromIntegral (natVal (Proxy @n))

-- | Get both dimensions of a matrix type as a tuple.
dimVal2 :: forall m n. (KnownNat m, KnownNat n) => (Int, Int)
dimVal2 = (dimVal @m, dimVal @n)

-- | Index into a matrix (0-based, unchecked).
--
-- @mat '!' (i, j)@ returns the element at row \(i\), column \(j\).
--
-- __Warning__: No bounds checking is performed. Out-of-bounds access
-- results in undefined behaviour for unboxed\/primitive representations.
(!) :: M.Manifest r e => Matrix m n r e -> (Int, Int) -> e
(!) (MkMatrix arr) (i, j) = M.index' arr (i :. j)

-- | Index into a vector (0-based, unchecked).
--
-- @vec '!.' i@ returns the element at position \(i\).
(!.) :: M.Manifest r e => Vector n r e -> Int -> e
(!.) (MkVector arr) i = M.index' arr i

-- | Reify a runtime 'Int' as a type-level 'GHC.TypeNats.Nat'.
--
-- The continuation receives a 'Proxy' carrying the reified type.
-- This is useful for working with matrices of runtime-determined size.
reifyDim :: Int -> (forall n. KnownNat n => Proxy n -> a) -> a
reifyDim n f = case someNatVal (fromIntegral n) of
  SomeNat p -> f p

-- | Reify two runtime 'Int's as type-level 'GHC.TypeNats.Nat's.
reifyDim2 :: Int -> Int -> (forall m n. (KnownNat m, KnownNat n) => Proxy m -> Proxy n -> a) -> a
reifyDim2 m n f = reifyDim m $ \pm -> reifyDim n $ \pn -> f pm pn

-- | Create a matrix using a pure indexing function (sequential computation).
--
-- @
-- makeMatrix \@3 \@3 \@P $ \\i j -> fromIntegral (i * 3 + j)
-- @
--
-- Internally uses massiv's @'Data.Massiv.Array.Delayed'@ representation as
-- an intermediate before computing to the target representation @r@.
makeMatrix :: forall m n r e. (KnownNat m, KnownNat n, M.Manifest r e)
           => (Int -> Int -> e) -> Matrix m n r e
makeMatrix f =
  let r = dimVal @m
      c = dimVal @n
  in MkMatrix $ M.compute @r $ M.makeArray @D Seq (M.Sz2 r c) (\(i :. j) -> f i j)

-- | Create a vector using a pure indexing function (sequential computation).
makeVector :: forall n r e. (KnownNat n, M.Manifest r e)
           => (Int -> e) -> Vector n r e
makeVector f =
  let sz = dimVal @n
  in MkVector $ M.compute @r $ M.makeArray @D Seq (M.Sz1 sz) f

-- | Create a matrix using a pure indexing function with specified
-- 'Data.Massiv.Array.Comp' strategy.
--
-- Use @Par@ for parallel construction of large matrices:
--
-- @
-- makeMatrixComp \@1000 \@1000 \@P Par $ \\i j -> ...
-- @
makeMatrixComp :: forall m n r e. (KnownNat m, KnownNat n, M.Manifest r e)
               => Comp -> (Int -> Int -> e) -> Matrix m n r e
makeMatrixComp comp f =
  let r = dimVal @m
      c = dimVal @n
  in MkMatrix $ M.compute @r $ M.makeArray @D comp (M.Sz2 r c) (\(i :. j) -> f i j)

-- | Create a vector using a pure indexing function with specified 'Comp'.
makeVectorComp :: forall n r e. (KnownNat n, M.Manifest r e)
               => Comp -> (Int -> e) -> Vector n r e
makeVectorComp comp f =
  let sz = dimVal @n
  in MkVector $ M.compute @r $ M.makeArray @D comp (M.Sz1 sz) f

-- | Create a matrix using a mutable 'ST' computation.
--
-- The action receives a pre-allocated mutable array of the correct size.
-- All writes must be within bounds. The mutable array is frozen after
-- the action completes.
--
-- This is the primary mechanism for implementing algorithms with in-place
-- updates (e.g., LU factorization, Cholesky decomposition).
createMatrix :: forall m n r e. (KnownNat m, KnownNat n, M.Manifest r e)
             => (forall s. M.MArray s r Ix2 e -> ST s ()) -> Matrix m n r e
createMatrix action =
  let r = dimVal @m
      c = dimVal @n
      arr = M.createArrayST_ (M.Sz2 r c) action
  in MkMatrix arr

-- | Create a vector using a mutable 'ST' computation.
createVector :: forall n r e. (KnownNat n, M.Manifest r e)
             => (forall s. M.MArray s r Ix1 e -> ST s ()) -> Vector n r e
createVector action =
  let sz = dimVal @n
      arr = M.createArrayST_ (M.Sz1 sz) action
  in MkVector arr

-- | Create a matrix using a mutable computation with specified 'Comp'.
createMatrixComp :: forall m n r e. (KnownNat m, KnownNat n, M.Manifest r e)
                 => Comp -> (forall s. M.MArray s r Ix2 e -> ST s ()) -> Matrix m n r e
createMatrixComp _comp action =
  -- Note: createArrayST_ always runs sequentially; Comp is for delayed computations
  createMatrix @m @n action

-- | Create a vector using a mutable computation with specified 'Comp'.
createVectorComp :: forall n r e. (KnownNat n, M.Manifest r e)
                 => Comp -> (forall s. M.MArray s r Ix1 e -> ST s ()) -> Vector n r e
createVectorComp _comp action = createVector @n action

-- | Run a mutable operation on a /copy/ of the matrix, returning both the
-- action's result and the modified matrix. The original matrix is not modified.
withMutableMatrix :: (M.Manifest r e)
                  => Matrix m n r e
                  -> (forall s. M.MArray s r Ix2 e -> ST s a)
                  -> (a, Matrix m n r e)
withMutableMatrix (MkMatrix arr) action =
  let (result, arr') = M.withMArrayST arr action
  in (result, MkMatrix arr')

-- | Run a mutable operation on a /copy/ of the vector.
withMutableVector :: (M.Manifest r e)
                  => Vector n r e
                  -> (forall s. M.MArray s r Ix1 e -> ST s a)
                  -> (a, Vector n r e)
withMutableVector (MkVector arr) action =
  let (result, arr') = M.withMArrayST arr action
  in (result, MkVector arr')

-- | Like 'withMutableMatrix' but discards the action's result.
withMutableMatrix_ :: (M.Manifest r e)
                   => Matrix m n r e
                   -> (forall s. M.MArray s r Ix2 e -> ST s ())
                   -> Matrix m n r e
withMutableMatrix_ mat action = snd $ withMutableMatrix mat action

-- | Like 'withMutableVector' but discards the action's result.
withMutableVector_ :: (M.Manifest r e)
                   => Vector n r e
                   -> (forall s. M.MArray s r Ix1 e -> ST s ())
                   -> Vector n r e
withMutableVector_ vec action = snd $ withMutableVector vec action

-- | The \(n \times n\) identity matrix \(I_n\).
--
-- \[
--   I_{ij} = \begin{cases} 1 & \text{if } i = j \\ 0 & \text{otherwise} \end{cases}
-- \]
identityMatrix :: forall n r e. (KnownNat n, M.Manifest r e, Num e)
               => Matrix n n r e
identityMatrix = makeMatrix @n @n @r $ \i j -> if i == j then 1 else 0

-- | The \(m \times n\) zero matrix.
zeroMatrix :: forall m n r e. (KnownNat m, KnownNat n, M.Manifest r e, Num e)
           => Matrix m n r e
zeroMatrix = makeMatrix @m @n @r $ \_ _ -> 0

-- | The zero vector of dimension \(n\).
zeroVector :: forall n r e. (KnownNat n, M.Manifest r e, Num e)
           => Vector n r e
zeroVector = makeVector @n @r $ const 0