From 75ab7f5a322b1200f3f81e5c2c89f868421ee200 Mon Sep 17 00:00:00 2001 From: Leo Date: Thu, 16 Dec 2021 15:53:37 -0500 Subject: [PATCH] move some utilities in YaoExtensions to Yao.EasyBuild submodule. (#315) * integrate utilities in YaoExtensions. * not exporting EasyBuild * clean up EasyBuild * fsim_circuit * rm KMod and mathgate, add hadamard test * add phase estimation * rename * clean up * add docstrings --- src/EasyBuild/block_extension/FSimGate.jl | 51 +++++++++ src/EasyBuild/block_extension/blocks.jl | 3 + src/EasyBuild/block_extension/shortcuts.jl | 17 +++ src/EasyBuild/easybuild.jl | 13 +++ src/EasyBuild/general_U4.jl | 47 ++++++++ src/EasyBuild/google53.jl | 104 ++++++++++++++++++ src/EasyBuild/hadamardtest.jl | 44 ++++++++ src/EasyBuild/hamiltonians.jl | 31 ++++++ src/EasyBuild/phaseestimation.jl | 53 +++++++++ src/EasyBuild/qft_circuit.jl | 20 ++++ src/EasyBuild/supremacy_circuit.jl | 64 +++++++++++ src/EasyBuild/variational_circuit.jl | 114 ++++++++++++++++++++ src/Yao.jl | 4 + test/easybuild/block_extension/blocks.jl | 5 + test/easybuild/block_extension/shortcuts.jl | 23 ++++ test/easybuild/easybuild.jl | 33 ++++++ test/easybuild/general_U4.jl | 17 +++ test/easybuild/google53.jl | 30 ++++++ test/easybuild/hadamardtest.jl | 45 ++++++++ test/easybuild/hamiltonians.jl | 11 ++ test/easybuild/phaseestimation.jl | 67 ++++++++++++ test/easybuild/supremacy_circuit.jl | 16 +++ test/easybuild/variational_circuit.jl | 41 +++++++ test/runtests.jl | 4 + 24 files changed, 857 insertions(+) create mode 100644 src/EasyBuild/block_extension/FSimGate.jl create mode 100644 src/EasyBuild/block_extension/blocks.jl create mode 100644 src/EasyBuild/block_extension/shortcuts.jl create mode 100644 src/EasyBuild/easybuild.jl create mode 100644 src/EasyBuild/general_U4.jl create mode 100644 src/EasyBuild/google53.jl create mode 100644 src/EasyBuild/hadamardtest.jl create mode 100644 src/EasyBuild/hamiltonians.jl create mode 100644 src/EasyBuild/phaseestimation.jl create mode 100644 src/EasyBuild/qft_circuit.jl create mode 100644 src/EasyBuild/supremacy_circuit.jl create mode 100644 src/EasyBuild/variational_circuit.jl create mode 100644 test/easybuild/block_extension/blocks.jl create mode 100644 test/easybuild/block_extension/shortcuts.jl create mode 100644 test/easybuild/easybuild.jl create mode 100644 test/easybuild/general_U4.jl create mode 100644 test/easybuild/google53.jl create mode 100644 test/easybuild/hadamardtest.jl create mode 100644 test/easybuild/hamiltonians.jl create mode 100644 test/easybuild/phaseestimation.jl create mode 100644 test/easybuild/supremacy_circuit.jl create mode 100644 test/easybuild/variational_circuit.jl diff --git a/src/EasyBuild/block_extension/FSimGate.jl b/src/EasyBuild/block_extension/FSimGate.jl new file mode 100644 index 000000000..64525b196 --- /dev/null +++ b/src/EasyBuild/block_extension/FSimGate.jl @@ -0,0 +1,51 @@ +export FSimGate, fsim_block + +""" + FSimGate{T<:Number} <: PrimitiveBlock{2} + +The two parameter `FSim` gate. + +References +------------------------- +* Arute, Frank, et al. "Quantum supremacy using a programmable superconducting processor." Nature 574.7779 (2019): 505-510. +""" +mutable struct FSimGate{T<:Number} <: PrimitiveBlock{2} + theta::T + phi::T +end + +function Base.:(==)(fs1::FSimGate, fs2::FSimGate) + return fs1.theta == fs2.theta && fs1.phi == fs2.phi +end + +function YaoAPI.mat(::Type{T}, fs::FSimGate) where T + θ, ϕ = fs.theta, fs.phi + T[1 0 0 0; + 0 cos(θ) -im*sin(θ) 0; + 0 -im*sin(θ) cos(θ) 0; + 0 0 0 exp(-im*ϕ)] +end + +YaoAPI.iparams_eltype(::FSimGate{T}) where T = T +YaoAPI.getiparams(fs::FSimGate{T}) where T = (fs.theta, fs.phi) +function YaoAPI.setiparams!(fs::FSimGate{T}, θ, ϕ) where T + fs.theta = θ + fs.phi = ϕ + return fs +end + +YaoBlocks.@dumpload_fallback FSimGate FSimGate +YaoBlocks.Optimise.to_basictypes(fs::FSimGate) = fsim_block(fs.theta, fs.phi) + +""" + fsim_block(θ::Real, ϕ::Real) + +The circuit representation of FSim gate. +""" +function fsim_block(θ::Real, ϕ::Real) + if θ ≈ π/2 + return cphase(2,2,1,-ϕ)*SWAP*rot(kron(Z,Z), -π/2)*put(2,1=>phase(-π/4)) + else + return cphase(2,2,1,-ϕ)*rot(SWAP,2*θ)*rot(kron(Z,Z), -θ)*put(2,1=>phase(θ/2)) + end +end \ No newline at end of file diff --git a/src/EasyBuild/block_extension/blocks.jl b/src/EasyBuild/block_extension/blocks.jl new file mode 100644 index 000000000..b9472cee2 --- /dev/null +++ b/src/EasyBuild/block_extension/blocks.jl @@ -0,0 +1,3 @@ +import .YaoBlocks: _apply! +include("shortcuts.jl") +include("FSimGate.jl") diff --git a/src/EasyBuild/block_extension/shortcuts.jl b/src/EasyBuild/block_extension/shortcuts.jl new file mode 100644 index 000000000..f3f9eae43 --- /dev/null +++ b/src/EasyBuild/block_extension/shortcuts.jl @@ -0,0 +1,17 @@ +export ISWAP, SqrtX, SqrtY, SqrtW, singlet_block +export ISWAPGate, SqrtXGate, SqrtYGate, SqrtWGate, CPhaseGate + +const CPhaseGate{N, T} = ControlBlock{N,<:ShiftGate{T},<:Any} + +@const_gate ISWAP = PermMatrix([1,3,2,4], [1,1.0im,1.0im,1]) +@const_gate SqrtX = [0.5+0.5im 0.5-0.5im; 0.5-0.5im 0.5+0.5im] +@const_gate SqrtY = [0.5+0.5im -0.5-0.5im; 0.5+0.5im 0.5+0.5im] +# √W is a non-Clifford gate +@const_gate SqrtW = mat(rot((X+Y)/sqrt(2), π/2)) + +""" + singlet_block(θ::Real, ϕ::Real) + +The circuit block for initialzing a singlet state. +""" +singlet_block() = chain(put(2, 1=>chain(X, H)), control(2, -1, 2=>X)) \ No newline at end of file diff --git a/src/EasyBuild/easybuild.jl b/src/EasyBuild/easybuild.jl new file mode 100644 index 000000000..2ec177908 --- /dev/null +++ b/src/EasyBuild/easybuild.jl @@ -0,0 +1,13 @@ +module EasyBuild +using YaoBlocks, YaoBlocks.LuxurySparse, YaoBlocks.YaoAPI, YaoBlocks.YaoArrayRegister +using YaoBlocks.LinearAlgebra +include("block_extension/blocks.jl") +include("general_U4.jl") +include("phaseestimation.jl") +include("qft_circuit.jl") +include("hamiltonians.jl") +include("variational_circuit.jl") +include("supremacy_circuit.jl") +include("google53.jl") +include("hadamardtest.jl") +end \ No newline at end of file diff --git a/src/EasyBuild/general_U4.jl b/src/EasyBuild/general_U4.jl new file mode 100644 index 000000000..64413b8ec --- /dev/null +++ b/src/EasyBuild/general_U4.jl @@ -0,0 +1,47 @@ +""" +Impliments PRA 69.062321. +""" + +export general_U2, general_U4 + +""" + general_U2(θ1, θ2, θ3; ϕ=nothing) + +A general single qubits gate: ``e^(iϕ)R_z(θ_3)R_y(θ_2)R_z(θ_1)``. +Leave `ϕ` as `nothing` to fix the global phase. +""" +function general_U2(θ1, θ2, θ3; ϕ=nothing) + gate = Rz(θ3) * Ry(θ2) * Rz(θ1) + if ϕ !== nothing + push!(gate, phase(ϕ)) + end + return gate +end + +""" + general_U4([params...]) -> AbstractBlock + +A general two qubits gate decomposed to (CNOT, Ry, Rz), parameters default to 0. + +!!!note + + Although the name is U(4), This is actually a SU(4) gate up to a phase, the phase `det(dispatch!(general_U4(), :random))` is fixed to -1. +""" +general_U4() = general_U4(zeros(15)) +function general_U4(params) + if length(params) != 15 + throw(ArgumentError("The number of parameters must be 15, got $(length(params))")) + end + return chain(2, [ + put(2, 1=>general_U2(params[1:3]...)), + put(2, 2=>general_U2(params[4:6]...)), + cnot(2, 2, 1), + put(2, 1=>Rz(params[7])), + put(2, 2=>Ry(params[8])), + cnot(2, 1, 2), + put(2, 2=>Ry(params[9])), + cnot(2, 2, 1), + put(2, 1=>general_U2(params[10:12]...)), + put(2, 2=>general_U2(params[13:15]...)) + ]) +end diff --git a/src/EasyBuild/google53.jl b/src/EasyBuild/google53.jl new file mode 100644 index 000000000..b91a274e3 --- /dev/null +++ b/src/EasyBuild/google53.jl @@ -0,0 +1,104 @@ +export Lattice53, rand_google53 + +entangler_google53(nbits::Int, i::Int, j::Int) = put(nbits, (i,j)=>FSimGate(π/2, π/6)) + +struct Lattice53 + labels::Matrix{Int} +end + +function Lattice53(;nbits::Int=53) + config = ones(Bool, 5, 12) + config[end,2:2:end] .= false + config[1, 7] = false + labels = zeros(Int, 5, 12) + k = 0 + for (i,c) in enumerate(config) + if c + k += 1 + labels[i] = k + k>=nbits && break + end + end + return Lattice53(labels) +end + +nbits(lattice::Lattice53) = maximum(lattice.labels) + +function Base.getindex(lattice::Lattice53, i, j) + 1<=i<=size(lattice.labels, 1) && 1<=j<=size(lattice.labels, 2) ? lattice.labels[i,j] : 0 +end +upperleft(lattice::Lattice53,i,j) = lattice[i-j%2,j-1] +lowerleft(lattice::Lattice53,i,j) = lattice[i+(j-1)%2,j-1] +upperright(lattice::Lattice53,i,j) = lattice[i-j%2,j+1] +lowerright(lattice::Lattice53,i,j) = lattice[i+(j-1)%2,j+1] + +function pattern53(lattice::Lattice53, chr::Char) + res = Tuple{Int,Int}[] + # i0, di, j0, dj and direction + di = 1 + (chr>'D') + dj = 2 - (chr>'D') + j0 = 1 + min(dj-1, mod(chr-'A',2)) + direction = 'C'<=chr<='F' ? lowerright : upperright + for j=j0:dj:12 + i0 = chr>'D' ? mod((chr-'D') + (j-(chr>='G'))÷2, 2) : 1 + for i = i0:di:5 + src = lattice[i, j] + dest = direction(lattice, i, j) + src!=0 && dest !=0 && push!(res, (src, dest)) + end + end + return res +end + +function print_lattice53(lattice, pattern) + for i_=1:10 + i = (i_+1)÷2 + for j=1:12 + if i_%2 == j%2 && lattice[i,j]!=0 + print(" ∘ ") + else + print(" ") + end + end + println() + for j=1:12 + if i_%2 == j%2 && lattice[i,j]!=0 + hasll = (lowerleft(lattice, i, j), lattice[i,j]) in pattern + haslr = (lattice[i,j], lowerright(lattice, i, j)) in pattern + print(hasll ? "/ " : " ") + print(haslr ? " \\" : " ") + else + print(" ") + end + end + println() + end +end + +""" + rand_google53(depth::Int; nbits=53) -> AbstactBlock + +Google supremacy circuit with 53 qubits, also know as the Sycamore quantum supremacy circuits. + +References +------------------------- +* Arute, Frank, et al. "Quantum supremacy using a programmable superconducting processor." Nature 574.7779 (2019): 505-510. +""" +function rand_google53(depth::Int; nbits::Int=53) + c = chain(nbits) + lattice = Lattice53(nbits=nbits) + k = 0 + for pattern in Iterators.cycle(['A', 'B', 'C', 'D', 'C', 'D', 'A', 'B']) + push!(c, rand_google53_layer(lattice, pattern)) + k += 1 + k>=depth && break + end + return c +end + +function rand_google53_layer(lattice, pattern) + nbit = nbits(lattice) + chain(nbit, chain(nbit, [put(nbit, i=>rand([SqrtW, SqrtX, SqrtY])) for i=1:nbit]), + chain(nbit, [entangler_google53(nbit,i,j) for (i,j) in pattern53(lattice, pattern)]) + ) +end diff --git a/src/EasyBuild/hadamardtest.jl b/src/EasyBuild/hadamardtest.jl new file mode 100644 index 000000000..4af7bd07e --- /dev/null +++ b/src/EasyBuild/hadamardtest.jl @@ -0,0 +1,44 @@ +export hadamard_test, hadamard_test_circuit, swap_test_circuit + +""" + hadamard_test_circuit(U::AbstractBlock, ϕ::Real) + +The Hadamard test circuit. + +References +----------------------- +* [Wiki](https://en.wikipedia.org/wiki/Hadamard_test_(quantum_computation)) +""" +function hadamard_test_circuit(U::AbstractBlock{N}, ϕ::Real) where N + chain(N+1, put(N+1, 1=>H), + put(N+1, 1=>Rz(ϕ)), + control(N+1, 1, 2:N+1=>U), # get matrix first, very inefficient + put(N+1, 1=>H) + ) +end + +function hadamard_test(U::AbstractBlock{N}, reg::AbstractRegister, ϕ::Real) where N + c = hadamard_test_circuit(U, ϕ::Real) + reg = join(reg, zero_state(1)) + expect(put(N+1, 1=>Z), reg |> c) +end + +""" + swap_test_circuit(nbit::Int, nstate::Int, ϕ::Real) + +The swap test circuit for computing the overlap between multiple density matrices. +The `nbit` and `nstate` specifies the number of qubit in each state and how many state we want to compare. + +References +----------------------- +* Ekert, Artur K., et al. "Direct estimations of linear and nonlinear functionals of a quantum state." Physical review letters 88.21 (2002): 217901. +""" +function swap_test_circuit(nbit::Int, nstate::Int, ϕ::Real) + N = nstate*nbit + 1 + + chain(N, put(N, 1=>H), + put(N, 1=>Rz(ϕ)), + chain(N, [chain(N, [control(N, 1, (i+(k*nbit-nbit)+1, i+k*nbit+1)=>SWAP) for i=1:nbit]) for k=1:nstate-1]), # get matrix first, very inefficient + put(N, 1=>H) + ) +end \ No newline at end of file diff --git a/src/EasyBuild/hamiltonians.jl b/src/EasyBuild/hamiltonians.jl new file mode 100644 index 000000000..79510cd76 --- /dev/null +++ b/src/EasyBuild/hamiltonians.jl @@ -0,0 +1,31 @@ +export heisenberg, transverse_ising + +""" + heisenberg(nbit::Int; periodic::Bool=true) + +1D Heisenberg hamiltonian defined as ``\\sum_{i=1}^{n} X_{i}X_{i+1} + Y_{i}Y_{i+1} + Z_{i}Z_{i+1}``, where ``n`` is specified by `nbit`. +`periodic` means the boundary condition is periodic. + +References +---------------------- +* de Oliveira, Mário J. "Ground-state properties of the spin-1/2 antiferromagnetic Heisenberg chain obtained by use of a Monte Carlo method." Physical Review B 48.9 (1993): 6141-6143. +""" +function heisenberg(nbit::Int; periodic::Bool=true) + map(1:(periodic ? nbit : nbit-1)) do i + j=i%nbit+1 + repeat(nbit,X,(i,j)) + repeat(nbit, Y, (i,j)) + repeat(nbit, Z, (i,j)) + end |> sum +end + +""" + transverse_ising(nbit::Int, h::Number; periodic::Bool=true) + +1D transverse Ising hamiltonian defined as ``\\sum_{i=1}^{n} hX_{i} + Z_{i}Z_{i+1}``, where ``n`` is specified by `nbit`. +`periodic` means the boundary condition is periodic. +""" +function transverse_ising(nbit::Int, h::Number; periodic::Bool=true) + ising_term = map(1:(periodic ? nbit : nbit-1)) do i + repeat(nbit,Z,(i,i%nbit+1)) + end |> sum + ising_term + h*sum(map(i->put(nbit,i=>X), 1:nbit)) +end diff --git a/src/EasyBuild/phaseestimation.jl b/src/EasyBuild/phaseestimation.jl new file mode 100644 index 000000000..0bfeb307a --- /dev/null +++ b/src/EasyBuild/phaseestimation.jl @@ -0,0 +1,53 @@ +export phase_estimation_circuit, phase_estimation_analysis + +""" + phase_estimation_circuit(unitarygate::GeneralMatrixBlock, n_reg, n_b) -> ChainBlock + +Phase estimation circuit. Input arguments are + +* `unitarygate`: the input unitary matrix. +* `n_reg`: the number of bits to store phases, +* `n_b`: the number of bits to store vector. + +References +---------------------- +[Wiki](https://en.wikipedia.org/wiki/Quantum_phase_estimation_algorithm) +""" +function phase_estimation_circuit(unitarygate::GeneralMatrixBlock, n_reg::Int, n_b::Int) + nbit = n_b + n_reg + # Apply Hadamard Gate. + hs = repeat(nbit, H, 1:n_reg) + + # Construct a control circuit. + control_circuit = chain(nbit) + for i = 1:n_reg + push!(control_circuit, control(nbit, (i,), (n_reg+1:nbit...,)=>unitarygate)) + if i != n_reg + unitarygate = matblock(mat(unitarygate) * mat(unitarygate)) + end + end + + # Inverse QFT Block. + iqft = subroutine(nbit, qft_circuit(n_reg)',[1:n_reg...,]) + chain(hs, control_circuit, iqft) +end + +""" + phase_estimation_analysis(eigenvectors::Matrix, reg::ArrayReg) -> Tuple + +Analyse phase estimation result using state projection. +It returns a tuple of (most probable configuration, the overlap matrix, the relative probability for this configuration) +`eigenvectors` is the eigen vectors of the unitary gate matrix, while `reg` is the result of phase estimation. +""" +function phase_estimation_analysis(eigenvectors::AbstractMatrix, reg::ArrayReg) + overlap = eigenvectors'*state(reg) + amp_relative = Float64[] + bs = Int[] + + for b in basis(overlap) + mc = argmax(view(overlap, b+1, :) .|> abs)-1 + push!(amp_relative, abs2(overlap[b+1, mc+1])/sum(overlap[b+1, :] .|> abs2)) + push!(bs, mc) + end + bs, overlap, amp_relative +end \ No newline at end of file diff --git a/src/EasyBuild/qft_circuit.jl b/src/EasyBuild/qft_circuit.jl new file mode 100644 index 000000000..9a5f48104 --- /dev/null +++ b/src/EasyBuild/qft_circuit.jl @@ -0,0 +1,20 @@ +export qft_circuit + +""" + cphase(nbits, i, j, θ) + +Control-phase gate. +""" +cphase(nbits::Int, i::Int, j::Int, θ::T) where T = control(nbits, i, j=>shift(θ)) + +""" + qft_circuit(n) + +The quantum Fourer transformation (QFT) circuit. + +References +------------------------ +* [Wiki](https://en.wikipedia.org/wiki/Quantum_Fourier_transform) +""" +qft_circuit(n::Int) = chain(n, hcphases(n, i) for i = 1:n) +hcphases(n, i) = chain(n, i==j ? put(i=>H) : cphase(n, j, i, 2π/(2^(j-i+1))) for j in i:n); \ No newline at end of file diff --git a/src/EasyBuild/supremacy_circuit.jl b/src/EasyBuild/supremacy_circuit.jl new file mode 100644 index 000000000..170883755 --- /dev/null +++ b/src/EasyBuild/supremacy_circuit.jl @@ -0,0 +1,64 @@ +export rand_supremacy2d + +# control-Z entangler. +cz_entangler(n::Int, pairs) = chain(n, control(n, [ctrl], target=>Z) for (ctrl, target) in pairs) + +""" + rand_supremacy2d(nx::Int, ny::Int, depth::Int) -> AbstactBlock + +The circuit proposed for realizing quantum supermacy in a near-term device. + +References +------------------------------- +* Boixo, Sergio, et al. "Characterizing quantum supremacy in near-term devices." Nature Physics 14.6 (2018): 595-600. + +!!! note + + Some restrictions are loosed, please check this circuit carefully. +""" +function rand_supremacy2d(nx::Int, ny::Int, depth::Int) + nbits = nx*ny + entanglers = map(pair->cz_entangler(nbits, pair), pair_supremacy(nx, ny)) + gateset = [ConstGate.T, SqrtX, SqrtY] + c = chain(nbits, repeat(nbits, H, 1:nbits)) + pre_occ = (1:nbits...,) + pre_gates = Vector{PrimitiveBlock}(fill(H,nbits)) + hastgate = zeros(Bool, nbits) + for i=1:depth-2 + ent = entanglers[mod1(i,length(entanglers))] + unit = chain(nbits, [ent]) + occ = occupied_locs(ent) + for loc in setdiff(pre_occ, occ) + g1 = hastgate[loc] ? rand(setdiff(gateset, (pre_gates[loc],))) : (hastgate[loc]=true; T) + push!(unit, put(nbits, loc=>g1)) + pre_gates[loc] = g1 + end + pre_occ = occ + push!(c, unit) + end + depth!=1 && push!(c, repeat(nbits, H, 1:nbits)) + return c +end + +# obtain supremacy pairing patterns +function pair_supremacy(nx::Int, ny::Int; periodic=false) + Kx = [0.25 0.5; 0.25 -0.5] + Ky = [0.5 0.25; -0.5 0.25] + out = Vector[] + li = LinearIndices((nx, ny)) + + for (dx, dy, isxdirection) in [(2,0,true), (0,0,true), (0,3,false), (0,1,false), + (3,0,true), (1,0,true), (0,0,false), (0,2,false)] + res = Pair{Int, Int}[] + for i = 1:nx, j=1:ny + if (periodic || (isxdirection ? i li[i_, j_]) + end + end + push!(out, res) + end + + return out +end \ No newline at end of file diff --git a/src/EasyBuild/variational_circuit.jl b/src/EasyBuild/variational_circuit.jl new file mode 100644 index 000000000..7d254ca9d --- /dev/null +++ b/src/EasyBuild/variational_circuit.jl @@ -0,0 +1,114 @@ +using YaoArrayRegister.StatsBase: sample + +export variational_circuit + +################## Entangler ################### +""" + pair_ring(n::Int) -> Vector + +Pair ring entanglement layout. +""" +pair_ring(n::Int) = [i=>mod(i, n)+1 for i=1:n] + +""" + pair_square(m::Int, n::Int) -> Vector + +Pair square entanglement layout. +""" +function pair_square(m::Int, n::Int; periodic=false) + res = Vector{Pair{Int, Int}}(undef, (m-!periodic)*n+m*(n-!periodic)) + li = LinearIndices((m, n)) + k = 1 + for i = 1:2:m, j=1:n + if periodic || i li[i%m+1, j] + k+=1 + end + end + for i = 2:2:m, j=1:n + if periodic || i li[i%m+1, j] + k+=1 + end + end + for i = 1:m, j=1:2:n + if periodic || j li[i, j%n+1] + k+=1 + end + end + for i = 1:m, j=2:2:n + if periodic || j li[i, j%n+1] + k+=1 + end + end + res +end + +###################### rotor and rotorset ##################### +""" + merged_rotor(noleading::Bool=false, notrailing::Bool=false) -> ChainBlock{1, ComplexF64} + +Single qubit arbitrary rotation unit, set parameters notrailing, noleading true to remove trailing and leading Z gates. + +!!! note + + Here, `merged` means `Rz(η)⋅Rx(θ)⋅Rz(ξ)` are multiplied first, this kind of operation if now allowed in differentiable + circuit with back-propagation (`:BP`) mode (just because we are lazy to implement it!). + But is a welcoming component in quantum differentiation. +""" +merged_rotor(noleading::Bool=false, notrailing::Bool=false) = noleading ? (notrailing ? Rx(0.0) : chain(Rx(0.0), Rz(0.0))) : (notrailing ? chain(Rz(0.0), Rx(0.0)) : chain(Rz(0.0), Rx(0.0), Rz(0.0))) + +""" + rotor(nbit::Int, ibit::Int, noleading::Bool=false, notrailing::Bool=false) -> ChainBlock{nbit, ComplexF64} + +Arbitrary rotation unit (put in `nbit` space), set parameters notrailing, noleading true to remove trailing and leading Z gates. +""" +function rotor(nbit::Int, ibit::Int, noleading::Bool=false, notrailing::Bool=false) + rt = chain(nbit, [put(nbit, ibit=>Rz(0.0)), put(nbit, ibit=>Rx(0.0)), put(nbit, ibit=>Rz(0.0))]) + noleading && popfirst!(rt) + notrailing && pop!(rt) + rt +end + +rotorset(::Val{:Merged}, nbit::Int, noleading::Bool=false, notrailing::Bool=false) = chain(nbit, [put(nbit, j=>merged_rotor(noleading, notrailing)) for j=1:nbit]) +rotorset(::Val{:Split}, nbit::Int, noleading::Bool=false, notrailing::Bool=false) = chain(nbit, [rotor(nbit, j, noleading, notrailing) for j=1:nbit]) +rotorset(mode::Symbol, nbit::Int, noleading::Bool=false, notrailing::Bool=false) = rotorset(Val(mode), nbit, noleading, notrailing) + +""" + variational_circuit(nbit[, nlayer][, pairs]; mode=:Split, do_cache=false, entangler=cnot) + +A kind of widely used differentiable quantum circuit, angles in the circuit is randomely initialized. +Input arguments are + +* `pairs` is list of `Pair`s for entanglers in a layer, default to `pair_ring` structure, +* `mode` can be :Split or :Merged, +* `do_cache` decides whether cache the entangler matrix or not, +* `entangler` is a constructor returns a two qubit gate, `f(n,i,j) -> gate`. + The default value is `cnot(n,i,j)`. + +References +------------------------- +1. Kandala, A., Mezzacapo, A., Temme, K., Takita, M., Chow, J. M., & Gambetta, J. M. (2017). + Hardware-efficient Quantum Optimizer for Small Molecules and Quantum Magnets. Nature Publishing Group, 549(7671), 242–246. + https://doi.org/10.1038/nature23879. +""" +function variational_circuit(nbit, nlayer, pairs; mode=:Split, do_cache=false, entangler=cnot) + circuit = chain(nbit) + + ent = chain(nbit, entangler(nbit, i, j) for (i, j) in pairs) + if do_cache + ent = ent |> cache + end + has_param = nparameters(ent) != 0 + for i = 1:(nlayer + 1) + i!=1 && push!(circuit, has_param ? deepcopy(ent) : ent) + push!(circuit, rotorset(mode, nbit, i==1, i==nlayer+1)) + end + circuit +end + +variational_circuit(n::Int; kwargs...) = variational_circuit(n, 3, pair_ring(n); kwargs...) + +variational_circuit(nbit::Int, nlayer::Int; kwargs...) = variational_circuit(nbit, nlayer, pair_ring(nbit), kwargs...) \ No newline at end of file diff --git a/src/Yao.jl b/src/Yao.jl index 1cb44d0a7..5ee12d2b0 100644 --- a/src/Yao.jl +++ b/src/Yao.jl @@ -14,8 +14,12 @@ Extensible Framework for Quantum Algorithm Design for Humans. """ const 幺 = Yao +include("EasyBuild/easybuild.jl") + using Reexport @reexport using YaoBase, YaoArrayRegister, YaoBlocks, YaoSym +export EasyBuild +using YaoBase.BitBasis using YaoBlocks: color, diff --git a/test/easybuild/block_extension/blocks.jl b/test/easybuild/block_extension/blocks.jl new file mode 100644 index 000000000..a37fd217d --- /dev/null +++ b/test/easybuild/block_extension/blocks.jl @@ -0,0 +1,5 @@ +using Test, Yao.EasyBuild + +@testset "shortcuts" begin + include("shortcuts.jl") +end diff --git a/test/easybuild/block_extension/shortcuts.jl b/test/easybuild/block_extension/shortcuts.jl new file mode 100644 index 000000000..c0f652363 --- /dev/null +++ b/test/easybuild/block_extension/shortcuts.jl @@ -0,0 +1,23 @@ +using Test, Yao.EasyBuild, YaoBlocks +using YaoBlocks.Optimise: replace_block, to_basictypes, simplify +using YaoBlocks: parse_ex + +@testset "gates" begin + @test isunitary(FSimGate(0.5, 0.6)) + fs = FSimGate(π/2, π/6) + @test eval(parse_ex(dump_gate(fs), 1)) == fs + cphase(nbits, i::Int, j::Int, θ::T) where T = control(nbits, i, j=>shift(θ)) + ic = ISWAP*cphase(2, 2, 1, π/6) + @test mat(fs) ≈ mat(ic)' + ISWAP_ = SWAP*rot(kron(Z,Z), π/2) + @test Matrix(ISWAP) ≈ Matrix(ISWAP_)*exp(im*π/4) + + fs_ = to_basictypes(fs) + @test mat(fs) ≈ Matrix(fs_) + + c = chain(put(3,(3,1)=>fs), chain(put(3,(1,2)=>fs), put(3, (1,3)=>fs), put(3, (3,2)=>fs))) + c_ = simplify(replace_block(fs=>fs_, c), rules=[to_basictypes]) + c = chain(put(3,(3,1)=>fs_), chain(put(3,(1,2)=>fs_), put(3, (1,3)=>fs_), put(3, (3,2)=>fs_))) + + @test Matrix(c) ≈ Matrix(simplify(c_, rules=[to_basictypes])) +end diff --git a/test/easybuild/easybuild.jl b/test/easybuild/easybuild.jl new file mode 100644 index 000000000..667cfd5e9 --- /dev/null +++ b/test/easybuild/easybuild.jl @@ -0,0 +1,33 @@ +using Test, Yao.EasyBuild + +@testset "block_extension" begin + include("block_extension/blocks.jl") +end + +@testset "variational_circuit" begin + include("variational_circuit.jl") +end + +@testset "hamiltonians" begin + include("hamiltonians.jl") +end + +@testset "supremacy_circuit" begin + include("supremacy_circuit.jl") +end + +@testset "google53" begin + include("google53.jl") +end + +@testset "general_U4" begin + include("general_U4.jl") +end + +@testset "phaseestimation" begin + include("phaseestimation.jl") +end + +@testset "hadamardtest" begin + include("hadamardtest.jl") +end diff --git a/test/easybuild/general_U4.jl b/test/easybuild/general_U4.jl new file mode 100644 index 000000000..7e8d3aee6 --- /dev/null +++ b/test/easybuild/general_U4.jl @@ -0,0 +1,17 @@ +using Yao.EasyBuild, Test +@testset "general U2 U4" begin + c = general_U2(0.5, 0.7, 0.9; ϕ=0.8) + @test length(c) == 4 + c = general_U2(0.5, 0.7, 0.9) + @test length(c) == 3 + + params = rand(17)*2π + @test_throws ArgumentError general_U4(params) + params = rand(15)*2π + g = general_U4(params) + gc = gatecount(g) + @test gc[typeof(Ry(0.0))] == 6 + @test gc[typeof(Rz(0.0))] == 9 + @test gc[typeof(cnot(2,2,1))] == 3 + @test parameters(g) ≈ params +end diff --git a/test/easybuild/google53.jl b/test/easybuild/google53.jl new file mode 100644 index 000000000..7d6f5acd3 --- /dev/null +++ b/test/easybuild/google53.jl @@ -0,0 +1,30 @@ +using Test +using BitBasis +using Yao.EasyBuild +using Yao.EasyBuild: pattern53, upperright, lowerleft, upperleft, lowerright +using YaoBlocks.Optimise: to_basictypes, simplify, replace_block + +lattice = Lattice53() +@test upperright(lattice,1,1) == 0 +@test upperright(lattice,2,1) == lattice[1,2] +@test upperright(lattice,1,2) == lattice[1,3] +@test lowerright(lattice,1,1) == lattice[1,2] +@test lowerright(lattice,1,2) == lattice[2,3] +@test upperleft(lattice,1,2) == lattice[1,1] +@test upperleft(lattice,1,3) == 0 +@test upperleft(lattice,2,3) == lattice[1,2] +@test lowerleft(lattice,1,2) == lattice[2,1] +@test lowerleft(lattice,2,3) == lattice[2,2] + +lattice = Lattice53() +for (sym, nvar) in [('A', 24), ('B', 19), ('C', 23), ('D', 20), + ('E', 22), ('F', 21), ('G', 21), ('H', 22)] + @test length(pattern53(lattice, sym)) == nvar +end + +@testset "circuit build" begin + c = rand_google53(4) + @test length(collect_blocks(FSimGate, c)) == 86 + @test nparameters(c) == 86*2 + @test length(collect_blocks(PrimitiveBlock{1}, c)) == 53*4 +end diff --git a/test/easybuild/hadamardtest.jl b/test/easybuild/hadamardtest.jl new file mode 100644 index 000000000..a62bbec01 --- /dev/null +++ b/test/easybuild/hadamardtest.jl @@ -0,0 +1,45 @@ +using Yao.EasyBuild, Yao +using Test +using LinearAlgebra +using YaoBlocks.ConstGate + +single_swap_test_circuit(ϕ::Real) = hadamard_test_circuit(SWAP, ϕ) +single_swap_test(reg, ϕ::Real) = hadamard_test(SWAP, reg, ϕ) + +@testset "state overlap" begin + reg1 = rand_state(3) |> focus!(1,2) + rho1 = reg1 |> ρ + reg2 = rand_state(3) |> focus!(1,2) + rho2 = reg2 |> ρ + reg3 = rand_state(3) |> focus!(1,2) + rho3 = reg3 |> ρ + desired = tr(Matrix(rho1)*Matrix(rho2)) + c = swap_test_circuit(2, 2, 0) + res = expect(put(5, 1=>Z), join(join(reg2, reg1), zero_state(1)) |> c) |> tr + @test desired ≈ res + desired = tr(Matrix(rho1)*Matrix(rho2)*Matrix(rho3)) |> real + c = swap_test_circuit(2, 3, 0) + res = expect(put(7, 1=>Z), reduce(join, [reg3, reg2, reg1, zero_state(1)]) |> c) |> tr |> real + @test desired ≈ res + desired = tr(Matrix(rho1)*Matrix(rho2)*Matrix(rho3)) |> imag + c = swap_test_circuit(2, 3, -π/2) + res = expect(put(7, 1=>Z), reduce(join, [reg3, reg2, reg1, zero_state(1)]) |> c) |> tr |> real + @test desired ≈ res +end + +@testset "hadamard test" begin + nbit = 4 + U = put(nbit, 2=>Rx(0.2)) + reg = rand_state(nbit) + + @test hadamard_test(U, reg, 0.0) ≈ real(expect(U, reg)) + @test hadamard_test(U, reg, -π/2) ≈ imag(expect(U, reg)) + + reg = zero_state(2) |> EasyBuild.singlet_block() + @test single_swap_test(reg, 0) ≈ -1 + + reg = zero_state(2) + @test single_swap_test(reg, 0) ≈ 1 + reg = product_state(2, 0b11) + @test single_swap_test(reg, 0) ≈ 1 +end \ No newline at end of file diff --git a/test/easybuild/hamiltonians.jl b/test/easybuild/hamiltonians.jl new file mode 100644 index 000000000..9f7b482f9 --- /dev/null +++ b/test/easybuild/hamiltonians.jl @@ -0,0 +1,11 @@ +using Yao.EasyBuild +using Test +using Yao.ConstGate + +@testset "solving hamiltonian" begin + nbit = 8 + h = heisenberg(nbit) |> cache + @test ishermitian(h) + h = transverse_ising(nbit, 1.0) + @test ishermitian(h) +end diff --git a/test/easybuild/phaseestimation.jl b/test/easybuild/phaseestimation.jl new file mode 100644 index 000000000..5d8f2b145 --- /dev/null +++ b/test/easybuild/phaseestimation.jl @@ -0,0 +1,67 @@ +using Yao.EasyBuild +using BitBasis +using Test, LinearAlgebra + +""" +random phase estimation problem setup. +""" +function rand_phaseest_setup(N::Int) + U = rand_unitary(1< adjoint) ≈ join(reg2, reg1) +end + +@testset "phaseest, non-eigen" begin + # Generate a random matrix. + N = 3 + U, b, ϕs, evec = rand_phaseest_setup(N) + + # Define ArrayReg and U operator. + M = 6 + reg1 = zero_state(M) + reg2 = ArrayReg(b) + UG = matblock(U); + + # run circuit + reg= join(reg2, reg1) + pe = phase_estimation_circuit(UG, M, N) + apply!(reg, pe) + + # measure + bs, proj, amp_relative = phase_estimation_analysis(evec, focus!(reg, M+1:M+N)) + @test isapprox(ϕs, bfloat.(bs, nbits=M), atol=0.05) +end \ No newline at end of file diff --git a/test/easybuild/supremacy_circuit.jl b/test/easybuild/supremacy_circuit.jl new file mode 100644 index 000000000..4de922c37 --- /dev/null +++ b/test/easybuild/supremacy_circuit.jl @@ -0,0 +1,16 @@ +using Yao.EasyBuild +using Yao.EasyBuild: pair_supremacy, cz_entangler +using Test + +@testset "SqrtX" begin + @test mat(SqrtX)^2 ≈ mat(X) + @test mat(SqrtY)^2 ≈ mat(Y) + @test cz_entangler(5, [1=>2, 4=>5]) == chain(control(5, 1, 2=>Z), control(5, 4, 5=>Z)) +end + +@testset "supremacy" begin + c = rand_supremacy2d(4, 4, 8) + @test length(c) == 8 + @test length(pair_supremacy(6,6)) == 8 + @test pair_supremacy(6,6)[1] == [7=>8, 19=>20, 31=>32, 3=>4, 15=>16, 27=>28, 11=>12, 23=>24, 35=>36] +end diff --git a/test/easybuild/variational_circuit.jl b/test/easybuild/variational_circuit.jl new file mode 100644 index 000000000..6e4de87a1 --- /dev/null +++ b/test/easybuild/variational_circuit.jl @@ -0,0 +1,41 @@ +using Test +using Yao.EasyBuild +using Yao.EasyBuild: pair_ring, pair_square, merged_rotor, rotor, rotorset + +@testset "pairs geometries" begin + @test pair_ring(3) == [1=>2,2=>3,3=>1] + ps = pair_square(2, 2; periodic=false) + @test length(ps) == 4 + for item in [1=>2, 3=>4, 1=>3, 2=>4] + @test item in ps + end + + ps = pair_square(2, 2; periodic=true) + @test length(ps) == 8 + for item in [1=>2, 3=>4, 2=>1, 4=>3, 1=>3, 2=>4, 3=>1, 4=>2] + @test item in ps + end +end + +@testset "rotter, collect_blocks, num_gradient, opgrad" begin + @test merged_rotor(true, true) == Rx(0.0) + @test merged_rotor(false, false) == merged_rotor() == chain(Rz(0.0), Rx(0.0), Rz(0.0)) + @test merged_rotor(false, true) == chain(Rz(0.0), Rx(0.0)) + @test merged_rotor(true, false) == chain(Rx(0.0), Rz(0.0)) + @test collect_blocks(RotationGate, rotorset(:Merged, 5, true, false)) |> length == 10 + + @test rotor(5, 2, true, true) isa ChainBlock + @test rotor(5, 2, true, true) |> length == 1 + @test rotor(5, 2, true, true) |> nqubits == 5 + @test collect_blocks(PutBlock{<:Any, <:Any, <:RotationGate}, rotorset(:Split, 5, true, false)) |> length == 10 +end + +@testset "entangler" begin + c = variational_circuit(5; entangler=(n,i,j)->put(n,(i,j)=>ConstGate.CZ)) + @test nparameters(c) == 50 + c = variational_circuit(5; entangler=(n,i,j)->put(n,(i,j)=>rot(ConstGate.CNOT, 0.0))) + @test nparameters(c) == 65 + ps = randn(nparameters(c)) + dispatch!(c, ps) + @test parameters(c) ≈ ps +end diff --git a/test/runtests.jl b/test/runtests.jl index ef4acd670..d49d6db21 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1 +1,5 @@ using Test, Yao + +@testset "easybuld" begin + include("easybuild/easybuild.jl") +end \ No newline at end of file