packages feed

moonlight-linalg-0.1.0.0: src-dense/Moonlight/LinAlg/Internal/Backend/Smith.hs

{-# LANGUAGE AllowAmbiguousTypes #-}

module Moonlight.LinAlg.Internal.Backend.Smith
  ( SmithNormalForm (..),
    SmithDiagonalForm (..),
    smithNormalFormPure,
    smithDiagonalFormPure,
  )
where

import Control.Monad (foldM)
import Data.Bifunctor (first)
import Data.Kind (Type)
import Data.List (minimumBy)
import Data.Maybe (mapMaybe)
import Data.Ord (comparing)
import Data.Vector qualified as Box
import GHC.TypeNats (KnownNat, Nat)
import Moonlight.Algebra.Pure.Ring
  ( EuclideanDomain (..),
    GCDDomain (..),
    IntegralDomain (..),
    mkNonZeroDivisor,
  )
import Moonlight.Core
  ( AdditiveGroup (..),
    AdditiveMonoid (..),
    MoonlightError (..),
    MultiplicativeMonoid (..),
    checkedNonNegativeProduct,
  )
import Moonlight.LinAlg.Internal.Backend.RowOps (identityRows)
import Moonlight.LinAlg.Internal.Backend.RowStore
  ( RowStore,
    columnStore,
    replaceColumnStore,
    replaceRowStore,
    rowStoreFlatten,
    rowStoreFromRows,
    rowStoreRowAt,
    rowStoreShape,
    rowStoreValueAt,
    swapColumnsStore,
    swapRowsStore,
  )
import Moonlight.LinAlg.Internal.Primitives
  ( ColumnIndex,
    RowIndex,
    columnIndexInt,
    columnIndices,
    mkColumnIndex,
    mkRowIndex,
    rowIndexInt,
    rowIndices,
  )
import Moonlight.LinAlg.Pure.Dense.Types
  ( Matrix,
    fromListMatrix,
  )
import qualified Moonlight.LinAlg.Pure.Dense.Types as DenseTypes
import Prelude

type SmithNormalForm :: Nat -> Nat -> Type -> Type
data SmithNormalForm r c a = SmithNormalForm
  { smithLeft :: Matrix r r a,
    smithDiagonal :: Matrix r c a,
    smithRight :: Matrix c c a,
    smithLeftInverse :: Matrix r r a,
    smithRightInverse :: Matrix c c a
  }

type SmithDiagonalForm :: Nat -> Nat -> Type -> Type
newtype SmithDiagonalForm r c a = SmithDiagonalForm
  { smithDiagonalMatrix :: Matrix r c a
  }

type SmithWitnessState :: Type -> Type
data SmithWitnessState a = SmithWitnessState
  { smithWitnessLeft :: RowStore a,
    smithWitnessRight :: RowStore a,
    smithWitnessLeftInverse :: RowStore a,
    smithWitnessRightInverse :: RowStore a
  }
  deriving stock (Eq)

type SmithState :: Type -> Type
data SmithState a = SmithState
  { smithStateMatrix :: RowStore a,
    smithStateWitness :: Maybe (SmithWitnessState a)
  }
  deriving stock (Eq)

matrixValueAt :: RowIndex -> ColumnIndex -> RowStore a -> Either MoonlightError a
matrixValueAt rowIndex columnIndex =
  rowStoreValueAt
    (InvariantViolation ("Smith normal form entry lookup failed at " <> show (rowIndex, columnIndex)))
    rowIndex
    columnIndex

rowIndexAsColumn :: Int -> RowIndex -> Either MoonlightError ColumnIndex
rowIndexAsColumn columnCount rowIndex =
  mkColumnIndex
    (InvariantViolation ("Smith inverse witness row/column conversion failed at row " <> show rowIndex))
    columnCount
    (rowIndexInt rowIndex)

columnIndexAsRow :: Int -> ColumnIndex -> Either MoonlightError RowIndex
columnIndexAsRow rowCount columnIndex =
  mkRowIndex
    (InvariantViolation ("Smith inverse witness column/row conversion failed at column " <> show columnIndex))
    rowCount
    (columnIndexInt columnIndex)

swapRowsState :: RowIndex -> RowIndex -> SmithState a -> Either MoonlightError (SmithState a)
swapRowsState leftIndex rightIndex stateValue = do
  swappedMatrix <-
    swapRowsStore
      (InvariantViolation ("Smith row swap failed at " <> show (leftIndex, rightIndex)))
      leftIndex
      rightIndex
      (smithStateMatrix stateValue)
  swappedWitness <- traverse swapWitness (smithStateWitness stateValue)
  Right
    stateValue
      { smithStateMatrix = swappedMatrix,
        smithStateWitness = swappedWitness
      }
  where
    swapWitness witnessValue = do
      swappedLeft <-
        swapRowsStore
          (InvariantViolation ("Smith left witness row swap failed at " <> show (leftIndex, rightIndex)))
          leftIndex
          rightIndex
          (smithWitnessLeft witnessValue)
      leftColumn <- rowIndexAsColumn (fst (rowStoreShape (smithWitnessLeftInverse witnessValue))) leftIndex
      rightColumn <- rowIndexAsColumn (fst (rowStoreShape (smithWitnessLeftInverse witnessValue))) rightIndex
      swappedLeftInverse <-
        swapColumnsStore
          (InvariantViolation ("Smith left inverse column swap failed at " <> show (leftColumn, rightColumn)))
          leftColumn
          rightColumn
          (smithWitnessLeftInverse witnessValue)
      Right
        witnessValue
          { smithWitnessLeft = swappedLeft,
            smithWitnessLeftInverse = swappedLeftInverse
          }

swapColsState :: ColumnIndex -> ColumnIndex -> SmithState a -> Either MoonlightError (SmithState a)
swapColsState leftIndex rightIndex stateValue = do
  swappedMatrix <-
    swapColumnsStore
      (InvariantViolation ("Smith column swap failed at " <> show (leftIndex, rightIndex)))
      leftIndex
      rightIndex
      (smithStateMatrix stateValue)
  swappedWitness <- traverse swapWitness (smithStateWitness stateValue)
  Right
    stateValue
      { smithStateMatrix = swappedMatrix,
        smithStateWitness = swappedWitness
      }
  where
    swapWitness witnessValue = do
      swappedRight <-
        swapColumnsStore
          (InvariantViolation ("Smith right witness column swap failed at " <> show (leftIndex, rightIndex)))
          leftIndex
          rightIndex
          (smithWitnessRight witnessValue)
      leftRow <- columnIndexAsRow (fst (rowStoreShape (smithWitnessRightInverse witnessValue))) leftIndex
      rightRow <- columnIndexAsRow (fst (rowStoreShape (smithWitnessRightInverse witnessValue))) rightIndex
      swappedRightInverse <-
        swapRowsStore
          (InvariantViolation ("Smith right inverse row swap failed at " <> show (leftRow, rightRow)))
          leftRow
          rightRow
          (smithWitnessRightInverse witnessValue)
      Right
        witnessValue
          { smithWitnessRight = swappedRight,
            smithWitnessRightInverse = swappedRightInverse
          }

rowLinearCombination ::
  (AdditiveGroup a, MultiplicativeMonoid a) =>
  a ->
  Box.Vector a ->
  a ->
  Box.Vector a ->
  Either MoonlightError (Box.Vector a)
rowLinearCombination leftCoefficient leftRow rightCoefficient rightRow =
  if Box.length leftRow == Box.length rightRow
    then
      Right
        ( Box.zipWith
            (\leftEntry rightEntry -> (leftCoefficient `mul` leftEntry) `add` (rightCoefficient `mul` rightEntry))
            leftRow
            rightRow
        )
    else Left (InvariantViolation "Smith row combination length mismatch")

rowCombine ::
  (AdditiveGroup a, MultiplicativeMonoid a) =>
  Box.Vector a ->
  Box.Vector a ->
  a ->
  Either MoonlightError (Box.Vector a)
rowCombine targetRow sourceRow coefficient =
  rowLinearCombination one targetRow (neg coefficient) sourceRow

rowAddScaled ::
  (AdditiveGroup a, MultiplicativeMonoid a) =>
  Box.Vector a ->
  Box.Vector a ->
  a ->
  Either MoonlightError (Box.Vector a)
rowAddScaled targetRow sourceRow coefficient =
  rowLinearCombination one targetRow coefficient sourceRow

replaceRowPair ::
  MoonlightError ->
  RowIndex ->
  Box.Vector a ->
  RowIndex ->
  Box.Vector a ->
  RowStore a ->
  Either MoonlightError (RowStore a)
replaceRowPair failure leftIndex leftRow rightIndex rightRow rows =
  replaceRowStore failure leftIndex leftRow rows
    >>= replaceRowStore failure rightIndex rightRow

replaceColumnPair ::
  MoonlightError ->
  ColumnIndex ->
  Box.Vector a ->
  ColumnIndex ->
  Box.Vector a ->
  RowStore a ->
  Either MoonlightError (RowStore a)
replaceColumnPair failure leftIndex leftColumn rightIndex rightColumn rows =
  replaceColumnStore failure leftIndex leftColumn rows
    >>= replaceColumnStore failure rightIndex rightColumn

rowPairTransform ::
  (AdditiveGroup a, MultiplicativeMonoid a) =>
  MoonlightError ->
  RowIndex ->
  RowIndex ->
  a ->
  a ->
  a ->
  a ->
  RowStore a ->
  Either MoonlightError (RowStore a)
rowPairTransform failure leftIndex rightIndex aa ab ba bb rows = do
  leftRow <- rowStoreRowAt failure leftIndex rows
  rightRow <- rowStoreRowAt failure rightIndex rows
  transformedLeft <- rowLinearCombination aa leftRow ab rightRow
  transformedRight <- rowLinearCombination ba leftRow bb rightRow
  replaceRowPair failure leftIndex transformedLeft rightIndex transformedRight rows

columnPairTransform ::
  (AdditiveGroup a, MultiplicativeMonoid a) =>
  MoonlightError ->
  ColumnIndex ->
  ColumnIndex ->
  a ->
  a ->
  a ->
  a ->
  RowStore a ->
  Either MoonlightError (RowStore a)
columnPairTransform failure leftIndex rightIndex aa ab ba bb rows = do
  leftColumn <- columnStore failure leftIndex rows
  rightColumn <- columnStore failure rightIndex rows
  transformedLeft <- rowLinearCombination aa leftColumn ab rightColumn
  transformedRight <- rowLinearCombination ba leftColumn bb rightColumn
  replaceColumnPair failure leftIndex transformedLeft rightIndex transformedRight rows

columnAddScaledInRows ::
  (AdditiveGroup a, MultiplicativeMonoid a) =>
  ColumnIndex ->
  ColumnIndex ->
  a ->
  RowStore a ->
  Either MoonlightError (RowStore a)
columnAddScaledInRows targetIndex sourceIndex coefficient rows = do
  targetColumn <- columnStore (InvariantViolation ("Smith column lookup failed at column " <> show targetIndex)) targetIndex rows
  sourceColumn <- columnStore (InvariantViolation ("Smith column lookup failed at column " <> show sourceIndex)) sourceIndex rows
  updatedColumn <- rowAddScaled targetColumn sourceColumn coefficient
  replaceColumnStore
    (InvariantViolation ("Smith column replacement failed at column " <> show targetIndex))
    targetIndex
    updatedColumn
    rows

rowAddScaledInRows ::
  (AdditiveGroup a, MultiplicativeMonoid a) =>
  RowIndex ->
  RowIndex ->
  a ->
  RowStore a ->
  Either MoonlightError (RowStore a)
rowAddScaledInRows targetIndex sourceIndex coefficient rows = do
  targetRow <-
    rowStoreRowAt
      (InvariantViolation ("Smith inverse witness target row missing at index " <> show targetIndex))
      targetIndex
      rows
  sourceRow <-
    rowStoreRowAt
      (InvariantViolation ("Smith inverse witness source row missing at index " <> show sourceIndex))
      sourceIndex
      rows
  updatedRow <- rowAddScaled targetRow sourceRow coefficient
  replaceRowStore
    (InvariantViolation ("Smith inverse witness row replacement failed at index " <> show targetIndex))
    targetIndex
    updatedRow
    rows

rowCombineState ::
  (AdditiveGroup a, MultiplicativeMonoid a) =>
  RowIndex ->
  RowIndex ->
  a ->
  SmithState a ->
  Either MoonlightError (SmithState a)
rowCombineState targetIndex sourceIndex coefficient stateValue = do
  sourceMatrixRow <-
    rowStoreRowAt
      (InvariantViolation ("Smith row-combine source matrix row missing at index " <> show sourceIndex))
      sourceIndex
      (smithStateMatrix stateValue)
  targetMatrixRow <-
    rowStoreRowAt
      (InvariantViolation ("Smith row-combine target matrix row missing at index " <> show targetIndex))
      targetIndex
      (smithStateMatrix stateValue)
  updatedMatrixRow <- rowCombine targetMatrixRow sourceMatrixRow coefficient
  updatedMatrixRows <-
    replaceRowStore
      (InvariantViolation ("Smith row-combine matrix replacement failed at index " <> show targetIndex))
      targetIndex
      updatedMatrixRow
      (smithStateMatrix stateValue)
  updatedWitness <- traverse updateWitness (smithStateWitness stateValue)
  Right
    stateValue
      { smithStateMatrix = updatedMatrixRows,
        smithStateWitness = updatedWitness
      }
  where
    updateWitness witnessValue = do
      sourceLeftRow <-
        rowStoreRowAt
          (InvariantViolation ("Smith row-combine source witness row missing at index " <> show sourceIndex))
          sourceIndex
          (smithWitnessLeft witnessValue)
      targetLeftRow <-
        rowStoreRowAt
          (InvariantViolation ("Smith row-combine target witness row missing at index " <> show targetIndex))
          targetIndex
          (smithWitnessLeft witnessValue)
      updatedLeftRow <- rowCombine targetLeftRow sourceLeftRow coefficient
      updatedLeftRows <-
        replaceRowStore
          (InvariantViolation ("Smith row-combine witness replacement failed at index " <> show targetIndex))
          targetIndex
          updatedLeftRow
          (smithWitnessLeft witnessValue)
      sourceColumn <- rowIndexAsColumn (fst (rowStoreShape (smithWitnessLeftInverse witnessValue))) sourceIndex
      targetColumn <- rowIndexAsColumn (fst (rowStoreShape (smithWitnessLeftInverse witnessValue))) targetIndex
      updatedLeftInverseRows <-
        columnAddScaledInRows
          sourceColumn
          targetColumn
          coefficient
          (smithWitnessLeftInverse witnessValue)
      Right
        witnessValue
          { smithWitnessLeft = updatedLeftRows,
            smithWitnessLeftInverse = updatedLeftInverseRows
          }

colCombineState ::
  (AdditiveGroup a, MultiplicativeMonoid a) =>
  ColumnIndex ->
  ColumnIndex ->
  a ->
  SmithState a ->
  Either MoonlightError (SmithState a)
colCombineState targetIndex sourceIndex coefficient stateValue = do
  matrixTarget <- columnStore (InvariantViolation ("Smith column lookup failed at column " <> show targetIndex)) targetIndex (smithStateMatrix stateValue)
  matrixSource <- columnStore (InvariantViolation ("Smith column lookup failed at column " <> show sourceIndex)) sourceIndex (smithStateMatrix stateValue)
  updatedMatrixColumn <- rowCombine matrixTarget matrixSource coefficient
  updatedMatrix <-
    replaceColumnStore
      (InvariantViolation ("Smith column replacement failed at column " <> show targetIndex))
      targetIndex
      updatedMatrixColumn
      (smithStateMatrix stateValue)
  updatedWitness <- traverse updateWitness (smithStateWitness stateValue)
  Right
    stateValue
      { smithStateMatrix = updatedMatrix,
        smithStateWitness = updatedWitness
      }
  where
    updateWitness witnessValue = do
      rightTarget <- columnStore (InvariantViolation ("Smith right witness column lookup failed at column " <> show targetIndex)) targetIndex (smithWitnessRight witnessValue)
      rightSource <- columnStore (InvariantViolation ("Smith right witness column lookup failed at column " <> show sourceIndex)) sourceIndex (smithWitnessRight witnessValue)
      updatedRightColumn <- rowCombine rightTarget rightSource coefficient
      updatedRight <-
        replaceColumnStore
          (InvariantViolation ("Smith right witness column replacement failed at column " <> show targetIndex))
          targetIndex
          updatedRightColumn
          (smithWitnessRight witnessValue)
      sourceRow <- columnIndexAsRow (fst (rowStoreShape (smithWitnessRightInverse witnessValue))) sourceIndex
      targetRow <- columnIndexAsRow (fst (rowStoreShape (smithWitnessRightInverse witnessValue))) targetIndex
      updatedRightInverseRows <-
        rowAddScaledInRows
          sourceRow
          targetRow
          coefficient
          (smithWitnessRightInverse witnessValue)
      Right
        witnessValue
          { smithWitnessRight = updatedRight,
            smithWitnessRightInverse = updatedRightInverseRows
          }

exactQuotient :: EuclideanDomain a => String -> a -> a -> Either MoonlightError a
exactQuotient context numerator denominator = do
  (quotientValue, remainderValue) <- divideWithRemainderChecked context numerator denominator
  if isZero remainderValue
    then Right quotientValue
    else Left (InvariantViolation ("Smith exact quotient had nonzero remainder during " <> context))

divideWithRemainderChecked :: EuclideanDomain a => String -> a -> a -> Either MoonlightError (a, a)
divideWithRemainderChecked context numerator denominator =
  case mkNonZeroDivisor denominator of
    Nothing -> Left (InvariantViolation ("Smith division received a zero divisor during " <> context))
    Just divisor -> Right (divideWithRemainder numerator divisor)

dividesNonZero :: EuclideanDomain a => a -> a -> Bool
dividesNonZero denominator numerator =
  case mkNonZeroDivisor denominator of
    Nothing -> False
    Just divisor -> isZero (snd (divideWithRemainder numerator divisor))

gcdCombineRowsState ::
  EuclideanDomain a =>
  RowIndex ->
  RowIndex ->
  ColumnIndex ->
  SmithState a ->
  Either MoonlightError (SmithState a)
gcdCombineRowsState pivotRow candidateRow pivotColumn stateValue = do
  pivotValue <- matrixValueAt pivotRow pivotColumn (smithStateMatrix stateValue)
  entryValue <- matrixValueAt candidateRow pivotColumn (smithStateMatrix stateValue)
  let (gcdValue, pivotCoefficient, entryCoefficient) = extendedGcdDomain pivotValue entryValue
  pivotQuotient <- exactQuotient "row gcd pivot quotient" pivotValue gcdValue
  entryQuotient <- exactQuotient "row gcd entry quotient" entryValue gcdValue
  updatedMatrix <-
    rowPairTransform
      (InvariantViolation ("Smith row gcd transform failed at " <> show (pivotRow, candidateRow)))
      pivotRow
      candidateRow
      pivotCoefficient
      entryCoefficient
      (neg entryQuotient)
      pivotQuotient
      (smithStateMatrix stateValue)
  updatedWitness <- traverse (updateWitness pivotCoefficient entryCoefficient pivotQuotient entryQuotient) (smithStateWitness stateValue)
  Right
    stateValue
      { smithStateMatrix = updatedMatrix,
        smithStateWitness = updatedWitness
      }
  where
    updateWitness pivotCoefficient entryCoefficient pivotQuotient entryQuotient witnessValue = do
      updatedLeft <-
        rowPairTransform
          (InvariantViolation ("Smith left witness row gcd transform failed at " <> show (pivotRow, candidateRow)))
          pivotRow
          candidateRow
          pivotCoefficient
          entryCoefficient
          (neg entryQuotient)
          pivotQuotient
          (smithWitnessLeft witnessValue)
      pivotColumnWitness <- rowIndexAsColumn (fst (rowStoreShape (smithWitnessLeftInverse witnessValue))) pivotRow
      candidateColumnWitness <- rowIndexAsColumn (fst (rowStoreShape (smithWitnessLeftInverse witnessValue))) candidateRow
      updatedLeftInverse <-
        columnPairTransform
          (InvariantViolation ("Smith left inverse row gcd transform failed at " <> show (pivotColumnWitness, candidateColumnWitness)))
          pivotColumnWitness
          candidateColumnWitness
          pivotQuotient
          entryQuotient
          (neg entryCoefficient)
          pivotCoefficient
          (smithWitnessLeftInverse witnessValue)
      Right
        witnessValue
          { smithWitnessLeft = updatedLeft,
            smithWitnessLeftInverse = updatedLeftInverse
          }

gcdCombineColsState ::
  EuclideanDomain a =>
  RowIndex ->
  ColumnIndex ->
  ColumnIndex ->
  SmithState a ->
  Either MoonlightError (SmithState a)
gcdCombineColsState pivotRow pivotColumn candidateColumn stateValue = do
  pivotValue <- matrixValueAt pivotRow pivotColumn (smithStateMatrix stateValue)
  entryValue <- matrixValueAt pivotRow candidateColumn (smithStateMatrix stateValue)
  let (gcdValue, pivotCoefficient, entryCoefficient) = extendedGcdDomain pivotValue entryValue
  pivotQuotient <- exactQuotient "column gcd pivot quotient" pivotValue gcdValue
  entryQuotient <- exactQuotient "column gcd entry quotient" entryValue gcdValue
  updatedMatrix <-
    columnPairTransform
      (InvariantViolation ("Smith column gcd transform failed at " <> show (pivotColumn, candidateColumn)))
      pivotColumn
      candidateColumn
      pivotCoefficient
      entryCoefficient
      (neg entryQuotient)
      pivotQuotient
      (smithStateMatrix stateValue)
  updatedWitness <- traverse (updateWitness pivotCoefficient entryCoefficient pivotQuotient entryQuotient) (smithStateWitness stateValue)
  Right
    stateValue
      { smithStateMatrix = updatedMatrix,
        smithStateWitness = updatedWitness
      }
  where
    updateWitness pivotCoefficient entryCoefficient pivotQuotient entryQuotient witnessValue = do
      updatedRight <-
        columnPairTransform
          (InvariantViolation ("Smith right witness column gcd transform failed at " <> show (pivotColumn, candidateColumn)))
          pivotColumn
          candidateColumn
          pivotCoefficient
          entryCoefficient
          (neg entryQuotient)
          pivotQuotient
          (smithWitnessRight witnessValue)
      pivotRowWitness <- columnIndexAsRow (fst (rowStoreShape (smithWitnessRightInverse witnessValue))) pivotColumn
      candidateRowWitness <- columnIndexAsRow (fst (rowStoreShape (smithWitnessRightInverse witnessValue))) candidateColumn
      updatedRightInverse <-
        rowPairTransform
          (InvariantViolation ("Smith right inverse column gcd transform failed at " <> show (pivotRowWitness, candidateRowWitness)))
          pivotRowWitness
          candidateRowWitness
          pivotQuotient
          entryQuotient
          (neg entryCoefficient)
          pivotCoefficient
          (smithWitnessRightInverse witnessValue)
      Right
        witnessValue
          { smithWitnessRight = updatedRight,
            smithWitnessRightInverse = updatedRightInverse
          }

findPivot :: EuclideanDomain a => RowIndex -> ColumnIndex -> RowStore a -> Either MoonlightError (Maybe (RowIndex, ColumnIndex, a))
findPivot startRow startCol rows =
  let (rowCount, columnCount) = rowStoreShape rows
   in traverse
        ( \rowIndex ->
            traverse
              ( \columnIndex ->
                  fmap
                    (\value -> if isZero value then Nothing else Just (rowIndex, columnIndex, value))
                    (matrixValueAt rowIndex columnIndex rows)
              )
              (dropWhile (< startCol) (columnIndices columnCount))
        )
        (dropWhile (< startRow) (rowIndices rowCount))
        >>= \candidateRows ->
          let candidateTriples = mapMaybe id (concat candidateRows)
           in Right
                ( if null candidateTriples
                    then Nothing
                    else Just (minimumBy (comparing (degree . (\(_, _, value) -> value))) candidateTriples)
                )

columnCleared :: IntegralDomain a => RowIndex -> ColumnIndex -> RowStore a -> Either MoonlightError Bool
columnCleared pivotRow pivotColumn rows =
  fmap
    and
    ( traverse
        ( \rowIndex ->
            if rowIndex == pivotRow
              then Right True
              else fmap isZero (matrixValueAt rowIndex pivotColumn rows)
        )
        (rowIndices (fst (rowStoreShape rows)))
    )

rowCleared :: IntegralDomain a => RowIndex -> ColumnIndex -> RowStore a -> Either MoonlightError Bool
rowCleared pivotRow pivotColumn rows =
  rowStoreRowAt
    (InvariantViolation ("Smith row-clear pivot row missing at index " <> show pivotRow))
    pivotRow
    rows
    >>= \pivotRowValues ->
      Right
        ( all
            (\(columnIndex, value) -> columnIndex == columnIndexInt pivotColumn || isZero value)
            (zip [0 :: Int ..] (Box.toList pivotRowValues))
        )

clearColumn ::
  forall a.
  EuclideanDomain a =>
  RowIndex ->
  ColumnIndex ->
  SmithState a ->
  Either MoonlightError (SmithState a)
clearColumn pivotRow pivotColumn stateValue =
  let rows = smithStateMatrix stateValue
      (rowCount, _) = rowStoreShape rows
   in traverse
        ( \rowIndex ->
            fmap
              (\entryValue -> if rowIndex /= pivotRow && not (isZero entryValue) then Just rowIndex else Nothing)
              (matrixValueAt rowIndex pivotColumn rows)
        )
        (rowIndices rowCount)
        >>= \candidateMarks ->
          case mapMaybe id candidateMarks of
            [] -> Right stateValue
            candidateRow : _ -> do
              pivotValue <- matrixValueAt pivotRow pivotColumn rows
              entryValue <- matrixValueAt candidateRow pivotColumn rows
              if isZero pivotValue
                then Left (InvariantViolation "Smith normal form pivot became zero during column reduction")
                else do
                  (quotientValue, remainderValue) <- divideWithRemainderChecked "column reduction" entryValue pivotValue
                  reducedState <-
                    if isZero remainderValue
                      then rowCombineState candidateRow pivotRow quotientValue stateValue
                      else gcdCombineRowsState pivotRow candidateRow pivotColumn stateValue
                  clearColumn pivotRow pivotColumn reducedState

clearRow ::
  forall a.
  EuclideanDomain a =>
  RowIndex ->
  ColumnIndex ->
  SmithState a ->
  Either MoonlightError (SmithState a)
clearRow pivotRow pivotColumn stateValue =
  let rows = smithStateMatrix stateValue
   in rowStoreRowAt
        (InvariantViolation ("Smith row reduction pivot row missing at index " <> show pivotRow))
        pivotRow
        rows
        >>= \pivotRowValues ->
          case map fst (filter (\(columnIndex, value) -> columnIndex /= pivotColumn && not (isZero value)) (zip (columnIndices (Box.length pivotRowValues)) (Box.toList pivotRowValues))) of
            [] -> Right stateValue
            candidateCol : _ -> do
              pivotValue <- matrixValueAt pivotRow pivotColumn rows
              entryValue <- matrixValueAt pivotRow candidateCol rows
              if isZero pivotValue
                then Left (InvariantViolation "Smith normal form pivot became zero during row reduction")
                else do
                  (quotientValue, remainderValue) <- divideWithRemainderChecked "row reduction" entryValue pivotValue
                  reducedState <-
                    if isZero remainderValue
                      then colCombineState candidateCol pivotColumn quotientValue stateValue
                      else gcdCombineColsState pivotRow pivotColumn candidateCol stateValue
                  clearRow pivotRow pivotColumn reducedState

normalizePivot ::
  forall a.
  EuclideanDomain a =>
  RowIndex ->
  ColumnIndex ->
  Int ->
  SmithState a ->
  Either MoonlightError (SmithState a)
normalizePivot pivotRow pivotColumn remainingBudget stateValue
  | remainingBudget <= 0 = Left (InvariantViolation "Smith normal form normalization exhausted iteration budget")
  | otherwise = do
      clearedColumn <- columnCleared pivotRow pivotColumn (smithStateMatrix stateValue)
      clearedRow <- rowCleared pivotRow pivotColumn (smithStateMatrix stateValue)
      if clearedColumn && clearedRow
        then Right stateValue
        else do
          columnReduced <- clearColumn pivotRow pivotColumn stateValue
          rowReduced <- clearRow pivotRow pivotColumn columnReduced
          if smithStateMatrix rowReduced == smithStateMatrix stateValue
            then Left (InvariantViolation "Smith normal form normalization stalled before reaching diagonal form")
            else normalizePivot pivotRow pivotColumn (remainingBudget - 1) rowReduced

smithStep ::
  forall a.
  EuclideanDomain a =>
  Int ->
  Int ->
  Int ->
  Int ->
  SmithState a ->
  Either MoonlightError (SmithState a)
smithStep pivotIndex rowCount columnCount normalizationBudget stateValue
  | pivotIndex >= min rowCount columnCount = Right stateValue
  | otherwise = do
      pivotRowIndex <-
        mkRowIndex
          (InvariantViolation ("Smith normal form pivot row out of bounds at index " <> show pivotIndex))
          rowCount
          pivotIndex
      pivotColumnIndex <-
        mkColumnIndex
          (InvariantViolation ("Smith normal form pivot column out of bounds at index " <> show pivotIndex))
          columnCount
          pivotIndex
      pivotCandidate <- findPivot pivotRowIndex pivotColumnIndex (smithStateMatrix stateValue)
      case pivotCandidate of
        Nothing -> Right stateValue
        Just (pivotRow, pivotCol, _) -> do
          pivotMoved <- swapRowsState pivotRowIndex pivotRow stateValue >>= swapColsState pivotColumnIndex pivotCol
          normalized <- normalizePivot pivotRowIndex pivotColumnIndex normalizationBudget pivotMoved
          smithStep (pivotIndex + 1) rowCount columnCount normalizationBudget normalized

enforceDivisibilityChain ::
  forall a.
  EuclideanDomain a =>
  Int ->
  Int ->
  Int ->
  Int ->
  SmithState a ->
  Either MoonlightError (SmithState a)
enforceDivisibilityChain rowCount columnCount normalizationBudget divisibilityBudget stateValue =
  go divisibilityBudget stateValue
  where
    diagSize = min rowCount columnCount

    go remainingBudget currentState
      | remainingBudget <= 0 =
          case findViolation 0 (smithStateMatrix currentState) of
            Nothing -> Right currentState
            Just _ -> Left (InvariantViolation "Smith normal form divisibility chain exhausted iteration budget")
      | otherwise =
          case findViolation 0 (smithStateMatrix currentState) of
            Nothing -> Right currentState
            Just violationIndex -> do
              rowI <-
                mkRowIndex
                  (InvariantViolation ("divisibility chain row index out of bounds at " <> show violationIndex))
                  rowCount
                  violationIndex
              rowJ <-
                mkRowIndex
                  (InvariantViolation ("divisibility chain row index out of bounds at " <> show (violationIndex + 1)))
                  rowCount
                  (violationIndex + 1)
              colI <-
                mkColumnIndex
                  (InvariantViolation ("divisibility chain column index out of bounds at " <> show violationIndex))
                  columnCount
                  violationIndex
              colJ <-
                mkColumnIndex
                  (InvariantViolation ("divisibility chain column index out of bounds at " <> show (violationIndex + 1)))
                  columnCount
                  (violationIndex + 1)
              combined <- rowCombineState rowI rowJ (neg one) currentState
              normalizedI <- normalizePivot rowI colI normalizationBudget combined
              normalizedJ <- normalizePivot rowJ colJ normalizationBudget normalizedI
              go (remainingBudget - 1) normalizedJ

    findViolation idx matrixRows
      | idx + 1 >= diagSize = Nothing
      | otherwise =
          case diagonalPair idx matrixRows of
            Left _ -> Nothing
            Right (dI, dJ)
              | isZero dI -> findViolation (idx + 1) matrixRows
              | isZero dJ -> findViolation (idx + 1) matrixRows
              | dividesNonZero dI dJ -> findViolation (idx + 1) matrixRows
              | otherwise -> Just idx

    diagonalPair idx matrixRows = do
      rowI <- mkRowIndex (InvariantViolation "divisibility diagonal lookup") rowCount idx
      colI <- mkColumnIndex (InvariantViolation "divisibility diagonal lookup") columnCount idx
      rowJ <- mkRowIndex (InvariantViolation "divisibility diagonal lookup") rowCount (idx + 1)
      colJ <- mkColumnIndex (InvariantViolation "divisibility diagonal lookup") columnCount (idx + 1)
      dI <- matrixValueAt rowI colI matrixRows
      dJ <- matrixValueAt rowJ colJ matrixRows
      Right (dI, dJ)

smithStateFromRows ::
  [[a]] ->
  Maybe (SmithWitnessState a) ->
  SmithState a
smithStateFromRows rows witnessValue =
  SmithState
    { smithStateMatrix = rowStoreFromRows rows,
      smithStateWitness = witnessValue
    }

fullWitnessState ::
  (AdditiveGroup a, MultiplicativeMonoid a) =>
  Int ->
  Int ->
  SmithWitnessState a
fullWitnessState rowCount columnCount =
  SmithWitnessState
    { smithWitnessLeft = rowStoreFromRows (identityRows rowCount),
      smithWitnessRight = rowStoreFromRows (identityRows columnCount),
      smithWitnessLeftInverse = rowStoreFromRows (identityRows rowCount),
      smithWitnessRightInverse = rowStoreFromRows (identityRows columnCount)
    }

runSmithState ::
  forall r c a.
  (KnownNat r, KnownNat c, EuclideanDomain a) =>
  Maybe (SmithWitnessState a) ->
  Matrix r c a ->
  Either MoonlightError (SmithState a)
runSmithState witnessValue matrixValue = do
  let (rowCount, columnCount) = DenseTypes.matrixShape matrixValue
      diagonalSize = min rowCount columnCount
  matrixCardinality <- checkedSmithProduct "matrix normalization budget" rowCount columnCount
  normalizationCardinality <- checkedSmithProduct "matrix normalization budget" matrixCardinality 2
  divisibilityCardinality <- checkedSmithProduct "divisibility-chain budget" diagonalSize diagonalSize
  let normalizationBudget = max 1 normalizationCardinality
  rows <- DenseTypes.matrixToRows matrixValue
  let initialState = smithStateFromRows rows witnessValue
  steppedState <- smithStep 0 rowCount columnCount normalizationBudget initialState
  repairedState <- enforceDivisibilityChain rowCount columnCount normalizationBudget divisibilityCardinality steppedState
  normalizeDiagonalUnits rowCount columnCount repairedState

checkedSmithProduct :: String -> Int -> Int -> Either MoonlightError Int
checkedSmithProduct context leftFactor rightFactor =
  first
    (const (InvariantViolation ("Smith " <> context <> " exceeds Int range")))
    (checkedNonNegativeProduct leftFactor rightFactor)

normalizeDiagonalUnits ::
  forall a.
  EuclideanDomain a =>
  Int ->
  Int ->
  SmithState a ->
  Either MoonlightError (SmithState a)
normalizeDiagonalUnits rowCount columnCount stateValue =
  foldM normalizeAt stateValue [0 .. min rowCount columnCount - 1]
  where
    normalizeAt currentState indexValue = do
      rowIndex <- mkRowIndex (InvariantViolation ("Smith unit normalization row index failed at " <> show indexValue)) rowCount indexValue
      columnIndex <- mkColumnIndex (InvariantViolation ("Smith unit normalization column index failed at " <> show indexValue)) columnCount indexValue
      entryValue <- matrixValueAt rowIndex columnIndex (smithStateMatrix currentState)
      let canonicalValue = gcdDomain entryValue zero
      if canonicalValue == entryValue
        then Right currentState
        else do
          (unitValue, remainderValue) <- divideWithRemainderChecked "unit normalization" entryValue canonicalValue
          if remainderValue == zero
            then do
              inverseUnit <-
                case unitInverse unitValue of
                  Just value -> Right value
                  Nothing -> Left (InvariantViolation ("Smith unit normalization met a nonunit cofactor at " <> show indexValue))
              scaledMatrix <- scaleRowStore rowIndex inverseUnit (smithStateMatrix currentState)
              scaledWitness <- traverse (scaleWitness rowIndex unitValue inverseUnit) (smithStateWitness currentState)
              Right
                currentState
                  { smithStateMatrix = scaledMatrix,
                    smithStateWitness = scaledWitness
                  }
            else Left (InvariantViolation ("Smith unit normalization division was inexact at " <> show indexValue))

    scaleWitness :: RowIndex -> a -> a -> SmithWitnessState a -> Either MoonlightError (SmithWitnessState a)
    scaleWitness rowIndex unitValue inverseUnit witnessValue = do
      scaledLeft <- scaleRowStore rowIndex inverseUnit (smithWitnessLeft witnessValue)
      witnessColumn <- rowIndexAsColumn (fst (rowStoreShape (smithWitnessLeftInverse witnessValue))) rowIndex
      scaledLeftInverse <- scaleColumnStore witnessColumn unitValue (smithWitnessLeftInverse witnessValue)
      Right
        witnessValue
          { smithWitnessLeft = scaledLeft,
            smithWitnessLeftInverse = scaledLeftInverse
          }

scaleRowStore ::
  MultiplicativeMonoid a =>
  RowIndex ->
  a ->
  RowStore a ->
  Either MoonlightError (RowStore a)
scaleRowStore rowIndex factor store = do
  let failure = InvariantViolation ("Smith unit normalization row scale failed at " <> show rowIndex)
  rowValue <- rowStoreRowAt failure rowIndex store
  replaceRowStore failure rowIndex (Box.map (mul factor) rowValue) store

scaleColumnStore ::
  MultiplicativeMonoid a =>
  ColumnIndex ->
  a ->
  RowStore a ->
  Either MoonlightError (RowStore a)
scaleColumnStore columnIndex factor store = do
  let failure = InvariantViolation ("Smith unit normalization column scale failed at " <> show columnIndex)
  columnValue <- columnStore failure columnIndex store
  replaceColumnStore failure columnIndex (Box.map (mul factor) columnValue) store

smithNormalFormPure ::
  forall r c a.
  (KnownNat r, KnownNat c, EuclideanDomain a) =>
  Matrix r c a ->
  Either MoonlightError (SmithNormalForm r c a)
smithNormalFormPure matrixValue = do
  let (rowCount, columnCount) = DenseTypes.matrixShape matrixValue
  finalState <- runSmithState (Just (fullWitnessState rowCount columnCount)) matrixValue
  witnessValue <-
    case smithStateWitness finalState of
      Just value -> Right value
      Nothing -> Left (InvariantViolation "Smith full decomposition lost witness state")
  leftMatrix <- fromListMatrix @r @r (rowStoreFlatten (smithWitnessLeft witnessValue))
  diagonalMatrix <- fromListMatrix @r @c (rowStoreFlatten (smithStateMatrix finalState))
  rightMatrix <- fromListMatrix @c @c (rowStoreFlatten (smithWitnessRight witnessValue))
  leftInverseMatrix <- fromListMatrix @r @r (rowStoreFlatten (smithWitnessLeftInverse witnessValue))
  rightInverseMatrix <- fromListMatrix @c @c (rowStoreFlatten (smithWitnessRightInverse witnessValue))
  pure
    SmithNormalForm
      { smithLeft = leftMatrix,
        smithDiagonal = diagonalMatrix,
        smithRight = rightMatrix,
        smithLeftInverse = leftInverseMatrix,
        smithRightInverse = rightInverseMatrix
      }

smithDiagonalFormPure ::
  forall r c a.
  (KnownNat r, KnownNat c, EuclideanDomain a) =>
  Matrix r c a ->
  Either MoonlightError (SmithDiagonalForm r c a)
smithDiagonalFormPure matrixValue = do
  finalState <- runSmithState Nothing matrixValue
  diagonalMatrix <- fromListMatrix @r @c (rowStoreFlatten (smithStateMatrix finalState))
  pure (SmithDiagonalForm diagonalMatrix)