linear-massiv-0.1.0.0: src/Numeric/LinearAlgebra/Massiv/BLAS/Level2.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
-- |
-- Module : Numeric.LinearAlgebra.Massiv.BLAS.Level2
-- Copyright : (c) Nadia Chambers 2026
-- License : BSD-3-Clause
-- Maintainer : nadia.chambers@iohk.io
-- Stability : experimental
--
-- = BLAS Level 2: Matrix–Vector Operations
--
-- This module provides type-safe, dimension-indexed wrappers around the
-- standard BLAS Level 2 kernels. These are /matrix–vector/ operations
-- whose arithmetic cost is \(O(m \, n)\) for an \(m \times n\) matrix,
-- one level above the \(O(n)\) vector–vector operations of Level 1.
--
-- The central operation is the /Gaxpy/ — Generalized Saxpy — which
-- computes \(y \leftarrow \alpha A x + \beta y\). It can be viewed
-- as a sequence of Saxpy updates (one per row or one per column),
-- giving rise to two natural loop orderings:
--
-- * Golub, G. H. & Van Loan, C. F. (2013). /Matrix Computations/,
-- 4th edition (GVL4). Johns Hopkins University Press.
-- __Chapter 1, Sections 1.1.3–1.1.4__, pp. 8–12.
--
-- Specifically:
--
-- * __Algorithm 1.1.3__ (Row-Oriented Gaxpy, p. 8) — The @i@-th
-- component of the result is a dot product:
-- \(y_i \leftarrow a_i^{T} x + y_i\), where \(a_i^{T}\) is the
-- @i@-th row of \(A\).
--
-- * __Algorithm 1.1.4__ (Column-Oriented Gaxpy, p. 9) — The result
-- vector is updated one column at a time via Saxpy:
-- \(y \leftarrow A(:,\!j) \, x_j + y\), for \(j = 1, \ldots, n\).
--
-- The module also provides the rank-1 outer-product update
-- \(A \leftarrow A + \alpha x y^{T}\) (BLAS @GER@), which is the
-- matrix analogue of the Saxpy at Level 1 and plays a key role in LU
-- factorisation (GVL4, Section 3.2, p. 112).
--
-- == Complexity
--
-- All operations in this module are \(O(m \, n)\) for an
-- \(m \times n\) matrix.
module Numeric.LinearAlgebra.Massiv.BLAS.Level2
( -- * Matrix–vector multiply — Gaxpy (Algorithms 1.1.3–1.1.4, GVL4 pp. 8–9)
gemv
, matvec
, matvecP
-- * Rank-1 update (GVL4 Section 1.1.4, p. 10)
, ger
) where
import qualified Data.Massiv.Array as M
import Data.Massiv.Array (Ix2(..), Sz(..), Comp(..), unwrapByteArray, unwrapByteArrayOffset,
unwrapMutableByteArray, unwrapMutableByteArrayOffset)
import GHC.TypeNats (KnownNat)
import Numeric.LinearAlgebra.Massiv.Types
import Numeric.LinearAlgebra.Massiv.Internal
import Numeric.LinearAlgebra.Massiv.Internal.Kernel (rawGemv)
-- | General matrix–vector multiply (BLAS @GEMV@).
--
-- __GVL4 Reference:__ Algorithms 1.1.3 (Row Gaxpy, p. 8) and 1.1.4
-- (Column Gaxpy, p. 9).
--
-- Given \(A \in \mathbb{R}^{m \times n}\),
-- \(x \in \mathbb{R}^{n}\), \(y \in \mathbb{R}^{m}\), and scalars
-- \(\alpha, \beta\), computes
--
-- \[
-- y \;\leftarrow\; \alpha \, A \, x \;+\; \beta \, y
-- \]
--
-- The implementation uses a /row-oriented/ traversal (Algorithm 1.1.3):
-- for each row \(i\) the dot product \(a_i^{T} x\) is formed, scaled by
-- \(\alpha\), and added to \(\beta \, y_i\).
--
-- ==== Type-safety guarantees
--
-- * \(A\) is @m x n@, \(x\) is @n@, \(y\) and the result are @m@.
-- * The shared inner dimension @n@ is enforced at compile time, so a
-- dimension mismatch is a type error.
--
-- ==== Complexity
--
-- \(O(m \, n)\) — two floating-point operations per matrix entry
-- (one multiply, one add in the inner product), plus \(O(m)\) work
-- for the \(\alpha / \beta\) scaling.
gemv :: forall m n r e. (KnownNat m, KnownNat n, M.Manifest r e, Num e)
=> e -> Matrix m n r e -> Vector n r e -> e -> Vector m r e -> Vector m r e
gemv alpha mat x beta y =
let r = dimVal @m
c = dimVal @n
in makeVector @m @r $ \i ->
let axi = foldl (\acc j -> acc + (mat ! (i, j)) * (x !. j)) 0 [0..c-1]
in alpha * axi + beta * (y !. i)
-- | Simple matrix–vector multiply (specialisation of 'gemv').
--
-- __GVL4 Reference:__ Algorithm 1.1.3 (Row Gaxpy, p. 8), with
-- \(\alpha = 1\) and \(\beta = 0\).
--
-- Given \(A \in \mathbb{R}^{m \times n}\) and
-- \(x \in \mathbb{R}^{n}\), computes
--
-- \[
-- y \;=\; A \, x
-- \]
--
-- This is a convenience wrapper equivalent to @'gemv' 1 a x 0 zero@
-- but avoids allocating or requiring an initial @y@ vector.
--
-- ==== Type-safety guarantees
--
-- * \(A\) is @m x n@, \(x\) is @n@, the result is @m@.
-- * The inner dimension @n@ is checked at compile time.
--
-- ==== Complexity
--
-- \(O(m \, n)\).
matvec :: forall m n r e. (KnownNat m, KnownNat n, M.Manifest r e, Num e)
=> Matrix m n r e -> Vector n r e -> Vector m r e
matvec mat x =
let c = dimVal @n
in makeVector @m @r $ \i ->
foldl (\acc j -> acc + (mat ! (i, j)) * (x !. j)) 0 [0..c-1]
{-# NOINLINE [1] matvec #-}
-- | Specialised raw-array matvec for P Double.
matvecP :: forall m n. (KnownNat m, KnownNat n)
=> Matrix m n M.P Double -> Vector n M.P Double -> Vector m M.P Double
matvecP (MkMatrix a) (MkVector x) =
createVector @m @M.P $ \mc ->
rawGemv (unwrapByteArray a) (unwrapByteArrayOffset a) (dimVal @n)
(unwrapByteArray x) (unwrapByteArrayOffset x)
(unwrapMutableByteArray mc) (unwrapMutableByteArrayOffset mc)
(dimVal @m)
{-# NOINLINE matvecP #-}
{-# RULES "matvec/P/Double" forall (a :: Matrix m n M.P Double)
(x :: Vector n M.P Double).
matvec a x = matvecP a x #-}
-- | Rank-1 update — outer product (BLAS @GER@).
--
-- __GVL4 Reference:__ Section 1.1.4, p. 10. The rank-1 update is
-- the matrix-level analogue of the Saxpy and appears as the inner
-- kernel in outer-product formulations of LU factorisation
-- (GVL4 Section 3.2, Algorithm 3.2.1, p. 112).
--
-- Given \(x \in \mathbb{R}^{m}\), \(y \in \mathbb{R}^{n}\),
-- \(A \in \mathbb{R}^{m \times n}\), and a scalar \(\alpha\),
-- computes
--
-- \[
-- A \;\leftarrow\; A \;+\; \alpha \, x \, y^{T}
-- \]
--
-- Equivalently, each entry is updated as
-- \(a_{ij} \leftarrow a_{ij} + \alpha \, x_i \, y_j\).
--
-- ==== Type-safety guarantees
--
-- * \(x\) is @m@, \(y\) is @n@, \(A\) and the result are @m x n@.
-- * All dimension constraints are enforced at compile time.
--
-- ==== Complexity
--
-- \(O(m \, n)\) — one fused multiply-add per matrix entry.
ger :: forall m n r e. (KnownNat m, KnownNat n, M.Manifest r e, Num e)
=> e -> Vector m r e -> Vector n r e -> Matrix m n r e -> Matrix m n r e
ger alpha x y mat =
makeMatrix @m @n @r $ \i j ->
(mat ! (i, j)) + alpha * (x !. i) * (y !. j)