Prepare Greenberger–Horne–Zeilinger state with Quantum Circuit
First, you have to use this package in Julia.
using Yao
Now, we just define the circuit according to the circuit image below:
circuit = chain(
4,
put(1=>X),
repeat(H, 2:4),
control(2, 1=>X),
control(4, 3=>X),
control(3, 1=>X),
control(4, 3=>X),
repeat(H, 1:4),
)
nqubits: 4, datatype: Complex{Float64}
chain
├─ put on (1)
│ └─ X gate
├─ repeat on (2, 3, 4)
│ └─ H gate
├─ control(2)
│ └─ (1,) X gate
├─ control(4)
│ └─ (3,) X gate
├─ control(3)
│ └─ (1,) X gate
├─ control(4)
│ └─ (3,) X gate
└─ repeat on (1, 2, 3, 4)
└─ H gate
Let me explain what happens here.
Put single qubit gate X to location 1
we have an X
gate applied to the first qubit.
We need to tell Yao
to put this gate on the first qubit by
put(4, 1=>X)
nqubits: 4, datatype: Complex{Float64}
put on (1)
└─ X gate
We use Julia's Pair
to denote the gate and its location in the circuit, for two-qubit gate, you could also use a tuple of locations:
put(4, (1, 2)=>swap(2, 1, 2))
nqubits: 4, datatype: Complex{Float64}
put on (1, 2)
└─ swap(1, 2)
But, wait, why there's no 4
in the definition above? This is because all the functions in Yao
that requires to input the number of qubits as its first arguement could be lazy (curried), and let other constructors to infer the total number of qubits later, e.g
put(1=>X)
(n -> put(n, 1 => X gate))
which will return a lambda that ask for a single arguement n
.
put(1=>X)(4)
nqubits: 4, datatype: Complex{Float64}
put on (1)
└─ X gate
Apply the same gate on different locations
next we should put Hadmard gates on all locations except the 1st qubits.
We provide repeat
to apply the same block repeatly, repeat can take an iterator of desired locations, and like put
, we can also leave the total number of qubits there.
repeat(H, 2:4)
(n -> repeat(n, H gate, 2, 3, 4))
Define control gates
In Yao, we could define controlled gates by feeding a gate to control
control(4, 2, 1=>X)
nqubits: 4, datatype: Complex{Float64}
control(2)
└─ (1,) X gate
Like many others, you could leave the number of total qubits there, and infer it later.
control(2, 1=>X)
(n -> control(n, 2, 1 => X gate))
Composite each part together
This will create a ControlBlock
, the concept of block in Yao basically just means quantum operators, since the quantum circuit itself is a quantum operator, we could create a quantum circuit by composite each part of.
Here, we use chain
to chain each part together, a chain of quantum operators means to apply each operators one by one in the chain. This will create a ChainBlock
.
circuit = chain(
4,
put(1=>X),
repeat(H, 2:4),
control(2, 1=>X),
control(4, 3=>X),
control(3, 1=>X),
control(4, 3=>X),
repeat(H, 1:4),
)
nqubits: 4, datatype: Complex{Float64}
chain
├─ put on (1)
│ └─ X gate
├─ repeat on (2, 3, 4)
│ └─ H gate
├─ control(2)
│ └─ (1,) X gate
├─ control(4)
│ └─ (3,) X gate
├─ control(3)
│ └─ (1,) X gate
├─ control(4)
│ └─ (3,) X gate
└─ repeat on (1, 2, 3, 4)
└─ H gate
You can check the type of it with typeof
typeof(circuit)
ChainBlock{4,Complex{Float64}}
Construct GHZ state from 00...00
For simulation, we provide a builtin register type called ArrayReg
, we will use the simulated register in this example.
First, let's create $|00⋯00⟩$, you can create it with zero_state
zero_state(4)
ArrayReg{1, Complex{Float64}, Array...}
active qubits: 4/4
Or we also provide bit string literals to create arbitrary eigen state
ArrayReg(bit"0000")
ArrayReg{1, Complex{Float64}, Array...}
active qubits: 4/4
They will both create a register with Julia's builtin Array
as storage.
Feed Registers to Circuits
Circuits can be applied to registers with apply!
apply!(zero_state(4), circuit)
ArrayReg{1, Complex{Float64}, Array...}
active qubits: 4/4
or you can use pipe operator |>
, when you want to chain several operations together, here we measure the state right after the circuit for 1000
times
results = zero_state(4) |> circuit |> r->measure(r, nshots=1000)
1000-element Array{Int64,1}:
15
0
0
0
15
15
0
15
0
0
⋮
15
15
15
15
15
0
0
0
0
GHZ state will collapse to $|0000⟩$ or $|1111⟩$.