HABQT-0.1.0.0: src/HABQTlib/Data.hs
{-# LANGUAGE DeriveGeneric #-}
{-|
Module : HABQTlib.Data
This module contains data types and helper functions for working with quantum
state vectors and density matrices.
-}
module HABQTlib.Data
( Dim
, Rank
, NumberOfParticles
, QBitNum
, Weight
, MHMCiter
, OptIter
, OutputVerb(..)
, DensityMatrix(..)
, truncateRank
, PureStateVector(..)
, pureStateLikelihood
, svToDM
, WeighedDensityMatrix(..)
, mkWDM
, mkWDM1
, WeighedPureStateVector(..)
, (<+>)
, fidelity
, fidelityDM
, validSV
, validDM
, validPartNum
, validRank
, validMHMCiter
, validOptIter
, validQBitN
) where
import Control.Newtype.Generics (Newtype, unpack)
import Data.Bool.HT (select)
import Data.Complex
import Data.Validation
import GHC.Generics (Generic)
import qualified Numeric.LinearAlgebra as LA
import Numeric.LinearAlgebra (Matrix)
-- | Dimension of Hilbert space.
type Dim = Int
-- | Rank of mixed state.
type Rank = Int
-- | Number of particles per rank.
type NumberOfParticles = Int
-- | Number of quantum bits.
type QBitNum = Int
-- | Number of MHMC iterations to perform when resampling.
type MHMCiter = Int
-- | Number of optimisation steps to perform when searching for optimal
-- measurment.
type OptIter = Int
-- | Weight associated with a particle.
type Weight = Double
-- | Output verbosity settings.
data OutputVerb
= NoOutput -- ^ No stdout output
| FidOutput -- ^ Only output fidelities and weights of hierarchical mean estimates
| FullOutput -- ^ Full output, including resampling diagnostic information
deriving (Eq, Show, Ord)
-- | Density matrix are stored as hmatrix matrices of complex doubles.
newtype DensityMatrix = DensityMatrix
{ getDensityMatrix :: Matrix (Complex Double)
} deriving (Eq, Show, Generic)
-- | Pure state vectors are stored as hmatrix matrices of complex doubles.
-- Such matrices only have one column.
newtype PureStateVector = PureStateVector
{ getStateVector :: Matrix (Complex Double)
} deriving (Eq, Show, Generic)
instance Newtype DensityMatrix
instance Newtype PureStateVector
-- | Check whether a pure state vector is properly normed.
validSV :: PureStateVector -> Validation [String] PureStateVector
validSV =
validate
["State vector must have unit norm."]
(\x -> abs (1 - LA.norm_2 (unpack x)) < 1e-12)
-- | Verify that density matrix is Hermitian and has trace 1.
validDM :: DensityMatrix -> Validation [String] DensityMatrix
validDM =
let traceU :: LA.Matrix (Complex Double) -> Bool
traceU dm =
(abs (1 - (magnitude . LA.sumElements . LA.takeDiag) dm) < 1e-12)
hermU dm = (LA.norm_2 (dm - LA.tr dm) < 1e-6)
both = ((&&) <$> traceU <*> hermU) . unpack
dmM = ["Density matrix must be Hermitian and have trace of 1."]
in validate dmM both
qnM :: [String]
qnM = ["Number of quantum bits must be a positive integer."]
-- | Verify that number of quantum bits is positive.
validQBitN :: QBitNum -> Validation [String] QBitNum
validQBitN = validate qnM (> 0)
miM :: [String]
miM = ["Number of MHMC iterations must be a positive integer."]
-- | Verify that number of MHMC iterations is a positive integer.
validMHMCiter :: MHMCiter -> Validation [String] MHMCiter
validMHMCiter = validate miM (> 0)
pnM :: [String]
pnM = ["Number of particles per rank must be a positive integer."]
-- | Verify that particle number is a positive integer.
validPartNum :: NumberOfParticles -> Validation [String] NumberOfParticles
validPartNum = validate pnM (> 0)
rM :: [String]
rM = ["Rank must be a positive integer."]
-- | Verify that rank is a positive integer. Setting rank to be higher than
-- the dimension of space creates poinless performance overhead, but isn't
-- prevented by validation.
validRank :: Rank -> Validation [String] Rank
validRank = validate rM (> 0)
oiM :: [String]
oiM = ["Number of POVM optimisation iterations must be a positive integer."]
-- | Verify that number of POVM optimisation iterations is positive.
validOptIter :: OptIter -> Validation [String] OptIter
validOptIter = validate oiM (> 0)
-- | Weighed density matrix where weight is stored separately as first
-- coordinate of a tuple.
newtype WeighedDensityMatrix = WeighedDensityMatrix
{ getWDM :: (Weight, DensityMatrix)
} deriving (Eq, Show, Generic)
-- | A shorter alias for curried 'WeighedDensityMatrix' constructor.
mkWDM :: Weight -> DensityMatrix -> WeighedDensityMatrix
mkWDM w dm = WeighedDensityMatrix (w, dm)
-- | Alias for @'mkWDM' 1@.
mkWDM1 :: DensityMatrix -> WeighedDensityMatrix
mkWDM1 = mkWDM 1
-- | Weighed state vector where weight is stored separately as first coordinate
-- of a tuple.
newtype WeighedPureStateVector = WeighedPureStateVector
{ getWSV :: (Weight, PureStateVector)
} deriving (Eq, Show, Generic)
instance Newtype WeighedDensityMatrix
instance Newtype WeighedPureStateVector
-- | Fidelity (probability of measurement) between pure states.
fidelity :: PureStateVector -> PureStateVector -> Double
fidelity (PureStateVector sv1) (PureStateVector sv2) =
let ips = magnitude (LA.atIndex (LA.tr sv1 LA.<> sv2) (0, 0)) ^ (2 :: Int)
in select ips [(ips < 0, 0), (ips > 1, 1)]
-- | Fidelity (probability of measurement) between mixed states.
fidelityDM :: DensityMatrix -> DensityMatrix -> Double
fidelityDM (DensityMatrix dm1) (DensityMatrix dm2) =
let (u1, s1) = LA.leftSV dm1
(u2, s2) = LA.leftSV dm2
ss1 = LA.cmap (\x -> sqrt x :+ 0) s1
ss2 = LA.cmap (\x -> sqrt x :+ 0) s2
in LA.norm_nuclear
(u1 LA.<> LA.diag ss1 LA.<> LA.tr u1 LA.<> u2 LA.<> LA.diag ss2 LA.<>
LA.tr u2) ^
(2 :: Int)
-- | Calculate the density matrix of a given pure state.
svToDM :: PureStateVector -> DensityMatrix
svToDM (PureStateVector sv) = DensityMatrix $ sv LA.<> LA.tr sv
infix 8 <+>
-- | Given two weighed density matrixes, compute their mixture. Associative
-- operation.
(<+>) :: WeighedDensityMatrix -> WeighedDensityMatrix -> WeighedDensityMatrix
wdm0 <+> wdm1 =
WeighedDensityMatrix
(w0 + w1, DensityMatrix $ LA.scale c0 dm0 + LA.scale c1 dm1)
where
up = fmap unpack . unpack
(w0, dm0) = up wdm0
(w1, dm1) = up wdm1
cs = LA.fromList [w0 :+ 0, w1 :+ 0]
csn = LA.scale (1 / LA.sumElements cs) cs
c0 = csn LA.! 0
c1 = csn LA.! 1
-- | Probability of obtaining a measurement result when projecting a system in
-- mixed state determined by a density matrix onto a pure state.
pureStateLikelihood :: PureStateVector -> DensityMatrix -> Double
pureStateLikelihood (PureStateVector sv) (DensityMatrix dm) =
let p =
LA.magnitude . LA.sumElements . LA.takeDiag $ LA.tr sv LA.<> dm LA.<> sv
in select p [(p < 0, 0), (p > 1, 1)]
-- | Set smallest eigenvalues of a weighed density matrix to zero until
-- specified rank is reached.
truncateRank :: Rank -> WeighedDensityMatrix -> WeighedDensityMatrix
truncateRank targetRank (WeighedDensityMatrix (w, DensityMatrix dm)) =
let (u, s, _) = LA.svd dm
st = LA.real $ LA.subVector 0 targetRank s
ut = u LA.¿ [0 .. (targetRank - 1)]
stn = LA.scale (1 / LA.sumElements st) st
in WeighedDensityMatrix
(w, DensityMatrix $ ut LA.<> LA.diag stn LA.<> LA.tr ut)