linear-massiv-0.1.0.0: src/Numeric/LinearAlgebra/Massiv/Solve/Triangular.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
-- |
-- Module : Numeric.LinearAlgebra.Massiv.Solve.Triangular
-- Copyright : (c) Nadia Chambers 2026
-- License : BSD-3-Clause
-- Maintainer : nadia.chambers@iohk.io
-- Stability : experimental
--
-- = Triangular System Solvers
--
-- Forward and back substitution for lower- and upper-triangular linear
-- systems, following Golub & Van Loan, /Matrix Computations/, 4th edition
-- (GVL4), Section 3.1, pp. 106--113.
--
-- The section presents four algorithmic variants organised along two axes:
--
-- * __Row-oriented vs. column-oriented__ inner loops, affecting data-access
-- patterns and cache behaviour.
-- * __General vs. unit-triangular__ coefficient matrices, where the unit
-- variants avoid division by the (known-to-be-one) diagonal.
--
-- This module exposes the following mapping:
--
-- +-------------------+---------------------------+---------------------------------+
-- | Function | Algorithm | Reference |
-- +===================+===========================+=================================+
-- | 'forwardSub' | Row-oriented forward sub | GVL4 Algorithm 3.1.1, p. 106 |
-- +-------------------+---------------------------+---------------------------------+
-- | 'backSub' | Row-oriented back sub | GVL4 Algorithm 3.1.2, p. 107 |
-- +-------------------+---------------------------+---------------------------------+
-- | 'forwardSubUnit' | Column-oriented fwd sub | GVL4 Algorithm 3.1.3, p. 108 |
-- +-------------------+---------------------------+---------------------------------+
-- | 'backSubUnit' | Column-oriented back sub | GVL4 Algorithm 3.1.4, p. 109 |
-- +-------------------+---------------------------+---------------------------------+
--
-- == Complexity
--
-- Every solver performs \(O(n^2 / 2)\) floating-point operations (flops) for
-- an \(n \times n\) triangular system (GVL4 p. 109).
--
-- == Type Safety
--
-- The matrix dimension \(n\) is tracked at the type level via 'KnownNat',
-- so the compiler statically guarantees that the coefficient matrix is
-- square and that the right-hand side vector has a conforming length.
module Numeric.LinearAlgebra.Massiv.Solve.Triangular
( -- * Forward substitution (\(Lx = b\))
forwardSub
-- * Back substitution (\(Ux = b\))
, backSub
-- * Unit-triangular variants
, forwardSubUnit
, backSubUnit
) where
import qualified Data.Massiv.Array as M
import Data.Massiv.Array (Ix1, Ix2(..), Sz(..))
import GHC.TypeNats (KnownNat)
import Numeric.LinearAlgebra.Massiv.Types
import Numeric.LinearAlgebra.Massiv.Internal
-- | Row-oriented forward substitution (GVL4 Algorithm 3.1.1, p. 106).
--
-- Solves the lower-triangular system \(Lx = b\) where \(L \in \mathbb{R}^{n \times n}\)
-- is lower triangular with nonzero diagonal entries.
--
-- ==== Mathematical definition
--
-- For \(j = 1, \ldots, n\):
--
-- \[
-- x_j = \frac{1}{\ell_{jj}} \left( b_j - \sum_{k=1}^{j-1} \ell_{jk}\, x_k \right)
-- \]
--
-- ==== Type-safety guarantees
--
-- The type-level natural \(n\) ('KnownNat') ensures that \(L\) is square
-- and that \(b\) has exactly \(n\) entries. A dimension mismatch is a
-- compile-time error.
--
-- ==== Complexity
--
-- \(O(n^2 / 2)\) flops (GVL4 p. 109).
--
-- ==== Reference
--
-- Golub & Van Loan, /Matrix Computations/, 4th ed., Algorithm 3.1.1
-- (Row-Oriented Forward Substitution), p. 106.
forwardSub :: forall n r e. (KnownNat n, M.Manifest r e, Fractional e)
=> Matrix n n r e -> Vector n r e -> Vector n r e
forwardSub l b = createVector @n $ \mx -> do
let nn = dimVal @n
-- Copy b into the mutable result
mapM_ (\i -> M.write_ mx i (b !. i)) [0..nn-1]
-- Forward elimination
mapM_ (\j -> do
xj <- M.readM mx j
let ldiag = l ! (j, j)
xj' = xj / ldiag
M.write_ mx j xj'
-- Update remaining entries
mapM_ (\i -> do
xi <- M.readM mx i
M.write_ mx i (xi - (l ! (i, j)) * xj')
) [j+1..nn-1]
) [0..nn-1]
-- | Row-oriented back substitution (GVL4 Algorithm 3.1.2, p. 107).
--
-- Solves the upper-triangular system \(Ux = b\) where \(U \in \mathbb{R}^{n \times n}\)
-- is upper triangular with nonzero diagonal entries.
--
-- ==== Mathematical definition
--
-- For \(j = n, n-1, \ldots, 1\):
--
-- \[
-- x_j = \frac{1}{u_{jj}} \left( b_j - \sum_{k=j+1}^{n} u_{jk}\, x_k \right)
-- \]
--
-- ==== Type-safety guarantees
--
-- The type-level natural \(n\) ('KnownNat') ensures that \(U\) is square
-- and that \(b\) has exactly \(n\) entries. A dimension mismatch is a
-- compile-time error.
--
-- ==== Complexity
--
-- \(O(n^2 / 2)\) flops (GVL4 p. 109).
--
-- ==== Reference
--
-- Golub & Van Loan, /Matrix Computations/, 4th ed., Algorithm 3.1.2
-- (Row-Oriented Back Substitution), p. 107.
backSub :: forall n r e. (KnownNat n, M.Manifest r e, Fractional e)
=> Matrix n n r e -> Vector n r e -> Vector n r e
backSub u b = createVector @n $ \mx -> do
let nn = dimVal @n
-- Copy b into the mutable result
mapM_ (\i -> M.write_ mx i (b !. i)) [0..nn-1]
-- Back elimination
mapM_ (\j -> do
xj <- M.readM mx j
let udiag = u ! (j, j)
xj' = xj / udiag
M.write_ mx j xj'
-- Update remaining entries
mapM_ (\i -> do
xi <- M.readM mx i
M.write_ mx i (xi - (u ! (i, j)) * xj')
) [0..j-1]
) [nn-1, nn-2..0]
-- | Column-oriented forward substitution for unit lower triangular systems
-- (GVL4 Algorithm 3.1.3, p. 108).
--
-- Solves \(Lx = b\) where \(L \in \mathbb{R}^{n \times n}\) is /unit/ lower
-- triangular, i.e. \(\ell_{jj} = 1\) for all \(j\). Because the diagonal is
-- implicitly one, no division is needed and the constraint relaxes from
-- 'Fractional' to 'Num'.
--
-- ==== Mathematical definition
--
-- For \(j = 1, \ldots, n\):
--
-- \[
-- x_j = b_j - \sum_{k=1}^{j-1} \ell_{jk}\, x_k
-- \]
--
-- The implementation uses a /column-oriented/ (saxpy) loop: once \(x_j\) is
-- determined, rows \(i > j\) are updated by subtracting \(\ell_{ij}\, x_j\).
--
-- ==== Type-safety guarantees
--
-- Identical to 'forwardSub': the dimensions are enforced at compile time
-- via 'KnownNat'.
--
-- ==== Complexity
--
-- \(O(n^2 / 2)\) flops (GVL4 p. 109).
--
-- ==== Reference
--
-- Golub & Van Loan, /Matrix Computations/, 4th ed., Algorithm 3.1.3
-- (Column-Oriented Forward Substitution), p. 108.
forwardSubUnit :: forall n r e. (KnownNat n, M.Manifest r e, Num e)
=> Matrix n n r e -> Vector n r e -> Vector n r e
forwardSubUnit l b = createVector @n $ \mx -> do
let nn = dimVal @n
-- Copy b into the mutable result
mapM_ (\i -> M.write_ mx i (b !. i)) [0..nn-1]
-- Column-oriented forward substitution
mapM_ (\j -> do
xj <- M.readM mx j
-- Subtract l(i,j)*xj from x(i) for i > j
mapM_ (\i -> do
xi <- M.readM mx i
M.write_ mx i (xi - (l ! (i, j)) * xj)
) [j+1..nn-1]
) [0..nn-1]
-- | Column-oriented back substitution for unit upper triangular systems
-- (GVL4 Algorithm 3.1.4, p. 109).
--
-- Solves \(Ux = b\) where \(U \in \mathbb{R}^{n \times n}\) is /unit/ upper
-- triangular, i.e. \(u_{jj} = 1\) for all \(j\). Because the diagonal is
-- implicitly one, no division is needed and the constraint relaxes from
-- 'Fractional' to 'Num'.
--
-- ==== Mathematical definition
--
-- For \(j = n, n-1, \ldots, 1\):
--
-- \[
-- x_j = b_j - \sum_{k=j+1}^{n} u_{jk}\, x_k
-- \]
--
-- The implementation uses a /column-oriented/ loop: once \(x_j\) is known,
-- rows \(i < j\) are updated by subtracting \(u_{ij}\, x_j\).
--
-- ==== Type-safety guarantees
--
-- Identical to 'backSub': the dimensions are enforced at compile time via
-- 'KnownNat'.
--
-- ==== Complexity
--
-- \(O(n^2 / 2)\) flops (GVL4 p. 109).
--
-- ==== Reference
--
-- Golub & Van Loan, /Matrix Computations/, 4th ed., Algorithm 3.1.4
-- (Column-Oriented Back Substitution), p. 109.
backSubUnit :: forall n r e. (KnownNat n, M.Manifest r e, Num e)
=> Matrix n n r e -> Vector n r e -> Vector n r e
backSubUnit u b = createVector @n $ \mx -> do
let nn = dimVal @n
-- Copy b into the mutable result
mapM_ (\i -> M.write_ mx i (b !. i)) [0..nn-1]
-- Column-oriented back substitution
mapM_ (\j -> do
xj <- M.readM mx j
-- Subtract u(i,j)*xj from x(i) for i < j
mapM_ (\i -> do
xi <- M.readM mx i
M.write_ mx i (xi - (u ! (i, j)) * xj)
) [0..j-1]
) [nn-1, nn-2..0]