packages feed

cybus-0.2.0.0: src/Cybus/Mat.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneKindSignatures #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilyDependencies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ViewPatterns #-}

{- |
Module      : Cybus.Mat
Description : type level indexed multi-dimensional matrix
Copyright   : (c) Grant Weyburne, 2022
License     : BSD-3
-}
module Cybus.Mat (
  type Mat,
  mVec,
  mIndices,
  pattern Mat,
  pattern MatU,
  Vec,
  Mat2,
  Mat3,
  Mat4,
  Mat5,
  Mat6,
  MatN,

  -- * cons/snoc lenses
  ConsMatC (..),
  SnocMatC (..),
  Eof1 (..),
  EofN (..),

  -- * tuple conversions
  MatTupleC (..),
  MatTupleT,
  ListTupleCInternal (..),

  -- * converters
  MatConvertersC (..),
  nestedListToMatValidated,
  nestedNonEmptyToMatValidated,
  MatToNestedVecT,

  -- * bulk construct matrix
  mkMat,
  mkMatC,
  mat,
  mat',
  vec,
  vec',
  mat2,
  mat2',
  gen',
  gen,
  mm,
  buildMat,

  -- * vector/matrix builders
  (.:),
  se1,
  (.::),
  se2,
  (.|),
  (.||),

  -- * indexing
  ixMat,
  ixMat',
  setMat,
  updateMat,
  indexMat,
  finMatRows,

  -- * reverse
  reverseRows,

  -- * sort
  sortByRows,
  multMat,
  -- dot,
  DotC (..),

  -- * zip
  zipWithMat,
  zipWithMat3,
  zipMat,
  zipWithMatA,
  izipWith,
  izipWithM,

  -- * general
  cartesian,
  pureMat,
  replicateMat,

  -- * row operations
  deleteRow,
  deleteRow',
  insertRow,
  insertRow',
  swapRow,
  swapRow',
  _row,
  _row',
  rows,
  unrows,
  _rows,
  wrapRows1,
  indexRow,

  -- * column operations
  deleteCol,
  deleteCol',
  insertCol,
  insertCol',
  swapCol,
  swapCol',
  _col,
  _col',
  swapMat,
  swapMat',
  appendV,
  appendH,
  permutationsMat,
  findMatElems,

  -- * bulk updates
  bulkMat,
  updatesMat,
  getsMat,
  setsMat,
  nonEmptyMatsToMat,

  -- * reshape
  _transposeMat,
  transposeMat,
  toND,
  MatToNDT,
  toVec,
  toMat2,
  toMat3,
  concatMat,
  redim,
  reverseDim,

  -- * subset and slicing
  SliceC (..),
  SliceT,
  SliceC' (..),
  SliceT',
  slice,
  sliceUpdate,
  sliceToFinMat,
  SliceToFinMatT,
  ixSlice,
  ixSlice',
  subsetRows,
  subsetCols,
  diagonal,
  rowsToMat,

  -- * splitting
  chunkNV,
  chunkNVMat,

  -- * leaf methods
  LeafC (..),
  traverseLeafSimple,
  mapLeafSimple,
  foldMapLeaf,
  foldMapLeafR,
  mapLeaf,
  mapLeafS,
  mapLeafSimpleS,
  foldLeaf,
  toLeaves,
  mapCols,
  mapCols',

  -- * read/show/print methods
  ShowMatC (..),
  ShowOpts (..),
  defShowOpts,
  prtMat,
  showMat,
  readMatP,
  readMat,
  readMat2,
  readVec,

  -- * row lenses
  Row1 (..),
  Row2 (..),
  Row3 (..),
  Row4 (..),
  Row5 (..),
  Row6 (..),
  Row7 (..),
  Row8 (..),
  Row9 (..),
  Row10 (..),

  -- * column lenses
  _c1,
  _c2,
  _c3,
  _c4,
  _c5,
  _c6,
  _c7,
  _c8,
  _c9,
  _c10,

  -- * miscellaneous
  finMatMatrix,
  finMatMatrix',

  -- * scans
  scanlVec,
  scanrVec,
  postscanlMat,
  postscanrMat,

  -- ** coercion methods to set the dimensions of a matrix
  dim1,
  dim2,
  dim3,
  dim4,
  dim5,
  dim6,
  dim7,
  dim8,
  dim9,
  dim10,
) where

import Control.Applicative
import Control.Applicative.Backwards
import Control.Arrow
import Control.DeepSeq
import Control.Monad
import qualified Control.Monad.State.Strict as S
import Control.Monad.Zip
import Cybus.Fin
import Cybus.FinMat
import Cybus.NatHelper
import Data.Bool
import Data.Coerce
import Data.Distributive
import Data.Foldable
import Data.Foldable.WithIndex
import qualified Data.Functor.Apply as Apply
import qualified Data.Functor.Bind as Bind
import Data.Functor.Identity
import Data.Functor.Rep
import Data.Functor.WithIndex
import Data.Kind
import qualified Data.List as L
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.List.NonEmpty as N
import Data.Pos
import Data.Semigroup
import Data.Semigroup.Foldable
import Data.Semigroup.Traversable
import Data.String
import Data.Traversable.WithIndex
import Data.Tuple
import Data.Vector (Vector)
import qualified Data.Vector as V
import GHC.Enum
import qualified GHC.Exts as GE (IsList (..))
import GHC.Generics (Generic, Generic1)
import qualified GHC.Read as GR
import GHC.Stack
import qualified GHC.TypeLits as GL
import GHC.TypeNats (Nat)
import qualified GHC.TypeNats as GN
import Primus.Enum
import Primus.Error
import Primus.Fold
import Primus.Lens
import Primus.NonEmpty
import Primus.Num1
import Primus.One
import Primus.Rep
import qualified Primus.TypeLevel as TP
import qualified Text.ParserCombinators.ReadP as P
import qualified Text.ParserCombinators.ReadPrec as PC

-- | definition of a matrix
type Mat :: [Nat] -> Type -> Type
data Mat ns a = MatUnsafe !(Vector a) !(NonEmpty Pos)
  deriving stock (Functor, Traversable, Foldable, Generic, Generic1, Eq, Ord)
  deriving anyclass (NFData, NFData1)

-- | accessor for the relative position within a matrix
mVec :: Mat ns a -> Vector a
mVec (MatUnsafe v _) = v

-- | accessor for the indices of a matrix
mIndices :: Mat ns a -> NonEmpty Pos
mIndices (MatUnsafe _ ns) = ns

-- | convenient type synonym for a 1d matrix
type Vec :: Nat -> Type -> Type
type Vec n = Mat '[n]

-- | convenient type synonym for a 2d matrix
type Mat2 :: Nat -> Nat -> Type -> Type
type Mat2 n m = Mat '[n, m]

-- | convenient type synonym for a 3d matrix
type Mat3 :: Nat -> Nat -> Nat -> Type -> Type
type Mat3 n m p = Mat '[n, m, p]

-- | convenient type synonym for a 4d matrix
type Mat4 :: Nat -> Nat -> Nat -> Nat -> Type -> Type
type Mat4 n m p q = Mat '[n, m, p, q]

-- | convenient type synonym for a 5d matrix
type Mat5 :: Nat -> Nat -> Nat -> Nat -> Nat -> Type -> Type
type Mat5 n m p q r = Mat '[n, m, p, q, r]

-- | convenient type synonym for a 6d matrix
type Mat6 :: Nat -> Nat -> Nat -> Nat -> Nat -> Nat -> Type -> Type
type Mat6 n m p q r s = Mat '[n, m, p, q, r, s]

-- | convenient type synonym for specifying the dimensions of a matrix using the 'NN' type family
type MatN :: Nat -> Type -> Type
type MatN n = Mat (NN n)

-- | readonly pattern synonym for a matrix
{-# COMPLETE Mat #-}

pattern Mat ::
  forall (ns :: [Nat]) a.
  Vector a ->
  NonEmpty Pos ->
  Mat ns a
pattern Mat v ps <- MatUnsafe v ps

{-# COMPLETE MatIU #-}

-- | bidirectional pattern synonym for simple validation of a matrix before construction
pattern MatIU ::
  forall (ns :: [Nat]) a.
  HasCallStack =>
  Vector a ->
  NonEmpty Pos ->
  Mat ns a
pattern MatIU v ps <-
  MatUnsafe v ps
  where
    MatIU = frp .@ mkMat -- dont change this: frp is needed

{-# COMPLETE MatU #-}

-- | bidirectional pattern synonym for validating a matrix before construction with 'NS' constraint for additional typelevel validation
pattern MatU ::
  forall (ns :: [Nat]) a.
  (NS ns, HasCallStack) =>
  Vector a ->
  NonEmpty Pos ->
  Mat ns a
pattern MatU v ps <-
  MatUnsafe v ps
  where
    MatU = frp .@ mkMatC -- dont change this: frp is needed

instance (Bounded a, Enum a) => Num1 (Mat ns a) where
  fromInteger1 = toEnumTraversable
  toInteger1 = fromEnumFoldable1 -- need this as Enum is only Int but containers can be larger ie Integer

instance (Enum a, Bounded a, NS ns) => Enum (Mat ns a) where
  toEnum = forceRight "Enum Mat:toEnum" . toEnumRep . toInteger
  fromEnum = forceRight "Enum Mat:fromEnum" . integerToIntSafe . fromEnumFoldable1
  enumFrom = boundedEnumFrom
  enumFromThen = boundedEnumFromThen

instance (NS ns, Bounded a) => Bounded (Mat ns a) where
  minBound = pure minBound
  maxBound = pure maxBound

instance (c ~ Char, NS ns) => IsString (Mat ns c) where
  fromString = mat

-- | generate a 'Mat' using a list
mat, mat' :: forall ns a. (HasCallStack, NS ns) => [a] -> Mat ns a
mat = fr . matImpl False
mat' = fr . matImpl True

matImpl :: forall ns a. NS ns => Bool -> [a] -> Either String (Mat ns a)
matImpl b = \case
  [] -> Left "matImpl: no data"
  x : xs -> do
    let ns = fromNSP @ns
        n = productP ns
    (as, zs) <- splitAt1GE n (x :| xs)
    case (b, zs) of
      (True, _ : _) -> Left "matImpl: found extras"
      _o -> Right $ MatU (V.fromListN (unP n) (N.toList as)) ns

-- | used by 'pure' so dont call pure from here
pureMat :: forall ns a. NS ns => a -> Mat ns a
pureMat a =
  let ns = fromNSP @ns
   in MatU (V.replicate (productPInt ns) a) ns

-- | creates a matrix of first dimension "n" by replicating the input matrix "n" times
replicateMat :: forall n n1 ns a. PosT n => Mat (n1 ': ns) a -> Mat (n ': n1 ': ns) a
replicateMat (Mat v ns) =
  let n = fromNP @n
   in MatIU (V.concat (replicate (unP n) v)) (n N.<| ns)

instance (NS ns, Num a) => Num (Mat ns a) where
  (+) = liftA2 (+)
  (-) = liftA2 (-)
  (*) = liftA2 (*)
  negate = fmap negate
  signum = fmap signum
  fromInteger = pure . fromInteger
  abs = fmap abs

instance (NS ns, Fractional a) => Fractional (Mat ns a) where
  (/) = liftA2 (/)
  recip = fmap recip
  fromRational = pure . fromRational

instance NS ns => Applicative (Mat ns) where
  pure = pureMat
  (<*>) = ap2

ap2 :: Mat ns (a -> b) -> Mat ns a -> Mat ns b
ap2 (Mat vab ps) (Mat va _) = MatIU (V.zipWith id vab va) ps -- ziplist style

ap3 :: NS ns => (a -> Mat ns b) -> Mat ns a -> Mat ns b
ap3 f = imap (\fn -> indexMat fn . f)

instance Apply.Apply (Mat ns) where
  (<.>) = ap2

instance NS ns => Monad (Mat ns) where
  (>>=) = flip ap3

instance NS ns => Bind.Bind (Mat ns) where
  (>>-) = flip ap3

instance NS ns => MonadZip (Mat ns) where
  mzipWith = zipWithMat

-- | zip two matrices using a combining function
zipWithMat :: (a -> b -> c) -> Mat ns a -> Mat ns b -> Mat ns c
zipWithMat f (Mat v ps) (Mat w _) = MatIU (V.zipWith f v w) ps

-- | zip three matrices using a combining function
zipWithMat3 :: (a -> b -> c -> d) -> Mat ns a -> Mat ns b -> Mat ns c -> Mat ns d
zipWithMat3 f (Mat v ps) (Mat w _) (Mat x _) = MatIU (V.zipWith3 f v w x) ps

-- | zip two matrices
zipMat :: Mat ns a -> Mat ns b -> Mat ns (a, b)
zipMat = zipWithMat (,)

-- | 'zipWithMat' with an Applicative or use 'Primus.Fold.zipWithT' but that needs a 'NS' constraint
zipWithMatA ::
  Applicative f =>
  (a -> b -> f c) ->
  Mat ns a ->
  Mat ns b ->
  f (Mat ns c)
zipWithMatA f = traverse (uncurry f) .@ zipMat

-- | 'zipWithMat' with an index or use 'Primus.Rep.izipWithR'
izipWith ::
  NS ns =>
  (FinMat ns -> a -> b -> c) ->
  Mat ns a ->
  Mat ns b ->
  Mat ns c
izipWith f = zipWithMat3 f finMatMatrix

-- | 'zipWithMatA' with an index or use 'Primus.Rep.izipWithR' if "f" is 'Data.Distributive.Distributive'
izipWithM ::
  (NS ns, Applicative f) =>
  (FinMat ns -> a -> b -> f c) ->
  Mat ns a ->
  Mat ns b ->
  f (Mat ns c)
izipWithM f = itraverse (uncurry . f) .@ zipMat

instance Foldable1 (Mat ns) where
  foldMap1 f = foldMap1 f . nep . V.toList . mVec -- cant be empty (dont use 'toNonEmpty')

instance Traversable1 (Mat ns) where
  traverse1 f (Mat v ps) =
    case V.toList v of
      [] -> programmError "Mat: traverse1: empty vector"
      a : as -> (\(b :| bs) -> MatIU (V.fromList (b : bs)) ps) <$> traverse1 f (a :| as)

instance Semigroup a => Semigroup (Mat ns a) where
  (<>) = zipWithMat (<>)
instance (Monoid a, NS ns) => Monoid (Mat ns a) where
  mempty = pure mempty

instance NS ns => FunctorWithIndex (FinMat ns) (Mat ns) where
  imap f = snd . L.mapAccumL (\i a -> (i + 1, f (FinMatU i (fromNSP @ns)) a)) 0

instance NS ns => FoldableWithIndex (FinMat ns) (Mat ns) where
  ifoldMap f = fold . imap f

-- todo: write a dedicated version
instance NS ns => TraversableWithIndex (FinMat ns) (Mat ns) where
  itraverse f = sequenceA . imap f

instance NS ns => Distributive (Mat ns) where
  collect agb fa =
    let z = agb <$> fa
     in imap (\fm -> const ((V.! fmPos fm) . mVec <$> z)) (pure ())

-- | index into a matrix
indexMat :: FinMat ns -> Mat ns a -> a
indexMat fm = (V.! fmPos fm) . mVec

-- | create a matrix of matrix indices for a given size "ns"
finMatMatrix :: forall ns. NS ns => Mat ns (FinMat ns)
finMatMatrix = finMatMatrix' (pure ())

-- | fill an existing matrix with indices
finMatMatrix' :: forall ns x. NS ns => Mat ns x -> Mat ns (FinMat ns)
finMatMatrix' = imap const

instance NS ns => Representable (Mat ns) where
  type Rep (Mat ns) = FinMat ns
  tabulate f = imap (const . f) (pure ())
  index = flip indexMat

instance NS ns => GE.IsList (Mat ns a) where
  type Item (Mat ns a) = a
  fromList = snd . fr . fillTraversable (pure ())
  toList = toListMat

-- | validate before creating a matrix
mkMat :: forall ns a. Vector a -> NonEmpty Pos -> Either String (Mat ns a)
mkMat v ps =
  let n1 = productPInt ps
      n2 = V.length v
      ret = n1 == n2
   in if ret
        then Right (MatUnsafe v ps)
        else Left $ "\n\nproduct of " ++ show (fromPositives ps) ++ "=" ++ show n1 ++ "\nvector length=" ++ show n2 ++ "\n"

-- | validate before creating a matrix with extra 'NS' constraint to check that "ns" and 'mIndices' match
mkMatC :: forall ns a. NS ns => Vector a -> NonEmpty Pos -> Either String (Mat ns a)
mkMatC v ps = do
  let ps1 = fromNSP @ns
  if ps == ps1
    then mkMat v ps
    else Left $ "\nns mismatch: expected: " ++ show (fromPositives ps1) ++ " but found " ++ show (fromPositives ps)

-- | generate a matrix passing the indices at that element to a user callback function
gen' :: forall ns a. NS ns => ([Int] -> a) -> Mat ns a
gen' f = tabulate (f . fromPositives . finMatToNonEmpty @ns)

-- | generate a matrix passing a relative position of the element to a user callback function
gen :: forall ns a. NS ns => (Int -> a) -> Mat ns a
gen f = tabulate (f . fmPos)

-- | generate a matrix using relative position starting at one
mm :: forall ns. NS ns => Mat ns Int
mm = gen (+ 1)

-- | lens that accesses a value inside a mat given a concrete mat index
ixMat :: forall (ns :: [Nat]) a. FinMat ns -> Lens' (Mat ns a) a
ixMat i = lens (indexMat i) (\s b -> setMat b i s)

-- | lens that accesses a value inside a mat using a type level index
ixMat' ::
  forall (is :: [Nat]) (ns :: [Nat]) a.
  FinMatC is ns =>
  Lens' (Mat ns a) a
ixMat' = ixMat (finMatC @is @ns)

-- | sets a value in a matrix
setMat :: a -> FinMat ns -> Mat ns a -> Mat ns a
setMat a fm (Mat v ps) = MatIU (V.update v (V.singleton (fmPos fm, a))) ps

-- | updates a value in a matrix
updateMat :: (a -> a) -> FinMat ns -> Mat ns a -> Mat ns a
updateMat f (FinMat i _) (Mat v ps) =
  let (v1, v2) = V.splitAt i v
   in case V.uncons v2 of
        Just (a, v2') -> MatIU (v1 <> V.cons (f a) v2') ps
        Nothing -> programmError $ "updateMat: i=" ++ show i

-- | cons a value with a 1d matrix
(.:) :: forall n a a'. a ~ a' => a -> Vec n a' -> Vec (1 GN.+ n) a'
a .: Mat v (p :| ps) = MatIU (V.cons a v) (succP p :| ps)

infixr 4 .:

-- | cons a matrix with a one-higher dimension matrix
(.::) :: forall n m ns a. Mat (m ': ns) a -> Mat (n ': m ': ns) a -> Mat (1 GN.+ n ': m ': ns) a
Mat v (_ :| _) .:: Mat v1 (p1 :| ps1) = MatIU (v <> v1) (succP p1 :| ps1)

infixr 3 .::

-- | combine two values together into 1d matrix
(.|) :: forall a a'. a ~ a' => a -> a' -> Vec 2 a'
a .| a' = MatU (V.cons a (V.singleton a')) (_2P :| [])

infixr 4 .|

-- | combine two matrices
(.||) :: forall m ns a. Mat (m ': ns) a -> Mat (m ': ns) a -> Mat (2 ': m ': ns) a
Mat v (_ :| _) .|| Mat v1 (p1 :| ps1) = MatIU (v <> v1) (_2P :| p1 : ps1)

infixr 3 .||

-- | last element in a 1d matrix
se1 :: forall a. a -> Vec 1 a
se1 a = MatU (V.singleton a) (_1P :| [])

-- | last element in a 2d or greater matrix
se2 :: forall n ns a. Mat (n ': ns) a -> Mat (1 ': n ': ns) a
se2 (Mat v ps) = MatIU v (_1P N.<| ps)

-- | create a 1d matrix from a list of values
vec :: forall n a. (HasCallStack, PosT n) => [a] -> Vec n a
vec = mat @'[n]

-- | create a 1d matrix from a list of values with the exact number of elements
vec' :: forall n a. (HasCallStack, PosT n) => [a] -> Vec n a
vec' = mat' @'[n]

-- | create a 2d matrix from a list of values
mat2 :: forall n m a. (HasCallStack, PosT n, PosT m) => [a] -> Mat2 n m a
mat2 = mat @'[n, m]

-- | create a 2d matrix from a list of values with the exact number of elements
mat2' :: forall n m a. (HasCallStack, PosT n, PosT m) => [a] -> Mat2 n m a
mat2' = mat' @'[n, m]

-- | map each column
mapCols ::
  forall n m ns a b.
  (FinMat (m ': n ': ns) -> Vec (TP.LastT (n ': ns)) a -> Vec (TP.LastT (n ': ns)) b) ->
  Mat (n ': m ': ns) a ->
  Mat (n ': m ': ns) b
mapCols f = transposeMat . mapLeafSimple f . transposeMat

-- | map each column with user state
mapCols' ::
  forall n m ns a b c.
  (FinMat (m ': n ': ns) -> c -> Vec (TP.LastT (n ': ns)) a -> (c, Vec (TP.LastT (n ': ns)) b)) ->
  c ->
  Mat (n ': m ': ns) a ->
  (c, Mat (n ': m ': ns) b)
mapCols' f c = fmap transposeMat . mapLeafSimpleS f c . transposeMat

-- | traverse over a nested leaf matrix only allowing changes to "a"
traverseLeafSimple ::
  (LeafC ns, Applicative m) =>
  (FinMat ns -> Vec (TP.LastT ns) a -> m (Vec (TP.LastT ns) b)) ->
  Mat ns a ->
  m (Mat ns b)
traverseLeafSimple f = fmap fromLeavesInternalC . traverseLeafC f

-- | map over a nested leaf matrix only allowing changes to "a"
mapLeafSimple ::
  LeafC ns =>
  (FinMat ns -> Vec (TP.LastT ns) a -> Vec (TP.LastT ns) b) ->
  Mat ns a ->
  Mat ns b
mapLeafSimple f = fromLeavesInternalC . runIdentity . traverseLeafC (Identity .@ f)

-- | foldmap over a nested leaf matrix
foldMapLeaf
  , foldMapLeafR ::
    (Monoid z, LeafC ns) =>
    (FinMat ns -> Vec (TP.LastT ns) a -> z) ->
    Mat ns a ->
    z
foldMapLeaf f = getConst . traverseLeafC (Const .@ f)
foldMapLeafR f = getConst . forwards . traverseLeafC ((Backwards . Const) .@ f)

-- | map over a nested leaf matrix
mapLeaf ::
  LeafC ns =>
  (FinMat ns -> Vec (TP.LastT ns) a -> b) ->
  Mat ns a ->
  Mat (TP.InitT ns) b
mapLeaf f = runIdentity . traverseLeafC (Identity .@ f)

-- | map over a nested leaf matrix with state
mapLeafS ::
  LeafC ns =>
  (FinMat ns -> c -> Vec (TP.LastT ns) a -> (c, b)) ->
  c ->
  Mat ns a ->
  (c, Mat (TP.InitT ns) b)
mapLeafS f c0 = swap . flip S.runState c0 . traverseLeafC (\i a -> S.state $ \c -> swap (f i c a))

-- | map over a nested leaf matrix only allowing changes to "a" and access to user state
mapLeafSimpleS ::
  LeafC ns =>
  (FinMat ns -> c -> Vec (TP.LastT ns) a -> (c, Vec (TP.LastT ns) b)) ->
  c ->
  Mat ns a ->
  (c, Mat ns b)
mapLeafSimpleS f c0 =
  second fromLeavesInternalC . swap . flip S.runState c0 . traverseLeafC (\i a -> S.state $ \c -> swap (f i c a))

-- | fold over a nested leaf matrix
foldLeaf ::
  LeafC ns =>
  (FinMat ns -> c -> Vec (TP.LastT ns) a -> c) ->
  c ->
  Mat ns a ->
  c
foldLeaf f = fst .@ mapLeafS g
 where
  g fn c m = (f fn c m, ())

-- | convert to nested matrix with 1d leaves
toLeaves ::
  LeafC ns =>
  Mat ns a ->
  Mat (TP.InitT ns) (Vec (TP.LastT ns) a)
toLeaves = mapLeaf (const id)

-- | methods for accessing all the leaf rows of a matrix: restricted to 2d hence this class
class LeafC ns where
  traverseLeafC ::
    Applicative m =>
    (FinMat ns -> Vec (TP.LastT ns) a -> m b) ->
    Mat ns a ->
    m (Mat (TP.InitT ns) b)
  fromLeavesInternalC ::
    Mat (TP.InitT ns) (Vec (TP.LastT ns) a) ->
    Mat ns a

instance
  GL.TypeError ( 'GL.Text "LeafC '[]: rows for empty indices are not supported") =>
  LeafC '[]
  where
  traverseLeafC = compileError "LeafC:traverseLeafC"
  fromLeavesInternalC = compileError "LeafC:fromLeavesInternalC"

instance
  GL.TypeError ( 'GL.Text "LeafC: rows for 1D are not supported") =>
  LeafC '[n]
  where
  traverseLeafC = compileError "LeafC:traverseLeafC"
  fromLeavesInternalC = compileError "LeafC:fromLeavesInternalC"

instance LeafC (n ': m ': ns) where
  traverseLeafC f w@(Mat _ (n :| ps)) =
    case ps of
      m : ns ->
        let (ny0, nx) = unsnoc1 (m :| ns)
            ny = n :| ny0
            g x = S.state $ \i -> (f (frp $ mkFinMat i (n :| m : ns)) x, i + unP nx)
            zs = frp $ chunkNVMat (units1 (productP ny)) (nx :| []) w
            tbs = sequenceA $ flip S.evalState 0 $ traverse g zs
         in (\zz -> MatIU (V.fromList (N.toList zz)) ny) <$> tbs
      [] -> programmError "traverseLeafC: missing indices"

  fromLeavesInternalC = coerce . concatMat

-- | get the start index for each row in a mat
finMatRows :: forall ns. NS ns => NonEmpty (FinMat ns)
finMatRows =
  let (xs, _) = unsnoc1 (fromNSP @ns)
      ns = appendL1 xs (_1P :| [])
      fns = sequenceA $ N.map enumTo1 ns
   in forceRightP "finMatRows" $ traverse1 nonEmptyToFinMat fns

-- | reverse each row in a matrix
reverseRows :: LeafC ns => Mat ns a -> Mat ns a
reverseRows = mapLeafSimple (\_ (MatUnsafe v ps) -> MatUnsafe (V.reverse v) ps)

-- | sort each row of a mat using underlying 'Vec'
sortByRows :: LeafC ns => (a -> a -> Ordering) -> Mat ns a -> Mat ns a
sortByRows f = frp . wrapRows1 (N.sortBy f)

-- | visit each leaf row with a function from a nonempty to a nonempty list
wrapRows1 :: LeafC ns => (NonEmpty a -> NonEmpty b) -> Mat ns a -> Either String (Mat ns b)
wrapRows1 f = traverseLeafSimple (const (wrap1 f))

-- | reverse the dimensions of a matrix
reverseDim :: Mat ns a -> Mat (ReverseT ns) a
reverseDim (Mat v ps) = MatIU v (N.reverse ps)

-- | resize a mat
redim :: forall ms ns a. (NS ms, ProductT ns ~ ProductT ms) => Mat ns a -> Mat ms a
redim (Mat v _) = MatU v (fromNSP @ms)

{- | describes the resulting type of taking a slice from the mat
 but the indices must match pointwise unlike SliceT so we can use the concrete FinMat to specify the indices
-}
type SliceT' :: [Nat] -> [Nat] -> Type -> Type
type family SliceT' ns' ns a where
  SliceT' '[] (_ ': _) _ = GL.TypeError ( 'GL.Text "SliceT' '[] (_ ': _): not defined for empty indices ns'")
  SliceT' (_ ': _) '[] _ = GL.TypeError ( 'GL.Text "SliceT' (_ ': _) '[]: not defined for empty indices ns")
  SliceT' '[] '[] _ = GL.TypeError ( 'GL.Text "SliceT' '[] '[]: not defined for empty indices ns and ns'")
  SliceT' '[n] '[n] a = a
  SliceT' (_ ': n' ': ns') '[_] _ =
    GL.TypeError
      ( 'GL.Text "SliceT': there are more ns' indices (lhs) than the actual matrix ns indices (rhs):"
          'GL.:<>: 'GL.Text " extra ns'="
          'GL.:<>: 'GL.ShowType (n' ': ns')
      )
  SliceT' '[n] (n ': n1 ': ns) a = Mat (n1 ': ns) a
  SliceT' (n ': n1' ': ns') (n ': n1 ': ns) a = SliceT' (n1' ': ns') (n1 ': ns) a
-- todo: this condition doesnt fire in SliceC'
-- sliceC' (finMatC @(NN 11) @(NN 29)) (mm @(NN 235))
  SliceT' (n' ': _) (n ': _) _ =
    GL.TypeError
      ( 'GL.Text "SliceT': indices need to match pointwise:"
          'GL.:<>: 'GL.Text "ie n' /= n:"
          'GL.:<>: 'GL.ShowType n'
          'GL.:<>: 'GL.Text " /= "
          'GL.:<>: 'GL.ShowType n
      )

{- | allows viewing and updating a slice of a mat using concrete indices
 inference is better with n ~ n' but since we have committed to a instance
 we are missing nice errors when the indices dont match: eg
 sliceC' @'[1] @'[7] (FinMat 0 (_7P :| [])) (mm @(NN 7))
-}
type SliceC' :: [Nat] -> [Nat] -> Constraint
class SliceC' ns' ns where
  sliceC' :: FinMat ns' -> Mat ns a -> SliceT' ns' ns a
  sliceUpdateC' :: FinMat ns' -> Mat ns a -> SliceT' ns' ns a -> Mat ns a

instance GL.TypeError ( 'GL.Text "SliceC' '[] (n ': ns): empty indices ns'") => SliceC' '[] (n ': ns) where
  sliceC' = compileError "sliceC'"
  sliceUpdateC' = compileError "sliceUpdateC'"
instance GL.TypeError ( 'GL.Text "SliceC' (n' ': ns') '[]: empty indices ns") => SliceC' (n' ': ns') '[] where
  sliceC' = compileError "sliceC'"
  sliceUpdateC' = compileError "sliceUpdateC'"
instance GL.TypeError ( 'GL.Text "SliceC' '[] '[]: empty indices ns and ns'") => SliceC' '[] '[] where
  sliceC' = compileError "sliceC'"
  sliceUpdateC' = compileError "sliceUpdateC'"

instance n ~ n' => SliceC' '[n'] '[n] where
  sliceC' (FinMat i _) (Mat v _) =
    case v V.!? i of
      Nothing -> programmError $ "sliceC': index " ++ show i ++ " out of bounds"
      Just a -> a
  sliceUpdateC' (FinMat i _) (Mat v ps) b =
    let (v1, v2) = V.splitAt i v
     in case V.uncons v2 of
          Just (_, v3) -> MatIU (v1 <> V.cons b v3) ps
          Nothing -> programmError $ "sliceUpdateC': index " ++ show i ++ " out of bounds"
instance n ~ n' => SliceC' '[n'] (n ': m ': ns) where
  sliceC' (FinMat i _) (Mat v (_ :| ps)) =
    case ps of
      m : ns ->
        let ps1 = m :| ns
            len1 = productPInt ps1
         in MatIU (V.slice (i * len1) len1 v) ps1
      [] -> programmError $ "sliceC': index " ++ show i ++ ": missing indices"

  sliceUpdateC' (FinMat i0 _) (Mat v w@(_ :| ps)) b =
    let len = productPInt ps
        i = i0 + 1
        v1 = V.slice 0 ((i - 1) * len) v
        v2 = V.slice (i * len) (productPInt w - i * len) v
     in MatIU (v1 <> mVec b <> v2) w

instance
  (n ~ n', SliceC' (n1' ': ns') (n1 ': ns)) =>
  SliceC' (n ': n1' ': ns') (n' ': n1 ': ns)
  where
  sliceC' fm@(FinMat _ (_ :| n1ns')) w@(Mat _ (n :| _)) =
    let x :| xs = finMatToNonEmpty fm
        i = unP x - 1
     in case (xs, n1ns') of
          (x1 : x1s, n1 : ns') ->
            let fn1 = frp $ nonEmptyToFinMat' (x1 :| x1s) (n1 :| ns')
             in sliceC' @(n1' ': ns') @(n1 ': ns) fn1 (sliceC' @'[n'] @(n ': n1 ': ns) (frp $ mkFinMat i (n :| [])) w)
          ([], _) -> programmError "sliceC': missing ns' indices"
          (_, []) -> programmError "sliceC': missing ns indices"
  sliceUpdateC' fm@(FinMat _ (_ :| n1ns')) (Mat v w@(_ :| ps0)) b =
    -- carve out the piece that is to be updated and pass that down then patch it all back together
    let x :| xs = finMatToNonEmpty fm
        i = unP x
     in case (ps0, xs, n1ns') of
          (_ : ns, x1 : x1s, n1 : ns') ->
            let fn1 = frp $ nonEmptyToFinMat' (x1 :| x1s) (n1 :| ns')
                ps1 = n1 :| ns
                len = productPInt ps1
                v1 = V.slice 0 ((i - 1) * len) v
                v2 = V.slice (i * len) (productPInt w - i * len) v
                m1 = MatIU (V.slice ((i - 1) * len) len v) ps1
                mx = sliceUpdateC' @(n1' ': ns') @(n1 ': ns) fn1 m1 b
             in MatIU (v1 <> mVec mx <> v2) w
          ([], _, _) -> programmError "sliceUpdateC': missing matrix indices"
          (_, [], _) -> programmError "sliceUpdateC': missing ns' indices"
          (_, _, []) -> programmError "sliceUpdateC': missing finmat indices"

instance (GL.TypeError ( 'GL.Text "SliceC': too many indices ns': length ns' > length ns")) => SliceC' (n' ': n1' ': ns') '[n] where
  sliceC' = compileError "sliceC'"
  sliceUpdateC' = compileError "sliceUpdateC'"

-- | describes the resulting type of taking a slice from the matrix
type SliceToFinMatT :: [Nat] -> [Nat] -> [Nat]
type family SliceToFinMatT is ns where
  SliceToFinMatT (_ ': _) '[] =
    GL.TypeError ( 'GL.Text "SliceToFinMatT (_ ': _) '[]: 'is' is empty")
  SliceToFinMatT '[] (_ ': _) =
    GL.TypeError ( 'GL.Text "SliceToFinMatT '[] (_ ': _): 'ns' is empty")
  SliceToFinMatT '[] '[] =
    GL.TypeError ( 'GL.Text "SliceToFinMatT '[] '[]: 'is' and 'ns' are empty")
  SliceToFinMatT '[_] '[n] = '[n]
  SliceToFinMatT (_ ': i ': is) '[_] =
    GL.TypeError
      ( 'GL.Text "SliceToFinMatT: 'is' is larger in length than 'ns':"
          'GL.:<>: 'GL.Text " extra 'is'="
          'GL.:<>: 'GL.ShowType (i ': is)
      )
  SliceToFinMatT '[_] (n ': _ ': _) = '[n]
  SliceToFinMatT (_ ': i1 ': is) (n ': n1 ': ns) = n ': SliceToFinMatT (i1 ': is) (n1 ': ns)

-- | converts a typelevel slice to a concrete 'FinMat'
sliceToFinMat ::
  forall is ns.
  (NS (SliceToFinMatT is ns), NS is, NS ns) =>
  FinMat (SliceToFinMatT is ns)
sliceToFinMat =
  let is = fromNSP @is
      ns = fromNSP @ns
   in frp $ nonEmptyToFinMat (N.zipWith const is ns)

-- | get a slice by converting a typelevel slice to a concrete FinMat based slice
slice ::
  forall is ns a z.
  (z ~ SliceToFinMatT is ns, NS is, NS ns, NS z, SliceC' z ns) =>
  Mat ns a ->
  SliceT' z ns a
slice = sliceC' (sliceToFinMat @is @ns)

-- | update a slice by converting a typelevel slice to a concrete FinMat based slice
sliceUpdate ::
  forall is ns a z.
  (z ~ SliceToFinMatT is ns, NS is, NS ns, NS z, SliceC' z ns) =>
  Mat ns a ->
  SliceT' z ns a ->
  Mat ns a
sliceUpdate = sliceUpdateC' (sliceToFinMat @is @ns)

-- | describes the resulting type of taking a slice from the mat
type SliceT :: [Nat] -> [Nat] -> Type -> Type
type family SliceT is ns a where
  SliceT '[] (_ ': _) _ = GL.TypeError ( 'GL.Text "SliceT '[] (_ ': _): not defined for empty indices ns'")
  SliceT (_ ': _) '[] _ = GL.TypeError ( 'GL.Text "SliceT (_ ': _) '[]: not defined for empty indices ns")
  SliceT '[] '[] _ = GL.TypeError ( 'GL.Text "SliceT '[] '[]: not defined for empty indices ns and ns'")
  SliceT '[_] '[_] a = a
  SliceT (_ ': i ': is) '[_] _ =
    GL.TypeError
      ( 'GL.Text "SliceT: 'is' must be a smaller in length than 'ns'"
          'GL.:<>: 'GL.Text " extra 'is'="
          'GL.:<>: 'GL.ShowType (i ': is)
      )
  SliceT '[_] (_ ': n1 ': ns) a = Mat (n1 ': ns) a
  SliceT (_ ': i1 ': is) (_ ': n1 ': ns) a = SliceT (i1 ': is) (n1 ': ns) a

-- | allows viewing and updating a slice of a mat
type SliceC :: [Nat] -> [Nat] -> Constraint
class SliceC is ns where
  sliceC :: Mat ns a -> SliceT is ns a
  sliceUpdateC :: Mat ns a -> SliceT is ns a -> Mat ns a

instance GL.TypeError ( 'GL.Text "SliceC '[] (n ': ns): empty indices ns'") => SliceC '[] (n ': ns) where
  sliceC = compileError "SliceC:sliceC"
  sliceUpdateC = compileError "sliceUpdateC"
instance GL.TypeError ( 'GL.Text "SliceC (n' ': ns') '[]: empty indices ns") => SliceC (n' ': ns') '[] where
  sliceC = compileError "SliceC:sliceC"
  sliceUpdateC = compileError "SliceC:sliceUpdateC"
instance GL.TypeError ( 'GL.Text "SliceC '[] '[]: empty indices ns and ns'") => SliceC '[] '[] where
  sliceC = compileError "SliceC:sliceC"
  sliceUpdateC = compileError "SliceC:sliceUpdateC"

instance FinT i n => SliceC '[i] '[n] where
  sliceC (Mat v _) =
    let i = fromN @i - 1
     in case v V.!? i of
          Nothing -> programmError $ "sliceC: index " ++ show i ++ " out of bounds"
          Just a -> a
  sliceUpdateC (Mat v ps) b =
    let i = fromN @i - 1
        (v1, v2) = V.splitAt i v
     in case V.uncons v2 of
          Just (_, v3) -> MatIU (v1 <> V.cons b v3) ps
          Nothing -> programmError $ "sliceUpdateC: index " ++ show i ++ " out of bounds"
instance FinT i n => SliceC '[i] (n ': m ': ns) where
  sliceC (Mat v (_ :| ps)) =
    case ps of
      m : ns ->
        let i = fromN @i - 1
            ps1 = m :| ns
            len1 = productPInt ps1
         in MatIU (V.slice (i * len1) len1 v) ps1
      [] -> programmError $ "sliceUpdateC: index " ++ show (fromN @i) ++ ": missing indices"

  sliceUpdateC (Mat v w@(_ :| ps)) b =
    let i = fromN @i
        len = productPInt ps
        v1 = V.slice 0 ((i - 1) * len) v
        v2 = V.slice (i * len) (productPInt w - i * len) v
     in MatIU (v1 <> mVec b <> v2) w

instance
  (FinT i n, SliceC (i1 ': is) (n1 ': ns)) =>
  SliceC (i ': i1 ': is) (n ': n1 ': ns)
  where
  sliceC w =
    sliceC @(i1 ': is) @(n1 ': ns) (sliceC @'[i] @(n ': n1 ': ns) w)
  sliceUpdateC (Mat v w@(_ :| ps0)) b =
    -- carve out the piece that is to be updated and pass that down then patch it all back together
    case ps0 of
      n1 : ns ->
        let i = fromN @i
            ps1 = n1 :| ns
            len = productPInt ps1
            v1 = V.slice 0 ((i - 1) * len) v
            v2 = V.slice (i * len) (productPInt w - i * len) v
            m1 = MatIU (V.slice ((i - 1) * len) len v) ps1
            mx = sliceUpdateC @(i1 ': is) @(n1 ': ns) m1 b
         in MatIU (v1 <> mVec mx <> v2) w
      [] -> programmError $ "sliceUpdateC: index " ++ show (fromN @i) ++ ": missing indices"

instance (GL.TypeError ( 'GL.Text "too many indices 'is': length is > length ns")) => SliceC (i ': i1 ': is) '[n] where
  sliceC = compileError "sliceC (2)"
  sliceUpdateC = compileError "sliceUpdateC (2)"

-- | a lens indexing the outermost slice
_row ::
  forall (i :: Nat) (ns :: [Nat]) a.
  (SliceC '[i] ns) =>
  Lens' (Mat ns a) (SliceT '[i] ns a)
_row = ixSlice @'[i]

-- | a lens for acccessing a column
_col ::
  forall (i :: Nat) n m ns a.
  (FinT i m) =>
  Lens' (Mat (n ': m ': ns) a) (Mat (n ': ns) a)
_col = _transposeMat . _row @i

-- | a lens for accessing a slice of a mat
ixSlice ::
  forall (is :: [Nat]) (ns :: [Nat]) a.
  (SliceC is ns) =>
  Lens' (Mat ns a) (SliceT is ns a)
ixSlice =
  lens
    (sliceC @is)
    (sliceUpdateC @is)

-- | a lens indexing a row using a concrete index 'Fin'
_row' ::
  forall (n :: Nat) (ns :: [Nat]) a.
  (SliceC' '[n] ns) =>
  Fin n ->
  Lens' (Mat ns a) (SliceT' '[n] ns a)
_row' (Fin i _) = ixSlice' @'[n] (frp $ mkFinMat (unP i - 1) (succP i :| []))

-- | a lens for acccessing a column
_col' ::
  forall n m ns a.
  Fin m ->
  Lens' (Mat (n ': m ': ns) a) (Mat (n ': ns) a)
_col' fn = _transposeMat . _row' fn

-- | a lens for accessing a slice of a mat
ixSlice' ::
  forall (ns' :: [Nat]) (ns :: [Nat]) a.
  (SliceC' ns' ns) =>
  FinMat ns' ->
  Lens' (Mat ns a) (SliceT' ns' ns a)
ixSlice' fm =
  lens
    (sliceC' @ns' fm)
    (sliceUpdateC' @ns' fm)

-- | break up into rows
rows ::
  forall n m ns a.
  Mat (n ': m ': ns) a ->
  Vec n (Mat (m ': ns) a)
rows w@(Mat _ (n :| ps)) =
  case ps of
    m : ns ->
      let zs = frp $ chunkNVMat (unitsF @[] n) (m :| ns) w
       in MatIU (V.fromList zs) (n :| [])
    [] -> programmError "rows: missing indices"

-- | unbust from rows @see 'rows'
unrows ::
  forall n m ns a.
  Vec n (Mat (m ': ns) a) ->
  Mat (n ': m ': ns) a
unrows = concatMat

-- | split up a matrix into matrix chunks of dimension "ps" and fill a container "tz"
chunkNVMat ::
  forall ns t x a z.
  Traversable t =>
  t z ->
  NonEmpty Pos ->
  Mat x a ->
  Either String (t (Mat ns a))
chunkNVMat tz ps = (fmap . fmap) (`MatIU` ps) . chunkNV tz (productP ps) . mVec

-- | split up a vector into chunks of size "n" and fill a container "tz"
chunkNV ::
  forall t a z.
  Traversable t =>
  t z ->
  Pos ->
  Vector a ->
  Either String (t (Vector a))
chunkNV tz (Pos n) = chunkN' g tz
 where
  g = Right . swap . V.splitAt n

-- 4 conditions:
--   1: (n:|[m]) a X (m:|[p]) b == (n:|[p]) (a->b->c)
--   2: (n:|m:q:ns) a X (m:|[p]) b == (n:|[p]) ((q:|ns) a -> b -> c)
--   3: (n:|m:q:ns) a X (m:|p:r:xs) b == (n:|[p]) ((q:|ns) a -> (r:|xs) b -> c)
--   4: (n:|[m]) a X (m:|p:r:xs) b == (n:|[p]) (a -> (r:|xs) b -> c)

-- | generalised dot product
type DotC :: [Nat] -> [Nat] -> Type -> Type -> Type -> Type -> Constraint
class
  DotC ns ns' a b fa fb
    | ns ns' a -> fa
    , ns ns' b -> fb
    , ns ns' fa -> a
    , ns ns' fb -> b
    , fa fb a b -> ns ns'
  where
  dotC ::
    (fa -> fb -> c) ->
    (NonEmpty c -> d) ->
    Mat (n ': m ': ns) a ->
    Mat (m ': p ': ns') b ->
    Mat2 n p d

instance DotC '[] '[] a b a b where
  dotC = dot
instance DotC (q ': ns) '[] a b (Mat (q ': ns) a) b where
  dotC f g m1 m2 = dot f g (toMat2 m1) m2
instance DotC '[] (r ': xs) a b a (Mat (r ': xs) b) where
  dotC f g m1 m2 = dot f g m1 (toMat2 m2)
instance DotC (q ': ns) (r ': xs) a b (Mat (q ': ns) a) (Mat (r ': xs) b) where
  dotC f g m1 m2 = dot f g (toMat2 m1) (toMat2 m2)

-- | base case for generalised dot product
dot ::
  forall n m p a b c d.
  (a -> b -> c) ->
  (NonEmpty c -> d) ->
  Mat2 n m a ->
  Mat2 m p b ->
  Mat2 n p d
dot f g w1@(Mat _ (n :| ps1)) w2@(Mat _ (_ :| ps2)) =
  case (ps1, ps2) of
    ([m], [p]) ->
      let z1 = frp $ chunkNLen1 n m w1
          z2 = N.transpose $ frp $ chunkNLen1 m p w2
          w = liftA2 ((g . frp) .@ zipWithExact f) z1 z2
       in MatIU (V.fromList $ N.toList w) (n :| [p])
    o -> programmError $ "dot: missing indices " ++ show o

-- | multiply two matrices together
multMat ::
  forall n m p a.
  Num a =>
  Mat2 n m a ->
  Mat2 m p a ->
  Mat2 n p a
multMat = dot (*) sum1

-- | delete a row
deleteRow ::
  forall (i :: Nat) (n :: Nat) (ns :: [Nat]) a.
  FinT i (1 GN.+ n) =>
  Mat (1 GN.+ n ': ns) a ->
  Mat (n ': ns) a
deleteRow = deleteRow' (finC @i @(1 GN.+ n))

-- | delete a row using a concrete index
deleteRow' ::
  forall n ns a.
  Fin (1 GN.+ n) ->
  Mat (1 GN.+ n ': ns) a ->
  Mat (n ': ns) a
deleteRow' (Fin (Pos i) _) (Mat v (sn :| ps)) =
  let n = frp $ predP sn
      n1 = productPInt ps
      s = (i - 1) * n1
      v1 = V.slice 0 s v
      v2 = V.slice (s + n1) (productPInt (sn :| ps) - s - n1) v
   in MatIU (v1 <> v2) (n :| ps)

-- | delete a row from a matrix
insertRow ::
  forall i n m ns a.
  FinT i (1 GN.+ n) =>
  Mat (m ': ns) a ->
  Mat (n ': m ': ns) a ->
  Mat (1 GN.+ n ': m ': ns) a
insertRow = insertRow' (finC @i @(1 GN.+ n))

-- | same as 'insertRow' but using a typelevel witness for the index
insertRow' ::
  forall n m ns a.
  Fin (1 GN.+ n) ->
  Mat (m ': ns) a ->
  Mat (n ': m ': ns) a ->
  Mat (1 GN.+ n ': m ': ns) a
insertRow' (Fin (Pos i) _) (Mat v0 _) (Mat v (p :| ps)) =
  let s = (i - 1) * productPInt ps
      v1 = V.slice 0 s v
      v2 = V.slice s (productPInt (p :| ps) - s) v
   in MatIU (v1 <> v0 <> v2) (succP p :| ps)

-- | delete a column from a matrix (2d or higher)
deleteCol ::
  forall (i :: Nat) (n :: Nat) (n1 :: Nat) ns a.
  FinT i (1 GN.+ n1) =>
  Mat (n ': (1 GN.+ n1) ': ns) a ->
  Mat (n ': n1 ': ns) a
deleteCol = deleteCol' (finC @i @(1 GN.+ n1))

-- | same as 'deleteCol' but using a typelevel witness for the index
deleteCol' ::
  forall (n :: Nat) (n1 :: Nat) ns a.
  Fin (1 GN.+ n1) ->
  Mat (n ': (1 GN.+ n1) ': ns) a ->
  Mat (n ': n1 ': ns) a
deleteCol' fn = transposeMat @n1 @n . deleteRow' @n1 fn . transposeMat @n @(1 GN.+ n1)

-- | insert a column into a mat (2d and above)
insertCol ::
  forall (i :: Nat) (n :: Nat) (n1 :: Nat) ns a.
  FinT i (1 GN.+ n1) =>
  Mat (n ': ns) a ->
  Mat (n ': n1 ': ns) a ->
  Mat (n ': (1 GN.+ n1) ': ns) a
insertCol = insertCol' (finC @i @(1 GN.+ n1))

-- | same as 'insertCol' but using a typelevel witness 'Fin'
insertCol' ::
  forall (n :: Nat) (n1 :: Nat) ns a.
  Fin (1 GN.+ n1) ->
  Mat (n ': ns) a ->
  Mat (n ': n1 ': ns) a ->
  Mat (n ': (1 GN.+ n1) ': ns) a
insertCol' fn v = transposeMat @(1 GN.+ n1) @n . insertRow' fn v . transposeMat @n @n1

-- | swaps mat rows (1d or more)
swapRow ::
  forall (i :: Nat) (j :: Nat) (n :: Nat) ns a.
  (FinT i n, FinT j n) =>
  Mat (n ': ns) a ->
  Mat (n ': ns) a
swapRow = swapRow' (finC @i) (finC @j)

-- | swaps mat rows (1d or more)
swapRow' ::
  forall (n :: Nat) ns a.
  Fin n ->
  Fin n ->
  Mat (n ': ns) a ->
  Mat (n ': ns) a
swapRow' (Fin ix _) (Fin jx _) z@(Mat v w@(_ :| ps)) =
  let (Pos i, Pos j) = bool id swap (ix > jx) (ix, jx)
      len = productPInt ps
   in if i == j
        then z
        else
          let s0 = (i - 1) * len
              s1 = (j - 1) * len
              x1 = V.slice 0 s0 v
              x2 = V.slice s0 len v
              x3 = V.slice (s0 + len) (s1 - s0 - len) v
              x4 = V.slice s1 len v
              x5 = V.slice (s1 + len) (productPInt w - s1 - len) v
           in MatIU (x1 <> x4 <> x3 <> x2 <> x5) w

-- | swaps mat rows (2d or more)
swapCol ::
  forall (i :: Nat) (j :: Nat) (n :: Nat) (n1 :: Nat) ns a.
  (FinT i n1, FinT j n1) =>
  Mat (n ': n1 ': ns) a ->
  Mat (n ': n1 ': ns) a
swapCol = swapCol' (finC @i) (finC @j)

-- | swaps mat rows (2d or more)
swapCol' ::
  forall (n :: Nat) (n1 :: Nat) ns a.
  Fin n1 ->
  Fin n1 ->
  Mat (n ': n1 ': ns) a ->
  Mat (n ': n1 ': ns) a
swapCol' fni fnj = transposeMat . swapRow' fni fnj . transposeMat

-- | swaps a single value "a" from any location to any other location using type level indexes
swapMat ::
  forall (is :: [Nat]) (js :: [Nat]) ns a.
  (FinMatC is ns, FinMatC js ns) =>
  Mat ns a ->
  Mat ns a
swapMat = swapMat' (finMatC @is @ns) (finMatC @js @ns)

-- | same as 'swapMat' but using typelevel witnesses 'FinMat'
swapMat' ::
  forall ns a.
  FinMat ns ->
  FinMat ns ->
  Mat ns a ->
  Mat ns a
swapMat' (FinMat i _) (FinMat j _) (Mat v ps) =
  MatIU (V.update v (V.fromList [(i, v V.! j), (j, v V.! i)])) ps

-- | append two matrices vertically
appendV ::
  Mat (n ': ns) a ->
  Mat (n' ': ns) a ->
  Mat ((n GN.+ n') ': ns) a
appendV (Mat v (p :| ps)) (Mat v1 (p1 :| _)) =
  MatIU (v <> v1) ((p +! p1) :| ps)

-- | append two matrices horizontally
appendH ::
  forall n m m' ns a.
  Mat (n ': m ': ns) a ->
  Mat (n ': m' ': ns) a ->
  Mat (n ': (m GN.+ m') ': ns) a
appendH w@(Mat _ (n :| ps)) w1@(Mat _ (n' :| ps1))
  | n == n' =
      case (ps, ps1) of
        ([], _) -> programmError "appendH:lhs missing indices"
        (_, []) -> programmError "appendH:rhs missing indices"
        (m : ns, m' : ns')
          | ns == ns' ->
              let x1 = frp $ chunkNV (unitsF n) (productP (m :| ns)) (mVec w)
                  x2 = frp $ chunkNV (unitsF @[] n) (productP (m' :| ns')) (mVec w1)
                  ret = frp $ zipWithExact (<>) x1 x2
                  ps2 = n :| ([m +! m'] <> ns)
               in MatIU (V.concat ret) ps2
          | otherwise -> programmError $ "appendH:ns/=ns' " ++ show (ns, ns')
  | otherwise = programmError $ "appendH: n/=n' " ++ show (n, n')

-- | return a mat as a permutation of a list (1d only) todo: extend to multidimensions
permutationsMat :: forall n a. Vec n a -> Mat2 (FacT n) n a
permutationsMat (Mat v (p :| _)) =
  let ret = L.permutations (V.toList v)
      lhs = productP (enumTo1 p)
   in MatIU (V.fromList $ concat ret) (lhs :| [p])

-- | find all elements in a mat that match the predicate
findMatElems :: NS ns => (a -> Bool) -> Mat ns a -> [(FinMat ns, a)]
findMatElems p = ifoldMap (\i a -> bool [] [(i, a)] (p a))

-- | generate a 'Mat' with the given past and future rep values and a user state
buildMat ::
  forall ns a b.
  NS ns =>
  ([FinMat ns] -> [FinMat ns] -> b -> FinMat ns -> (b, a)) ->
  b ->
  (b, Mat ns a)
buildMat = buildRepL

-- | cartesian product of two matrices with a combining function
cartesian ::
  (a -> b -> c) ->
  Mat (n ': ns) a ->
  Mat (n' ': ns') b ->
  Mat (n ': ns TP.++ n' ': ns') c
cartesian f (Mat v ps) (Mat v1 ps1) =
  MatIU (liftA2 f v v1) (ps <> ps1)

-- | lens for bulk updates/gets on a matrix
bulkMat :: Vec x (FinMat ns) -> Lens' (Mat ns a) (Vec x a)
bulkMat fins =
  lens
    (getsMat fins)
    (\m lst -> setsMat (zipMat fins lst) m)

-- | bulk updates on a mat
updatesMat ::
  forall ns t a b.
  Foldable t =>
  (FinMat ns -> a -> b -> a) ->
  t (FinMat ns, b) ->
  Mat ns a ->
  Mat ns a
updatesMat f = flip (L.foldl' g)
 where
  g m (fm, b) = updateMat (\a -> f fm a b) fm m

-- | bulk gets from a mat: replaces the container of indices with the corresponding values
getsMat :: forall ns a t. Functor t => t (FinMat ns) -> Mat ns a -> t a
getsMat lst m = (`indexMat` m) <$> lst

-- | bulk updates on a mat
setsMat ::
  forall ns t a.
  Foldable t =>
  t (FinMat ns, a) ->
  Mat ns a ->
  Mat ns a
setsMat = flip (L.foldl' g)
 where
  g :: Mat ns a -> (FinMat ns, a) -> Mat ns a
  g m (fm, a) = setMat a fm m

-- | convert a matrix to a nested tuple
type MatTupleT :: [Nat] -> Type -> Type
type family MatTupleT ns a where
  MatTupleT '[] _ = GL.TypeError ('GL.Text "MatTupleT '[]: undefined for empty indices")
  MatTupleT '[n] a = ListTupleT n a
  MatTupleT (n ': n1 ': ns) a = ListTupleT n (MatTupleT (n1 ': ns) a)

-- | convert a between a matrix and a nested tuple
type MatTupleC :: [Nat] -> Type -> Constraint
class MatTupleC ns a where
  toTupleC ::
    Mat ns a ->
    -- | convert a 'Mat' to a nested tuple
    MatTupleT ns a
  fromTupleC ::
    MatTupleT ns a ->
    -- | convert a well-formed nested tuple of type "a" to 'Mat'
    Mat ns a
  fmapTupleMatC ::
    (a -> b) ->
    MatTupleT ns a ->
    -- | fmap over a well-formed nested tuple
    MatTupleT ns b
  traversalTupleMatC ::
    -- | traversal over a well-formed nested tuple
    Traversal (MatTupleT ns a) (MatTupleT ns b) a b

instance GL.TypeError ('GL.Text "MatTupleC '[]: undefined for empty indices") => MatTupleC '[] a where
  toTupleC = compileError "MatTupleC:toTupleC"
  fromTupleC = compileError "MatTupleC:fromTupleC"
  fmapTupleMatC = compileError "MatTupleC:fmapTupleMatC"
  traversalTupleMatC = compileError "MatTupleC:traversalTupleMatC"

instance ListTupleCInternal n => MatTupleC '[n] a where
  toTupleC  = toTupleCInternal
  fromTupleC = fromTupleCInternal
  fmapTupleMatC = fmapTupleInternal
  traversalTupleMatC = traversalTupleCInternal
instance
  (ListTupleCInternal n, NS (n1 ': ns), MatTupleC (n1 ': ns) a) =>
  MatTupleC (n ': n1 ': ns) a
  where
  toTupleC lst = toTupleCInternal @n (fmap (toTupleC @(n1 ': ns)) (rows @n lst))
  fromTupleC x =
    let Mat v (n' :| _) = fromTupleCInternal (fmapTupleInternal (fromTupleC @(n1 ': ns)) x)
        xs = foldMap mVec v
        ps1 = n' N.<| fromNSP @(n1 ': ns)
     in MatIU @(n ': n1 ': ns) xs ps1

  fmapTupleMatC f x = fmapTupleInternal (fmapTupleMatC @(n1 ': ns) f) x
  traversalTupleMatC afa = traversalTupleCInternal @n (traversalTupleMatC @(n1 ': ns) afa)

-- | fmap over a n-tuple
fmapTupleInternal :: ListTupleCInternal n => (a -> b) -> ListTupleT n a -> ListTupleT n b
fmapTupleInternal f = runIdentity . traversalTupleCInternal (Identity . f)

-- | conversions between n-tuple and 'Vec'
type ListTupleCInternal :: Nat -> Constraint
class ListTupleCInternal n where
  -- | convert a 'Vec' to a tuple
  toTupleCInternal :: Vec n a -> ListTupleT n a

  -- | convert a tuple of type "a" to 'Vec'
  fromTupleCInternal :: ListTupleT n a -> Vec n a

  -- | traversal over a tuple
  traversalTupleCInternal :: Traversal (ListTupleT n a) (ListTupleT n b) a b

instance ListTupleCInternal 1 where
  toTupleCInternal (toNonEmptyMat -> a :| []) = One a
  toTupleCInternal _o = programmError "ListTupleCInternal 1"
  fromTupleCInternal (One a) = se1 a
  traversalTupleCInternal afa (One a) = One <$> afa a
instance ListTupleCInternal 2 where
  toTupleCInternal (toNonEmptyMat -> a1 :| [a2]) = (a1, a2)
  toTupleCInternal _o = programmError "ListTupleCInternal 2"
  fromTupleCInternal (a1, a2) = a1 .| a2
  traversalTupleCInternal afa (a1, a2) = (,) <$> afa a1 <*> afa a2
instance ListTupleCInternal 3 where
  toTupleCInternal (toNonEmptyMat -> a1 :| [a2, a3]) = (a1, a2, a3)
  toTupleCInternal _o = programmError "ListTupleCInternal 3"
  fromTupleCInternal (a1, a2, a3) = a1 .: a2 .| a3
  traversalTupleCInternal afa (a1, a2, a3) = (,,) <$> afa a1 <*> afa a2 <*> afa a3
instance ListTupleCInternal 4 where
  toTupleCInternal (toNonEmptyMat -> a1 :| [a2, a3, a4]) = (a1, a2, a3, a4)
  toTupleCInternal _o = programmError "ListTupleCInternal 4"
  fromTupleCInternal (a1, a2, a3, a4) = a1 .: a2 .: a3 .| a4
  traversalTupleCInternal afa (a1, a2, a3, a4) = (,,,) <$> afa a1 <*> afa a2 <*> afa a3 <*> afa a4
instance ListTupleCInternal 5 where
  toTupleCInternal (toNonEmptyMat -> a1 :| [a2, a3, a4, a5]) = (a1, a2, a3, a4, a5)
  toTupleCInternal _o = programmError "ListTupleCInternal 5"
  fromTupleCInternal (a1, a2, a3, a4, a5) = a1 .: a2 .: a3 .: a4 .| a5
  traversalTupleCInternal afa (a1, a2, a3, a4, a5) = (,,,,) <$> afa a1 <*> afa a2 <*> afa a3 <*> afa a4 <*> afa a5
instance ListTupleCInternal 6 where
  toTupleCInternal (toNonEmptyMat -> a1 :| [a2, a3, a4, a5, a6]) = (a1, a2, a3, a4, a5, a6)
  toTupleCInternal _o = programmError "ListTupleCInternal 6"
  fromTupleCInternal (a1, a2, a3, a4, a5, a6) = a1 .: a2 .: a3 .: a4 .: a5 .| a6
  traversalTupleCInternal afa (a1, a2, a3, a4, a5, a6) = (,,,,,) <$> afa a1 <*> afa a2 <*> afa a3 <*> afa a4 <*> afa a5 <*> afa a6
instance ListTupleCInternal 7 where
  toTupleCInternal (toNonEmptyMat -> a1 :| [a2, a3, a4, a5, a6, a7]) = (a1, a2, a3, a4, a5, a6, a7)
  toTupleCInternal _o = programmError "ListTupleCInternal 7"
  fromTupleCInternal (a1, a2, a3, a4, a5, a6, a7) = a1 .: a2 .: a3 .: a4 .: a5 .: a6 .| a7
  traversalTupleCInternal afa (a1, a2, a3, a4, a5, a6, a7) = (,,,,,,) <$> afa a1 <*> afa a2 <*> afa a3 <*> afa a4 <*> afa a5 <*> afa a6 <*> afa a7
instance ListTupleCInternal 8 where
  toTupleCInternal (toNonEmptyMat -> a1 :| [a2, a3, a4, a5, a6, a7, a8]) = (a1, a2, a3, a4, a5, a6, a7, a8)
  toTupleCInternal _o = programmError "ListTupleCInternal 8"
  fromTupleCInternal (a1, a2, a3, a4, a5, a6, a7, a8) = a1 .: a2 .: a3 .: a4 .: a5 .: a6 .: a7 .| a8
  traversalTupleCInternal afa (a1, a2, a3, a4, a5, a6, a7, a8) = (,,,,,,,) <$> afa a1 <*> afa a2 <*> afa a3 <*> afa a4 <*> afa a5 <*> afa a6 <*> afa a7 <*> afa a8
instance ListTupleCInternal 9 where
  toTupleCInternal (toNonEmptyMat -> a1 :| [a2, a3, a4, a5, a6, a7, a8, a9]) = (a1, a2, a3, a4, a5, a6, a7, a8, a9)
  toTupleCInternal _o = programmError "ListTupleCInternal 9"
  fromTupleCInternal (a1, a2, a3, a4, a5, a6, a7, a8, a9) = a1 .: a2 .: a3 .: a4 .: a5 .: a6 .: a7 .: a8 .| a9
  traversalTupleCInternal afa (a1, a2, a3, a4, a5, a6, a7, a8, a9) = (,,,,,,,,) <$> afa a1 <*> afa a2 <*> afa a3 <*> afa a4 <*> afa a5 <*> afa a6 <*> afa a7 <*> afa a8 <*> afa a9
instance ListTupleCInternal 10 where
  toTupleCInternal (toNonEmptyMat -> a1 :| [a2, a3, a4, a5, a6, a7, a8, a9, a10]) = (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10)
  toTupleCInternal _o = programmError "ListTupleCInternal 10"
  fromTupleCInternal (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) = a1 .: a2 .: a3 .: a4 .: a5 .: a6 .: a7 .: a8 .: a9 .| a10
  traversalTupleCInternal afa (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) = (,,,,,,,,,) <$> afa a1 <*> afa a2 <*> afa a3 <*> afa a4 <*> afa a5 <*> afa a6 <*> afa a7 <*> afa a8 <*> afa a9 <*> afa a10

-- | an iso for transposing a matrix
_transposeMat :: Iso (Mat (n ': m ': ns) a) (Mat (n ': m ': ns) b) (Mat (m ': n ': ns) a) (Mat (m ': n ': ns) b)
_transposeMat = iso transposeMat transposeMat

-- | transpose a 2d or larger matrix
transposeMat :: forall n m ns a. Mat (n ': m ': ns) a -> Mat (m ': n ': ns) a
transposeMat w@(Mat _ (n :| ps)) =
  case ps of
    [] -> programmError "transposeMat"
    m : ns ->
      let ys = frp $ chunkNLen1 n (productP (m :| ns)) w
          zs = N.transpose $ N.map (chunksOf1 (productP ns)) ys
       in MatIU (V.fromList $ N.toList $ sconcat $ sconcat zs) (m :| (n : ns))

-- | validate and convert from a nested list to a matrix
nestedListToMatValidated :: forall ns x a. (x ~ ListNST ns a, ValidateNestedListC x (ValidateNestedListT x), MatConvertersC ns) => ListNST ns a -> Either String (Mat ns a)
nestedListToMatValidated w = do
  _ <- validateNestedList w
  nestedListToMatC w

-- | validate and convert from a nested nonempty list to a matrix
nestedNonEmptyToMatValidated :: forall ns x a. (x ~ NonEmptyNST ns a, ValidateNestedNonEmptyC x (ValidateNestedNonEmptyT x), MatConvertersC ns) => NonEmptyNST ns a -> Either String (Mat ns a)
nestedNonEmptyToMatValidated w = do
  _ <- validateNestedNonEmpty w
  nestedNonEmptyToMatC w

-- | class with methods to convert to and from Mat using nested structures
class MatConvertersC ns where
  -- | convert a 'Mat' to nested 'Vec'
  matToNestedVecC :: Mat ns a -> MatToNestedVecT ns a

  -- | convert a nested 'Vec' to a 'Mat'
  nestedVecToMatC :: MatToNestedVecT ns a -> Mat ns a

  -- | convert a 'Mat' to a nested list
  matToNestedListC :: Mat ns a -> ListNST ns a

  -- | convert a nested list to a 'Mat'
  nestedListToMatC :: ListNST ns a -> Either String (Mat ns a)

  -- | convert a 'Mat' to a nested nonempty list
  matToNestedNonEmptyC :: Mat ns a -> NonEmptyNST ns a

  -- | convert a nested nonempty list to a 'Mat'
  nestedNonEmptyToMatC :: NonEmptyNST ns a -> Either String (Mat ns a)

instance GL.TypeError ('GL.Text "MatConvertersC '[]: undefined for empty indices") => MatConvertersC '[] where
  matToNestedVecC = compileError "MatConvertersC"
  nestedVecToMatC = compileError "MatConvertersC"
  matToNestedListC = compileError "MatConvertersC"
  matToNestedNonEmptyC = compileError "MatConvertersC"
  nestedListToMatC = compileError "MatConvertersC"
  nestedNonEmptyToMatC = compileError "MatConvertersC"

instance PosT n => MatConvertersC '[n] where
  matToNestedVecC = id
  nestedVecToMatC = id
  matToNestedListC = toListMat
  matToNestedNonEmptyC = toNonEmptyMat
  nestedListToMatC = matImpl True
  nestedNonEmptyToMatC = matImpl True . N.toList
instance (PosT n, MatConvertersC (m ': ns)) => MatConvertersC (n ': m ': ns) where
  matToNestedVecC lst = fmap matToNestedVecC (rows @n lst)
  nestedVecToMatC lst@(Mat _ (n :| _)) =
    let zs@(Mat _ (m :| ns) :| _) = toNonEmptyMat $ fmap (nestedVecToMatC @(m ': ns)) lst
        ys = foldMap mVec zs
     in MatIU ys (n :| m : ns)
  matToNestedListC w = toListMat $ fmap (matToNestedListC @(m ': ns)) (rows @n w)
  matToNestedNonEmptyC w = toNonEmptyMat $ fmap (matToNestedNonEmptyC @(m ': ns)) (rows @n w)
  nestedListToMatC = \case
    [] -> Left "nestedListToMatC: no data"
    w : ws -> nonEmptyMatsToMat =<< traverse (nestedListToMatC @(m ': ns)) (w :| ws)
  nestedNonEmptyToMatC w = nonEmptyMatsToMat =<< traverse (nestedNonEmptyToMatC @(m ': ns)) w

-- | create a matrix of one dimension higher from rows of a sub matrix
nonEmptyMatsToMat :: forall n m ns a t. (Foldable1 t, PosT n) => t (Mat (m ': ns) a) -> Either String (Mat (n ': m ': ns) a)
nonEmptyMatsToMat (toNonEmpty -> xs@(Mat _ ps :| _)) = do
  let n = fromNP @n
  ret <- lengthExact1 n xs
  pure $ MatIU (sconcat (fmap mVec ret)) (n N.<| ps)

-- | converts mat dimensions to a nested list
type MatToNestedVecT :: [Nat] -> Type -> Type
type family MatToNestedVecT ns a where
  MatToNestedVecT '[] _ = GL.TypeError ('GL.Text "MatToNestedVecT '[]: undefined for empty indices")
  MatToNestedVecT '[n] a = Vec n a
  MatToNestedVecT (n ': n1 ': ns) a = Vec n (MatToNestedVecT (n1 ': ns) a)

-- | type synonym for the result of nesting a matrix: @see 'toND'
type MatToNDT :: Nat -> [Nat] -> Type -> Type
type MatToNDT i ns a = Mat (MatToMatNTA (NatToPeanoT i) ns) (Mat (MatToMatNTB (NatToPeanoT i) ns) a)

-- | create a nested matrix going "i" levels down: noop is not supported ie 4D matrix to a 4D matrix
matToNDImpl ::
  forall (i :: Nat) (ns :: [Nat]) a.
  PosT i =>
  Mat ns a ->
  MatToNDT i ns a
matToNDImpl w@(Mat _ ps) =
  let i = fromNP @i
      (ps1, bs) = splitAt1 i ps
   in case bs of
        y : ys ->
          let ps2 = y :| ys
              xs = frp $ chunkNVMat (unitsF (productP ps1)) ps2 w
           in MatIU (V.fromList xs) ps1
        [] -> programmError "toND:missing indices to the right"

type MatToMatNTA :: Peano -> [Nat] -> [Nat]
type family MatToMatNTA i ns where
  MatToMatNTA _ '[] =
    GL.TypeError ( 'GL.Text "MatToMatNTA '[]: empty indices")
  MatToMatNTA ( 'S 'Z) '[_] =
    GL.TypeError ( 'GL.Text "MatToMatNTA: noop as the depth 'i' is the same as the number of indices")
  MatToMatNTA ( 'S 'Z) (n ': _ ': _) = '[n]
  MatToMatNTA ( 'S _) '[_] =
    GL.TypeError ( 'GL.Text "MatToMatNTA: depth is more than the number of indices")
  MatToMatNTA ( 'S ( 'S i)) (n ': m ': ns) = n : MatToMatNTA ( 'S i) (m ': ns)

type MatToMatNTB :: Peano -> [Nat] -> [Nat]
type family MatToMatNTB i ns where
  MatToMatNTB _ '[] =
    GL.TypeError ( 'GL.Text "MatToMatNTB: empty indices")
  MatToMatNTB ( 'S 'Z) '[_] =
    GL.TypeError ( 'GL.Text "MatToMatNTB: noop as the depth 'i' is the same as the number of indices")
  MatToMatNTB ( 'S 'Z) (_ ': m ': ns) = m ': ns
  MatToMatNTB ( 'S _) '[_] =
    GL.TypeError ( 'GL.Text "MatToMatNTB: depth is more than the number of indices")
  MatToMatNTB ( 'S ( 'S i)) (_ ': m ': ns) = MatToMatNTB ( 'S i) (m ': ns)

-- | create a nd matrix using a Nat @see 'toND
toND :: forall i ns a. i <=! i => Mat ns a -> MatToNDT i ns a
toND = matToNDImpl @i

-- | create a nested 1d matrix @see 'toND
toVec :: Mat ns a -> MatToNDT 1 ns a
toVec = toND @1

-- | create a nested 2d matrix @see 'toND
toMat2 :: Mat ns a -> MatToNDT 2 ns a
toMat2 = toND @2

-- | create a nested 3d matrix @see 'toND
toMat3 :: Mat ns a -> MatToNDT 3 ns a
toMat3 = toND @3

-- | squash a single nested matrix together into one
concatMat ::
  forall (n :: Nat) (ns :: [Nat]) (m :: Nat) (ms :: [Nat]) a.
  Mat (n ': ns) (Mat (m ': ms) a) ->
  Mat (n ': (ns TP.++ m ': ms)) a
concatMat w =
  let hd :| tl = toNonEmptyMat w
   in MatIU (V.concat (map mVec (hd : tl))) (mIndices w <> mIndices hd)

-- | gets the diagonal elements of a 2d or greater square matrix: the diagonal of a n * n * ns matrix results in a n * ns matrix
diagonal :: Mat (n ': n ': ns) a -> Mat (n ': ns) a
diagonal (Mat v (n :| ps)) =
  case ps of
    _n : ns ->
      let len = productPInt ns
          xs = map (\i -> V.slice (i * (unP n + 1) * len) len v) [0 .. unP n - 1]
       in MatIU (V.concat xs) (n :| ns)
    [] -> programmError "diagonal: missing indices"

-- | take a subset of a matrix using the start and end rows
subsetRows ::
  forall i j n ns a.
  DiffTC i j n =>
  Mat (n ': ns) a ->
  Mat (DiffT i j n ': ns) a
subsetRows (Mat v (_ :| ns)) =
  let i = fromNP @i
      j = fromNP @j
      n1 = (unP i - 1) * productPInt ns
      n' = frp $ withOp2 ((-) . (+ 1)) j i
      ps1 = n' :| ns
   in MatIU (V.slice n1 (productPInt ps1) v) ps1

-- todo use FinMat versions of subsetRows and subsetCols ie not just typelevel: need typelevel for the count of rows/cols so no point

-- | take a subset of a matrix using the start and end columns
subsetCols ::
  forall i j m n ns a.
  DiffTC i j n =>
  Mat (m ': n ': ns) a ->
  Mat (m ': (DiffT i j n ': ns)) a
subsetCols = transposeMat . subsetRows @i @j . transposeMat

-- | isomorphism for nesting/unnesting a matrix one level deep
_rows ::
  forall n m ns a b.
  Iso
    (Mat (n ': m ': ns) a)
    (Mat (n ': m ': ns) b)
    (Vec n (Mat (m ': ns) a))
    (Vec n (Mat (m ': ns) b))
_rows = iso rows unrows

toListMat :: Mat ns a -> [a]
toListMat = toList

toNonEmptyMat :: Mat ns a -> NonEmpty a
toNonEmptyMat = toNonEmpty

-- | specialised version of 'readMat' for 'Vec'
readVec ::
  ( MatConvertersC '[n]
  , PosT n
  , Read [a]
  ) =>
  ReadS (Vec n a)
readVec = P.readP_to_S (readMatP defShowOpts)

-- | specialised version of 'readMat' for 'Mat2'
readMat2 ::
  ( MatConvertersC '[n, m]
  , PosT n
  , PosT m
  , Read [[a]]
  ) =>
  ReadS (Mat2 n m a)
readMat2 = P.readP_to_S (readMatP defShowOpts)

-- | read in a matrix as a nested list using default 'ShowOpts'
readMat ::
  forall ns a.
  ( MatConvertersC ns
  , NS ns
  , Read (ListNST ns a)
  ) =>
  ReadS (Mat ns a)
readMat = P.readP_to_S (readMatP defShowOpts)

instance (MatConvertersC ns, NS ns, Read (ListNST ns a)) => Read (Mat ns a) where
  readPrec = PC.readP_to_Prec (const (readMatP defShowOpts))

-- | reader for 'showFin'
readMatP ::
  forall ns a.
  ( MatConvertersC ns
  , NS ns
  , Read (ListNST ns a)
  ) =>
  ShowOpts ->
  P.ReadP (Mat ns a)
readMatP opts = do
  P.skipSpaces
  let ns = fromNSP @ns
  ns' <-
    (P.string "Mat@" *> pPositives '[' ']')
      P.+++ (P.string "Vec@" *> fmap pure pPosInt)
      P.+++ ((\n m -> n :| [m]) <$ P.string "Mat2@(" <*> pPosInt <* P.char ',' <*> pPosInt <* P.char ')')
  when (ns /= ns') P.pfail
  xs <- PC.readPrec_to_P (GR.readPrec @(ListNST ns a)) 1
  ret <- either (const P.pfail) pure (nestedListToMatC xs)
  when (addNewline ns opts) $ void $ P.char '\n'
  return ret

-- | print a matrix
prtMat :: forall ns a. (ShowMatC ns, Show a) => ShowOpts -> Mat ns a -> IO ()
prtMat = putStrLn .@ showMat

-- | show options for 'Mat'
data ShowOpts = ShowOpts
  { smIndent0 :: !Int
  -- ^ first indentation
  , smIndentN :: !Int
  -- ^ every subsequent indentation
  , smDivvy :: !Bool
  -- ^ split out into 'Vec' and 'Mat2' otherwise lump everything into 'Mat'
  , smInline1D :: !Bool
  -- ^ inline vector: large impact to output
  , smInlineNewLineEof :: !Bool
  -- ^ newline after each inlined vector: large impact to output
  , smOtherNewLineEof :: !Bool
  -- ^ newline after each except if inlined:large impact to output
  }
  deriving stock (Show, Eq, Ord)

-- | default show options for 'Mat'
defShowOpts :: ShowOpts
defShowOpts =
  ShowOpts
    { smIndent0 = 2
    , smIndentN = 0
    , smDivvy = True
    , smInline1D = True
    , smInlineNewLineEof = False
    , smOtherNewLineEof = True
    }

addNewline :: NonEmpty Pos -> ShowOpts -> Bool
addNewline (_ :| ns) opts =
  if null ns && smInline1D opts
    then smInlineNewLineEof opts
    else smOtherNewLineEof opts

instance (Show a, ShowMatC ns, NS ns) => Show (Mat ns a) where
  show = showMat defShowOpts

-- | show a matrix
showMat :: forall ns a. (ShowMatC ns, Show a) => ShowOpts -> Mat ns a -> String
showMat opts w@(Mat _ (n :| ns)) =
  let s = showMatC opts w
      zs = L.intercalate "\n" s
      ret = case (smDivvy opts, ns) of
        (True, []) -> "Vec@" ++ show (unP n) ++ bool "\n" " " (smInline1D opts)
        (True, [m]) -> "Mat2@" ++ show (unP n, unP m) ++ "\n"
        (_, _) -> "Mat@" ++ show (fromPositives (n : ns)) ++ "\n"
   in ret ++ zs ++ bool mempty "\n" (addNewline (n :| ns) opts)

-- | class with methods to convert to and from Mat using nested structures
class ShowMatC ns where
  -- | show a matrix
  showMatC :: Show a => ShowOpts -> Mat ns a -> [String]
  showMatC = showMatC' 1 1

  showMatC' :: Show a => Int -> Int -> ShowOpts -> Mat ns a -> [String]

instance GL.TypeError ( 'GL.Text "ShowMatC '[]: empty indices") => ShowMatC '[] where
  showMatC' = compileError "ShowMatC '[]:showMatC'"

instance ShowMatC '[n] where
  showMatC' i j _ (Mat v _) =
    let ret0 = show (V.toList v)
     in L.lines $ ret0 ++ if i == j then mempty else ","

instance ShowMatC (m ': ns) => ShowMatC (n ': m ': ns) where
  showMatC' i j opts w@(Mat _ (n :| _)) =
    let xs = toListMat $ rows w
        zz = replicate (3 + smIndent0 opts) ' ' -- 3 == length of "],["
        f s = [replicate (smIndent0 opts) ' ' ++ s]
        opts' = opts{smIndent0 = smIndentN opts}
        g i1 x1 = map (zz <>) (showMatC' (unP n) i1 opts' x1)
        s2 = concat $ zipWith g [1 ..] xs
     in case (i, j) of
          (1, 1) -> f "[" ++ s2 ++ f "]"
          (_, 1) -> f "[" ++ s2
          (_, _)
            | i == j -> f "],[" ++ s2 ++ f "]"
            | otherwise -> f "],[" ++ s2

-- | lens into row 1
class Row1 s a | s -> a where
  _r1 :: Lens' s a

-- | lens into row 2
class Row2 s a | s -> a where
  _r2 :: Lens' s a

-- | lens into row 3
class Row3 s a | s -> a where
  _r3 :: Lens' s a

-- | lens into row 4
class Row4 s a | s -> a where
  _r4 :: Lens' s a

-- | lens into row 5
class Row5 s a | s -> a where
  _r5 :: Lens' s a

-- | lens into row 6
class Row6 s a | s -> a where
  _r6 :: Lens' s a

-- | lens into row 7
class Row7 s a | s -> a where
  _r7 :: Lens' s a

-- | lens into row 8
class Row8 s a | s -> a where
  _r8 :: Lens' s a

-- | lens into row 9
class Row9 s a | s -> a where
  _r9 :: Lens' s a

-- | lens into row 10
class Row10 s a | s -> a where
  _r10 :: Lens' s a

-- | lens into the first row in a 2d or greater matrix
instance FinT 1 n => Row1 (Mat (n ': m ': ns) a) (Mat (m ': ns) a) where
  _r1 = _row @1

-- |  lens into the first element in a 1d matrix
instance FinT 1 n => Row1 (Vec n a) a where
  _r1 = _row @1

instance (FinT 2 n) => Row2 (Mat (n ': m ': ns) a) (Mat (m ': ns) a) where
  _r2 = _row @2

instance (FinT 2 n) => Row2 (Vec n a) a where
  _r2 = _row @2

instance (FinT 3 n) => Row3 (Mat (n ': m ': ns) a) (Mat (m ': ns) a) where
  _r3 = _row @3

instance (FinT 3 n) => Row3 (Vec n a) a where
  _r3 = _row @3

instance (FinT 4 n) => Row4 (Mat (n ': m ': ns) a) (Mat (m ': ns) a) where
  _r4 = _row @4

instance (FinT 4 n) => Row4 (Vec n a) a where
  _r4 = _row @4

instance (FinT 5 n) => Row5 (Mat (n ': m ': ns) a) (Mat (m ': ns) a) where
  _r5 = _row @5

instance (FinT 5 n) => Row5 (Vec n a) a where
  _r5 = _row @5

instance (FinT 6 n) => Row6 (Mat (n ': m ': ns) a) (Mat (m ': ns) a) where
  _r6 = _row @6

instance (FinT 6 n) => Row6 (Vec n a) a where
  _r6 = _row @6

instance (FinT 7 n) => Row7 (Mat (n ': m ': ns) a) (Mat (m ': ns) a) where
  _r7 = _row @7

instance (FinT 7 n) => Row7 (Vec n a) a where
  _r7 = _row @7

instance (FinT 8 n) => Row8 (Mat (n ': m ': ns) a) (Mat (m ': ns) a) where
  _r8 = _row @8

instance (FinT 8 n) => Row8 (Vec n a) a where
  _r8 = _row @8

instance (FinT 9 n) => Row9 (Mat (n ': m ': ns) a) (Mat (m ': ns) a) where
  _r9 = _row @9

instance (FinT 9 n) => Row9 (Vec n a) a where
  _r9 = _row @9

instance (FinT 10 n) => Row10 (Mat (n ': m ': ns) a) (Mat (m ': ns) a) where
  _r10 = _row @10

instance (FinT 10 n) => Row10 (Vec n a) a where
  _r10 = _row @10

-- | lens into column 1 of a matrix
_c1 :: FinT 1 m => Lens' (Mat (n ': (m : ns)) a) (Mat (n ': ns) a)
_c1 = _col @1

-- | lens into column 2 of a matrix
_c2 :: FinT 2 m => Lens' (Mat (n ': (m : ns)) a) (Mat (n ': ns) a)
_c2 = _col @2

-- | lens into column 3 of a matrix
_c3 :: FinT 3 m => Lens' (Mat (n ': (m : ns)) a) (Mat (n ': ns) a)
_c3 = _col @3

-- | lens into column 4 of a matrix
_c4 :: FinT 4 m => Lens' (Mat (n ': (m : ns)) a) (Mat (n ': ns) a)
_c4 = _col @4

-- | lens into column 5 of a matrix
_c5 :: FinT 5 m => Lens' (Mat (n ': (m : ns)) a) (Mat (n ': ns) a)
_c5 = _col @5

-- | lens into column 6 of a matrix
_c6 :: FinT 6 m => Lens' (Mat (n ': (m : ns)) a) (Mat (n ': ns) a)
_c6 = _col @6

-- | lens into column 7 of a matrix
_c7 :: FinT 7 m => Lens' (Mat (n ': (m : ns)) a) (Mat (n ': ns) a)
_c7 = _col @7

-- | lens into column 8 of a matrix
_c8 :: FinT 8 m => Lens' (Mat (n ': (m : ns)) a) (Mat (n ': ns) a)
_c8 = _col @8

-- | lens into column 9 of a matrix
_c9 :: FinT 9 m => Lens' (Mat (n ': (m : ns)) a) (Mat (n ': ns) a)
_c9 = _col @9

-- | lens into column 10 of a matrix
_c10 :: FinT 10 m => Lens' (Mat (n ': (m : ns)) a) (Mat (n ': ns) a)
_c10 = _col @10

-- | marker representing the last value in a 1d matrix ie singleton
data Eof1 = Eof1 deriving stock (Show, Eq, Generic)

-- | marker representing the last row in a nd matrix ie singleton
data EofN = EofN deriving stock (Show, Eq, Generic)

type ConsMatCTA :: [Nat] -> Type -> Type
type family ConsMatCTA ns a where
  ConsMatCTA '[] _ = GL.TypeError ( 'GL.Text "ConsMatCTA '[]: empty indices")
  ConsMatCTA '[1] a = a
  ConsMatCTA '[_] a = a
  ConsMatCTA (1 ': m ': ns) a = Mat (m ': ns) a
  ConsMatCTA (_ ': m ': ns) a = Mat (m ': ns) a

type ConsMatCTB :: [Nat] -> Type -> Type
type family ConsMatCTB ns a where
  ConsMatCTB '[] _ = GL.TypeError ( 'GL.Text "ConsMatCTB '[]: empty indices")
  ConsMatCTB '[1] _ = Eof1
  ConsMatCTB '[n] a = Vec (n GN.- 1) a
  ConsMatCTB (1 ': _ ': _) _ = EofN
  ConsMatCTB (n ': m ': ns) a = Mat ((n GN.- 1) ': m ': ns) a

-- | iso and lenses to uncons a matrix
type ConsMatC :: [Nat] -> Type -> Type -> Constraint
class ConsMatC ns a b where
  consMat ::
    Iso
      (Mat ns a)
      (Mat ns b)
      (ConsMatCTA ns a, ConsMatCTB ns a)
      (ConsMatCTA ns b, ConsMatCTB ns b)
  headMat :: a ~ b => Lens' (Mat ns a) (ConsMatCTA ns a)
  headMat = consMat . _Fst

  tailMat :: a ~ b => Lens' (Mat ns a) (ConsMatCTB ns a)
  tailMat = consMat . _Snd

instance
  {-# OVERLAPPING #-}
  ( ConsMatCTA '[1] a ~ a
  , ConsMatCTA '[1] b ~ b
  , ConsMatCTB '[1] a ~ Eof1
  , ConsMatCTB '[1] b ~ Eof1
  ) =>
  ConsMatC '[1] a b
  where
  consMat = iso (\m -> (V.head (mVec m), Eof1)) (\(a, Eof1) -> se1 a)
instance
  {-# OVERLAPPABLE #-}
  ( ConsMatCTA '[n] a ~ a
  , ConsMatCTA '[n] b ~ b
  , ConsMatCTB '[n] a ~ Vec (n GN.- 1) a
  , ConsMatCTB '[n] b ~ Vec (n GN.- 1) b
  ) =>
  ConsMatC '[n] a b
  where
  consMat =
    iso
      ( \(Mat v0 (sn :| ps)) ->
          let n = frp $ predP sn
           in case V.uncons v0 of -- stay within Vector
                Nothing -> programmError "consMat '[1 GN.+ n]: no data"
                Just (a, v) -> (a, MatIU v (n :| ps))
      )
      (\(a, Mat v (p :| ps)) -> MatIU (V.cons a v) (succP p :| ps))

instance
  {-# OVERLAPPING #-}
  ( ConsMatCTA (1 ': m ': ns) a ~ Mat (m ': ns) a
  , ConsMatCTA (1 ': m ': ns) b ~ Mat (m ': ns) b
  , ConsMatCTB (1 ': m ': ns) a ~ EofN
  , ConsMatCTB (1 ': m ': ns) b ~ EofN
  ) =>
  ConsMatC (1 ': n1 ': ns) a b
  where
  consMat =
    iso
      ( \(Mat v (_ :| ps)) ->
          case ps of
            m : ns -> (MatIU v (m :| ns), EofN)
            [] -> programmError "consMat (1 ': m ': ns): missing indices"
      )
      (\(Mat v ps, EofN) -> MatIU v (_1P N.<| ps))

instance
  {-# OVERLAPPING #-}
  ( ConsMatCTA (n ': m ': ns) a ~ Mat (m ': ns) a
  , ConsMatCTA (n ': m ': ns) b ~ Mat (m ': ns) b
  , ConsMatCTB (n ': m ': ns) a ~ Mat ((n GN.- 1) ': m ': ns) a
  , ConsMatCTB (n ': m ': ns) b ~ Mat ((n GN.- 1) ': m ': ns) b
  ) =>
  ConsMatC (n ': m ': ns) a b
  where
  consMat =
    iso
      ( \(Mat v (sn :| ps)) ->
          case ps of
            m : ns ->
              let n = frp $ predP sn
                  ps1 = m :| ns
                  ps2 = n :| (m : ns)
                  (v1, v2) = V.splitAt (productPInt ps1) v
               in ( MatIU v1 ps1
                  , MatIU v2 ps2
                  )
            [] -> programmError "consMat:(1 GN.+ n ': m ': ns): missing indices"
      )
      (\(Mat v1 _, Mat v2 (p2 :| ps2)) -> MatIU (v1 <> v2) (succP p2 :| ps2))

-- | iso and lenses to unsnoc a matrix
type SnocMatC :: [Nat] -> Type -> Type -> Constraint
class SnocMatC ns a b where
  snocMat ::
    Iso
      (Mat ns a)
      (Mat ns b)
      (ConsMatCTB ns a, ConsMatCTA ns a)
      (ConsMatCTB ns b, ConsMatCTA ns b)

  initMat :: a ~ b => Lens' (Mat ns a) (ConsMatCTB ns a)
  initMat = snocMat . _Fst

  lastMat :: a ~ b => Lens' (Mat ns a) (ConsMatCTA ns a)
  lastMat = snocMat . _Snd

instance {-# OVERLAPPING #-} SnocMatC '[1] a b where
  snocMat =
    iso
      (\m -> (Eof1, V.last (mVec m)))
      (\(Eof1, a) -> MatIU (V.singleton a) (_1P :| []))
instance
  {-# OVERLAPPABLE #-}
  ( ConsMatCTB '[n] a ~ Vec (n GN.- 1) a
  , ConsMatCTB '[n] b ~ Vec (n GN.- 1) b
  ) =>
  SnocMatC '[n] a b
  where
  snocMat =
    iso
      ( \(Mat v0 (sn :| ps)) ->
          let n = frp $ predP sn
           in case V.unsnoc v0 of
                Nothing -> programmError "snocMat '[1 GN.+ n]: no data"
                Just (v, a) -> (MatIU v (n :| ps), a)
      )
      (\(Mat v (p :| ps), a) -> MatIU (V.snoc v a) (succP p :| ps))

instance {-# OVERLAPPING #-} SnocMatC (1 ': n1 ': ns) a b where
  snocMat =
    iso
      ( \(Mat v (_ :| ps)) ->
          case ps of
            m : ns ->
              (EofN, MatIU v (m :| ns))
            [] -> programmError "snocMat '[1 GN.+ n]: missing indices"
      )
      (\(EofN, Mat v ps) -> MatIU v (_1P N.<| ps))

instance
  {-# OVERLAPPABLE #-}
  ( ConsMatCTB (n ': m ': ns) a ~ Mat ((n GN.- 1) ': m ': ns) a
  , ConsMatCTB (n ': m ': ns) a ~ Mat ((n GN.- 1) ': m ': ns) b
  ) =>
  SnocMatC (n ': m ': ns) a b
  where
  snocMat =
    iso
      ( \(Mat v (sn :| ps)) ->
          case ps of
            m : ns ->
              let n = frp $ predP sn
                  ps1 = m :| ns
                  ps2 = n :| (m : ns)
                  (v2, v1) = V.splitAt (productPInt ps2) v
               in ( MatIU v2 ps2
                  , MatIU v1 ps1
                  )
            [] -> programmError "snocMat:(1 GN.+ n ': m ': ns): missing indices"
      )
      (\(Mat v1 (p1 :| ps1), Mat v2 _) -> MatIU (v1 <> v2) (succP p1 :| ps1))

-- | construct a new matrix based on a 1d matrix of row witnesses
rowsToMat ::
  forall x n m ns a.
  Vec x (Fin n) ->
  Mat (n ': m ': ns) a ->
  Mat (x ': m ': ns) a
rowsToMat w1@(Mat _ (x :| _)) w2@(Mat _ (_ :| ps)) =
  MatIU (V.concat $ toListMat $ fmap (\fn -> mVec (indexRow fn w2)) w1) (x :| ps)

-- | get a row from a matrix using a concrete index see '_row''
indexRow :: Fin n -> Mat (n ': m ': ns) a -> Mat (m ': ns) a
indexRow (Fin (Pos i) _n) (Mat v (_ :| ps)) =
  case ps of
    m : ns ->
      let s = (i - 1) * len
          len = productPInt (m :| ns)
       in MatIU (V.slice s len v) (m :| ns)
    [] -> programmError "indexRow: missing indices"

-- | 'Data.List.scanr' for a vector
scanrVec :: forall n a b. (a -> b -> b) -> b -> Vec n a -> Vec (n GN.+ 1) b
scanrVec f c (Mat v (p :| ps)) =
  MatIU (V.scanr' f c v) (succP p :| ps)

-- | 'Data.List.scanl'' for a vector
scanlVec :: forall n a b. (b -> a -> b) -> b -> Vec n a -> Vec (n GN.+ 1) b
scanlVec f c (Mat v (p :| ps)) =
  MatIU (V.scanl' f c v) (succP p :| ps)

{- | @see 'Data.Vector.postscanr''
 concrete version of 'Primus.Fold.postscanr
-}
postscanrMat :: forall ns a b. (a -> b -> b) -> b -> Mat ns a -> Mat ns b
postscanrMat f c (Mat v ps) =
  MatIU (V.postscanr' f c v) ps

{- | @see 'Data.Vector.postscanl''
 concrete version of 'Primus.Fold.postscanl'
-}
postscanlMat :: forall ns a b. (b -> a -> b) -> b -> Mat ns a -> Mat ns b
postscanlMat f c (Mat v ps) =
  MatIU (V.postscanl' f c v) ps

-- | matrix of dimension 1
dim1 :: Vec n a -> Vec n a
dim1 = id

-- | matrix of dimension 2
dim2 :: Mat2 n m a -> Mat2 n m a
dim2 = id

-- | matrix of dimension 3
dim3 :: Mat '[n, m, p] a -> Mat '[n, m, p] a
dim3 = id

-- | matrix of dimension 4
dim4 :: Mat '[n, m, p, q] a -> Mat '[n, m, p, q] a
dim4 = id

-- | matrix of dimension 5
dim5 :: Mat '[n, m, p, q, r] a -> Mat '[n, m, p, q, r] a
dim5 = id

-- | matrix of dimension 6
dim6 :: Mat '[n, m, p, q, r, s] a -> Mat '[n, m, p, q, r, s] a
dim6 = id

-- | matrix of dimension 7
dim7 :: Mat '[n, m, p, q, r, s, t] a -> Mat '[n, m, p, q, r, s, t] a
dim7 = id

-- | matrix of dimension 8
dim8 :: Mat '[n, m, p, q, r, s, t, u] a -> Mat '[n, m, p, q, r, s, t, u] a
dim8 = id

-- | matrix of dimension 9
dim9 :: Mat '[n, m, p, q, r, s, t, u, v] a -> Mat '[n, m, p, q, r, s, t, u, v] a
dim9 = id

-- | matrix of dimension 10
dim10 :: Mat '[n, m, p, q, r, s, t, u, v, w] a -> Mat '[n, m, p, q, r, s, t, u, v, w] a
dim10 = id