{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE GADTs #-}
module Numeric.LAPACK.Matrix.Symmetric.Unified where

import qualified Numeric.LAPACK.Matrix.Mosaic.Unpacked as Unpacked
import qualified Numeric.LAPACK.Matrix.Mosaic.Packed as Packed
import qualified Numeric.LAPACK.Matrix.Mosaic.Generic as Mosaic
import qualified Numeric.LAPACK.Matrix.Mosaic.Private as MosaicPriv
import qualified Numeric.LAPACK.Matrix.Basic as Basic
import qualified Numeric.LAPACK.Matrix.Private as Matrix
import qualified Numeric.LAPACK.Matrix.Layout.Private as Layout
import qualified Numeric.LAPACK.Matrix.Shape.Omni as Omni
import qualified Numeric.LAPACK.Matrix.Extent.Private as Extent
import qualified Numeric.LAPACK.Vector as Vector
import qualified Numeric.LAPACK.Scalar as Scalar
import Numeric.LAPACK.Matrix.Mosaic.Private
         (Mosaic, recheck, copyTriangleToTemp, forPointers,
          diagonalPointers, columnMajorPointers, rowMajorPointers,
          withPacking, withPackingLinear, runPacking,
          noLabel, label, applyFuncPair, triArg,
          Labelled(Labelled))
import Numeric.LAPACK.Matrix.Layout.Private
         (Order(RowMajor,ColumnMajor), uploFromOrder, uploOrder)
import Numeric.BLAS.Matrix.Modifier
         (Conjugation(NonConjugated, Conjugated), conjugatedOnRowMajor,
          Transposition(NonTransposed, Transposed), transposeOrder)
import Numeric.LAPACK.Matrix.Private (Full, General, Square)
import Numeric.LAPACK.Linear.Private (solver, withDeterminantInfo, diagonalMsg)
import Numeric.LAPACK.Vector (Vector)
import Numeric.LAPACK.Scalar (RealOf, zero, one)
import Numeric.LAPACK.Shape.Private (Unchecked(Unchecked))
import Numeric.LAPACK.Private
         (fill, copyBlock, copyToTemp,
          condConjugate, copyCondConjugate, condConjugateToTemp,
          withAutoWorkspace, pointerSeq)

import qualified Numeric.LAPACK.FFI.Complex as LapackComplex
import qualified Numeric.LAPACK.FFI.Generic as LapackGen
import qualified Numeric.BLAS.FFI.Complex as BlasComplex
import qualified Numeric.BLAS.FFI.Real as BlasReal
import qualified Numeric.BLAS.FFI.Generic as BlasGen
import qualified Numeric.Netlib.Utility as Call
import qualified Numeric.Netlib.Class as Class

import qualified Data.Array.Comfort.Storable.Unchecked as Array
import qualified Data.Array.Comfort.Shape as Shape
import Data.Array.Comfort.Storable.Unchecked (Array(Array))

import Foreign.Marshal.Array (advancePtr)
import Foreign.C.Types (CInt, CChar)
import Foreign.ForeignPtr (ForeignPtr, withForeignPtr)
import Foreign.Ptr (Ptr)
import Foreign.Storable (Storable, peek)

import qualified System.IO.Lazy as LazyIO
import System.IO.Unsafe (unsafePerformIO)

import Control.Monad.Trans.Cont (ContT(ContT), evalContT)
import Control.Monad.IO.Class (liftIO)
import Control.Applicative ((<$>))



data MirrorSingleton mirror where
   SimpleMirror :: MirrorSingleton Layout.SimpleMirror
   ConjugateMirror :: MirrorSingleton Layout.ConjugateMirror

deriving instance Eq (MirrorSingleton mirror)
deriving instance Show (MirrorSingleton mirror)

class (Layout.Mirror mirror) => Mirror mirror where
   autoMirror :: MirrorSingleton mirror
instance Mirror Layout.SimpleMirror where autoMirror = SimpleMirror
instance Mirror Layout.ConjugateMirror where autoMirror = ConjugateMirror

narrowMirror :: (Mirror mirror) => f mirror -> MirrorSingleton mirror
narrowMirror _ = autoMirror

conjugationFromMirror :: (Mirror mirror) => f mirror -> Conjugation
conjugationFromMirror mirror =
   case narrowMirror mirror of
      SimpleMirror -> NonConjugated
      ConjugateMirror -> Conjugated



{- |
> let b = takeHalf a
> ==>
> isTriangular b && a == addTransposed b
-}
takeHalf ::
   (Shape.C sh, Class.Floating a) =>
   Unpacked.Mosaic mirror Shape.Upper sh a ->
   Unpacked.Triangular Shape.Upper sh a
takeHalf (Array shape a) =
   Array.unsafeCreateWithSize
         shape{Layout.mosaicMirror = Layout.NoMirror} $
      \size bPtr -> evalContT $ do
   let n = Shape.size $ Layout.mosaicSize shape
   aPtr <- ContT $ withForeignPtr a
   nPtr <- Call.cint n
   alphaPtr <- Call.number 0.5
   incxPtr <- Call.cint (n+1)
   liftIO $ do
      copyBlock size aPtr bPtr
      BlasGen.scal nPtr alphaPtr bPtr incxPtr


toSquare ::
   (Mirror mirror, Shape.C sh, Class.Floating a) =>
   Packed.Mosaic mirror Shape.Upper sh a -> Square sh a
toSquare (Array (Layout.Mosaic _pack mirror _uplo order sh) a) =
   Array.unsafeCreate (Layout.square order sh) $ \bPtr ->
   withForeignPtr a $ \aPtr -> evalContT $ do

   let conj = conjugationFromMirror mirror
   let n = Shape.size sh
   incxPtr <- Call.cint 1
   incyPtr <- Call.cint n
   liftIO $ case order of
      RowMajor ->
         forPointers (rowMajorPointers n bPtr aPtr) $
               \nPtr (dstPtr,srcPtr) -> do
            copyCondConjugate conj nPtr srcPtr incxPtr dstPtr incyPtr
            BlasGen.copy nPtr srcPtr incxPtr dstPtr incxPtr
      ColumnMajor ->
         forPointers (columnMajorPointers n bPtr aPtr) $
               \nPtr ((dstRowPtr,dstColumnPtr),srcPtr) -> do
            copyCondConjugate conj nPtr srcPtr incxPtr dstRowPtr incyPtr
            BlasGen.copy nPtr srcPtr incxPtr dstColumnPtr incxPtr


addMirrored ::
   (Mirror mirror, Shape.C sh, Class.Floating a) =>
   Square sh a -> Packed.Mosaic mirror Shape.Upper sh a
addMirrored (Array (Layout.Full order extent) a) =
   let sh = Extent.squareSize extent
       mirror = Layout.autoMirror
       shape =
         Layout.Mosaic Layout.Packed mirror Layout.Upper order sh
   in Array.unsafeCreate shape $ \bPtr -> evalContT $ do

   let conj = conjugationFromMirror mirror
   let n = Shape.size sh
   alphaPtr <- Call.number one
   incxPtr <- Call.cint 1
   incnPtr <- Call.cint n
   aPtr <- ContT $ withForeignPtr a
   liftIO $ case order of
      RowMajor ->
         forPointers (rowMajorPointers n aPtr bPtr) $
            \nPtr (srcPtr,dstPtr) -> do
         copyCondConjugate conj nPtr srcPtr incnPtr dstPtr incxPtr
         BlasGen.axpy nPtr alphaPtr srcPtr incxPtr dstPtr incxPtr
      ColumnMajor ->
         forPointers (columnMajorPointers n aPtr bPtr) $
            \nPtr ((srcRowPtr,srcColumnPtr),dstPtr) -> do
         copyCondConjugate conj nPtr srcRowPtr incnPtr dstPtr incxPtr
         BlasGen.axpy nPtr alphaPtr srcColumnPtr incxPtr dstPtr incxPtr


complementUnpacked ::
   (Class.Floating a) =>
   Conjugation -> Order -> Int -> Ptr a -> Ptr a -> IO ()
complementUnpacked conj order n aPtr bPtr = evalContT $ do
   inc1Ptr <- Call.cint 1
   incnPtr <- Call.cint n
   let number = take n . zip [0..]
   liftIO $ case order of
      RowMajor ->
         forPointers (number $ zip (pointerSeq 1 aPtr) (pointerSeq n bPtr)) $
               \nPtr (srcPtr,dstPtr) ->
            copyCondConjugate conj nPtr srcPtr incnPtr dstPtr inc1Ptr
      ColumnMajor ->
         forPointers (number $ zip (pointerSeq n aPtr) (pointerSeq 1 bPtr)) $
               \nPtr (srcPtr,dstPtr) ->
            copyCondConjugate conj nPtr srcPtr inc1Ptr dstPtr incnPtr

complement ::
   (Class.Floating a) =>
   Layout.PackingSingleton pack ->
   Conjugation -> Order -> Int -> Ptr a -> IO ()
complement pack conj order n bPtr = do
   case pack of
      Layout.Packed -> return ()
      Layout.Unpacked -> complementUnpacked conj order n bPtr bPtr


type SPMV ar a =
      Ptr CChar -> Ptr CInt -> Ptr ar -> Ptr a ->
      Ptr a -> Ptr CInt -> Ptr a -> Ptr a -> Ptr CInt -> IO ()

type SYMV ar a =
      Ptr CChar -> Ptr CInt -> Ptr ar -> Ptr a -> Ptr CInt ->
      Ptr a -> Ptr CInt -> Ptr a -> Ptr a -> Ptr CInt -> IO ()

multiplyVectorCont ::
   (Class.Floating ar, Class.Floating a) =>
   SPMV ar a ->
   SYMV ar a ->
   Layout.PackingSingleton pack ->
   Layout.UpLoSingleton uplo ->
   Layout.Order ->
   Int ->
   ForeignPtr a -> Ptr a -> Ptr a -> ContT () IO ()
multiplyVectorCont spmv symv pack uplo order n a xPtr yPtr = do
   uploPtr <- Call.char $ uploFromOrder $ uploOrder uplo order
   nPtr <- Call.cint n
   alphaPtr <- Call.number one
   aPtr <- ContT $ withForeignPtr a
   incxPtr <- Call.cint 1
   betaPtr <- Call.number zero
   incyPtr <- Call.cint 1
   withPacking pack $
      applyFuncPair (noLabel spmv) (noLabel symv)
         uploPtr nPtr alphaPtr (triArg aPtr n)
         xPtr incxPtr betaPtr yPtr incyPtr

-- Triangular is a bit different, it has no alpha
multiplyVector ::
   (Mirror mirror, Shape.C sh, Eq sh, Class.Floating a) =>
   Mosaic pack mirror uplo sh a -> Vector sh a -> Vector sh a
multiplyVector
   (Array (Layout.Mosaic pack mirror uplo order shA) a) (Array shX x) =
      Array.unsafeCreateWithSize shX $ \n yPtr -> do
   Call.assert "Symmetric.multiplyVector: width shapes mismatch" (shA == shX)
   evalContT $
      case narrowMirror mirror of
         ConjugateMirror -> do
            let conj = conjugatedOnRowMajor order
            xPtr <- condConjugateToTemp conj n x
            multiplyVectorCont BlasGen.hpmv BlasGen.hemv
               pack uplo order n a xPtr yPtr
            nPtr <- Call.cint n
            incyPtr <- Call.cint 1
            liftIO $ condConjugate conj nPtr yPtr incyPtr
         SimpleMirror -> do
            xPtr <- ContT $ withForeignPtr x
            case Scalar.complexSingletonOfFunctor x of
               Scalar.Real ->
                  multiplyVectorCont BlasReal.spmv BlasReal.symv
                     pack uplo order n a xPtr yPtr
               Scalar.Complex ->
                  multiplyVectorCont LapackComplex.spmv LapackComplex.symv
                     pack uplo order n a xPtr yPtr


multiplyFull ::
   (Layout.UpLo uplo, Extent.Measure meas, Extent.C vert, Extent.C horiz,
    Shape.C height, Eq height, Shape.C width, Class.Floating a) =>
   Mosaic pack mirror uplo height a ->
   Full meas vert horiz height width a ->
   Full meas vert horiz height width a
multiplyFull =
   Unpacked.multiplyFull Omni.Arbitrary . Mosaic.unpackDirty


forceUpper ::
   (Layout.Packing pack, Mirror mirror, Shape.C sh, Class.Floating a) =>
   Mosaic pack mirror uplo sh a ->
   Mosaic pack mirror Shape.Upper sh a
forceUpper a =
   case Layout.mosaicUplo $ Array.shape a of
      Layout.Upper -> a
      Layout.Lower -> upperFromLower a

upperFromLower ::
   (Layout.Packing pack, Mirror mirror, Shape.C sh, Class.Floating a) =>
   Mosaic pack mirror Shape.Lower sh a ->
   Mosaic pack mirror Shape.Upper sh a
upperFromLower
      (Array (Layout.Mosaic pack mirror Layout.Lower order sh) a) =

   (case narrowMirror mirror of
      SimpleMirror -> id
      ConjugateMirror -> Vector.conjugate) $
   Array
      (Layout.Mosaic
         pack mirror Layout.Upper (Layout.flipOrder order) sh)
      a


type SYR ar a f =
      Ptr CChar -> Ptr CInt -> Ptr ar -> Ptr a -> Ptr CInt -> Ptr a -> f

withSyrSingleton ::
   (Class.Floating a) => (Scalar.ComplexSingleton a -> SYR a a f) -> SYR a a f
withSyrSingleton f = f Scalar.complexSingleton

spr :: (Class.Floating a) => SYR a a (IO ())
spr = withSyrSingleton $ \sw ->
   case sw of
      Scalar.Real -> BlasReal.spr
      Scalar.Complex -> LapackComplex.spr

syr :: (Class.Floating a) => SYR a a (Ptr CInt -> IO ())
syr = withSyrSingleton $ \sw ->
   case sw of
      Scalar.Real -> BlasReal.syr
      Scalar.Complex -> LapackComplex.syr

outerCont ::
   (Class.Floating a, Class.Floating ar) =>
   SYR ar a (IO ()) ->
   SYR ar a (Ptr CInt -> IO ()) ->
   ForeignPtr a -> Int ->
   Layout.PackingSingleton pack ->
   Layout.UpLoSingleton uplo ->
   Ptr a -> ContT () IO ()
outerCont spr_ syr_ x n pack uplo aPtr = do
   uploPtr <- Call.char $ Layout.uploChar uplo
   nPtr <- Call.cint n
   alphaPtr <- Call.number one
   xPtr <- ContT $ withForeignPtr x
   incxPtr <- Call.cint 1
   withPacking pack $
      applyFuncPair (noLabel spr_) (noLabel syr_)
         uploPtr nPtr alphaPtr xPtr incxPtr (triArg aPtr n)

outer ::
   (Layout.Packing pack, Mirror mirror, Layout.UpLo uplo,
    Shape.C sh, Class.Floating a) =>
   Vector sh a -> Mosaic pack mirror uplo sh a
outer (Array sh x) =
   let n = Shape.size sh
       pack = Layout.autoPacking
       mirror = Layout.autoMirror
       uplo = Layout.autoUplo
       order = Layout.ColumnMajor
   in Array.unsafeCreateWithSize
         (Layout.Mosaic pack mirror uplo order sh) $
      \triSize aPtr -> do
   fill zero triSize aPtr
   evalContT $
      case (Scalar.complexSingletonOfFunctor x, narrowMirror mirror) of
         (Scalar.Complex, ConjugateMirror) ->
            outerCont BlasComplex.hpr BlasComplex.her x n pack uplo aPtr
         _ -> outerCont spr syr x n pack uplo aPtr
   complement pack (conjugationFromMirror mirror) (uploOrder uplo order) n aPtr

outerUpper ::
   (Layout.Packing pack, Mirror mirror, Shape.C sh, Class.Floating a) =>
   Order -> Vector sh a -> Mosaic pack mirror Shape.Upper sh a
outerUpper order x =
   case order of
      Layout.ColumnMajor -> outer x
      Layout.RowMajor -> upperFromLower $ outer x


upperShape ::
   (Layout.Mirror mirror) =>
   Layout.PackingSingleton pack ->
   Layout.MirrorSingleton mirror -> Order -> size ->
   Layout.Mosaic pack mirror Shape.Upper size
upperShape pack mirror = Layout.Mosaic pack mirror Layout.Upper

unpackedShape ::
   (Layout.Mirror mirror) =>
   Layout.MirrorSingleton mirror -> Order -> size ->
   Layout.Mosaic Layout.Unpacked mirror Shape.Upper size
unpackedShape = upperShape Layout.Unpacked

skipCheckCongruence ::
   ((sh -> Unchecked sh) -> matrix0 -> matrix1) ->
   (matrix1 -> Mosaic pack mirror uplo (Unchecked sh) a) ->
   matrix0 -> Mosaic pack mirror uplo sh a
skipCheckCongruence mapSize f  =  recheck . f . mapSize Unchecked


postHook :: (Monad m) => m () -> ContT r m ()
postHook hook = ContT $ \act -> do r <- act (); hook; return r

temporaryUnpacked ::
   (Mirror mirror, Class.Floating a) =>
   Layout.PackingSingleton pack -> MirrorSingleton mirror -> Order ->
   Int -> Ptr a -> ContT () IO (Ptr a)
temporaryUnpacked pack mirror order n cpPtr =
   case pack of
      Layout.Packed -> do
         cPtr <- Call.allocaArray (n*n)
         postHook $ MosaicPriv.pack order n cPtr cpPtr
         return cPtr
      Layout.Unpacked -> do
         postHook $
            complementUnpacked
               (conjugationFromMirror mirror) order n cpPtr cpPtr
         return cpPtr


gramianParameters ::
   (Extent.Measure meas, Extent.C vert, Extent.C horiz,
    Shape.C height, Shape.C width) =>
   MirrorSingleton mirror ->
   Transposition ->
   Order ->
   Extent.Extent meas vert horiz height width ->
   ((Int, Int), (Char, Char, Int))
gramianParameters mirror trans order extent =
   let (height, width) = Extent.dimensions extent
       n = Shape.size width
       k = Shape.size height
       mirrorChar =
         case mirror of
            SimpleMirror -> 'T'
            ConjugateMirror -> 'C'
       transChar =
         case transposeOrder trans order of
            ColumnMajor -> mirrorChar
            RowMajor -> 'N'
       lda =
         case order of
            ColumnMajor -> k
            RowMajor -> n
    in (case trans of NonTransposed -> (n,k); Transposed -> (k,n),
        (uploFromOrder order, transChar, lda))


{-
Another way to unify 'gramian' and 'gramianAdjoint'
would have been this function:

> gramianConjugation ::
>    Conjugation -> General height width a -> Hermitian width a

with

> gramianAdjoint a = gramianConjugation (transpose a)

but I would like to have

> order (gramianAdjoint a) = order a
-}
gramianIO ::
   (Mirror mirror, Class.Floating a) =>
   Layout.PackingSingleton pack -> MirrorSingleton mirror -> Order ->
   ForeignPtr a -> Ptr a ->
   ((Int, Int), (Char, Char, Int)) -> IO ()
gramianIO pack mirror order a cpPtr ((n,k), (uplo,trans,lda)) = evalContT $ do
   uploPtr <- Call.char uplo
   transPtr <- Call.char trans
   nPtr <- Call.cint n
   kPtr <- Call.cint k
   aPtr <- ContT $ withForeignPtr a
   ldaPtr <- Call.leadingDim lda
   cPtr <- temporaryUnpacked pack mirror order n cpPtr
   ldcPtr <- Call.leadingDim n
   case (k, mirror, Scalar.complexSingletonOfFunctor a) of
      (0, _, _) -> liftIO $ fill zero (n*n) cPtr
      (_, ConjugateMirror, Scalar.Complex) -> do
         alphaPtr <- Call.number one
         betaPtr <- Call.number zero
         liftIO $
            BlasComplex.herk uploPtr transPtr
               nPtr kPtr alphaPtr aPtr ldaPtr betaPtr cPtr ldcPtr
      _ -> do
         alphaPtr <- Call.number one
         betaPtr <- Call.number zero
         liftIO $
            BlasGen.syrk uploPtr transPtr
               nPtr kPtr alphaPtr aPtr ldaPtr betaPtr cPtr ldcPtr

gramian ::
   (Layout.Packing pack, Mirror mirror,
    Shape.C height, Shape.C width, Class.Floating a) =>
   Matrix.General height width a ->
   Mosaic pack mirror Shape.Upper width a
gramian (Array (Layout.Full order extent) a) =
   let pack = Layout.autoPacking
       mirror = Layout.autoMirror
   in Array.unsafeCreate (upperShape pack mirror order $ Extent.width extent) $
      \bPtr ->
         gramianIO pack (narrowMirror mirror) order a bPtr $
         gramianParameters (narrowMirror mirror) NonTransposed order extent

gramianTransposed ::
   (Layout.Packing pack, Mirror mirror,
    Shape.C height, Shape.C width, Class.Floating a) =>
   Matrix.General height width a ->
   Mosaic pack mirror Shape.Upper height a
gramianTransposed (Array (Layout.Full order extent) a) =
   let pack = Layout.autoPacking
       mirror = Layout.autoMirror
   in Array.unsafeCreate (upperShape pack mirror order $ Extent.height extent) $
      \bPtr ->
         gramianIO pack (narrowMirror mirror) order a bPtr $
         gramianParameters (narrowMirror mirror) Transposed order extent


scaledAnticommutatorIO ::
   (Mirror mirror, Class.Floating a) =>
   Layout.PackingSingleton pack -> MirrorSingleton mirror -> Order -> a ->
   ForeignPtr a -> ForeignPtr a -> Ptr a ->
   ((Int, Int), (Char, Char, Int)) -> IO ()
scaledAnticommutatorIO
   pack mirror order alpha a b cpPtr ((n,k), (uplo,trans,lda)) =
      evalContT $ do
   uploPtr <- Call.char uplo
   transPtr <- Call.char trans
   nPtr <- Call.cint n
   kPtr <- Call.cint k
   alphaPtr <- Call.number alpha
   aPtr <- ContT $ withForeignPtr a
   ldaPtr <- Call.leadingDim lda
   bPtr <- ContT $ withForeignPtr b
   let ldbPtr = ldaPtr
   cPtr <- temporaryUnpacked pack mirror order n cpPtr
   ldcPtr <- Call.leadingDim n
   case (mirror, Scalar.complexSingletonOfFunctor a) of
      (ConjugateMirror, Scalar.Complex) -> do
         betaPtr <- Call.number zero
         liftIO $
            BlasComplex.her2k uploPtr transPtr nPtr kPtr alphaPtr
               aPtr ldaPtr bPtr ldbPtr betaPtr cPtr ldcPtr
      _ -> do
         betaPtr <- Call.number zero
         liftIO $
            BlasGen.syr2k uploPtr transPtr nPtr kPtr alphaPtr
               aPtr ldaPtr bPtr ldbPtr betaPtr cPtr ldcPtr

scaledAnticommutator ::
   (Layout.Packing pack, Mirror mirror,
    Extent.Measure meas, Extent.C vert, Extent.C horiz,
    Shape.C height, Eq height, Shape.C width, Eq width, Class.Floating a) =>
   Layout.MirrorSingleton mirror ->
   a ->
   Full meas vert horiz height width a ->
   Full meas vert horiz height width a ->
   Mosaic pack mirror Shape.Upper width a
scaledAnticommutator mirror
      alpha arr (Array (Layout.Full order extentB) b) = do
   let (Array (Layout.Full _ extentA) a) = Basic.forceOrder order arr
       pack = Layout.autoPacking
   Array.unsafeCreate (upperShape pack mirror order $ Extent.width extentB) $
         \cPtr -> do
      Call.assert "Symmetric.anticommutator: extents mismatch"
         (extentA==extentB)
      scaledAnticommutatorIO pack (narrowMirror mirror) order alpha a b cPtr $
         gramianParameters (narrowMirror mirror) NonTransposed order extentB

scaledAnticommutatorTransposed ::
   (Layout.Packing pack, Mirror mirror,
    Extent.Measure meas, Extent.C vert, Extent.C horiz,
    Shape.C height, Eq height, Shape.C width, Eq width, Class.Floating a) =>
   Layout.MirrorSingleton mirror ->
   a ->
   Full meas vert horiz height width a ->
   Full meas vert horiz height width a ->
   Mosaic pack mirror Shape.Upper height a
scaledAnticommutatorTransposed mirror
      alpha arr (Array (Layout.Full order extentB) b) = do
   let (Array (Layout.Full _ extentA) a) = Basic.forceOrder order arr
       pack = Layout.autoPacking
   Array.unsafeCreate (upperShape pack mirror order $ Extent.height extentB) $
         \cPtr -> do
      Call.assert "Symmetric.anticommutatorTransposed: extents mismatch"
         (extentA==extentB)
      scaledAnticommutatorIO pack (narrowMirror mirror) order alpha a b cPtr $
         gramianParameters (narrowMirror mirror) Transposed order extentB


congruenceRealDiagonal ::
   (Layout.Packing pack, Mirror mirror,
    Shape.C height, Eq height, Shape.C width, Class.Floating a) =>
   Vector height (RealOf a) ->
   General height width a ->
   Mosaic pack mirror Shape.Upper width a
congruenceRealDiagonal d =
   skipCheckCongruence Basic.mapWidth $ \a ->
      scaledAnticommutator Layout.autoMirror 0.5 a $
         Basic.scaleRowsReal d a

congruenceRealDiagonalTransposed ::
   (Layout.Packing pack, Mirror mirror,
    Shape.C height, Shape.C width, Eq width, Class.Floating a) =>
   General height width a ->
   Vector width (RealOf a) ->
   Mosaic pack mirror Shape.Upper height a
congruenceRealDiagonalTransposed =
   flip $ \d -> skipCheckCongruence Basic.mapHeight $ \a ->
      scaledAnticommutatorTransposed Layout.autoMirror 0.5 a $
         Basic.scaleColumnsReal d a

congruence ::
   (Layout.Packing pack, Mirror mirror,
    Shape.C height, Eq height, Shape.C width, Class.Floating a) =>
   Unpacked.Mosaic mirror Shape.Upper height a ->
   Matrix.General height width a ->
   Mosaic pack mirror Shape.Upper width a
congruence b =
   skipCheckCongruence Basic.mapWidth $ \a ->
      scaledAnticommutator
         (Layout.mosaicMirror $ Array.shape b) one a $
      Unpacked.multiplyFull Omni.Arbitrary (takeHalf b) a

congruenceTransposed ::
   (Layout.Packing pack, Mirror mirror,
    Shape.C height, Shape.C width, Eq width, Class.Floating a) =>
   Matrix.General height width a ->
   Unpacked.Mosaic mirror Shape.Upper width a ->
   Mosaic pack mirror Shape.Upper height a
congruenceTransposed =
   flip $ \b -> skipCheckCongruence Basic.mapHeight $ \a ->
      scaledAnticommutatorTransposed
         (Layout.mosaicMirror $ Array.shape b) one a $
      Basic.swapMultiply
         (Unpacked.multiplyFull Omni.Arbitrary . Mosaic.transpose)
         a (takeHalf b)


solve ::
   (Mirror mirror, Extent.Measure meas, Extent.C vert, Extent.C horiz,
    Eq height, Shape.C height, Shape.C width, Class.Floating a) =>
   Mosaic pack mirror Shape.Upper height a ->
   Full meas vert horiz height width a ->
   Full meas vert horiz height width a
solve (Array shape@(Layout.Mosaic pack mirror _upper order sh) a) =
   solver (show mirror) sh $ \n nPtr nrhsPtr xPtr ldxPtr -> do
      uploPtr <- Call.char $ uploFromOrder order
      let conj = conjugationFromMirror mirror
      aPtr <- copyTriangleToTemp conj order (Shape.size shape) a
      ipivPtr <- Call.allocaArray n
      withPackingLinear diagonalMsg pack $ fmap autoWorkspace $
         uncurry applyFuncPair
            (case conj of
               Conjugated ->
                  (label "hpsv" LapackGen.hpsv, label "hesv" LapackGen.hesv)
               NonConjugated ->
                  (label "spsv" LapackGen.spsv, label "sysv" LapackGen.sysv))
            uploPtr nPtr nrhsPtr (triArg aPtr n) ipivPtr xPtr ldxPtr


inverse ::
   (Mirror mirror, Layout.UpLo uplo, Shape.C sh, Class.Floating a) =>
   Mosaic pack mirror uplo sh a ->
   Mosaic pack mirror uplo sh a
inverse (Array shape@(Layout.Mosaic pack mirror uplo order sh) a) =
   Array.unsafeCreateWithSize shape $ \triSize bPtr -> evalContT $ do

   let n = Shape.size sh
   let realOrder = uploOrder uplo order
   uploPtr <- Call.char $ uploFromOrder realOrder
   nPtr <- Call.cint n
   aPtr <- ContT $ withForeignPtr a
   ipivPtr <- Call.allocaArray n
   liftIO $ copyBlock triSize aPtr bPtr
   let conj = conjugationFromMirror mirror
   let (trf,tri) =
         case conj of
            Conjugated ->
               ((label "hptrf" LapackGen.hptrf, label "hetrf" LapackGen.hetrf),
                (label "hptri" LapackGen.hptri, label "hetri" LapackGen.hetri))
            NonConjugated ->
               ((label "sptrf" LapackGen.sptrf, label "sytrf" LapackGen.sytrf),
                (label "sptri" LapackGen.sptri, label "sytri" LapackGen.sytri))
   withPackingLinear diagonalMsg pack $ fmap autoWorkspace $
      uncurry applyFuncPair trf uploPtr nPtr (triArg bPtr n) ipivPtr
   workPtr <- Call.allocaArray n
   withPackingLinear diagonalMsg pack $
      uncurry applyFuncPair tri uploPtr nPtr (triArg bPtr n) ipivPtr workPtr
   liftIO $ complement pack conj realOrder n bPtr


blockDiagonalPointers ::
   (Storable a) =>
   Order -> [(Ptr CInt, Ptr a)] -> LazyIO.T [(Ptr a, Maybe (Ptr a, Ptr a))]
blockDiagonalPointers order =
   let go ((ipiv0Ptr,a0Ptr):ptrs0) = do
         ipiv <- LazyIO.interleave $ peek ipiv0Ptr
         (ext,ptrTuples) <-
            if ipiv >= 0
               then (,) Nothing <$> go ptrs0
               else
                  case ptrs0 of
                     [] -> error "Symmetric.determinant: incomplete 2x2 block"
                     (_ipiv1Ptr,a1Ptr):ptrs1 ->
                        let bPtr =
                              case order of
                                 ColumnMajor -> advancePtr a1Ptr (-1)
                                 RowMajor -> advancePtr a0Ptr 1
                        in (,) (Just (a1Ptr,bPtr)) <$> go ptrs1
         return $ (a0Ptr,ext) : ptrTuples
       go [] = return []
   in go

determinant ::
   (Mirror mirror, Layout.UpLo uplo,
    Shape.C sh, Class.Floating a, Class.Floating ar) =>
   ((Ptr a, Maybe (Ptr a, Ptr a)) -> IO ar) ->
   Mosaic pack mirror uplo sh a -> ar
determinant peekBlockDeterminant
   (Array shape@(Layout.Mosaic pack mirror uplo order sh) a) =
      unsafePerformIO $ evalContT $ do

   let conj = conjugationFromMirror mirror
   let n = Shape.size sh
   uploPtr <- Call.char $ uploFromOrder $ uploOrder uplo order
   nPtr <- Call.cint n
   aPtr <- copyToTemp (Shape.size shape) a
   ipivPtr <- Call.allocaArray n
   let diagPtrs =
         case pack of
            Layout.Packed -> diagonalPointers order n aPtr
            Layout.Unpacked -> take n $ pointerSeq (n+1) aPtr
   let (utrf,ptrf) =
         case conj of
            Conjugated ->
               (label "hptrf" LapackGen.hptrf, label "hetrf" LapackGen.hetrf)
            NonConjugated ->
               (label "sptrf" LapackGen.sptrf, label "sytrf" LapackGen.sytrf)
   let Labelled name makeTrf =
         runPacking pack $ fmap autoWorkspace $
         applyFuncPair utrf ptrf uploPtr nPtr (triArg aPtr n) ipivPtr
   trf <- makeTrf
   liftIO $
      withDeterminantInfo name trf
         ((return $!) =<<
          LazyIO.run
            (fmap product $
             mapM (LazyIO.interleave . peekBlockDeterminant) =<<
             blockDiagonalPointers order (zip (pointerSeq 1 ipivPtr) diagPtrs)))

autoWorkspace ::
   (Class.Floating a) => (Ptr a -> Ptr CInt -> info -> IO ()) -> info -> IO ()
autoWorkspace trf info =
   withAutoWorkspace $ \work lwork -> trf work lwork info