{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleContexts #-}

-- | This module provides a user-friendly interface to building
-- quantum circuits out of classical functions on booleans. It is
-- based on lower-level functionality provided by
-- "Quipper.Utils.Template".
-- 
-- Technically, the only functions to be used in this module are
-- @'decToCircMonad'@, a specialized version of @'decToMonad'@, and
-- @'unpack'@. The only useful datatype here is @'BoolParam'@.
-- 
-- One should not have to directly use the other things: they are only
-- for the internal use of Template Haskell to build quantum circuits
-- out of classical computation on booleans.
-- 
-- Note: in the following, we write circuits in ASCII form. The
-- following conventions are used. They are extended in obvious ways
-- when applicable (e.g. when writing a ternary gate).
-- 
-- > ---- : wire
-- > 
-- > 0 |-- : initialize an ancilla |0>
-- > 
-- > --| 0 : terminate an ancilla, asserting it was |0>
-- > 
-- >   +--+
-- >  -|  |- : a unary gate
-- >   +--+
-- > 
-- >   +--+
-- >  -|  |- 
-- >   |  |  : a binary gate
-- >  -|  |- 
-- >   +--+
-- >
-- >  -- --
-- >    X   : swap gate
-- >  -- --
-- > 
-- >  --x-- 
-- >    |   : controlled-not, applying NOT on the bottom wire if the top one is |1>
-- >  --N-- 
-- >
-- >  --o-- 
-- >    |   : controlled-not, applying NOT on the bottom wire if the top one is |0>
-- >  --N-- 

-- NOTE: They are only available because Template Haskell requires
-- them to be in a separate module and exported.

module Quipper.Internal.CircLifting (
  -- * Overview
  -- $ROLE 

  -- * A type of boolean parameters
  -- $BOOLPARAM 
  BoolParam(PTrue,PFalse),
  newBool,
  template_PFalse,
  template_PTrue,
    
  -- * Lifting classical functions to circuits
  -- $TH
  decToCircMonad,
  
-- $BUILDTEMPLATE_ANCHOR #build_circuit#
  
  -- * Syntactic sugar
  -- $BUILDTEMPLATE


  -- * Circuits for specific operations
  -- ** Boolean parameters
  
  template_newBool,

  -- ** Boolean constants
  template_False,
  template_True,
  -- ** Unary boolean operations
  template_not,
  -- ** Binary boolean operations
  template_symb_ampersand_symb_ampersand_,
  template_symb_vbar_symb_vbar_,
  template_bool_xor,
  -- ** The if-then-else operation
  -- $IF
  template_if,
  -- ** Equality test
  template_symb_equal_symb_equal_,

  -- * Generic unpacking
  CircLiftingUnpack(..)
  
) where

import Prelude
import Data.Map as Map
import qualified Data.List

import Quipper.Internal.Monad
import qualified Quipper.Internal.Monad
import Quipper.Internal.Circuit
import Quipper.Internal.Generic
import Quipper.Internal.QData
import Quipper.Utils.Auxiliary (list_of_blist,blist_empty)
import Quipper.Internal.Control
import Quipper.Internal.QClasses

import Quipper.Utils.Template



----------------------------------------------------------------------
-- * Overview

-- $ROLE Using the tool @'decToMonad'@ designed in "Quipper.Utils.Template", we
-- can easily generate quantum circuits. Indeed, suppose that we are given the classical oracle 
-- 
-- > toyOracle :: Bool -> Bool
-- > toyOracle a = f (g a) (h a)
-- 
-- for some @g,h :: Bool -> Bool@ and @f :: Bool -> Bool -> Bool@. If
-- /g/ and /h/ are given by quantum circuits of the form
--
-- >          +-----+
-- > input ---|     |-- input wire, assumed to be not modified by the box
-- >          |     |
-- >      0 |-|     |--- output (was ancilla wire)
-- >          +-----+
--
-- and if /f/ is given by
--
-- >          +-----+
-- > input ---|     |-- was input 1, assumed to be not modified
-- >          |     | 
-- > input ---|     |-- was input 2, assumed to be not modified
-- >          |     |
-- >     0 |--|     |-- output (was ancilla wire),
-- >          +-----+
--
-- we can compositionally generate a circuit @C@ for /toyOracle/ as follows.
-- 
-- >          +---+                    +---+
-- > input ---|   |-- -----------------|   |-- (output of g)
-- >          | g |  X  +---+          |   |
-- >     0 |--|   |-- --|   |--- ------| f |-- (output of h)
-- >          +---+     | h |   X      |   |                   (I)
-- >     0 |------------|   |--- - ----|   |-- (output of f)
-- >                    +---+     X    +---+
-- >                          0 |- ----------- (input of g)
-- >
--
-- Note that the resulting circuit is a classical, reversible circuit
-- (more precisely, the circuit defines a one-to-one function). In
-- order to obtain a reversible quantum circuit, one should then apply
-- the function @'Quipper.Internal.Classical.classical_to_reversible'@ to get the following (we
-- keep the same convention of wires as in the definition of @C@):
--
-- >        +---+     +---+
-- > input--|   |-----|   |-- still the input
-- >        |   |     |   |
-- >   0 |--|   |-----|   |--| 0
-- >        | C |     | D |                                    (II)
-- >   0 |--|   |--x--|   |--| 0
-- >        |   |  |  |   |
-- >   0 |--|   |--|--|   |--| 0
-- >        +---+  |  +---+
-- >               |
-- > output wire---N--------------.
--
-- Here @D@ is the inverse of @C@. We now have a circuit of the
-- canonical form, computing and then uncomputing its ancillas:
--
-- >     +-----------+
-- > a --|           |- a
-- >     | toyOracle |
-- > z --|           |- z + (f (g a) (h a))
-- >     +-----------+
--
----------------------------------------------------------------------
-- * A type of boolean parameters

-- $BOOLPARAM During the construction of a quantum circuit from
-- classical code, the type 'Bool' is mapped to the type
-- 'Qubit'. However, it is also sometimes useful to specify boolean
-- parameters to be used during circuit generation (for example, in
-- the BWT algorithm, the color is a parameter). For this purpose, we
-- provide a new type 'BoolParam', which is identical to 'Bool' in
-- most respects, except that it is not mapped to 'Qubit' during
-- circuit generation.

-- | A custom-design boolean type, not modified by circuit generation.
data BoolParam = PTrue | PFalse
  deriving (Eq, Show)

-- | Type-cast from BoolParam to Bool
newBool :: BoolParam -> Bool
newBool PTrue = True
newBool PFalse = False


-- | Lifted version of PFalse.
template_PFalse :: Circ BoolParam
template_PFalse = return PFalse

-- | Lifted version of PTrue.
template_PTrue :: Circ BoolParam
template_PTrue = return PTrue


----------------------------------------------------------------------
-- * Lifting classical functions to circuits

-- $TH The main tool for transforming a classical computation into a
-- quantum circuit is the function @'decToCircMonad'@. It inputs the
-- syntax tree of a classical function, and outputs the syntax tree of
-- a corresponding quantum circuit. The type 'Bool' is mapped to
-- 'Qubit'; the type 'BoolParam' is unchanged; and each function /f/ :
-- /a/ → /b/ is mapped to a function /f'/ : /a'/ → 'Circ' /b'/,
-- where /a'/ and /b'/ are the translations of the types /a/ and /b/,
-- respectively.
-- 
-- Most of the work is done by the lower-level function 
-- @'decToMonad'@ from the module "Quipper.Utils.Template". 
-- This lower-level function knows how to deal with many usual
-- constructs of the Haskell language, such as function applications,
-- lambda-abstractions, let-assignments, case-distinctions, and so
-- on. However, @'decToMonad'@ does not by default know how to deal
-- with the base cases, i.e., how to extract quantum circuits from
-- specific term constants such as @'&&'@, @'||'@, etc.
-- 
-- The purpose of the remainder of this module is to do just that. For
-- every constant or function @XXX@ that one may want to use in a
-- classical program, we provide an implementation @template_XXX@ as a
-- quantum circuit.  We refer to @template_XXX@ as the \"lifted\"
-- version of @XXX@.  The function @'decToCircMonad'@ is a version of
-- @'decToMonad'@ that knows about these liftings.



-- | Input the syntax tree of a classical function, and output the
-- syntax tree of a corresponding quantum function. The type 'Bool' is
-- mapped to 'Qubit'; the type 'BoolParam' is unchanged; and and each
-- function /f/ : /a/ → /b/ is mapped to a function /f'/ : /a'/ →
-- 'Circ' /b'/, where /a'/ and /b'/ are the translations of the types
-- /a/ and /b/, respectively. The function 'decToCircMonad' knows
-- about many built-in operations such as @'&&'@ and @'||'@, whose
-- circuit translations are defined below.
decToCircMonad :: Q [Dec] -> Q [Dec]
decToCircMonad x = decToMonad "Circ" x

-- $BUILDTEMPLATE_ANCHOR #build_circuit#

---------------------------------------------------------------------
-- * Syntactic sugar

-- $BUILDTEMPLATE Quipper comes equipped with syntactic sugar to ease
-- the use of the @'decToCircMonad'@ function.
-- 
-- Although the code
-- 
-- > $( decToCircMonad [d| f x = ... |] )
-- 
-- is valid, it is possible to use the special keyword
-- @build_circuit@, as follows:
-- 
-- > build_circuit
-- > f x = ...
-- 
-- This code is equivalent to
-- 
-- > f x = ...
-- > $( decToCircMonad [d| f x = ... |] )
-- 
-- In other words, it generates both a function @f@ of type @a -> ...@
-- and an object @template_f@ of type @Circ (a -> Circ ...)@.
-- 
-- The following spellings are recognized:
--
-- > build_circuit f x y z = ...
--
-- > build_circuit
-- > f x y z = ...
--
-- > build_circuit
-- > f :: a -> ...
-- > f x y z = ...

-- ----------------------------------------------------------------------
-- * Circuits for specific operations

-- ** Boolean parameters

-- | Lifted version of 'newBool':
-- 
-- > newBool :: BoolParam -> Bool.
--
-- Depending on the boolean parameter, the circuit is either 
-- 
-- > 0 |--
-- 
-- or
-- 
-- > 1 |--
template_newBool ::  Circ (BoolParam -> Circ Qubit)
template_newBool =  return $ \b -> case b of 
                             PTrue  -> qinit_qubit True
                             PFalse -> qinit_qubit False

----------------------------------------------------------------------
-- ** Boolean constants

-- | Lifted version of 'False':
-- 
-- > False :: Bool.
-- 
-- The circuit is
--
-- > 0 |--   output: quantum bit in state |0>
template_False :: Circ Qubit
template_False = qinit_qubit False

-- | Lifted version of 'True':
-- 
-- > True :: Bool.
-- 
-- The circuit is
--
-- > 1 |--   output: quantum bit in state |1>
template_True :: Circ Qubit
template_True = qinit_qubit True



----------------------------------------------------------------------
-- ** Unary boolean operations

-- | Lifted version of 'not':
-- 
-- > not :: Bool -> Bool.
-- 
-- The circuit is 
-- 
-- > a -----x--
-- >        |
-- >   1 |--N------- output: not a.
template_not ::  Circ (Qubit -> Circ Qubit)
template_not  = return $ \b -> do
          r <- qinit_qubit True;
          qnot_at r `controlled` b
          return r


----------------------------------------------------------------------
-- ** Binary boolean operations

-- | Lifted version of '&&':
-- 
-- > (&&) :: Bool -> Bool -> Bool.
-- 
-- The circuit is
-- 
-- > a -----x---
-- >        |
-- > b -----x---
-- >        |
-- >   0 |--N------- output: a and b.
template_symb_ampersand_symb_ampersand_ ::  Circ (Qubit -> Circ (Qubit -> Circ Qubit))
template_symb_ampersand_symb_ampersand_ =
  return $ \b1 -> return $ \b2 -> do 
         r <- qinit_qubit False;
         qnot_at r `controlled` [b1,b2];
         return r

-- | Lifted version of '||':
-- 
-- > (||) :: Bool -> Bool -> Bool.
-- 
-- The circuit is
-- 
-- > a -----o---
-- >        |
-- > b -----o---
-- >        |
-- >   1 |--N------- output: a or b.
template_symb_vbar_symb_vbar_ ::  Circ (Qubit -> Circ (Qubit -> Circ Qubit))
template_symb_vbar_symb_vbar_ = return $ \b1 -> return $ \b2 -> do 
         r <- qinit_qubit True; 
         qnot_at r `controlled` b1 .==. 0 .&&. b2 .==. 0;
         return r


-- | Lifted version of 'Quipper.Utils.Auxiliary.bool_xor':
-- 
-- > bool_xor :: Bool -> Bool -> Bool.
-- 
-- The circuit is
-- 
-- > a -----x-------
-- >        |
-- > b -----|---x---
-- >        |   |
-- >   0 |--N---N------ output: a xor b.
template_bool_xor ::  Circ (Qubit -> Circ (Qubit -> Circ Qubit))
template_bool_xor = return $ \b1 -> return $ \b2 -> do 
         r <- qinit_qubit False
         qnot_at r `controlled` b1
         qnot_at r `controlled` b2
         return r


----------------------------------------------------------------------
-- ** The if-then-else operation

-- $IF The last term we need to build is @'template_if'@, a term
-- describing the if-then-else construct as a circuit.

-- | Lifted version of the @if-then-else@ construction: 
-- 
-- > if-then-else :: Bool -> b -> b -> b         
-- 
-- We only allow first-order terms in the \"then\" and \"else\"
-- clauses.  The circuit is:
--
-- > q -----x---o---
-- >        |   |
-- > a -----x---|---
-- >        |   |
-- > b -----|---x---
-- >        |   |
-- >   0 |--N---N-------- wire output of the function.
template_if :: (QData b) => Circ Qubit -> Circ b -> Circ b -> Circ b
template_if x a b = do
   x' <- x; a' <- a; b' <- b; map2Q (testOnQubit x') (a',b')
   where
   testOnQubit :: Qubit -> (Qubit,Qubit) -> Circ Qubit
   testOnQubit x (a,b) = do
       r <- qinit_qubit False
       qnot_at r `controlled` x .==. 1 .&&. a .==. 1
       qnot_at r `controlled` x .==. 0 .&&. b .==. 1
       return r

-- ----------------------------------------------------------------------
-- * Operations of the Eq class
       
-- | Lifted version of the '==' operator:
-- 
-- > (==) :: Eq a => a -> a -> Bool
template_symb_equal_symb_equal_ :: (QEq qa) => Circ (qa -> Circ (qa -> Circ Qubit))
template_symb_equal_symb_equal_ = return $ \qx -> return $ \qy -> do (qx,qy,test) <- q_is_equal qx qy; return test

-- ----------------------------------------------------------------------
-- * Generic unpacking

-- $ The 'decToCircMonad' function produces (and also requires)
-- functions with somewhat unwieldy types. We define generic functions
-- for unpacking these types into a more useable format, and for
-- packing them back.
-- 
-- For example, @'Circ' (qa -> 'Circ' (qb -> 'Circ' qd))@ unpacks
-- into the type @qa -> qb -> 'Circ' qd@.
-- 
-- The class 'CircLiftingUnpack' keeps track of the unpacked and
-- packed versions of types; so it will have an instance
-- 
-- > @'CircLiftingUnpack' ('Circ' (qa -> 'Circ' (qb -> 'Circ' qd))) (qa -> qb -> 'Circ' qd)@, 
-- 
-- and provide functions 'unpack', 'pack' going back and forth between
-- these.
-- 
-- Note that 'pack' and 'unpack' do not in general form an
-- isomorphism, just a retraction of the packed type onto the unpacked
-- type.
-- 
-- Unfortunately the class cannot (in the current implementation) be
-- defined in full generality once and for all: whenever a user wishes
-- to use a new type @QFoo@ in circuit-building functions, she must
-- define an additional base case @'CircLiftingUnpack' ('Circ' QFoo)
-- ('Circ' QFoo)@ (with 'pack' and 'unpack' the identity) to use this
-- class with types involving @QFoo@.
-- 
-- The crucial case is 
-- 
-- > instance ('CircLiftingUnpack' ('Circ' b) b') => 'CircLiftingUnpack' ('Circ' (a -> 'Circ' b)) (a -> b')@.
-- 
-- Unfortunately, this requires @-XUndecidableInstances@, for somewhat
-- subtle reasons (see
-- <http://hackage.haskell.org/trac/haskell-prime/wiki/FunctionalDependencies#Restrictionsoninstances>,
-- <http://hackage.haskell.org/trac/haskell-prime/wiki/FunctionalDependencies#Modifiedcoveragecondition>).
-- 
-- The current implementation is fairly restricted, working
-- essentially only for cases like the examples above.  One can define
-- the unpacking more generally; but this restriction keeps the
-- definition much simpler, and suffices for most (all?) of the
-- circuit-generation functions we use.

-- | The 'decToCircMonad' function produces (and also requires)
-- functions with somewhat unwieldy types. The 'CircLiftingUnpack'
-- class defines generic functions for unpacking these types into a
-- more useable format, and for packing them back.
-- 
-- For example, @'Circ' (qa -> 'Circ' (qb -> 'Circ' qd))@ unpacks into
-- the type @qa -> qb -> 'Circ' qd@.
-- 
-- Note that 'pack' and 'unpack' do not in general form an
-- isomorphism, just a retraction of the packed type onto the unpacked
-- type.
class CircLiftingUnpack packed unpacked | packed -> unpacked, unpacked -> packed where
  unpack :: packed -> unpacked
  pack :: unpacked -> packed

instance (CircLiftingUnpack (Circ b) b') => CircLiftingUnpack (Circ (a -> Circ b)) (a -> b') where
  unpack cf x = unpack $ do f <- cf; f x
  pack f = return $ \x -> pack (f x)

instance CircLiftingUnpack (Circ Qubit) (Circ Qubit) where
  pack x = x
  unpack x = x

instance CircLiftingUnpack (Circ [a]) (Circ [a]) where
  pack x = x
  unpack x = x

instance CircLiftingUnpack (Circ ()) (Circ ()) where
  pack x = x
  unpack x = x

instance CircLiftingUnpack (Circ (a,b)) (Circ (a,b)) where
  pack x = x
  unpack x = x

instance CircLiftingUnpack (Circ (a,b,c)) (Circ (a,b,c)) where
  pack x = x
  unpack x = x

instance CircLiftingUnpack (Circ (a,b,c,d)) (Circ (a,b,c,d)) where
  pack x = x
  unpack x = x

instance CircLiftingUnpack (Circ (a,b,c,d,e)) (Circ (a,b,c,d,e)) where
  pack x = x
  unpack x = x

instance CircLiftingUnpack (Circ (a,b,c,d,e,f)) (Circ (a,b,c,d,e,f)) where
  pack x = x
  unpack x = x

instance CircLiftingUnpack (Circ (a,b,c,d,e,f,g)) (Circ (a,b,c,d,e,f,g)) where
  pack x = x
  unpack x = x