{-|
 -Module      : Qubits
 -Description : Definitions of |0>, |1>, |+> and |->
 -Copyright   : (c) Mihai Sebastian Ardelean, 2017
 -License     : BSD3
 -Maintainer  : ardeleanasm@gmail.com
 -Portability : POSIX
 -}
module Qubits
    ( 
          qOne
        , qZero
        , qPlus
        , qMinus
        , entangle
        , apply
        , (|>)
        , (|><|)
        , Qubit(..)
    ) where

import Numeric.LinearAlgebra hiding ( (|>) )
import Gates

data Qubit=
    Qubit {
            qubitState::Matrix C -- ^ Qubit constructor accepts a parameter of type Matrix C
          } deriving (Eq,Show)



{-|
 -  qZero function is used to represent a 0 qubit |0>
 
 >>>qZero
 (2><1)
 [ 1.0 :+ 0.0
 , 0.0 :+ 0.0 ]
 -}
qZero::Qubit
qZero=Qubit ((2><1)[1,0]::Matrix C)

    


{-|
 -  qOne function is used to represent a 1 qubit |1>
 
 >>>qOne
 (2><1)
 [ 0.0 :+ 0.0
 , 1.0 :+ 0.0 ]
 -}
qOne::Qubit
qOne=Qubit ((2><1) [0,1]::Matrix C)

{-|
 -  qPlus function is used to represent a + qubit |+>
 - |+> can be also obtained by applying Hadamard Gate on |0>. See 'Utils.apply' for more information

 >>>qPlus
 (2><1)
 [ 0.7071067811865475 :+ 0.0
 , 0.7071067811865475 :+ 0.0 ]
 -}
qPlus::Qubit
qPlus=Qubit ((2><1) [1/sqrt 2, 1/sqrt 2]::Matrix C)

{-|
 -  qMinus function is used to represent a - qubit |->
 - Same as |+>, qubit |-> can be obtained by applying Hadamard Gate on |1>. See 'Utils.apply' for more information
 
 >>>qMinus
 (2><1)
 [       0.7071067811865475 :+ 0.0
 , (-0.7071067811865475) :+ (-0.0) ]
 -}
qMinus::Qubit 
qMinus=Qubit ((2><1) [1/sqrt 2, -1/sqrt 2]::Matrix C)

{-|
  -  entangle function is used to perform the Kronecker product between two qubits.
  
  >>>entangle qZero qOne
(4><1)
 [ 0.0 :+ 0.0
 , 1.0 :+ 0.0
 , 0.0 :+ 0.0
 , 0.0 :+ 0.0 ]

 -}   
entangle::Qubit   -- ^ first 'Qubit' argument
        ->Qubit   -- ^ second 'Qubit' argument
        ->Qubit   -- ^ return value: 'Qubit'
entangle q1 q2=Qubit (kronecker (qubitState q1) (qubitState q2))


{-|
  -  apply function is used to apply a gate on a qubit
  
  >>>apply hGate qZero
 (2><1)
 [ 0.7071067811865475 :+ 0.0
 , 0.7071067811865475 :+ 0.0 ]
 -}   
apply::Gate   -- ^ 'Gate' argument
     ->Qubit  -- ^ 'Qubit' argument 
     ->Qubit  -- ^ return value: 'Qubit'
apply m v=Qubit (gateMatrix m <> qubitState v)

  


{-|
  -|> function have the same effect like `apply`.
  
  >>>qZero |> hGate 
 (2><1)
 [ 0.7071067811865475 :+ 0.0
 , 0.7071067811865475 :+ 0.0 ]
 -}   
(|>)::Qubit  -- ^ 'Qubit' argument 
    ->Gate   -- ^ 'Gate' argument
    ->Qubit  -- ^ return value: 'Qubit'
(|>)=flip apply


{-|
  -|><| function represents the outer product.
  
  >>>qZero |><| qZero 
 (2><2)
 [ 1.0 :+ 0.0, 0.0 :+ 0.0
 , 0.0 :+ 0.0, 0.0 :+ 0.0 ]}
 -}  
(|><|)::Qubit -- ^ 'Qubit' argument 
      ->Qubit -- ^ 'Qubit' argument 
      ->Gate  -- ^ return value: 'Gate'
q1 |><| q2=outerProduct
    where 
        q1Flatten=flatten $ qubitState q1
        q2Flatten=flatten $ qubitState q2
        outerProduct=Gate (q1Flatten `outer` q2Flatten)