{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE DeriveDataTypeable #-}
-- | This module contains the implementation of the various quantum circuits
-- that make up the boolean formula algorithm. Please see "Quipper.Algorithms.BF.Main"
-- for an overview of the boolean formula algorithm.
module Quipper.Algorithms.BF.BooleanFormula where
import Quipper
import Quipper.Internal
import Quipper.Algorithms.BF.Hex
import Quipper.Libraries.QFT
import Quipper.Libraries.QuantumIf
import Quipper.Libraries.Simulation
import Quipper.Libraries.Decompose
import Quipper.Utils.Auxiliary (mmap)
import Data.Typeable
-- ----------------------------------------------------------------------
-- * Classical data structures
-- ** Oracle description
-- $ We define a data structure to hold the various parameters that
-- are used to define an oracle.
-- | The input to the BF Algorithm is the description of an oracle to
-- represent a given size of hex board, and a given size for the phase
-- estimation register.
data BooleanFormulaOracle = BFO {
oracle_x_max :: Int, -- ^ The /x/-dimension of hex board.
oracle_y_max :: Int, -- ^ The /y/-dimension of hex board.
oracle_t :: Int, -- ^ Size of phase estimation register.
-- The number of moves remaining can
-- depend on the starting state of the HexBoard
oracle_s :: Int, -- ^ Number of moves remaining.
-- This should start as /x/⋅/y/, if no moves have been made.
oracle_m :: Int, -- ^ Size of the direction register,
-- i.e., size of labels on the BF tree.
-- This should be the ceiling of log(/x/⋅/y/).
start_board :: HexBoard, -- ^ A description of the starting state of the
-- board, and can be used to calculate /s/.
oracle_hex :: HexCircuit -- ^ An extra flag that we can use so that different
-- HEX circuits can be used instead of the full circuit.
}
-- | A type to define which Hex circuit to use.
data HexCircuit = Hex -- ^ The actual Hex circuit.
| Dummy -- ^ A Dummy Hex circuit.
| EmptyHex -- ^ Nothing.
-- | Create an oracle description. This only requires /x/, /y/, and
-- /t/ to be specified, as the remaining values can be calculated. The number of
-- moves remaining, /s/, is calculated as the total number of squares on the board,
-- and /m/ is calculated as the number of bits required to represent /s/+1.
createOracle :: Int -> Int -> Int -> BooleanFormulaOracle
createOracle x y t = BFO {
oracle_x_max = x,
oracle_y_max = y,
oracle_t = t,
oracle_s = s,
oracle_m = m,
start_board = (empty,empty),
oracle_hex = Hex
} where s = x * y
m = ceiling (log (fromIntegral (s+1)) / log 2)
empty = replicate s False
-- | A function to set the \"Dummy\" flag in the given oracle to the given
-- 'HexCircuit' value.
update_hex :: BooleanFormulaOracle -> HexCircuit -> BooleanFormulaOracle
update_hex bfo hex = bfo {oracle_hex = hex}
-- | Update the 'start_board' in the given oracle, with the given 'HexBoard'. This
-- also updates the 'oracle_s' field of the oracle to be in line with
-- the new 'start_board'.
update_start_board :: BooleanFormulaOracle -> HexBoard -> BooleanFormulaOracle
update_start_board bfo start = bfo {
oracle_s = s,
start_board = start
} where
x = oracle_x_max bfo
y = oracle_y_max bfo
s = (x*y) - (moves_made start)
-- | An oracle for a 9 by 7 Hex board, with the parameters:
-- /x/=9, /y/=7, /t/=189. The calculated values are: /s/=63, /m/=6.
full_oracle :: BooleanFormulaOracle
full_oracle = createOracle 9 7 189
-- | A smaller oracle for testing purposes. The numbers should be
-- chosen such that /x/⋅/y/ = 2[sup /n/]−1 for some /n/. Here, we set
-- /x/=3 and /y/=5, to give /x/⋅/y/=15. We arbitrarily set the size of
-- the phase estimation register to /t/=4.
test_oracle :: BooleanFormulaOracle
test_oracle = createOracle 5 3 4
-- ** Hex boards
-- | A hex board is specified by a pair of lists of booleans. For a
-- board of size /x/ by /y/, each list should contain /x/⋅/y/
-- elements. The first list is the \"blue\" bitmap, and the second is
-- the \"red\" maskmap.
type HexBoard = ([Bool],[Bool])
-- | A function to determine how many moves have been made on a given HexBoard.
-- This function assumes that the given 'HexBoard' is valid, in the sense that
-- no duplicate moves have been made.
moves_made :: HexBoard -> Int
moves_made (blue,red) = moves blue + moves red
where moves color = length (filter id color)
-- | A function to determine which spaces are still empty in the given HexBoard.
-- This function assumes that the given 'HexBoard' is valid, in the sense that
-- no duplicate moves have been made. This function will return a list of all the
-- empty spaces remaining, in strictly increasing order.
empty_spaces :: HexBoard -> [Int]
empty_spaces (blue,red) = empty_spaces' blue red 0
where
empty_spaces' [] [] _ = []
empty_spaces' [] _ _ = error "empty_spaces: Red and Blue boards of different length"
empty_spaces' _ [] _ = error "empty_spaces: Red and Blue boards of different length"
empty_spaces' (b:bs) (r:rs) n = if (b || r) then rest else (n:rest)
where rest = empty_spaces' bs rs (n+1)
-- ----------------------------------------------------------------------
-- * Quantum data structures
-- $ Some data structures to help in defining the algorithm.
-- | The phase estimation register is a simple register of qubits. This is kept
-- separate from the rest of the 'BooleanFormulaRegister' as it is this register
-- which will be measured at the end of the algorithm.
type PhaseEstimationRegister = [Qubit]
-- | The direction register is a simple register of qubits,
-- made explicit here so we can see that a \"position\" is a list of directions.
type GenericDirectionRegister a = [a]
-- | A type synonym defined as the 'Qubit' instance of a
-- 'GenericDirectionRegister'.
type DirectionRegister = GenericDirectionRegister Qubit
-- | The rest of the boolean formula algorithm requires a register which is
-- split into 3 main parts.
data GenericBooleanFormulaRegister a = BFR {
-- | The position register is split into two parts:
-- the leaf and paraleaf \"flags\".
position_flags :: (a,a),
-- | The current position, and how we got there, i.e., directions we followed.
-- Any position can be reached by at most /x/⋅/y/ directions.
position :: [GenericDirectionRegister a],
work_leaf :: a,
work_paraleaf :: a,
work_binary :: a,
work_height :: a,
work_r :: a,
work_rp :: a,
work_rpp :: a, -- ^ Seven flags that make up the work register.
direction :: GenericDirectionRegister a -- ^ The direction register.
}
deriving (Typeable, Show)
-- | A type synonym defined as the 'Qubit' instantiation of a
-- 'GenericBooleanFormulaRegister'.
type BooleanFormulaRegister = GenericBooleanFormulaRegister Qubit
-- | A function to add labels to the wires that make up a 'BooleanFormulaRegister'.
-- These labels correspond to the parts of the register.
labelBFR :: BooleanFormulaRegister -> Circ ()
labelBFR reg = do
let tuple = toTuple reg
label tuple (("pos-leaf","pos-paraleaf"),
"pos",
("leaf","paraleaf","binary","height","r","rp","rpp"),
"dir")
-- | A type synonym defined as the 'Bool' instantiation of a
-- 'GenericBooleanFormulaRegister'.
type BoolRegister = GenericBooleanFormulaRegister Bool
-- | Helper function to simplify the 'QCData' instance for 'BooleanFormulaRegister'.
-- Create a tuple from a 'GenericBooleanFormulaRegister'.
toTuple :: GenericBooleanFormulaRegister a -> ((a,a),[[a]],(a,a,a,a,a,a,a),[a])
toTuple r = (position_flags r,position r,(work_leaf r,work_paraleaf r,work_binary r,work_height r,work_r r,work_rp r,work_rpp r),direction r)
-- | Helper function to simplify the 'QCData' instance for 'BooleanFormulaRegister'.
-- Create a 'GenericBooleanFormulaRegister' from a tuple.
fromTuple :: ((a,a),[[a]],(a,a,a,a,a,a,a),[a]) -> GenericBooleanFormulaRegister a
fromTuple (pf,p,(wl,wp,wb,wh,wr,wrp,wrpp),d) = BFR {
position_flags = pf,
position = p,
work_leaf = wl,
work_paraleaf = wp,
work_binary = wb,
work_height = wh,
work_r = wr,
work_rp = wrp,
work_rpp = wrpp,
direction = d
}
type instance QCType x y (GenericBooleanFormulaRegister a) = GenericBooleanFormulaRegister (QCType x y a)
type instance QTypeB (GenericBooleanFormulaRegister a) = GenericBooleanFormulaRegister (QTypeB a)
instance QCData a => QCData (GenericBooleanFormulaRegister a) where
qcdata_mapM s f g xs = mmap fromTuple $ qcdata_mapM (toTuple s) f g (toTuple xs)
qcdata_zip s q c q' c' xs ys e = fromTuple $ qcdata_zip (toTuple s) q c q' c' (toTuple xs) (toTuple ys) e
qcdata_promote a x s = fromTuple $ qcdata_promote (toTuple a) (toTuple x) s
instance (Labelable a String) => Labelable (GenericBooleanFormulaRegister a) String where
label_rec r s = do
label_rec (position_flags r) s `dotted_indexed` "posflag"
label_rec (position r) s `dotted_indexed` "pos"
label_rec (work_leaf r) s `dotted_indexed` "leaf"
label_rec (work_paraleaf r) s `dotted_indexed` "paraleaf"
label_rec (work_binary r) s `dotted_indexed` "binary"
label_rec (work_height r) s `dotted_indexed` "height"
label_rec (work_r r) s `dotted_indexed` "r"
label_rec (work_rp r) s `dotted_indexed` "rp"
label_rec (work_rpp r) s `dotted_indexed` "rpp"
label_rec (direction r) s `dotted_indexed` "dir"
-- | Create an initial classical 'BooleanFormulaRegister' for a given oracle description.
-- The /position/ register is initialized in the /zero/ state that represents being
-- at label /zero/, or node /rpp/ in the tree. The work qubits are all initialized to
-- /zero/, as the first call to the /oracle/ circuit will set them accordingly for
-- the /position/ we are currently in. The /direction/ register is also set to /zero/
-- as this is the direction in which the node /rp/ is in. The given
-- 'BooleanFormulaOracle' is used to make sure the registers are of the correct
-- size, i.e., number of qubits.
createRegister :: BooleanFormulaOracle -> BoolRegister
createRegister oracle = BFR {
position_flags = (False,False),
position = replicate s (replicate m False),
work_leaf = False,
work_paraleaf = False,
work_binary = False,
work_height = False,
work_r = False,
work_rp = False,
work_rpp = False,
direction = replicate m False
} where
s = oracle_s oracle
m = oracle_m oracle
-- | Create a shape parameter for a 'BooleanFormulaRegister' of the
-- correct size.
registerShape :: BooleanFormulaOracle -> BooleanFormulaRegister
registerShape oracle = qshape (createRegister oracle)
-- | Initialize a 'BooleanFormulaRegister' from a 'BooleanFormulaOracle'.
initializeRegister :: BooleanFormulaOracle -> Circ BooleanFormulaRegister
initializeRegister oracle = qinit (createRegister oracle)
-- ======================================================================
-- * Oracle implementation
-- $ The functions in this implementation follow a separation of the boolean
-- formula algorithm into two parts. The first part corresponds to the
-- algorithms defined in this module. The second part consists of the
-- algorithms defined in "Quipper.Algorithms.BF.Hex". This separation relates to the
-- first part defining the quantum parts of the algorithm, including the
-- phase estimation, and the quantum walk, whereas the remaining four define
-- the classical implementation of the circuit for determining which player
-- has won a completed game of Hex, which is converted to a quantum circuit
-- using Quipper's \"build_circuit\" keyword.
--
-- Note that the circuits for the algorithms in this module have been tested
-- for performing a quantum walk on the tree defined for a given oracle (but
-- with a dummy function taking the place of the call to HEX).
-- | The overall Boolean Formula Algorithm. It initializes the
-- phase estimation register into an equal super-position of all 2[sup t] states,
-- and the other registers as defined previously. It then maps the exponentiated
-- version of the unitary /u/, as per phase estimation, before applying the
-- inverse QFT, and measuring the result.
qw_bf :: BooleanFormulaOracle -> Circ [Bit]
qw_bf oracle = do
-- initialize the phase estimation register,
-- and put it in an equal super-position
let t = oracle_t oracle
a <- qinit (replicate t False)
label a "a"
a <- mapUnary hadamard a
-- initialize the other boolean formula registers
b <- initializeRegister oracle
labelBFR b
-- we can use a separate recursive function to map the exp_u algorithm over a
let t = oracle_t oracle
map_exp_u oracle a b (t-1)
-- qft is defined, so we reverse it to get inverse qft
a <- (subroutine_inverse_qft oracle) a
-- we're only interested in the result of measuring a,
-- so we can discard all the qubits in the rest of the register
qdiscard b
measure a
-- | The inverse quantum Fourier transform as a boxed subroutine.
subroutine_inverse_qft :: BooleanFormulaOracle -> [Qubit] -> Circ [Qubit]
subroutine_inverse_qft o = box "QFT*" (reverse_generic_endo qft_little_endian)
-- | \"Map\" the application of the exponentiated unitary /u/
-- over the phase estimation register. That is, each qubit in the phase estimation
-- register is used as a control over a call to the unitary /u/, exponentiated to
-- the appropriate power.
map_exp_u :: BooleanFormulaOracle -> [Qubit] -> BooleanFormulaRegister -> Int -> Circ ()
map_exp_u _ [] _ _ = return ()
map_exp_u o (a:as) b l = do
let x_max = oracle_x_max o
-- we can move the control out of the exp_u function
exp_u o (2^(l-(length as))) b `controlled` a
map_exp_u o as b l
-- | Exponentiate the unitary /u/. In this implementation, this is
-- achieved by repeated application of /u/.
exp_u :: BooleanFormulaOracle -> Integer -> BooleanFormulaRegister -> Circ ()
exp_u _ 0 _ = return ()
exp_u o n_steps b = do
(subroutine_u o) b
exp_u o (n_steps-1) b
-- | The unitary /u/ represents a single step in the walk on the NAND tree. A call
-- to the oracle determines what type of node we are at (so we know which directions
-- are valid to step to), the call to diffuse sets the direction register to be a
-- super-position of all valid directions, the call to walk performs the step, and then
-- the call to undo oracle has to clean up the work registers that were set by the
-- call to the oracle. Note that the undo oracle step is not simply the inverse of the
-- oracle, as we have walked since the oracle was called.
u :: BooleanFormulaOracle -> BooleanFormulaRegister -> Circ ()
u o b = do
comment "U"
labelBFR b
(subroutine_oracle o) b
(subroutine_diffuse o) b
(subroutine_walk o) b
(subroutine_undo_oracle o) b
-- | The circuit for 'u' as a boxed subroutine.
subroutine_u :: BooleanFormulaOracle -> BooleanFormulaRegister -> Circ ()
subroutine_u o = box "U" (u o)
-- | Call the oracle to determine some extra information about where
-- we are in the tree. Essentially, the special cases are when were are at one of
-- the three \"low height\" nodes, or when we are at a node representing a complete
-- game of Hex, and we need to determine if this is a leaf, by calling the hex circuit,
-- which determines whether the node represents a completed game of hex in which
-- the red player has won.
oracle :: BooleanFormulaOracle -> BooleanFormulaRegister -> Circ ()
oracle o register = do
comment "ORACLE"
labelBFR register
let init = start_board o
let x_max = oracle_x_max o
let (is_leaf,is_paraleaf) = position_flags register
with_controls (is_leaf)
( -- if is_leaf
-- we are at a leaf node, so set "leaf"
do
let leaf = work_leaf register
qnot_at leaf
)
with_controls (is_leaf .==. False .&&. is_paraleaf .==. True)
( -- else if is_paraleaf
-- we are at a paraleaf node, so set "paraleaf"
do
let paraleaf = work_paraleaf register
qnot_at paraleaf
let binary = work_binary register
qnot_at binary
let pos = position register
let hex_subroutine = case oracle_hex o of
Hex -> box "HEX" (hex_oracle init (oracle_s o) x_max)
Dummy -> hex_oracle_dummy
EmptyHex -> \x -> return x
-- hex sets "binary" flag depending on whether the paraleaf is attached to a
-- a leaf, i.e., whether red has won or lost the game of hex.
hex_subroutine (pos,binary)
return ()
)
with_controls (is_leaf .==. False .&&. is_paraleaf .==. False)
( -- else
-- we're not at a leaf node, or paraleaf node
do
let pos = position register
-- are we at a "low height" node?
with_controls (controls is_paraleaf pos)
( -- we're at a "low height" node
do
let pos'' = pos !! (length pos - 2)
let pos_m = last pos''
with_controls pos_m
( -- if pos_m == 1
do
let height = work_height register
qnot_at height
)
let pos' = last pos
let pos_1 = pos' !! (length pos' - 2)
with_controls (pos_m .==. False .&&. pos_1 .==. True)
( -- else if pos_1 == 1
do
let r = work_r register
qnot_at r
)
let pos_0 = last pos'
with_controls (pos_m .==. False .&&. pos_1 .==. False .&&. pos_0 .==. True)
( -- else if pos_0 == 1
do
let rp = work_rp register
qnot_at rp
let binary = work_binary register
qnot_at binary
)
with_controls (pos_m .==. False .&&. pos_1 .==. False .&&. pos_0 .==. False)
( -- else
do
let rpp = work_rpp register
qnot_at rpp
)
)
)
-- | The circuit for the 'oracle' as a boxed subroutine.
subroutine_oracle :: BooleanFormulaOracle -> BooleanFormulaRegister -> Circ ()
subroutine_oracle o = box "Oracle" (oracle o)
-- | The controls to use, to see if we're at a \"low height\" node.
controls :: Qubit -> [DirectionRegister] -> [ControlList]
controls is_paraleaf pos = (is_paraleaf .==. False) : ctrls pos
where ctrls [] = []
ctrls [p] = []
ctrls [p,q] = []
ctrls (p:ps) = (last p .==. False) : ctrls ps
-- | Diffuse the direction register, to be a super-position of all valid
-- directions from the current node. Note, that this implementation of the boolean
-- formula algorithm does not applying the correct weighting scheme to the NAND graph,
-- which would require this function to diffuse with respect to the weighting scheme.
diffuse :: BooleanFormulaRegister -> Circ ()
diffuse register = do
comment "DIFFUSE"
labelBFR register
let binary = work_binary register
let dir = direction register
with_controls binary
( -- if binary == 1
do
let dir_0 = last dir
hadamard_at dir_0
)
let leaf = work_leaf register
let rpp = work_rpp register
with_controls (binary .==. False .&&. leaf .==. False .&&. rpp .==. False)
( -- else (controlled on binary == 0, leaf == 0, rpp == 0)
do
mapUnary hadamard dir
)
return ()
-- | The circuit for 'diffuse' as a boxed subroutine.
subroutine_diffuse :: BooleanFormulaOracle -> BooleanFormulaRegister -> Circ ()
subroutine_diffuse o = box "Diffuse" diffuse
-- | A datatype to use instead of passing integers to 'toParent' and 'toChild'
-- to define what needs to be shifted. This is used as only three different
-- shift widths are ever used in the algorithm.
data Where = Width -- ^ corresponds to shifting all qubits.
| M -- ^ corresponds to shifting only /m/+1 qubits.
| M2 -- ^ corresponds to shifting only 2/m/+1 qubits.
deriving Eq
-- | Define a step on the NAND graph, in the direction specified
-- by the direction register, and updates the direction register to be where
-- we have stepped from.
-- For this algorithm we have developed the 'if_then_elseQ' construct, which
-- gives us a nice way of constructing if/else statements acting on
-- boolean statements over qubits (see "Quipper.Libraries.QuantumIf").
walk :: BooleanFormulaRegister -> Circ ()
walk register = do
comment "WALK"
labelBFR register
let leaf = work_leaf register
let paraleaf = work_paraleaf register
let dir = direction register
let dir_0 = last dir
let (is_leaf,is_paraleaf) = position_flags register
let pos = position register
let pos_0 = last (last pos)
let pos_1 = last (init (last pos))
let height_1 = work_height register
let rpp = work_rpp register
let rp = work_rp register
let r = work_r register
let dir_all_1 = foldr1 (\x y -> And x y) (map A dir)
let boolean_statement_in = Or (A leaf) (And (A paraleaf) (Not (A dir_0)))
let boolean_statement_out = Or (A leaf) (And (A paraleaf) (A is_leaf))
if_then_elseQinv boolean_statement_in
( -- if leaf == 1 or (paraleaf == 1 and dir_0 == 0)
do
qnot_at is_leaf
)
( -- else (leaf == 0 and (paraleaf == 0 or dir_0 == 1))
do
let boolean_statement_in = And (A paraleaf) (A dir_0)
let boolean_statement_out = And (A paraleaf) (Not (dir_all_1))
if_then_elseQinv boolean_statement_in
( -- if paraleaf == 1 and dir_0 == 1
toParent Width register
-- now, dir /= 1..1, so dir_0 could be either 0 or 1
)
( -- else (paraleaf == 0 or dir_0 == 0)
do
let boolean_statement_in = Or (A rpp) (And (A rp) (A dir_0))
let boolean_statement_out = Or (A rpp) (And (A rp) (Not (A dir_0)))
if_then_elseQinv boolean_statement_in
( -- if rpp == 1 or (rp == 1 and dir_0 == 1 )
do
qnot_at pos_0
-- dir_0 should be changed,
-- as we're moving from rp to rpp, and rpp only has a child at 0
-- or we're moving from rpp to rp, and dir_0 should be set to 1 as
-- we have come from a parent
qnot_at dir_0
)
( -- else (rpp == 0 and (rp == 0 or dir_0 == 0))
do
let boolean_statement_in = Or (And (A rp) (Not (A dir_0))) (And (A r) dir_all_1)
let pos_m = last (last (init pos))
let boolean_statement_out = Or (And (A rp) dir_all_1) (And (A r) (And (Not dir_all_1) (Not (A pos_m))))
if_then_elseQinv boolean_statement_in
( -- if (rp == 1 and dir_0 == 0) or (r == 1 and dir == 1..1)
do
qnot_at pos_1
-- we know that pos_m == 0
-- dir is should be changed
-- when we move from rp to r, and when we move from r to rp
mapUnary qnot dir
return ()
)
( -- else ((rp == 0 or dir_0 == 1) and (r == 0 or dir /= 1..1))
do
let boolean_statement = A r
if_then_elseQ boolean_statement
( -- if r == 1
do
qnot_at pos_1
toChild M register
-- now dir == 1..1
-- we also know that pos_m == 1
)
( -- else
do
let boolean_statement_in = And (A height_1) (dir_all_1)
let boolean_statement_out = And (A height_1) (Not dir_all_1)
if_then_elseQinv boolean_statement_in
( -- if height_1 == 1 and dir == 1..1
do
toParent M register
qnot_at pos_1
-- now, dir /= 1..1
)
( -- else height_1 == 0 or dir /= 1..1
do
let boolean_statement = A height_1
if_then_elseQ boolean_statement
( -- if height_1 == 1 (and dir /= 1..1)
do
toChild M2 register
-- now dir == 1..1
)
( -- else (if height_1 == 0)
do
let boolean_statement_in = dir_all_1
let boolean_statement_out = Not dir_all_1
if_then_elseQinv boolean_statement_in
( -- if dir = 1..1
do
toParent Width register
-- now dir /= 1..1
)
( --else (dir /= 1..1)
do
toChild Width register
-- now dir == 1..1
) boolean_statement_out
)
) boolean_statement_out
)
) boolean_statement_out
) boolean_statement_out
) boolean_statement_out
) boolean_statement_out
return ()
-- | The circuit for 'walk' as a boxed subroutine.
subroutine_walk :: BooleanFormulaOracle -> BooleanFormulaRegister -> Circ ()
subroutine_walk o = box "Walk" walk
-- | Uncompute the various flags that were set by the initial call
-- to the oracle. It has to uncompute the flags depending on where we were before
-- the walk step, so isn't just the inverse of the oracle.
undo_oracle :: BooleanFormulaOracle -> BooleanFormulaRegister -> Circ ()
undo_oracle o register = do
comment "UNDO_ORACLE"
labelBFR register
let initHB = start_board o
let x_max = oracle_x_max o
let paraleaf = work_paraleaf register
let (is_leaf,is_paraleaf) = position_flags register
with_controls paraleaf
( do
let binary = work_binary register
let pos = position register
let dir = direction register
let hex_subroutine = case oracle_hex o of
Hex -> box "HEX" (hex_oracle initHB (oracle_s o) x_max)
Dummy -> hex_oracle_dummy
EmptyHex -> \x -> return x
hex_subroutine (pos,binary)
return ()
)
let leaf = work_leaf register
let dir = direction register
let dir_0 = last dir
let boolean_statement = And (Not (A is_leaf)) (And (A is_paraleaf) (Not (A dir_0)))
if_then_elseQ boolean_statement
( -- if is_leaf == 0 and is_paraleaf == 1 and dir_0 == 0
-- we went from a leaf to a paraleaf, so we can unset leaf
do
qnot_at leaf
)
( -- else
do
let binary = work_binary register
let pos = position register
let pos_w_2_m = last (head pos)
let dir_all_1 = foldr1 (\x y -> And x y) (map A dir)
let boolean_statement = Or (A is_leaf) (And (Not (A is_leaf)) (And (Not (A is_paraleaf)) (And (A pos_w_2_m) (Not (dir_all_1)))))
if_then_elseQ boolean_statement
( -- if is_leaf == 1 or (is_leaf == 0 and is_paraleaf == 0 and pos_w_2_m == 1 and dir /= 1..1)
-- we went from a paraleaf to a leaf, so unset binary and paraleaf
-- or we went from a paraleaf to its parent...
do
qnot_at binary
qnot_at paraleaf
)
( -- else
do
with_controls (init (controls is_paraleaf pos))
( -- if pos_sm,pos_(s-1)m,...,3. == 00...0
do
let height = work_height register
let r = work_r register
let rp = work_rp register
let pos_0 = last (last pos)
let pos_1 = last (init (last pos))
let pos_m = last (last (init pos))
let pos_2m = last (last (init (init pos)))
let boolean_statement = dir_all_1
if_then_elseQ boolean_statement
( -- if dir = 1...1
do
qnot_at height `controlled` pos_2m
qnot_at r `controlled` (pos_2m .==. False .&&. pos_m .==. True)
with_controls (pos_2m .==. False .&&. pos_m .==. False .&&. pos_1 .==. True)
(
do
qnot_at rp
qnot_at binary
)
)
( -- else
with_controls (pos_2m .==. False .&&. pos_m .==. False)
(
do
let rpp = work_rpp register
qnot_at height `controlled` pos_1
qnot_at rpp `controlled` (pos_1 .==. False .&&. dir_0 .==. True)
qnot_at r `controlled` (pos_1 .==. False .&&. dir_0 .==. False .&&. pos_0 .==. True)
with_controls (pos_1 .==. False .&&. dir_0 .==. False .&&. pos_0 .==. False)
(
do
qnot_at rp
qnot_at binary
)
)
)
) -- end if
)
)
return ()
-- | The circuit for 'undo_oracle' as a boxed subroutine.
subroutine_undo_oracle :: BooleanFormulaOracle -> BooleanFormulaRegister -> Circ ()
subroutine_undo_oracle o = box "Undo Oracle" (undo_oracle o)
-- | Define the circuit that updates the position register to be the
-- parent node of the current position.
toParent :: Where -> BooleanFormulaRegister -> Circ ()
toParent M2 _ = error "TOPARENT should never be called with 2m+1 as width"
toParent w register = do
let pos = position register :: [[Qubit]] -- of length x*y
let pos_firstM = last pos :: [Qubit] -- of length m
let pos_secondM = last (init pos) :: [Qubit] -- of length m
let pos_0 = last pos_firstM :: Qubit
let pos_m = last pos_secondM :: Qubit
let dir = direction register :: [Qubit] -- of length m
let (_,is_paraleaf) = position_flags register :: (Qubit,Qubit)
mapUnary qnot dir
mapBinary copy_from_to (reverse pos_firstM) (reverse dir)
if (w == Width) then
( do -- width
-- we need to shift everything to the right by m
shift_right pos
-- we need to shift is_paraleaf to x*y*m
copy_from_to is_paraleaf (last (head pos))
return ()
) else return ()
if (w == M) then
( do -- m+1
-- we need to "shift" pos_m to pos_0
copy_from_to pos_m pos_0
return ()
) else return ()
-- | @'copy_from_to' a b@: Sets the state of qubit /b/ to be the state of qubit /a/,
-- (and the state of /a/ is lost in the process, so this is not cloning).
-- It falls short of swapping /a/ and /b/, as we're not interested in preserving /a/.
copy_from_to :: Qubit -> Qubit -> Circ (Qubit,Qubit)
copy_from_to from to = do
qnot_at to `controlled` from
qnot_at from `controlled` to
return (from,to)
-- | Define the circuit that updates the position register to be the
-- child node of the current position.
toChild :: Where -> BooleanFormulaRegister -> Circ ()
toChild w register = do
let pos = position register :: [[Qubit]] -- of length x*y
let pos_firstM = last pos :: [Qubit] -- of length m
let pos_secondM = last (init pos) :: [Qubit] -- of length m
let pos_thirdM = last (init (init pos)) :: [Qubit] -- of length m
let pos_0 = last pos_firstM :: Qubit
let pos_m = last pos_secondM :: Qubit
let pos_2m = last pos_thirdM :: Qubit
let dir = direction register :: [Qubit] -- of length m
let (_,is_paraleaf) = position_flags register :: (Qubit,Qubit)
if (w == Width) then
( do -- width
-- we need to "shift" x*y*m to is_paraleaf
copy_from_to (last (head pos)) is_paraleaf
-- we need to "shift" everything to the left by "m"
shift_left pos
) else return ()
if (w == M2) then
( do -- 2m+1
-- we need to "shift" pos_m to pos_2m
copy_from_to pos_m pos_2m
-- we need to "shift" 0.. to m.. to
shift_left [pos_secondM,pos_firstM]
) else return ()
if (w == M) then
( do
-- we need to "shift" pos_0 to pos_m
copy_from_to pos_0 pos_m
return ()
) else return ()
mapBinary copy_from_to dir pos_firstM
mapUnary qnot dir
return ()
-- | Shift every qubit in a register to the left by one.
shift_left :: [DirectionRegister] -> Circ ()
shift_left [] = return ()
shift_left [d] = return ()
shift_left (d:d':ds) = do
mapBinary copy_from_to d' d
shift_left (d':ds)
-- | Shift every qubit in a register to the right by one.
shift_right :: [DirectionRegister] -> Circ ()
shift_right [] = return ()
shift_right [d] = return ()
shift_right (d:d':ds) = do
shift_right (d':ds)
mapBinary copy_from_to (reverse d) (reverse d')
-- the arguments are reversed to give a nice symmetry to the circuits
-- and should be equivalent to if they're not reversed
return ()
-- ----------------------------------------------------------------------
-- * Possible main functions
-- $ The following functions define various \main\ functions that can be called
-- from an overall \main\ function to display various parts of the
-- overall Boolean Formula Algorithm. The Boolean
-- Formula Algorithm is split into 13 sub-algorithms, each of which can be
-- displayed separately, or in various combinations.
-- | Displays the overall Boolean Formula circuit for a given oracle description.
main_circuit :: Format -> GateBase -> BooleanFormulaOracle -> IO ()
main_circuit f g oracle = print_simple f (decompose_generic g (qw_bf oracle))
-- | Display just 1 time-step of the given oracle,
-- i.e., one iteration of the 'u' from 'exp_u', with no controls.
main_u :: Format -> GateBase -> BooleanFormulaOracle -> IO ()
main_u f g o = print_generic f (decompose_generic g (u o)) (registerShape o)
-- | Display just 1 time-step of the 'walk' algorithm for the given oracle,
-- i.e., one iteration of /walk/, with no controls.
main_walk :: Format -> GateBase -> BooleanFormulaOracle -> IO ()
main_walk f g o = print_generic f (decompose_generic g walk) (registerShape o)
-- | Display just 1 time-step of the 'diffuse' algorithm for the given oracle,
-- i.e., one iteration of /diffuse/, with no controls.
main_diffuse :: Format -> GateBase -> BooleanFormulaOracle -> IO ()
main_diffuse f g o = print_generic f (decompose_generic g diffuse) (registerShape o)
-- | Display just 1 time-step of the 'oracle' algorithm for the given oracle,
-- i.e., one iteration of /oracle/, with no controls.
main_oracle :: Format -> GateBase -> BooleanFormulaOracle -> IO ()
main_oracle f g o = print_generic f (decompose_generic g (oracle o)) (registerShape o)
-- | Display just 1 time-step of the 'undo_oracle' algorithm for the given oracle,
-- i.e., one iteration of /undo_oracle/, with no controls.
main_undo_oracle :: Format -> GateBase -> BooleanFormulaOracle -> IO ()
main_undo_oracle f g o = print_generic f (decompose_generic g (undo_oracle o)) (registerShape o)
-- | Display the circuit for the Hex algorithm, for the given oracle,
-- i.e., one iteration of 'hex_oracle', with no controls.
main_hex :: Format -> GateBase -> BooleanFormulaOracle -> IO ()
main_hex f g o = print_generic f (decompose_generic g (hex_oracle init s x_max)) (pos,binary)
where
init = start_board o
s = oracle_s o
x_max = oracle_x_max o
reg = registerShape o
pos = position reg
binary = work_binary reg
-- | Display the circuit for the Checkwin_red algorithm, for the given oracle,
-- i.e., one iteration of 'checkwin_red_circuit', with no controls.
main_checkwin_red :: Format -> GateBase -> BooleanFormulaOracle -> IO ()
main_checkwin_red f g o = print_generic f (decompose_generic g (checkwin_red_circuit x_max)) (qshape redboard,qubit)
where
(redboard,_) = start_board o
x_max = oracle_x_max o
-- ----------------------------------------------------------------------
-- * Running the Boolean Formula Algorithm
-- $ The following functions define the way that the Boolean Formula
-- Algorithm would be run, if we had access to a quantum
-- computer. Indeed, the functions here interface with the
-- "Quipper.Libraries.Simulation.QuantumSimulation" quantum simulator
-- so that they can be built.
-- | Approximation of how the algorithm would be run if we had a quantum computer:
-- uses QuantumSimulation run_generic_io function. The output of the algorithm will
-- be all False only in the instance that the Blue player wins the game.
main_bf :: BooleanFormulaOracle -> IO Bool
main_bf oracle = do
output <- run_generic_io (undefined :: Double) (qw_bf oracle)
let result = if (or output) then True -- a /= 0 (Red Wins)
else False -- a == 0 (Blue Wins)
return result
-- | Display the result of 'main_bf',
-- i.e., either \"Red Wins\", or \"Blue Wins\" is the output.
whoWins :: BooleanFormulaOracle -> IO ()
whoWins oracle = do
result <- main_bf oracle
if result then putStrLn "Red Wins"
else putStrLn "Blue Wins"
-- | Run 'whoWins' for the given oracle, and its \"initial\" board.
main_whoWins :: BooleanFormulaOracle -> IO ()
main_whoWins o = whoWins o