diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..ee89540 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,24 @@ +### Summary + +Short summary of the changes. + +### Rationale + +Explain the rationale for the changes (links to relevant issue(s)). + +#### Implementation Details + +#### Additional notes + +
+ +***Check before merging:*** + +- [ ] All discussions are resolved +- [ ] New code is covered by appropriate tests +- [ ] Tests are passing locally and on CI +- [ ] The documentation is consistent with changes +- [ ] Any code that was copied from other sources has the paper/url in a comment and is compatible with the MIT licence +- [ ] Notebooks/examples not covered by unittests have been tested and updated as required +- [ ] The feature branch is up-to-date with the master branch (rebase if behind) +- [ ] Incremented the version string in Project.toml file diff --git a/.github/workflows/CompatHelper.yml b/.github/workflows/CompatHelper.yml new file mode 100644 index 0000000..cba9134 --- /dev/null +++ b/.github/workflows/CompatHelper.yml @@ -0,0 +1,16 @@ +name: CompatHelper +on: + schedule: + - cron: 0 0 * * * + workflow_dispatch: +jobs: + CompatHelper: + runs-on: ubuntu-latest + steps: + - name: Pkg.add("CompatHelper") + run: julia -e 'using Pkg; Pkg.add("CompatHelper")' + - name: CompatHelper.main() + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }} + run: julia -e 'using CompatHelper; CompatHelper.main()' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5493af5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,64 @@ +name: CI +on: + - push + - pull_request +jobs: + test: + name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + version: + - '1.5' + - 'nightly' + os: + - ubuntu-latest + - macOS-latest + - windows-latest + arch: + - x64 + steps: + - uses: actions/checkout@v2 + - uses: julia-actions/setup-julia@v1 + with: + version: ${{ matrix.version }} + arch: ${{ matrix.arch }} + - uses: actions/cache@v1 + env: + cache-name: cache-artifacts + with: + path: ~/.julia/artifacts + key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }} + restore-keys: | + ${{ runner.os }}-test-${{ env.cache-name }}- + ${{ runner.os }}-test- + ${{ runner.os }}- + - uses: julia-actions/julia-buildpkg@v1 + - uses: julia-actions/julia-runtest@v1 + - uses: julia-actions/julia-processcoverage@v1 + - uses: codecov/codecov-action@v1 + with: + file: lcov.info + docs: + name: Documentation + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: julia-actions/setup-julia@v1 + with: + version: '1' + - run: | + julia --project=docs -e ' + using Pkg + Pkg.develop(PackageSpec(path=pwd())) + Pkg.instantiate()' + - run: | + julia --project=docs -e ' + using Documenter: doctest + using QXSim + doctest(QXSim)' + - run: julia --project=docs docs/make.jl + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} diff --git a/Project.toml b/Project.toml index 7e5c088..55ff62b 100644 --- a/Project.toml +++ b/Project.toml @@ -1,15 +1,26 @@ name = "QXZoo" uuid = "f27fdc93-0c88-4b5a-91cb-e8275adac0f6" -authors = ["Lee J. O'Riordan "] +authors = ["QuantEx Team"] version = "0.1.0" [deps] DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" -Memoize = "c03570c3-d221-55d1-a50c-7939bbd78826" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Reexport = "189a3867-3050-52da-a836-e630ba90ab69" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" -Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" TestSetExtensions = "98d24dd4-01ad-11ea-1b02-c9a08f80db04" UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" + +[compat] +DataStructures = "0.18" +Reexport = "1.0" +StaticArrays = "1.0" +TestSetExtensions = "2.0.0" +julia = "1.5" + +[extras] +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["Test"] diff --git a/docs/Project.toml b/docs/Project.toml index dfa65cd..3879473 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,2 +1,5 @@ [deps] Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" + +[compat] +Documenter = "0.26" \ No newline at end of file diff --git a/docs/make.jl b/docs/make.jl index 610479f..7e74c31 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,5 +1,5 @@ push!(LOAD_PATH,"./src/") -using Documenter, QXZoo +using Documenter, QXZoo, Random makedocs( modules = [QXZoo], @@ -16,9 +16,17 @@ makedocs( "Home" => "index.md", "Examples" => "examples.md", "Manual" => Any[ - "Gates" => Any["gates_circuits/GateOps.md", "gates_circuits/DefaultGates.md", "gates_circuits/GateMap.md"], + "Gates" => Any[ "gates_circuits/GateOps.md", + "gates_circuits/DefaultGates.md", + "gates_circuits/CompositeGates.md", + "gates_circuits/GateMap.md", + ], "Circuits" => "gates_circuits/circuits.md", - "Algorithms" => Any[ "NCU" => "algo/ncu.md", "Grover" => "algo/grover.md" ], + "Algorithms" => Any[ "NCU" => "algo/ncu.md", + "Grover" => "algo/grover.md", + "QFT" => "algo/qft.md", + "RQC" => "algo/rqc.md" + ], ], ] ) diff --git a/docs/src/algo/grover.md b/docs/src/algo/grover.md index 9240456..00c52df 100644 --- a/docs/src/algo/grover.md +++ b/docs/src/algo/grover.md @@ -4,9 +4,8 @@ The Grover.jl module implements a Grover's search use-case. Assisted by Oracle.j To apply the Grover search algorithm, we require additional functionalities in the form an oracle to select the required state, and a diffusion operator to shift the state ampltiudes. ## API -### oracle.jl ```@docs -QXZoo.Grover.bitstring_ncu!(cct::QXZoo.Circuit.Circ, bitstring::Integer, ctrl_indices::Vector, tgt_idx, U::QXZoo.GateOps.GateSymbol, aux_indices::Vector=Int[]) +QXZoo.Grover.bitstring_ncu!(cct::QXZoo.Circuit.Circ, bitstring::Integer, ctrl_indices::Vector, tgt_idx::Int, U::QXZoo.GateOps.GateSymbol, aux_indices::Vector=Int[]) QXZoo.Grover.bitstring_phase_oracle!(cct::QXZoo.Circuit.Circ, bitstring::Integer, ctrl_indices::Vector, tgt_idx::Int, aux_indices::Vector=Int[]) QXZoo.Grover.apply_diffusion!(cct::QXZoo.Circuit.Circ, ctrl_indices::Vector, tgt_index::Int, aux_indices::Vector=Int[]) QXZoo.Grover.run_grover!(cct::QXZoo.Circuit.Circ, qubit_indices::Vector, state::Integer) @@ -22,27 +21,40 @@ To use the Grover module, we provide example code below to search for a state in ```@example 1 using QXZoo -# Set 5-qubit limit on circuit -num_qubits = 5 +# Set 10-qubit limit on circuit +num_qubits = 10 # Set bit-pattern to 11 (0b01011) bit_pattern = 11 -# Do not use optimised routines -use_aux_qubits = false - # Create empty circuit with qiven qubit count cct = QXZoo.Circuit.Circ(num_qubits) -# Initialise intermediate gates for use in NCU -QXZoo.NCU.init_intermed_gates(cct, num_qubits-1) - # Run Grover algorithm for given oracle bit-pattern -QXZoo.Grover.run_grover!(cct, collect(0:num_qubits-1), bit_pattern) +QXZoo.Grover.run_grover!(cct, collect(1:num_qubits), bit_pattern) + +println(cct) ``` -From here we can examine how many operations were generated as +Similarly, for an optimised variant of the same operations using more qubits: + +```@example 2 +using QXZoo + +# Set 10-qubit limit on Grover circuit (aux assisted) +num_qubits = 17 + +qubits_range = 1:Int((num_qubits+1)/2) + 1 +aux_range = Int((num_qubits+1)/2 + 2):num_qubits + +# Set bit-pattern to 11 (0b01011) +bit_pattern = 11 + +# Create empty circuit with qiven qubit count +cct = QXZoo.Circuit.Circ(num_qubits) + +# Run Grover algorithm for given oracle bit-pattern +QXZoo.Grover.run_grover!(cct, collect(qubits_range), bit_pattern, collect(aux_range)) -```@example 1 println(cct) ``` \ No newline at end of file diff --git a/docs/src/algo/ncu.md b/docs/src/algo/ncu.md index ea4ca88..9780c2a 100644 --- a/docs/src/algo/ncu.md +++ b/docs/src/algo/ncu.md @@ -8,9 +8,6 @@ The general principle follows the work of Barenco *et al.*, Phys. Rev. A 52, 345 ```@docs QXZoo.NCU.apply_ncu!(circuit::QXZoo.Circuit.Circ, q_ctrl::Vector, q_aux::Vector, q_tgt, U::QXZoo.GateOps.GateSymbol) -QXZoo.NCU.init_intermed_gates(circ::QXZoo.Circuit.Circ, num_ctrl::Union{Nothing, Int}) -QXZoo.NCU.register_gate(circ::QXZoo.Circuit.Circ, U::QXZoo.GateOps.GateSymbol, gate_f::Function) -QXZoo.NCU.gen_intermed_gates(ctrl_depth::Int, U::QXZoo.GateOps.GateSymbol) QXZoo.NCU.get_intermed_gate(U::QXZoo.GateOps.GateSymbol) ``` @@ -21,18 +18,18 @@ To use the NCU module, we provide example code below to apply an n-controlled Pa ```@example using QXZoo -# Set 5-qubit limit on circuit +# Set 10-qubit limit on circuit num_qubits = 10 # Create Pauli-Z gate-label for application -gate_z = QXZoo.DefaultGates.GateSymbols.z +gate_z = QXZoo.DefaultGates.GateSymbols.c_z # Create empty circuit with qiven qubit count cct = QXZoo.Circuit.Circ(num_qubits) -ctrl = collect(range(0, length=num_qubits-1 ) ) +ctrl = collect(range(1, length=num_qubits ) ) aux = [] -tgt = num_qubits-1 +tgt = num_qubits for i in ctrl QXZoo.Circuit.add_gatecall!(cct, QXZoo.DefaultGates.x(i) ) @@ -56,15 +53,15 @@ num_qubits = 19 cct = QXZoo.Circuit.Circ(num_qubits) -ctrl = collect(range(0, length=convert(Int, (num_qubits+1)/2 ))) -aux = collect(range( convert(Int, (num_qubits+1)/2+1), stop=num_qubits-1)) -tgt = convert(Int, maximum(ctrl)+1 ) +ctrl = collect(1:10) +tgt = 11 +aux = collect(11:19) for i in ctrl cct << QXZoo.DefaultGates.x(i) end -QXZoo.NCU.apply_ncu!(cct, ctrl, aux, tgt, QXZoo.GateOps.GateSymbol(:z)) +QXZoo.NCU.apply_ncu!(cct, ctrl, aux, tgt, DefaultGates.GateSymbols.c_z) println(cct) ``` \ No newline at end of file diff --git a/docs/src/algo/qft.md b/docs/src/algo/qft.md index e69de29..4b43860 100644 --- a/docs/src/algo/qft.md +++ b/docs/src/algo/qft.md @@ -0,0 +1,30 @@ +# Quantum Fourier Transform (QFT) +The `QFT` module enables the creation of circuit for $n$-qubit quantum Fourier transforms, and their inverses. + +## API +### rqc.jl +```@docs +QXZoo.QFT.apply_qft!(cct::QXZoo.Circuit.Circ, qubit_indices::Vector) +QXZoo.QFT.apply_qft!(cct::QXZoo.Circuit.Circ) +QXZoo.QFT.apply_iqft!(cct::QXZoo.Circuit.Circ, qubit_indices::Vector) +QXZoo.QFT.apply_iqft!(cct::QXZoo.Circuit.Circ) +QXZoo.QFT.swap_idx(qubit_indices::Vector) +``` + +## Example +The following demonstrates the application of the QFT to a subset of qubits in the register, and the IQFT to the entire register. + +```@example 1 +using QXZoo + +cct = QXZoo.Circuit.Circ(8) + +QXZoo.QFT.apply_qft!(cct, collect(1:4)) +QXZoo.QFT.apply_iqft!(cct) + +println(cct) + +for i in cct.circ_ops + println(i) +end +``` diff --git a/docs/src/algo/rqc.md b/docs/src/algo/rqc.md new file mode 100644 index 0000000..c0b3aeb --- /dev/null +++ b/docs/src/algo/rqc.md @@ -0,0 +1,32 @@ +# Random Quantum Circuits (RQC) +The `RQC` module aims to generate RQC capable of demonstrating results as those of Villalonga et al (npj Quantum Inf 5, 86 (2019)) and Arute et al. (Nature volume 574, 505–510 (2019)). + +## API +### rqc.jl +```@docs +QXZoo.RQC.RQC_DS(n::Int, m::Int) +QXZoo.RQC.random_gate!(rqc::QXZoo.RQC.RQC_DS, i::Int, j::Int, rng::Random.MersenneTwister) +QXZoo.RQC.patterns(rqc::QXZoo.RQC.RQC_DS) +QXZoo.RQC.create_RQC(rows::Int, cols::Int, depth::Int, seed::Union{Int, Nothing}=nothing; use_iswap::Bool=false, final_Hadamard_layer::Bool=false) +``` + +## Example +To use the Grover module, we provide example code below to search for a state in a 5-qubit quantum register marked by bit-pattern 11 (0b01011). + +```@example 1 +using QXZoo + +# Set 2D qubit grid size and circuit depth +rows = 4 +cols = 5 +depth = 7 + +# Create RQC circuit using built-in generator +cct = QXZoo.RQC.create_RQC(rows, cols, depth) + +println(cct) + +for i in cct.circ_ops + println(i) +end +``` \ No newline at end of file diff --git a/docs/src/gates_circuits/CompositeGates.md b/docs/src/gates_circuits/CompositeGates.md index e69de29..691a8de 100644 --- a/docs/src/gates_circuits/CompositeGates.md +++ b/docs/src/gates_circuits/CompositeGates.md @@ -0,0 +1,18 @@ +# Composite Gates +This module is used for gates that can be composed of more fundamental gate operations. For simplicity, we refer to these as `Composite Gates`, and represent them as their numerical matrix values. + +## CompositeGates.GateSymbols +As in `DefaultGates`, we store symbols for the gates available in CompositeGates, and generator functions for parameteric variants of these gates. This allows us to track the mappings as to be used by the gate type hierarchy and architecture. + +### Two Qubit Gates: static +```@docs +QXZoo.CompositeGates.swap(q_target1::Int, q_target2::Int) +QXZoo.CompositeGates.iswap(q_target1::Int, q_target2::Int) +``` + +### Two Qubit Gates: rotation +```@docs +QXZoo.CompositeGates.xx(q_target1::Int, q_target2::Int, θ::Number) +QXZoo.CompositeGates.yy(q_target1::Int, q_target2::Int, θ::Number) +QXZoo.CompositeGates.zz(q_target1::Int, q_target2::Int, θ::Number) +``` \ No newline at end of file diff --git a/docs/src/gates_circuits/DefaultGates.md b/docs/src/gates_circuits/DefaultGates.md index 2aeea5a..2a49be0 100644 --- a/docs/src/gates_circuits/DefaultGates.md +++ b/docs/src/gates_circuits/DefaultGates.md @@ -1,7 +1,7 @@ # DefaultGates ## DefaultGates.GateSymbols -Here we store symbols for the gates available in DefaultGates, and generator functions for parameteric variants of these gates. This allos us to track the mappings as to be used by the gate type hierarchy and architecture. +Here we store symbols for the gates available in DefaultGates, and generator functions for parameteric variants of these gates. This allows us to track the mappings as to be used by the gate type hierarchy and architecture. ## Commonly used gates diff --git a/docs/src/gates_circuits/GateMap.md b/docs/src/gates_circuits/GateMap.md index 95a102f..22c6eea 100644 --- a/docs/src/gates_circuits/GateMap.md +++ b/docs/src/gates_circuits/GateMap.md @@ -30,6 +30,8 @@ QXZoo.GateMap.x() QXZoo.GateMap.y() QXZoo.GateMap.z() QXZoo.GateMap.h() +QXZoo.GateMap.s() +QXZoo.GateMap.t() QXZoo.GateMap.I() QXZoo.GateMap.r_x(θ::Number) @@ -40,4 +42,9 @@ QXZoo.GateMap.r_phase(θ::Number) QXZoo.GateMap.c_x() QXZoo.GateMap.c_y() QXZoo.GateMap.c_z() + +QXZoo.GateMap.c_r_x(θ::Number) +QXZoo.GateMap.c_r_y(θ::Number) +QXZoo.GateMap.c_r_z(θ::Number) +QXZoo.GateMap.c_r_phase(θ::Number) ``` \ No newline at end of file diff --git a/docs/src/index.md b/docs/src/index.md index 25ef47c..3deef54 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -2,4 +2,4 @@ *QXZoo.jl* is a generator suite for quantum algorithms. Using a high-level approach to quantum circuit design, we create quantum circuits taking note of all intermediate gate-calls between qubits, performing operation caching and optimisation where possible. -Currently, we support a limited set of quantum algorithms (see the `Algorithms` dropdown for details). Details of the circuit generator modules and structs are given in the `Gates` and `Circuits` dropdowns. +Currently, we support a limited set of quantum algorithms (see the `MAnual/Algorithms` dropdown for details). Details of the circuit generator modules and structs are given in the `Gates` and `Circuits` dropdowns. diff --git a/src/algorithms/evolution.jl b/src/algorithms/evolution.jl index dc93da9..a7e37d2 100644 --- a/src/algorithms/evolution.jl +++ b/src/algorithms/evolution.jl @@ -6,7 +6,7 @@ using QXZoo.DefaultGates """ - pauli_string(θ::Number, pauli_gates::Vector{GateCall1}) + apply_pauli_string(θ::Number, pauli_gates::Vector{GateCall1}) Create a circuit representating a given exp Pauli string (eg exp(-i0.5*X*X*Y*Y)) diff --git a/src/algorithms/qft.jl b/src/algorithms/qft.jl index 09f0860..a3e0aad 100644 --- a/src/algorithms/qft.jl +++ b/src/algorithms/qft.jl @@ -5,6 +5,8 @@ using QXZoo.Circuit using QXZoo.DefaultGates using QXZoo.CompositeGates +export apply_qft!, apply_iqft! + """ apply_qft!(cct::Circuit.Circ, qubit_indices::Vector) @@ -17,8 +19,9 @@ function apply_qft!(cct::Circuit.Circ, qubit_indices::Vector) cct << DefaultGates.c_r_phase(qubit_indices[idx], qubit_indices[jdx], 2π/(2^(qubit_indices[jdx]-qubit_indices[idx]+1))) end end - for i = 1:convert(Int, floor(length(qubit_indices)//2)) - cct << CompositeGates.swap(i, length(qubit_indices)-i+1) + lhs, rhs = swap_idx(qubit_indices) + for i in 1:length(lhs) + cct << CompositeGates.swap(lhs[i], rhs[i]) end ; end @@ -36,9 +39,12 @@ apply_qft!(cct::Circuit.Circ) = apply_qft!(cct, collect(1:cct.num_qubits)) Apply the inverse quantum Fourier transform over the given qubit index range. """ function apply_iqft!(cct::Circuit.Circ, qubit_indices::Vector) - for i = convert(Int, floor(length(qubit_indices)//2)):-1:1 - cct << CompositeGates.swap(i, length(qubit_indices)-i+1) + lhs, rhs = swap_idx(qubit_indices) + + for i in 1:length(lhs) + cct << CompositeGates.swap(lhs[i], rhs[i]) end + for idx in length(qubit_indices):-1:1 for jdx in length(qubit_indices):-1:idx+1 cct << DefaultGates.c_r_phase(qubit_indices[idx], qubit_indices[jdx], -2π/(2^(qubit_indices[jdx]-qubit_indices[idx]+1))) @@ -55,4 +61,22 @@ Apply the inverse quantum Fourier transform over the entire qubit register """ apply_iqft!(cct::Circuit.Circ) = apply_iqft!(cct, collect(1:cct.num_qubits)) -end \ No newline at end of file + +""" + swap_idx(qubit_indices::Vector) + +Return the indices of the qubits required to apply the swap operations as part of the transform. +""" +function swap_idx(qubit_indices::Vector) + is_even = (length(qubit_indices) & 1 === 0) + mp = convert(Int, floor(length(qubit_indices)//2)) + lhs = @view qubit_indices[1:mp] + if is_even + rhs = @view qubit_indices[end:-1:mp+1] + else + rhs = @view qubit_indices[end:-1:mp+2] + end + return lhs, rhs +end + +end diff --git a/src/algorithms/rqc.jl b/src/algorithms/rqc.jl index 66e2322..bfc83d1 100644 --- a/src/algorithms/rqc.jl +++ b/src/algorithms/rqc.jl @@ -5,6 +5,8 @@ using QXZoo.DefaultGates using QXZoo.GateOps using Random +export create_RQC + # *************************************************************************** # # RQC functions # *************************************************************************** # @@ -50,7 +52,11 @@ struct RQC_DS single_qubit_gates::Dict{Int, AGateSymbol} end +""" + RQC creation structure. +Holds the circuit, and generator datatypes +""" function RQC_DS(n::Int, m::Int) RQC_DS(n, m, Circuit.Circ(n*m), -ones(Int, n, m), Dict(1=>DefaultGates.GateSymbols.t, 2=>sqrt(DefaultGates.GateSymbols.x), 3=>sqrt(DefaultGates.GateSymbols.y)) ) diff --git a/test/test_qft.jl b/test/test_qft.jl index c4e3331..194355a 100644 --- a/test/test_qft.jl +++ b/test/test_qft.jl @@ -47,8 +47,8 @@ QXZoo.QFT.apply_iqft!(cct1, collect(1:num_qubits)) - cct2 << CompositeGates.swap(2,4) cct2 << CompositeGates.swap(1,5) + cct2 << CompositeGates.swap(2,4) cct2 << DefaultGates.h(5) cct2 << DefaultGates.c_r_phase(4, 5, -π/2)