packages feed

moonlight-linalg-0.1.0.0: src-carrier/Moonlight/LinAlg/Internal/GF2/Xor.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE StandaloneKindSignatures #-}

module Moonlight.LinAlg.Internal.GF2.Xor
  ( PackedRow
  , packedRowWidth
  , packedRowNonZeroCount
  , emptyPackedRow
  , unitPackedRow
  , packedRowFromIndices
  , packedRowIndices
  , packedRowMember
  , packedRowIsZero
  , packedRowXor
  , packedRowRemap
  , PackedLinearMap
  , packedLinearMapDomain
  , packedLinearMapCodomain
  , packedLinearMapColumns
  , packedLinearMapFromColumns
  , packedLinearMapFromEntries
  , zeroPackedLinearMap
  , identityPackedLinearMap
  , applyPackedLinearMap
  , composePackedLinearMaps
  , addPackedLinearMaps
  , packedLinearMapIsZero
  , PackedSpan
  , emptyPackedSpan
  , packedSpanFromRows
  , reducePackedRow
  , admitPackedRow
  , ColumnReduction (..)
  , reducePackedColumns
  , PackedCoordinateSolver
  , packedCoordinateSolver
  , coordinatesInPackedBasis
  , inverseFromPackedBasisColumns
  , rankPackedRowsByReduction
  ) where

import Control.Monad (foldM, unless)
import Control.Monad.ST (ST, runST)
import Data.Bits
  ( bit
  , clearBit
  , complement
  , countTrailingZeros
  , popCount
  , testBit
  , xor
  , (.&.)
  )
import Data.IntMap.Strict (IntMap)
import Data.IntMap.Strict qualified as IntMap
import Data.Kind (Type)
import Data.Vector (Vector)
import Data.Vector qualified as V
import Data.Vector.Unboxed qualified as U
import Data.Vector.Unboxed.Mutable qualified as UM
import Data.Word (Word64)
import Moonlight.Core (MoonlightError (..))

wordBits :: Int
wordBits = 64

wordCountForWidth :: Int -> Int
wordCountForWidth widthValue
  | widthValue <= 0 = 0
  | otherwise = (widthValue + wordBits - 1) `div` wordBits

lastWordMask :: Int -> Word64
lastWordMask widthValue
  | widthValue <= 0 = 0
  | remainderValue == 0 = complement 0
  | otherwise = bit remainderValue - 1
  where
    remainderValue = widthValue `mod` wordBits

type PackedRow :: Type
data PackedRow = PackedRow
  { prWidth :: !Int
  , prWords :: !(U.Vector Word64)
  , prNonZeroCount :: !Int
  }
  deriving stock (Eq, Show)

packedRowWidth :: PackedRow -> Int
packedRowWidth = prWidth

packedRowNonZeroCount :: PackedRow -> Int
packedRowNonZeroCount = prNonZeroCount

packedRowFromWords :: Int -> U.Vector Word64 -> PackedRow
packedRowFromWords widthValue rawWords =
  let expectedWords = wordCountForWidth widthValue
      paddedWords =
        U.generate
          expectedWords
          (\wordIndex -> maybe 0 id (rawWords U.!? wordIndex))
      maskedWords
        | U.null paddedWords = U.empty
        | otherwise =
            U.imap
              (\wordIndex wordValue ->
                if wordIndex == U.length paddedWords - 1
                  then wordValue .&. lastWordMask widthValue
                  else wordValue
              )
              paddedWords
   in PackedRow
        { prWidth = widthValue
        , prWords = maskedWords
        , prNonZeroCount = U.foldl' (\countValue wordValue -> countValue + popCount wordValue) 0 maskedWords
        }

emptyPackedRow :: Int -> Either MoonlightError PackedRow
emptyPackedRow widthValue
  | widthValue < 0 =
      Left (InvariantViolation ("emptyPackedRow: negative width " <> show widthValue))
  | otherwise =
      Right (packedRowFromWords widthValue U.empty)

unitPackedRow :: String -> Int -> Int -> Either MoonlightError PackedRow
unitPackedRow context widthValue indexValue =
  packedRowFromIndices context widthValue [indexValue]

packedRowFromIndices :: String -> Int -> [Int] -> Either MoonlightError PackedRow
packedRowFromIndices context widthValue indicesValue = do
  unless (widthValue >= 0)
    (Left (InvariantViolation (context <> ": negative packed-row width " <> show widthValue)))
  traverse_ validateIndex indicesValue
  let wordValues =
        U.create $ do
          mutableWords <- UM.replicate (wordCountForWidth widthValue) 0
          traverse_ (toggleIndex mutableWords) indicesValue
          pure mutableWords
  Right (packedRowFromWords widthValue wordValues)
  where
    validateIndex indexValue
      | indexValue < 0 || indexValue >= widthValue =
          Left
            ( InvariantViolation
                ( context
                    <> ": packed coordinate "
                    <> show indexValue
                    <> " is outside width "
                    <> show widthValue
                )
            )
      | otherwise = Right ()

    toggleIndex :: UM.MVector state Word64 -> Int -> ST state ()
    toggleIndex mutableWords indexValue = do
      let wordIndex = indexValue `div` wordBits
          bitIndex = indexValue `mod` wordBits
      oldWord <- UM.read mutableWords wordIndex
      UM.write mutableWords wordIndex (oldWord `xor` bit bitIndex)

packedRowIndices :: PackedRow -> [Int]
packedRowIndices PackedRow {prWidth, prWords} =
  reverse (U.ifoldl' collectWord [] prWords)
  where
    collectWord accumulated wordIndex wordValue =
      collectBits accumulated (wordIndex * wordBits) wordValue

    collectBits accumulated baseIndex remainingWord
      | remainingWord == 0 = accumulated
      | otherwise =
          let bitIndex = countTrailingZeros remainingWord
              coordinateValue = baseIndex + bitIndex
              nextWord = clearBit remainingWord bitIndex
           in collectBits
                ( if coordinateValue < prWidth
                    then coordinateValue : accumulated
                    else accumulated
                )
                baseIndex
                nextWord

packedRowMember :: Int -> PackedRow -> Bool
packedRowMember indexValue PackedRow {prWidth, prWords}
  | indexValue < 0 || indexValue >= prWidth = False
  | otherwise =
      maybe
        False
        (`testBit` (indexValue `mod` wordBits))
        (prWords U.!? (indexValue `div` wordBits))

packedRowIsZero :: PackedRow -> Bool
packedRowIsZero = (== 0) . prNonZeroCount

xorSameWidth :: PackedRow -> PackedRow -> PackedRow
xorSameWidth leftRow rightRow =
  packedRowFromWords
    (prWidth leftRow)
    (U.zipWith xor (prWords leftRow) (prWords rightRow))

packedRowXor :: String -> PackedRow -> PackedRow -> Either MoonlightError PackedRow
packedRowXor context leftRow rightRow
  | prWidth leftRow /= prWidth rightRow =
      Left
        ( InvariantViolation
            ( context
                <> ": packed-row width mismatch "
                <> show (prWidth leftRow, prWidth rightRow)
            )
        )
  | otherwise = Right (xorSameWidth leftRow rightRow)

packedRowRemap ::
  String ->
  Int ->
  (Int -> Maybe Int) ->
  PackedRow ->
  Either MoonlightError PackedRow
packedRowRemap context targetWidth remapIndex sourceRow = do
  remappedIndices <- traverse remapOne (packedRowIndices sourceRow)
  packedRowFromIndices context targetWidth remappedIndices
  where
    remapOne sourceIndex =
      case remapIndex sourceIndex of
        Nothing ->
          Left
            ( InvariantViolation
                ( context
                    <> ": no target coordinate for source coordinate "
                    <> show sourceIndex
                )
            )
        Just targetIndex -> Right targetIndex

type PackedLinearMap :: Type
data PackedLinearMap = PackedLinearMap
  { plmDomain :: !Int
  , plmCodomain :: !Int
  , plmColumns :: !(Vector PackedRow)
  }
  deriving stock (Eq, Show)

packedLinearMapDomain :: PackedLinearMap -> Int
packedLinearMapDomain = plmDomain

packedLinearMapCodomain :: PackedLinearMap -> Int
packedLinearMapCodomain = plmCodomain

packedLinearMapColumns :: PackedLinearMap -> Vector PackedRow
packedLinearMapColumns = plmColumns

packedLinearMapFromColumns ::
  String ->
  Int ->
  Int ->
  Vector PackedRow ->
  Either MoonlightError PackedLinearMap
packedLinearMapFromColumns context domainValue codomainValue columnValues
  | domainValue < 0 || codomainValue < 0 =
      Left
        ( InvariantViolation
            ( context
                <> ": negative linear-map shape "
                <> show (codomainValue, domainValue)
            )
        )
  | V.length columnValues /= domainValue =
      Left
        ( InvariantViolation
            ( context
                <> ": received "
                <> show (V.length columnValues)
                <> " columns for domain dimension "
                <> show domainValue
            )
        )
  | otherwise = do
      traverse_ validateColumn (V.toList (V.indexed columnValues))
      Right
        PackedLinearMap
          { plmDomain = domainValue
          , plmCodomain = codomainValue
          , plmColumns = columnValues
          }
  where
    validateColumn (columnIndex, columnValue)
      | prWidth columnValue == codomainValue = Right ()
      | otherwise =
          Left
            ( InvariantViolation
                ( context
                    <> ": column "
                    <> show columnIndex
                    <> " has width "
                    <> show (prWidth columnValue)
                    <> ", expected "
                    <> show codomainValue
                )
            )

packedLinearMapFromEntries ::
  String ->
  Int ->
  Int ->
  [(Int, Int)] ->
  Either MoonlightError PackedLinearMap
packedLinearMapFromEntries context domainValue codomainValue entriesValue = do
  unless (domainValue >= 0 && codomainValue >= 0)
    (Left (InvariantViolation (context <> ": negative linear-map shape " <> show (codomainValue, domainValue))))
  traverse_ validateEntry entriesValue
  columnsValue <-
    traverse
      (\columnIndex ->
        packedRowFromIndices
          (context <> ": column " <> show columnIndex)
          codomainValue
          (IntMap.findWithDefault [] columnIndex entriesByColumn)
      )
      [0 .. domainValue - 1]
  packedLinearMapFromColumns context domainValue codomainValue (V.fromList columnsValue)
  where
    entriesByColumn =
      foldl'
        (\entryMap (rowIndex, columnIndex) ->
          IntMap.insertWith (flip (<>)) columnIndex [rowIndex] entryMap
        )
        IntMap.empty
        entriesValue

    validateEntry (rowIndex, columnIndex)
      | rowIndex < 0 || rowIndex >= codomainValue =
          Left
            ( InvariantViolation
                ( context
                    <> ": row index "
                    <> show rowIndex
                    <> " is outside codomain dimension "
                    <> show codomainValue
                )
            )
      | columnIndex < 0 || columnIndex >= domainValue =
          Left
            ( InvariantViolation
                ( context
                    <> ": column index "
                    <> show columnIndex
                    <> " is outside domain dimension "
                    <> show domainValue
                )
            )
      | otherwise = Right ()

zeroPackedLinearMap :: String -> Int -> Int -> Either MoonlightError PackedLinearMap
zeroPackedLinearMap context domainValue codomainValue
  | domainValue < 0 || codomainValue < 0 =
      Left
        ( InvariantViolation
            ( context
                <> ": negative linear-map shape "
                <> show (codomainValue, domainValue)
            )
        )
  | otherwise = do
      zeroColumn <- emptyPackedRow codomainValue
      packedLinearMapFromColumns context domainValue codomainValue (V.replicate domainValue zeroColumn)

identityPackedLinearMap :: String -> Int -> Either MoonlightError PackedLinearMap
identityPackedLinearMap context dimensionValue = do
  columnsValue <- traverse (unitPackedRow context dimensionValue) [0 .. dimensionValue - 1]
  packedLinearMapFromColumns context dimensionValue dimensionValue (V.fromList columnsValue)

xorIntoMutable :: UM.MVector state Word64 -> U.Vector Word64 -> ST state ()
xorIntoMutable mutableTarget sourceWords =
  traverse_ xorWordAt [0 .. UM.length mutableTarget - 1]
  where
    xorWordAt wordIndex = do
      oldWord <- UM.read mutableTarget wordIndex
      let sourceWord = maybe 0 id (sourceWords U.!? wordIndex)
      UM.write mutableTarget wordIndex (oldWord `xor` sourceWord)

applyPackedLinearMap :: String -> PackedLinearMap -> PackedRow -> Either MoonlightError PackedRow
applyPackedLinearMap context linearMap sourceRow
  | prWidth sourceRow /= plmDomain linearMap =
      Left
        ( InvariantViolation
            ( context
                <> ": vector width "
                <> show (prWidth sourceRow)
                <> " does not match map domain "
                <> show (plmDomain linearMap)
            )
        )
  | otherwise = do
      selectedColumns <-
        traverse
          (\columnIndex -> lookupVector (context <> ": source column") columnIndex (plmColumns linearMap))
          (packedRowIndices sourceRow)
      let resultWords =
            runST $ do
              mutableResult <- UM.replicate (wordCountForWidth (plmCodomain linearMap)) 0
              traverse_ (xorIntoMutable mutableResult . prWords) selectedColumns
              U.freeze mutableResult
      Right (packedRowFromWords (plmCodomain linearMap) resultWords)

composePackedLinearMaps ::
  String ->
  PackedLinearMap ->
  PackedLinearMap ->
  Either MoonlightError PackedLinearMap
composePackedLinearMaps context leftMap rightMap
  | plmDomain leftMap /= plmCodomain rightMap =
      Left
        ( InvariantViolation
            ( context
                <> ": incompatible map shapes "
                <> show (plmCodomain leftMap, plmDomain leftMap)
                <> " and "
                <> show (plmCodomain rightMap, plmDomain rightMap)
            )
        )
  | otherwise = do
      productColumns <-
        traverse
          (applyPackedLinearMap (context <> ": product column") leftMap)
          (plmColumns rightMap)
      packedLinearMapFromColumns context (plmDomain rightMap) (plmCodomain leftMap) productColumns

addPackedLinearMaps ::
  String ->
  PackedLinearMap ->
  PackedLinearMap ->
  Either MoonlightError PackedLinearMap
addPackedLinearMaps context leftMap rightMap
  | mapShape leftMap /= mapShape rightMap =
      Left
        ( InvariantViolation
            ( context
                <> ": linear-map shape mismatch "
                <> show (mapShape leftMap, mapShape rightMap)
            )
        )
  | otherwise =
      packedLinearMapFromColumns
        context
        (plmDomain leftMap)
        (plmCodomain leftMap)
        (V.zipWith xorSameWidth (plmColumns leftMap) (plmColumns rightMap))

packedLinearMapIsZero :: PackedLinearMap -> Bool
packedLinearMapIsZero = V.all packedRowIsZero . plmColumns

mapShape :: PackedLinearMap -> (Int, Int)
mapShape mapValue = (plmCodomain mapValue, plmDomain mapValue)

type PackedSpan :: Type
data PackedSpan = PackedSpan
  { psWidth :: !Int
  , psBasis :: !(IntMap PackedRow)
  }
  deriving stock (Eq, Show)

emptyPackedSpan :: Int -> Either MoonlightError PackedSpan
emptyPackedSpan widthValue
  | widthValue < 0 = Left (InvariantViolation ("emptyPackedSpan: negative width " <> show widthValue))
  | otherwise = Right PackedSpan {psWidth = widthValue, psBasis = IntMap.empty}

packedSpanFromRows :: String -> Int -> [PackedRow] -> Either MoonlightError PackedSpan
packedSpanFromRows context widthValue rowsValue = do
  initialSpan <- emptyPackedSpan widthValue
  foldM (\spanValue rowValue -> snd <$> admitPackedRow context rowValue spanValue) initialSpan rowsValue

mutablePivot :: Int -> UM.MVector state Word64 -> ST state (Maybe Int)
mutablePivot widthValue mutableWords = do
  candidates <- traverse pivotAtWord [0 .. UM.length mutableWords - 1]
  pure (foldr firstJust Nothing candidates)
  where
    pivotAtWord wordIndex = do
      wordValue <- UM.read mutableWords wordIndex
      pure
        ( if wordValue == 0
            then Nothing
            else
              let pivotIndex = wordIndex * wordBits + countTrailingZeros wordValue
               in if pivotIndex < widthValue then Just pivotIndex else Nothing
        )

    firstJust :: Maybe Int -> Maybe Int -> Maybe Int
    firstJust left right =
      case left of
        Nothing -> right
        Just _ -> left

reduceMutableWords :: Int -> IntMap PackedRow -> UM.MVector state Word64 -> ST state ()
reduceMutableWords widthValue basisRows mutableWords = do
  maybePivot <- mutablePivot widthValue mutableWords
  case maybePivot of
    Nothing -> pure ()
    Just pivotIndex ->
      case IntMap.lookup pivotIndex basisRows of
        Nothing -> pure ()
        Just basisRow -> do
          xorIntoMutable mutableWords (prWords basisRow)
          reduceMutableWords widthValue basisRows mutableWords

reducePackedRowUnchecked :: PackedSpan -> PackedRow -> PackedRow
reducePackedRowUnchecked PackedSpan {psWidth, psBasis} rowValue =
  let reducedWords = runST $ do
        mutableWords <- U.thaw (prWords rowValue)
        reduceMutableWords psWidth psBasis mutableWords
        U.freeze mutableWords
   in packedRowFromWords psWidth reducedWords

reducePackedRow :: String -> PackedSpan -> PackedRow -> Either MoonlightError PackedRow
reducePackedRow context spanValue@PackedSpan {psWidth} rowValue
  | prWidth rowValue /= psWidth =
      Left
        ( InvariantViolation
            ( context
                <> ": row width "
                <> show (prWidth rowValue)
                <> " does not match span width "
                <> show psWidth
            )
        )
  | otherwise = Right (reducePackedRowUnchecked spanValue rowValue)

packedRowPivot :: PackedRow -> Maybe Int
packedRowPivot PackedRow {prWidth, prWords} =
  U.ifoldl' firstPivot Nothing prWords
  where
    firstPivot (Just pivotIndex) _ _ =
      Just pivotIndex
    firstPivot Nothing wordIndex wordValue
      | wordValue == 0 = Nothing
      | otherwise =
          let pivotIndex = wordIndex * wordBits + countTrailingZeros wordValue
           in if pivotIndex < prWidth then Just pivotIndex else Nothing

admitPackedRow ::
  String ->
  PackedRow ->
  PackedSpan ->
  Either MoonlightError (Maybe PackedRow, PackedSpan)
admitPackedRow context candidateRow spanValue@PackedSpan {psWidth, psBasis} = do
  reducedRow <- reducePackedRow context spanValue candidateRow
  case packedRowPivot reducedRow of
    Nothing -> Right (Nothing, spanValue)
    Just pivotIndex ->
      Right
        ( Just reducedRow
        , PackedSpan
            { psWidth = psWidth
            , psBasis = IntMap.insert pivotIndex reducedRow psBasis
            }
        )

type TrackedBasisRow :: Type
data TrackedBasisRow = TrackedBasisRow
  { tbrData :: !PackedRow
  , tbrWitness :: !PackedRow
  }
  deriving stock (Eq, Show)

type ColumnReduction :: Type
data ColumnReduction = ColumnReduction
  { crIndependentIndices :: !(Vector Int)
  , crKernelBasis :: !(Vector PackedRow)
  }
  deriving stock (Eq, Show)

reduceMutableTracked ::
  Int ->
  IntMap TrackedBasisRow ->
  UM.MVector state Word64 ->
  UM.MVector state Word64 ->
  ST state ()
reduceMutableTracked dataWidth basisRows mutableData mutableWitness = do
  maybePivot <- mutablePivot dataWidth mutableData
  case maybePivot of
    Nothing -> pure ()
    Just pivotIndex ->
      case IntMap.lookup pivotIndex basisRows of
        Nothing -> pure ()
        Just TrackedBasisRow {tbrData, tbrWitness} -> do
          xorIntoMutable mutableData (prWords tbrData)
          xorIntoMutable mutableWitness (prWords tbrWitness)
          reduceMutableTracked dataWidth basisRows mutableData mutableWitness

reduceTrackedRows ::
  String ->
  IntMap TrackedBasisRow ->
  PackedRow ->
  PackedRow ->
  Either MoonlightError (PackedRow, PackedRow)
reduceTrackedRows context basisRows dataRow witnessRow = do
  traverse_ validateBasis (IntMap.toList basisRows)
  let (dataWords, witnessWords) =
        runST $ do
          mutableData <- U.thaw (prWords dataRow)
          mutableWitness <- U.thaw (prWords witnessRow)
          reduceMutableTracked (prWidth dataRow) basisRows mutableData mutableWitness
          frozenData <- U.freeze mutableData
          frozenWitness <- U.freeze mutableWitness
          pure (frozenData, frozenWitness)
  Right
    ( packedRowFromWords (prWidth dataRow) dataWords
    , packedRowFromWords (prWidth witnessRow) witnessWords
    )
  where
    validateBasis (pivotIndex, TrackedBasisRow {tbrData, tbrWitness})
      | pivotIndex < 0 || pivotIndex >= prWidth dataRow =
          Left (InvariantViolation (context <> ": tracked pivot outside data width: " <> show pivotIndex))
      | prWidth tbrData /= prWidth dataRow =
          Left (InvariantViolation (context <> ": tracked data width mismatch"))
      | prWidth tbrWitness /= prWidth witnessRow =
          Left (InvariantViolation (context <> ": tracked witness width mismatch"))
      | otherwise = Right ()

reducePackedColumns ::
  String ->
  Int ->
  Vector PackedRow ->
  Either MoonlightError ColumnReduction
reducePackedColumns context codomainWidth columnsValue = do
  unless (codomainWidth >= 0)
    (Left (InvariantViolation (context <> ": negative codomain width " <> show codomainWidth)))
  traverse_ validateColumn (V.toList (V.indexed columnsValue))
  let domainWidth = V.length columnsValue
  (_, independentReversed, kernelReversed) <-
    foldM
      (reduceColumn domainWidth)
      (IntMap.empty, [], [])
      (V.toList (V.indexed columnsValue))
  Right
    ColumnReduction
      { crIndependentIndices = V.fromList (reverse independentReversed)
      , crKernelBasis = V.fromList (reverse kernelReversed)
      }
  where
    validateColumn (columnIndex, columnValue)
      | prWidth columnValue == codomainWidth = Right ()
      | otherwise =
          Left
            ( InvariantViolation
                ( context
                    <> ": column "
                    <> show columnIndex
                    <> " has width "
                    <> show (prWidth columnValue)
                    <> ", expected "
                    <> show codomainWidth
                )
            )

    reduceColumn domainWidth (basisRows, independentReversed, kernelReversed) (columnIndex, columnValue) = do
      witnessValue <- unitPackedRow (context <> ": witness") domainWidth columnIndex
      (reducedData, reducedWitness) <- reduceTrackedRows context basisRows columnValue witnessValue
      case packedRowPivot reducedData of
        Nothing -> Right (basisRows, independentReversed, reducedWitness : kernelReversed)
        Just pivotIndex ->
          Right
            ( IntMap.insert
                pivotIndex
                TrackedBasisRow {tbrData = reducedData, tbrWitness = reducedWitness}
                basisRows
            , columnIndex : independentReversed
            , kernelReversed
            )

type PackedCoordinateSolver :: Type
data PackedCoordinateSolver = PackedCoordinateSolver
  { pcsAmbientWidth :: !Int
  , pcsBasisCardinality :: !Int
  , pcsBasisRows :: !(IntMap TrackedBasisRow)
  }
  deriving stock (Eq, Show)

packedCoordinateSolver ::
  String ->
  Int ->
  Vector PackedRow ->
  Either MoonlightError PackedCoordinateSolver
packedCoordinateSolver context ambientWidth basisColumns = do
  let basisCardinality = V.length basisColumns
  unless (ambientWidth >= 0)
    (Left (InvariantViolation (context <> ": negative ambient width " <> show ambientWidth)))
  unless (basisCardinality <= ambientWidth)
    ( Left
        ( InvariantViolation
            ( context
                <> ": basis cardinality "
                <> show basisCardinality
                <> " exceeds ambient width "
                <> show ambientWidth
            )
        )
    )
  basisRows <-
    foldM insertBasisColumn IntMap.empty (V.toList (V.indexed basisColumns))
  Right
    PackedCoordinateSolver
      { pcsAmbientWidth = ambientWidth
      , pcsBasisCardinality = basisCardinality
      , pcsBasisRows = basisRows
      }
  where
    insertBasisColumn basisRows (basisIndex, columnValue)
      | prWidth columnValue /= ambientWidth =
          Left
            ( InvariantViolation
                ( context
                    <> ": basis column "
                    <> show basisIndex
                    <> " has width "
                    <> show (prWidth columnValue)
                    <> ", expected "
                    <> show ambientWidth
                )
            )
      | otherwise = do
          witnessValue <- unitPackedRow (context <> ": basis witness") (V.length basisColumns) basisIndex
          (reducedData, reducedWitness) <-
            reduceTrackedRows (context <> ": basis reduction") basisRows columnValue witnessValue
          case packedRowPivot reducedData of
            Nothing ->
              Left
                ( InvariantViolation
                    ( context
                        <> ": supplied basis columns are linearly dependent at column "
                        <> show basisIndex
                    )
                )
            Just pivotIndex ->
              Right
                ( IntMap.insert
                    pivotIndex
                    TrackedBasisRow {tbrData = reducedData, tbrWitness = reducedWitness}
                    basisRows
                )

coordinatesInPackedBasis ::
  String ->
  PackedCoordinateSolver ->
  PackedRow ->
  Either MoonlightError (Maybe PackedRow)
coordinatesInPackedBasis context PackedCoordinateSolver {pcsAmbientWidth, pcsBasisCardinality, pcsBasisRows} vectorValue
  | prWidth vectorValue /= pcsAmbientWidth =
      Left
        ( InvariantViolation
            ( context
                <> ": vector width "
                <> show (prWidth vectorValue)
                <> " does not match ambient width "
                <> show pcsAmbientWidth
            )
        )
  | otherwise = do
      zeroWitness <- emptyPackedRow pcsBasisCardinality
      (reducedData, reducedWitness) <-
        reduceTrackedRows context pcsBasisRows vectorValue zeroWitness
      Right (if packedRowIsZero reducedData then Just reducedWitness else Nothing)

inverseFromPackedBasisColumns ::
  String ->
  Vector PackedRow ->
  Either MoonlightError PackedLinearMap
inverseFromPackedBasisColumns context basisColumns = do
  let dimensionValue = V.length basisColumns
  solver <- packedCoordinateSolver (context <> ": coordinate solver") dimensionValue basisColumns
  inverseColumns <- traverse (inverseColumn solver) [0 .. dimensionValue - 1]
  packedLinearMapFromColumns context dimensionValue dimensionValue (V.fromList inverseColumns)
  where
    inverseColumn solver columnIndex = do
      unitVector <- unitPackedRow (context <> ": inverse unit vector") (V.length basisColumns) columnIndex
      maybeCoordinates <- coordinatesInPackedBasis (context <> ": inverse coordinates") solver unitVector
      case maybeCoordinates of
        Nothing -> Left (InvariantViolation (context <> ": basis columns do not span the ambient space"))
        Just coordinatesValue -> Right coordinatesValue

rankPackedRowsByReduction :: Int -> [U.Vector Word64] -> Int
rankPackedRowsByReduction widthValue rowWords
  | widthValue <= 0 = 0
  | otherwise =
      IntMap.size
        ( psBasis
            ( foldl'
                admitUnchecked
                PackedSpan {psWidth = widthValue, psBasis = IntMap.empty}
                (packedRowFromWords widthValue <$> rowWords)
            )
        )
  where
    admitUnchecked spanValue@PackedSpan {psWidth, psBasis} rowValue =
      let reducedRow = reducePackedRowUnchecked spanValue rowValue
       in case packedRowPivot reducedRow of
            Nothing -> spanValue
            Just pivotIndex ->
              PackedSpan
                { psWidth = psWidth
                , psBasis = IntMap.insert pivotIndex reducedRow psBasis
                }

lookupVector :: String -> Int -> Vector value -> Either MoonlightError value
lookupVector context indexValue vectorValue =
  case vectorValue V.!? indexValue of
    Nothing ->
      Left
        ( InvariantViolation
            ( context
                <> ": index "
                <> show indexValue
                <> " is outside vector length "
                <> show (V.length vectorValue)
            )
        )
    Just value -> Right value

traverse_ :: Applicative f => (a -> f b) -> [a] -> f ()
traverse_ actionValue =
  foldr (\value accumulated -> actionValue value *> accumulated) (pure ())