trexio-hs-0.1.1: test/trexio-test.hs
import Control.Concurrent (threadDelay)
import Control.Exception.Safe
import Control.Monad
import Data.Bit.ThreadSafe (Bit)
import Data.Massiv.Array as Massiv hiding (Size, elem, forM, forM_, mapM, mapM_, take, zip, zipWith)
import Data.Massiv.Array qualified as Massiv
import Data.Maybe (catMaybes, fromJust)
import Data.Set qualified as Set
import Data.Text (Text)
import Debug.Trace
import Hedgehog (Gen, MonadGen, Size, forAll, property, (===))
import Hedgehog.Gen qualified as Gen
import Hedgehog.Range qualified as Range
import System.Directory
import System.IO.Temp
import TREXIO
import TREXIO.CooArray
import Test.Tasty
import Test.Tasty.HUnit
import Test.Tasty.Hedgehog
main :: IO ()
main = defaultMain tests
tests :: TestTree
tests =
testGroup
"TREXIO"
[ testGroup
"0D"
[ testGroup "Integers" . appFn $
[ ("nucleus.num", genDim, deleteNucleus, hasNucleusNum, readNucleusNum, writeNucleusNum)
, ("grid.max_ang_num", genPosInt, deleteGrid, hasGridMaxAngNum, readGridMaxAngNum, writeGridMaxAngNum)
, ("state.id", genIndex, deleteState, hasStateId, readStateId, writeStateId)
]
, testGroup "Floats" . appFn $
[ ("nucleus.repulsion", genPosFloat, deleteNucleus, hasNucleusRepulsion, readNucleusRepulsion, writeNucleusRepulsion)
]
, testGroup "Strings" . appFn $
[ ("metadata.description", genIdentifier, deleteMetadata, hasMetadataDescription, readMetadataDescription, writeMetadataDescription)
]
]
, testGroup
"1D"
[ testGroup "Integers" . appFn $
[ ("ecp.ang_num", genVector genInt, deleteEcp, hasEcpAngMom, readEcpAngMom, writeEcpAngMom)
]
, testGroup "Floats" . appFn $
[ ("basis.shell_factor", genVector genFloat, deleteBasis, hasBasisShellFactor, readBasisShellFactor, writeBasisShellFactor)
]
, testGroup "Strings" . appFn $
[ ("metadata.author", genVector genIdentifier, deleteMetadata, hasMetadataAuthor, readMetadataAuthor, writeMetadataAuthor)
]
, testProperty "Determinant IO" . property $ do
-- Generate occupation numbers
nMo <- forAll $ Gen.int (Range.linear 1 1000)
nUp <- forAll $ Gen.int (Range.linear 2 (nMo - 1))
nDn <- forAll $ Gen.int (Range.linear 2 (nMo - 1))
-- Generate determinants
dets <- forAll $ genDet nMo (nUp, nDn)
let Sz2 nDets _ = Massiv.size dets
-- Generate coefficients
let
coeffs <- forAll . fmap (Massiv.fromList Par) $ Gen.list (Range.singleton nDets) (Gen.double $ Range.linearFrac (-10) 10)
liftIO $ withSystemTempFile "trexio.dat" $ \fp _ ->
withTrexio fp FileWrite Hdf5 $ \trexio -> do
-- Write MO numbers and electron occupation numbers to the file
writeMoNum trexio nMo
writeElectronUpNum trexio nUp
writeElectronDnNum trexio nDn
nMo' <- readMoNum trexio
nUp' <- readElectronUpNum trexio
nDn' <- readElectronDnNum trexio
-- Check
nMo @?= nMo'
nUp @?= nUp'
nDn @?= nDn'
-- Write determinants
writeDeterminantList trexio dets
-- Read back determinants
readDets <- readDeterminantList trexio
-- Check for equality
dets @?= readDets
-- Write coefficients
writeDeterminantCoefficient trexio coeffs
-- Read back coefficients
coeffs' <- readDeterminantCoefficient trexio
coeffs @?= coeffs'
]
, testGroup
"2D"
[ testGroup "Dense" . appFn $
[ ("ao_1e_int.overlap", genMatrix SameAs1 genFloat, deleteAo1eInt, hasAo1eIntOverlap, readAo1eIntOverlap, writeAo1eIntOverlap)
]
, testGroup "Sparse" . appFn $
[ ("amplitude.single", genSparseArr2 SameAs1 genFloat, deleteAmplitude, hasAmplitudeSingle, readAmplitudeSingle, writeAmplitudeSingle)
]
]
, testGroup
"3D"
[ testGroup "Sparse" . appFn $
[ ("mo_2e_int.eri_lr_cholesky", genSparseArr3 SameAs1 SameAs1 genFloat, deleteMo2eInt, hasMo2eIntEriLrCholesky, readMo2eIntEriLrCholesky, writeMo2eIntEriLrCholesky)
]
]
, testGroup "4D" . appFn $
[ ("mo_2e_int.eri", genSparseArr4 SameAs1 SameAs1 SameAs1 genFloat, deleteMo2eInt, hasMo2eIntEri, readMo2eIntEri, writeMo2eIntEri)
]
, testGroup "6D" . appFn $
[ ("amplitude.triple", genSparseArr6 SameAs1 SameAs1 SameAs1 SameAs1 SameAs1 genFloat, deleteAmplitude, hasAmplitudeTriple, readAmplitudeTriple, writeAmplitudeTriple)
]
, testGroup "8D" . appFn $
[ ("amplitude.quadruple", genSparseArr8 SameAs1 SameAs1 SameAs1 SameAs1 SameAs1 SameAs1 SameAs1 genFloat, deleteAmplitude, hasAmplitudeQuadruple, readAmplitudeQuadruple, writeAmplitudeQuadruple)
]
]
where
appFn :: (Eq a, Show a) => [(TestName, Gen a, Trexio -> IO (), Trexio -> IO Bool, Trexio -> IO a, Trexio -> a -> IO ())] -> [TestTree]
appFn = fmap (\(name, val, delFn, hasFn, readFn, writeFn) -> testField name val delFn hasFn readFn writeFn)
ingoreExcp :: (MonadCatch m) => [ExitCode] -> m a -> m ()
ingoreExcp excps action = catch (void action) $ \e ->
if e `elem` excps
then return ()
else throw e
testField ::
(Eq a, Show a) =>
-- | Name of the test
TestName ->
-- | Value to write
Gen a ->
-- | The group delete function
(Trexio -> IO ()) ->
-- | The "Has" function
(Trexio -> IO Bool) ->
-- | The "Read" function
(Trexio -> IO a) ->
-- | The "Write" function
(Trexio -> a -> IO ()) ->
TestTree
testField name gen delFn hasFn readFn writeFn = testProperty name . property $ do
val <- forAll gen
fp <- liftIO $ emptySystemTempFile "trexio.h5"
trexio <- open fp FileUnsafe Hdf5
-- Mark operations as unsafe
safetyFlagU <- readMetadataUnsafe trexio
safetyFlagU === 1
markSafety trexio
safetyFlagS <- readMetadataUnsafe trexio
safetyFlagS === 0
-- Nothing should be there yet
has1 <- liftIO $ hasFn trexio
has1 === False
-- Reading should return a missing attribute exception
ingoreExcp [AttrMissing] (liftIO $ readFn trexio)
-- Write the value to the file
liftIO $ writeFn trexio val
-- Writing again should return an attribute already exists exception
-- ingoreExcp [AttrAlreadyExists, DSetAlreadyExists] (write trexio val)
-- Check if it is there
has2 <- liftIO $ hasFn trexio
has2 === True
-- Read it back
val' <- liftIO $ readFn trexio
val' === val
-- Delete the entire group
liftIO $ delFn trexio
-- Nothing should be there anymore
has3 <- liftIO $ hasFn trexio
has3 === False
close trexio
liftIO $ removeFile fp
data DimDep
= Independent
| SameAs1
| SameAs2
deriving (Show, Eq, Ord)
-- | Generate a random integer
genInt :: Gen Int
genInt = Gen.integral (Range.linearFrom 0 (-1_000_000) 1_000_000)
genPosInt :: Gen Int
genPosInt = Gen.integral (Range.linearFrom 0 0 100)
-- | Generate a @dim@ value, which is a positive integer
genDim :: Gen Int
genDim = Gen.integral (Range.linear 1 100)
-- | Generate a random index, which is a non-negative integer
genIndex :: Gen Int
genIndex = Gen.integral (Range.linear 0 1000)
genMoIndex :: Gen Word
genMoIndex = Gen.word (Range.linear 0 300)
genFloat :: Gen Double
genFloat = Gen.realFloat (Range.linearFrac (-1_000_000) 1_000_000)
genPosFloat :: Gen Double
genPosFloat = Gen.realFloat (Range.linearFrac 0 1_000_000)
-- | Generate a identifier, that is a single word without spaces or stuff
genIdentifier :: Gen Text
genIdentifier = Gen.text (Range.linear 1 10) Gen.alphaNum
-- | Generate a Massiv vector from elements from another generator
genVector :: (Manifest r a) => Gen a -> Gen (Vector r a)
genVector gen = do
dim <- genDim
Massiv.fromList Seq <$> Gen.list (Range.singleton dim) gen
-- | Generate a Massiv matrix from elements from another generator
genMatrix :: (Manifest r a, Ord a) => DimDep -> Gen a -> Gen (Matrix r a)
genMatrix dimDep2 gen = do
rows <- genDim
cols <- case dimDep2 of
Independent -> genDim
SameAs1 -> return rows
SameAs2 -> error "genMatrix: SameAs2 not supported for Dim2"
Massiv.fromLists' Seq <$> Gen.list (Range.singleton rows) (Gen.list (Range.singleton cols) gen)
genArr3 :: (Manifest r a, Ord a) => DimDep -> DimDep -> Gen a -> Gen (Array r Ix3 a)
genArr3 dimDep2 dimDep3 gen = do
d1 <- genDim
d2 <- case dimDep2 of
Independent -> genDim
SameAs1 -> return d1
SameAs2 -> error "genArr3: SameAs2 not supported for Dim2"
d3 <- case dimDep3 of
Independent -> genDim
SameAs1 -> return d1
SameAs2 -> return d2
vals <- Gen.list (Range.singleton $ d1 * d2 * d3) gen
pure . Massiv.resize' (Sz3 d1 d2 d3) $ Massiv.fromList Par vals
genSparseArr2 ::
forall r a.
(Manifest r a, Ord a, Manifest r Ix2, Stream r Ix1 Ix2) =>
DimDep ->
Gen a ->
Gen (CooArray r Ix2 a)
genSparseArr2 dimDep2 gen = scaleSparse $ do
d1 <- genDim
d2 <- case dimDep2 of
Independent -> genDim
SameAs1 -> return d1
SameAs2 -> error "genSparseArr2: SameAs2 not supported for Dim2"
let sz = Sz2 d1 d2
cooVals <-
catMaybes . Set.toAscList
<$> Gen.set
(Range.singleton $ d1 * d2)
( do
c1 <- Gen.int $ Range.linear 0 (d1 - 1)
c2 <- Gen.int $ Range.linear 0 (d2 - 1)
v <- gen
Gen.maybe . pure $ (c1 :. c2, v)
)
if null cooVals
then Gen.discard
else pure . fromJust $ mkCooArrayF sz cooVals
genSparseArr3 ::
forall r a.
(Manifest r a, Manifest r Ix3, Ord a, Stream r Ix1 Ix3) =>
DimDep ->
DimDep ->
Gen a ->
Gen (CooArray r Ix3 a)
genSparseArr3 dimDep2 dimDep3 gen = scaleSparse $ do
d1 <- genDim
d2 <- case dimDep2 of
Independent -> genDim
SameAs1 -> return d1
SameAs2 -> error "genSparseArr3: SameAs2 not supported for Dim2"
d3 <- case dimDep3 of
Independent -> genDim
SameAs1 -> return d1
SameAs2 -> return d2
let sz = Sz3 d1 d2 d3
cooVals <-
catMaybes . Set.toAscList
<$> Gen.set
(Range.singleton $ d1 * d2 * d3)
( do
c1 <- Gen.int $ Range.linear 0 (d1 - 1)
c2 <- Gen.int $ Range.linear 0 (d2 - 1)
c3 <- Gen.int $ Range.linear 0 (d3 - 1)
v <- gen
Gen.maybe . pure $ (c1 :> c2 :. c3, v)
)
if null cooVals
then Gen.discard
else pure . fromJust $ mkCooArrayF sz cooVals
genSparseArr4 ::
forall r a.
(Manifest r a, Ord a, Manifest r Ix4, Stream r Ix1 Ix4) =>
DimDep ->
DimDep ->
DimDep ->
Gen a ->
Gen (CooArray r Ix4 a)
genSparseArr4 dimDep2 dimDep3 dimDep4 gen = scaleSparse $ do
d1 <- genDim
d2 <- case dimDep2 of
Independent -> genDim
SameAs1 -> return d1
SameAs2 -> error "genSparseArr4: SameAs2 not implemented"
d3 <- case dimDep3 of
Independent -> genDim
SameAs1 -> return d1
SameAs2 -> return d2
d4 <- case dimDep4 of
Independent -> genDim
SameAs1 -> return d1
SameAs2 -> return d2
let sz = Sz4 d1 d2 d3 d4
cooVals <-
catMaybes . Set.toAscList
<$> Gen.set
(Range.singleton $ d1 * d2 * d3)
( do
c1 <- Gen.int $ Range.linear 0 (d1 - 1)
c2 <- Gen.int $ Range.linear 0 (d2 - 1)
c3 <- Gen.int $ Range.linear 0 (d3 - 1)
c4 <- Gen.int $ Range.linear 0 (d4 - 1)
v <- gen
Gen.maybe . pure $ (c1 :> c2 :> c3 :. c4, v)
)
if null cooVals
then Gen.discard
else pure . fromJust $ mkCooArrayF sz cooVals
genSparseArr6 ::
forall r a.
(Manifest r a, Ord a, Manifest r (IxN 6), Stream r Ix1 (IxN 6)) =>
DimDep ->
DimDep ->
DimDep ->
DimDep ->
DimDep ->
Gen a ->
Gen (CooArray r (IxN 6) a)
genSparseArr6 dimDep2 dimDep3 dimDep4 dimDep5 dimDep6 gen = scaleSparse $ do
d1 <- genDim
d2 <- case dimDep2 of
Independent -> genDim
SameAs1 -> return d1
SameAs2 -> error "genSparseArr4: SameAs2 not implemented"
d3 <- case dimDep3 of
Independent -> genDim
SameAs1 -> return d1
SameAs2 -> return d2
d4 <- case dimDep4 of
Independent -> genDim
SameAs1 -> return d1
SameAs2 -> return d2
d5 <- case dimDep5 of
Independent -> genDim
SameAs1 -> return d1
SameAs2 -> return d2
d6 <- case dimDep6 of
Independent -> genDim
SameAs1 -> return d1
SameAs2 -> return d2
let sz = Sz $ d1 :> d2 :> d3 :> d4 :> d5 :. d6
cooVals <-
catMaybes . Set.toAscList
<$> Gen.set
(Range.singleton $ d1 * d2 * d3)
( do
c1 <- Gen.int $ Range.linear 0 (d1 - 1)
c2 <- Gen.int $ Range.linear 0 (d2 - 1)
c3 <- Gen.int $ Range.linear 0 (d3 - 1)
c4 <- Gen.int $ Range.linear 0 (d4 - 1)
c5 <- Gen.int $ Range.linear 0 (d5 - 1)
c6 <- Gen.int $ Range.linear 0 (d6 - 1)
v <- gen
Gen.maybe . pure $ (c1 :> c2 :> c3 :> c4 :> c5 :. c6, v)
)
if null cooVals
then Gen.discard
else pure . fromJust $ mkCooArrayF sz cooVals
genSparseArr8 ::
forall r a.
(Manifest r a, Ord a, Manifest r (IxN 8), Stream r Ix1 (IxN 8)) =>
DimDep ->
DimDep ->
DimDep ->
DimDep ->
DimDep ->
DimDep ->
DimDep ->
Gen a ->
Gen (CooArray r (IxN 8) a)
genSparseArr8 dimDep2 dimDep3 dimDep4 dimDep5 dimDep6 dimDep7 dimDep8 gen = scaleSparse $ do
d1 <- genDim
d2 <- case dimDep2 of
Independent -> genDim
SameAs1 -> return d1
SameAs2 -> error "genSparseArr4: SameAs2 not implemented"
d3 <- case dimDep3 of
Independent -> genDim
SameAs1 -> return d1
SameAs2 -> return d2
d4 <- case dimDep4 of
Independent -> genDim
SameAs1 -> return d1
SameAs2 -> return d2
d5 <- case dimDep5 of
Independent -> genDim
SameAs1 -> return d1
SameAs2 -> return d2
d6 <- case dimDep6 of
Independent -> genDim
SameAs1 -> return d1
SameAs2 -> return d2
d7 <- case dimDep7 of
Independent -> genDim
SameAs1 -> return d1
SameAs2 -> return d2
d8 <- case dimDep8 of
Independent -> genDim
SameAs1 -> return d1
SameAs2 -> return d2
let sz = Sz $ d1 :> d2 :> d3 :> d4 :> d5 :> d6 :> d7 :. d8
cooVals <-
catMaybes . Set.toAscList
<$> Gen.set
(Range.singleton $ d1 * d2 * d3)
( do
c1 <- Gen.int $ Range.linear 0 (d1 - 1)
c2 <- Gen.int $ Range.linear 0 (d2 - 1)
c3 <- Gen.int $ Range.linear 0 (d3 - 1)
c4 <- Gen.int $ Range.linear 0 (d4 - 1)
c5 <- Gen.int $ Range.linear 0 (d5 - 1)
c6 <- Gen.int $ Range.linear 0 (d6 - 1)
c7 <- Gen.int $ Range.linear 0 (d7 - 1)
c8 <- Gen.int $ Range.linear 0 (d8 - 1)
v <- gen
Gen.maybe . pure $ (c1 :> c2 :> c3 :> c4 :> c5 :> c6 :> c7 :. c8, v)
)
if null cooVals
then Gen.discard
else pure . fromJust $ mkCooArrayF sz cooVals
scaleSparse :: (MonadGen m) => m a -> m a
scaleSparse = Gen.scale sz2zs
where
sz2zs :: Size -> Size
sz2zs x = round $ fromIntegral x * (0.25 :: Double)
-- | Generate multiple determinants valid for given system
genDet ::
-- | Number of MOs in the system
Int ->
-- | Number of Up and Down electrons
(Int, Int) ->
-- | List of determinants
Gen (Matrix U (Bit, Bit))
genDet nMo (nUp, nDn) = do
detsL <- Gen.set (Range.linear 1 100) detGen
dets <- case Massiv.stackOuterSlicesM . Set.toList $ detsL of
Nothing -> error "Failed to stack slices"
Just dets' -> return dets'
return . compute $ dets
where
occGen :: (MonadGen m) => Int -> m (Set.Set Int)
occGen nOcc = Gen.set (Range.singleton nOcc) (Gen.int (Range.linear 0 (nMo - 1)))
detGen :: (MonadGen m) => m (Massiv.Vector U (Bit, Bit))
detGen = do
-- Generate indices of occupied orbitals
occUp <- occGen nUp
occDn <- occGen nDn
-- Generate a single determinant
let detUp = makeArray @U Par (Sz nMo) $ \i -> if i `Set.member` occUp then 1 else 0
detDn = makeArray @U Par (Sz nMo) $ \i -> if i `Set.member` occDn then 1 else 0
det = Massiv.zip detUp detDn
return . compute $ det