diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index c31abce..080630b 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -47,6 +47,8 @@ jobs: Pkg.Registry.update() - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 + env: # ubuntu-latest has too little memory + BRAKET_SIM_LARGE_TESTS: ${{ matrix.os != 'ubuntu-latest'}} - uses: julia-actions/julia-processcoverage@v1 - uses: codecov/codecov-action@v3 with: diff --git a/.gitignore b/.gitignore index b8f01bd..9a399ca 100644 --- a/.gitignore +++ b/.gitignore @@ -3,9 +3,10 @@ *.jl.mem lcov.info *.profraw +*.json /Manifest.toml /docs/Manifest.toml /docs/build/ /.coverage .DS_Store -*/.DS_Store \ No newline at end of file +*/.DS_Store diff --git a/benchmark/gate_kernels.jl b/benchmark/gate_kernels.jl new file mode 100644 index 0000000..1cda723 --- /dev/null +++ b/benchmark/gate_kernels.jl @@ -0,0 +1,328 @@ +using BraketSimulator, PythonCall, BenchmarkTools + +max_qubits(::Val{:noise}) = 5 +max_qubits(::Val{:pure}) = 10 + +gate_operations = pyimport("braket.default_simulator.gate_operations") +noise_operations = pyimport("braket.default_simulator.noise_operations") +local_sv = pyimport("braket.default_simulator.state_vector_simulation") +local_dm = pyimport("braket.default_simulator.density_matrix_simulation") +qml = pyimport("pennylane") +np = pyimport("numpy") +pnp = pyimport("pennylane.numpy") +suite = BenchmarkGroup() +suite["gates"] = BenchmarkGroup() +suite["noise"] = BenchmarkGroup() +for gate in [ + "H", + "X", + "Y", + "Z", + "V", + "Vi", + "T", + "Ti", + "S", + "Si", + "Rx", + "Ry", + "Rz", + "GPi", + "GPi2", + "MS", + "PhaseShift", + "CNot", + "CY", + "CZ", + "CV", + "XX", + "XY", + "YY", + "ZZ", + "ECR", + "Swap", + "ISwap", + "PSwap", + "CCNot", + "CSwap", + "CPhaseShift", + "CPhaseShift00", + "CPhaseShift01", + "CPhaseShift10", +] + suite["gates"][gate] = BenchmarkGroup() +end +for noise in [ + "BitFlip", + "PhaseFlip", + "Depolarizing", + "PauliChannel", + "AmplitudeDamping", + "GeneralizedAmplitudeDamping", + "PhaseDamping", + "TwoQubitDepolarizing", + "TwoQubitPauliChannel", + "TwoQubitDephasing", + "Kraus", +] + suite["noise"][noise] = BenchmarkGroup() +end +for n_qubits = 4:2:max_qubits(Val(:pure)) + n_amps = 2^n_qubits + angle = π / 3.5 + angle2 = π / 5.2 + angle3 = π / 0.6 + for q in 0:0#n_qubits-1 + for (gate_str, gate, py_gate) in zip( + ["H", "X", "Y", "Z", "V", "Vi", "T", "Ti", "S", "Si"], + [BraketSimulator.H(), + BraketSimulator.X(), + BraketSimulator.Y(), + BraketSimulator.Z(), + BraketSimulator.V(), + BraketSimulator.Vi(), + BraketSimulator.T(), + BraketSimulator.Ti(), + BraketSimulator.S(), + BraketSimulator.Si(), + ], + [ + [gate_operations.Hadamard([q])], + [gate_operations.PauliX([q])], + [gate_operations.PauliY([q])], + [gate_operations.PauliZ([q])], + [gate_operations.V([q])], + [gate_operations.Vi([q])], + [gate_operations.T([q])], + [gate_operations.Ti([q])], + [gate_operations.S([q])], + [gate_operations.Si([q])], + ], + ) + suite["gates"][gate_str][(string(n_qubits), string(q), "Julia")] = + @benchmarkable BraketSimulator.apply_gate!($gate, sv, $q) setup = + (sv = zeros(ComplexF64, $n_amps)) + suite["gates"][gate_str][(string(n_qubits), string(q), "BraketSV")] = + @benchmarkable py_sv.evolve($py_gate) setup = + (py_sv = local_sv.StateVectorSimulation($n_qubits, 0, 1)) + end + for (gate_str, gate, py_gate) in zip( + ["Rx", "Ry", "Rz", "PhaseShift", "GPi", "GPi2"], + [BraketSimulator.Rx(angle), + BraketSimulator.Ry(angle), + BraketSimulator.Rz(angle), + BraketSimulator.PhaseShift(angle), + BraketSimulator.GPi(angle), + BraketSimulator.GPi2(angle), + ], + [ + gate_operations.RotX([q], angle), + gate_operations.RotY([q], angle), + gate_operations.RotZ([q], angle), + gate_operations.PhaseShift([q], angle), + gate_operations.GPi([q], angle), + gate_operations.GPi2([q], angle), + ], + ) + suite["gates"][gate_str][(string(n_qubits), string(q), "Julia")] = + @benchmarkable BraketSimulator.apply_gate!($gate, sv, $q) setup = + (sv = zeros(ComplexF64, $n_amps)) + suite["gates"][gate_str][(string(n_qubits), string(q), "BraketSV")] = + @benchmarkable py_sv.evolve([$py_gate]) setup = + (py_sv = local_sv.StateVectorSimulation($n_qubits, 0, 1)) + end + # do just one pair rather than setdiff(0:n_qubits-1, q) + # to avoid redundancies (benchmark results shouldn't depend + # on input qubits) + for q2 in [mod(q+1, n_qubits)] + for (gate_str, gate, py_gate) in zip( + [ + "XX", + "XY", + "YY", + "ZZ", + "CPhaseShift", + "CPhaseShift00", + "CPhaseShift10", + "CPhaseShift01", + "PSwap", + "MS", + ], + [ + BraketSimulator.XX(angle), + BraketSimulator.XY(angle), + BraketSimulator.YY(angle), + BraketSimulator.ZZ(angle), + BraketSimulator.CPhaseShift(angle), + BraketSimulator.CPhaseShift00(angle), + BraketSimulator.CPhaseShift10(angle), + BraketSimulator.CPhaseShift01(angle), + BraketSimulator.PSwap(angle), + BraketSimulator.MS(angle, angle2, angle3), + ], + [ + gate_operations.XX([q, q2], angle), + gate_operations.XY([q, q2], angle), + gate_operations.YY([q, q2], angle), + gate_operations.ZZ([q, q2], angle), + gate_operations.CPhaseShift([q, q2], angle), + gate_operations.CPhaseShift00([q, q2], angle), + gate_operations.CPhaseShift10([q, q2], angle), + gate_operations.CPhaseShift01([q, q2], angle), + gate_operations.PSwap([q, q2], angle), + gate_operations.MS([q, q2], angle, angle2, angle3), + ], + ) + suite["gates"][gate_str][(string(n_qubits), string(q), string(q2), "Julia")] = + @benchmarkable BraketSimulator.apply_gate!($gate, sv, $q, $q2) setup = + (sv = zeros(ComplexF64, $n_amps)) + suite["gates"][gate_str][(string(n_qubits), string(q), string(q2), "BraketSV")] = + @benchmarkable py_sv.evolve([$py_gate]) setup = + (py_sv = local_sv.StateVectorSimulation($n_qubits, 0, 1)) + end + for (gate_str, gate, py_gate) in zip( + ["CNot", "CY", "CZ", "CV", "Swap", "ISwap", "ECR"], + [BraketSimulator.CNot(), + BraketSimulator.CY(), + BraketSimulator.CZ(), + BraketSimulator.CV(), + BraketSimulator.Swap(), + BraketSimulator.ISwap(), + BraketSimulator.ECR(), + ], + [ + gate_operations.CX([q, q2]), + gate_operations.CY([q, q2]), + gate_operations.CZ([q, q2]), + gate_operations.CV([q, q2]), + gate_operations.Swap([q, q2]), + gate_operations.ISwap([q, q2]), + gate_operations.ECR([q, q2]), + ], + ) + suite["gates"][gate_str][(string(n_qubits), string(q), string(q2), "Julia")] = + @benchmarkable BraketSimulator.apply_gate!($gate, sv, $q, $q2) setup = + (sv = zeros(ComplexF64, $n_amps)) + suite["gates"][gate_str][(string(n_qubits), string(q), string(q2), "BraketSV")] = + @benchmarkable py_sv.evolve([$py_gate]) setup = + (py_sv = local_sv.StateVectorSimulation($n_qubits, 0, 1)) + end + # do just one pair rather than setdiff(0:n_qubits - 1, q, q2) + # to avoid redundancies (benchmark results shouldn't depend + # on input qubits) + for q3 in [mod(q+2, n_qubits)] + for (gate_str, gate, py_gate) in zip( + ["CCNot", "CSwap"], + [BraketSimulator.CCNot(), BraketSimulator.CSwap()], + [gate_operations.CCNot([q, q2, 2]), gate_operations.CSwap([q, q2, 2])], + ) + suite["gates"][gate_str][( + string(n_qubits), + string(q), + string(q2), + string(q3), + "Julia", + )] = @benchmarkable BraketSimulator.apply_gate!($gate, sv, $q, $q2, $q3) setup = + (sv = zeros(ComplexF64, $n_amps)) + suite["gates"][gate_str][( + string(n_qubits), + string(q), + string(q2), + string(q3), + "BraketSV", + )] = @benchmarkable py_sv.evolve([$py_gate]) setup = + (py_sv = local_sv.StateVectorSimulation($n_qubits, 0, 1)) + end + end + end + end +end + +for n_qubits = 2:2:max_qubits(Val(:noise)) + n_amps = 2^n_qubits + prob = 0.1 + gamma = 0.2 + # do just qubit rather than 0:n_qubits - 1 + # to avoid redundancies + for q in 0:0 + for (noise_str, noise, py_noise) in zip( + ["BitFlip", "PhaseFlip", "Depolarizing", "AmplitudeDamping", "PhaseDamping"], + [ + BraketSimulator.BitFlip(prob), + BraketSimulator.PhaseFlip(prob), + BraketSimulator.Depolarizing(prob), + BraketSimulator.AmplitudeDamping(prob), + BraketSimulator.PhaseDamping(prob), + ], + [ + noise_operations.BitFlip([q], prob), + noise_operations.PhaseFlip([q], prob), + noise_operations.Depolarizing([q], prob), + noise_operations.AmplitudeDamping([q], prob), + noise_operations.PhaseDamping([q], prob), + ], + ) + suite["noise"][noise_str][(string(n_qubits), string(q), "Julia")] = + @benchmarkable BraketSimulator.apply_noise!($noise, dm, $q) setup = + (dm = zeros(ComplexF64, $n_amps, $n_amps)) + suite["noise"][noise_str][(string(n_qubits), string(q), "BraketSV")] = + @benchmarkable py_sv.evolve([$py_noise]) setup = + (py_sv = local_dm.DensityMatrixSimulation($n_qubits, 0)) + end + # do just one pair rather than setdiff(0:n_qubits - 1, q, q2) + # to avoid redundancies (benchmark results shouldn't depend + # on input qubits) + for q2 in [mod(q+1, n_qubits)] + for (noise_str, noise, py_noise) in zip( + ["TwoQubitDepolarizing", "TwoQubitDephasing"], + [BraketSimulator.TwoQubitDepolarizing(prob), + BraketSimulator.TwoQubitDephasing(prob), + ], + [ + noise_operations.TwoQubitDepolarizing([q, q2], prob), + noise_operations.TwoQubitDephasing([q, q2], prob), + ], + ) + suite["noise"][noise_str][(string(n_qubits), string(q), string(q2), "Julia")] = + @benchmarkable BraketSimulator.apply_noise!($noise, dm, $q, $q2) setup = + (dm = zeros(ComplexF64, $n_amps, $n_amps)) + suite["noise"][noise_str][(string(n_qubits), string(q), string(q2), "BraketSV")] = + @benchmarkable py_sv.evolve([$py_noise]) setup = + (py_sv = local_dm.DensityMatrixSimulation($n_qubits, 0)) + end + end + suite["noise"]["GeneralizedAmplitudeDamping"][(string(n_qubits), string(q), "Julia")] = + @benchmarkable BraketSimulator.apply_noise!( + BraketSimulator.GeneralizedAmplitudeDamping($prob, $gamma), + dm, + $q, + ) setup = (dm = zeros(ComplexF64, $n_amps, $n_amps)) + suite["noise"]["GeneralizedAmplitudeDamping"][(string(n_qubits), string(q), "BraketSV")] = + @benchmarkable py_sv.evolve([ + noise_operations.GeneralizedAmplitudeDamping([$q], $prob, $gamma), + ]) setup = (py_sv = local_dm.DensityMatrixSimulation($n_qubits, 0)) + suite["noise"]["PauliChannel"][(string(n_qubits), string(q), "Julia")] = + @benchmarkable BraketSimulator.apply_noise!( + PauliChannel($prob, $gamma, 0.0), + dm, + $q, + ) setup = (dm = zeros(ComplexF64, $n_amps, $n_amps)) + suite["noise"]["PauliChannel"][(string(n_qubits), string(q), "BraketSV")] = + @benchmarkable py_sv.evolve([ + noise_operations.PauliChannel( + targets = [$q], + probX = $prob, + probY = $gamma, + probZ = 0.0, + ), + ]) setup = (py_sv = local_dm.DensityMatrixSimulation($n_qubits, 0)) + end +end +# this is expensive! only do it if we're sure we need to regen parameters +if !isfile("small_gate_kernel_params.json") + tune!(suite; verbose=true) + BenchmarkTools.save("small_gate_kernel_params.json", params(suite)) +end +loadparams!(suite, BenchmarkTools.load("small_gate_kernel_params.json")[1], :evals, :samples); +results = run(suite; verbose = true) +BenchmarkTools.save("small_gate_kernel_results.json", results) diff --git a/benchmark/ghz.jl b/benchmark/ghz.jl new file mode 100644 index 0000000..0910261 --- /dev/null +++ b/benchmark/ghz.jl @@ -0,0 +1,89 @@ +using BraketSimulator, PythonCall, BenchmarkTools + +max_qubits(::Val{:noise}) = 5 +max_qubits(::Val{:pure}) = 10 + +gate_operations = pyimport("braket.default_simulator.gate_operations") +noise_operations = pyimport("braket.default_simulator.noise_operations") +local_sv = pyimport("braket.default_simulator.state_vector_simulation") +local_dm = pyimport("braket.default_simulator.density_matrix_simulation") +qml = pyimport("pennylane") +np = pyimport("numpy") +pnp = pyimport("pennylane.numpy") +qiskit = pyimport("qiskit") +qiskit_aer = pyimport("qiskit_aer.primitives") + +function ghz_circuit(qubit_count::Int, shots::Int, noise::Bool) + ghz_circ = BraketSimulator.Circuit() + push!(ghz_circ.instructions, BraketSimulator.Instruction(BraketSimulator.H(), 0)) + noise && push!(ghz_circ.instructions, BraketSimulator.Instruction(BraketSimulator.BitFlip(0.1), 0)) + for target_qubit = 1:qubit_count-1 + push!(ghz_circ.instructions, BraketSimulator.Instruction(BraketSimulator.CNot(), BraketSimulator.QubitSet(0, target_qubit))) + if noise + push!(ghz_circ.instructions, BraketSimulator.Instruction(BraketSimulator.PhaseFlip(0.1), 0)) + push!(ghz_circ.instructions, BraketSimulator.Instruction(BraketSimulator.PhaseFlip(0.1), 1)) + end + end + shots == 0 && noise && (push!(ghz_circ.result_types, BraketSimulator.DensityMatrix())) + shots == 0 && !noise && (push!(ghz_circ.result_types, BraketSimulator.StateVector())) + return ghz_circ +end + +function braket_sv_ghz(qubit_count::Int) + ghz_ops = [] + push!(ghz_ops, gate_operations.Hadamard([0])) + for target_qubit in 1:qubit_count-1 + push!(ghz_ops, gate_operations.CNot([0, target_qubit])) + end + return ghz_ops +end + +function pl_ghz(qubit_count::Int) + ops = [qml.Hadamard(0)] + for target_qubit in 1:qubit_count -1 + push!(ops, qml.CNOT([0, target_qubit])) + end + return qml.tape.QuantumTape(ops, []) +end + +function qiskit_ghz(qubit_count::Int) + qc = qiskit.QuantumCircuit(qubit_count) + qc.h(0) + for target_qubit in 1:qubit_count -1 + qc.cx(0, target_qubit) + end + qc.measure_all() + return qc +end + +for mode in (:noise, :pure), n_qubits in 4:max_qubits(Val(mode)) + g = BenchmarkGroup() + for shots in (0, 100) + pl_shots = shots == 0 ? nothing : shots + if mode == :pure + g["Julia"] = @benchmarkable simulate(sim, circ, $shots) setup = (sim=StateVectorSimulator($n_qubits, $shots); circ = BraketSimulator.Program(ghz_circuit($n_qubits, $shots, false))) + elseif mode == :noise + g["Julia"] = @benchmarkable simulate(sim, circ, $shots) setup = (sim=DensityMatrixSimulator($n_qubits, $shots); circ = BraketSimulator.Program(ghz_circuit($n_qubits, $shots, true))) + end + if shots > 0 && mode == :pure && n_qubits <= 30 + # disabled for now as the existing Python simulator is very slow above 20 qubits + #=if n_qubits <= 27 + g["BraketSV"] = @benchmarkable sim.execute(circ) setup = (sim=qml.device("braket.local.qubit", backend="braket_sv", wires=$n_qubits, shots=$shots); circ = pl_ghz($n_qubits)) + end=# + g["Lightning.Qubit"] = @benchmarkable sim.execute(circ) setup = (sim=qml.device("lightning.qubit", wires=$n_qubits, shots=$pl_shots); circ = pl_ghz($n_qubits)) + g["Qiskit.Aer"] = @benchmarkable sim.run(circ, shots=$shots).result() setup = (sim=qiskit_aer.SamplerV2(); circ = [qiskit_ghz($n_qubits)]) + end + end + root_fn = "ghz_" * string(n_qubits) * "_" * string(mode) * "_" + param_fn = root_fn * "params.json" + result_fn = root_fn * "results.json" + # this is expensive! only do it if we're sure we need to regen parameters + if !isfile(param_fn) + tune!(g; verbose=true) + BenchmarkTools.save(param_fn, params(g)) + end + loadparams!(g, BenchmarkTools.load(param_fn)[1], :evals, :samples); + results = run(g; verbose = true) + BenchmarkTools.save(result_fn, results) +end + diff --git a/benchmark/qft.jl b/benchmark/qft.jl new file mode 100644 index 0000000..e7974c2 --- /dev/null +++ b/benchmark/qft.jl @@ -0,0 +1,98 @@ +using BraketSimulator, PythonCall, BenchmarkTools + +max_qubits(::Val{:noise}) = 10 +max_qubits(::Val{:pure}) = 20 + +gate_operations = pyimport("braket.default_simulator.gate_operations") +noise_operations = pyimport("braket.default_simulator.noise_operations") +local_sv = pyimport("braket.default_simulator.state_vector_simulation") +local_dm = pyimport("braket.default_simulator.density_matrix_simulation") +qml = pyimport("pennylane") +np = pyimport("numpy") +pnp = pyimport("pennylane.numpy") +qiskit = pyimport("qiskit") +qiskit_aer = pyimport("qiskit_aer.primitives") + +function qft_circuit(qubit_count::Int, noise::Bool) + qft_circ = BraketSimulator.Circuit() + for target_qubit = 0:qubit_count-1 + angle = π / 2 + push!(qft_circ.instructions, BraketSimulator.Instruction(BraketSimulator.H(), target_qubit)) + noise && push!(qft_circ.instructions, BraketSimulator.Instruction(BraketSimulator.BitFlip(0.1), target_qubit)) + for control_qubit = target_qubit+1:qubit_count-1 + push!(qft_circ.instructions, BraketSimulator.Instruction(BraketSimulator.CPhaseShift(angle), [control_qubit, target_qubit])) + noise && push!(qft_circ.instructions, BraketSimulator.Instruction(BraketSimulator.PhaseFlip(0.1), target_qubit)) + noise && push!(qft_circ.instructions, BraketSimulator.Instruction(BraketSimulator.PhaseFlip(0.1), control_qubit)) + angle /= 2 + end + end + push!(qft_circ.result_types, BraketSimulator.Probability()) + return qft_circ +end + +function braket_sv_qft(qubit_count::Int, noise::Bool) + qft_ops = [] + for target_qubit in 0:qubit_count-1 + angle = π / 2 + push!(qft_ops, gate_operations.Hadamard([target_qubit])) + noise && push!(qft_ops, noise_operations.BitFlip(0.1, [target_qubit])) + for control_qubit in target_qubit + 1:qubit_count-1 + push!(qft_ops, gate_operations.CPhaseShift([control_qubit, target_qubit], angle)) + noise && push!(qft_ops, noise_operations.PhaseFlip(0.1, [control_qubit])) + noise && push!(qft_ops, noise_operations.PhaseFlip(0.1, [target_qubit])) + angle /= 2 + end + end + return qft_ops +end + +function pl_qft(qubit_count::Int) + qft_ops = [qml.PauliX(0)] + push!(qft_ops, qml.QFT(wires=collect(0:qubit_count-1))) + tape = qml.tape.QuantumTape(qft_ops, [qml.expval(qml.operation.Tensor(qml.PauliZ(0), qml.PauliX(1)))]) + return tape.expand(depth=5) +end + +function qiskit_qft(qubit_count::Int) + qc = qiskit.QuantumCircuit(qubit_count) + for target_qubit in 0:qubit_count -1 + angle = π / 2 + qc.h(target_qubit) + for control_qubit in target_qubit + 1:qubit_count-1 + qc.cp(angle, control_qubit, target_qubit) + angle /= 2 + end + end + qc.measure_all() + return qc +end + + +for mode in (:noise, :pure), n_qubits in 4:max_qubits(Val(mode)) + g = BenchmarkGroup() + for shots in (0, 100) + if mode == :pure + g["Julia-$shots"] = @benchmarkable simulate(sim, circ, $shots) setup = (sim=StateVectorSimulator($n_qubits, $shots); circ = BraketSimulator.Program(qft_circuit($n_qubits, false))) + elseif mode == :noise + g["Julia-$shots"] = @benchmarkable simulate(sim, circ, $shots) setup = (sim=DensityMatrixSimulator($n_qubits, $shots); circ = BraketSimulator.Program(qft_circuit($n_qubits, true))) + end + if shots > 0 && n_qubits <= 30 && mode == :pure + if n_qubits <= 27 + g["BraketSV-$shots"] = @benchmarkable sim.execute(circ) setup = (sim=qml.device("braket.local.qubit", backend="braket_sv", wires=$n_qubits, shots=$shots); circ = pl_qft($n_qubits)) + end + g["Lightning.Qubit-$shots"] = @benchmarkable sim.execute(circ) setup = (sim=qml.device("lightning.qubit", wires=$n_qubits, shots=$shots); circ = pl_qft($n_qubits)) + g["Qiskit.Aer-$shots"] = @benchmarkable sim.run(circ, shots=$shots).result() setup = (sim=qiskit_aer.SamplerV2(); circ = [qiskit_qft($n_qubits)]) + end + end + root_fn = "qft_" * string(n_qubits) * "_" * string(mode) * "_" + param_fn = root_fn * "params.json" + result_fn = root_fn * "results.json" + # this is expensive! only do it if we're sure we need to regen parameters + if !isfile(param_fn) + tune!(g; verbose=true) + BenchmarkTools.save(param_fn, params(g)) + end + loadparams!(g, BenchmarkTools.load(param_fn)[1], :evals, :samples); + results = run(g; verbose = true) + BenchmarkTools.save(result_fn, results) +end diff --git a/coverage.jl b/coverage.jl index b2f694d..6a7259f 100644 --- a/coverage.jl +++ b/coverage.jl @@ -14,8 +14,7 @@ end for fi in readdir("src") if endswith(fi, ".jl") - println("Coverage for file $fi") - @show get_summary(process_file(joinpath("src", fi))) + println("Coverage for file $fi: $(get_summary(process_file(joinpath("src", fi))))") end end diff --git a/docs/make.jl b/docs/make.jl index 0153d30..13f0aaa 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -20,6 +20,7 @@ makedocs(; "Noises" => "noises.md", "Observables" => "observables.md", "Results" => "results.md", + "Internals" => "internals.md", ], ) diff --git a/docs/src/index.md b/docs/src/index.md index 1c58d37..3ec7e4c 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -48,7 +48,7 @@ julia> push!(c.result_types, Amplitude([repeat("0", n_qubits), repeat("1", n_qub julia> sim = StateVectorSimulator(n_qubits, 0); # use the state vector simulator (without noise) -julia> res = simulate(sim, Program(c), n_qubits, 0); +julia> res = simulate(sim, Program(c), 0); julia> res.resultTypes[1].value Dict{String, ComplexF64} with 2 entries: diff --git a/docs/src/internals.md b/docs/src/internals.md new file mode 100644 index 0000000..218fc33 --- /dev/null +++ b/docs/src/internals.md @@ -0,0 +1,14 @@ +```@meta +CurrentModule = BraketSimulator +``` + +# Internals + +These functions are for internal use for preparing IRs for simulation and validating properties to make sure all instructions and results are supported. + +```@docs +BraketSimulator._combine_operations +BraketSimulator._prepare_program +BraketSimulator._get_measured_qubits +BraketSimulator._compute_results +``` diff --git a/ext/BraketSimulatorBraketExt/BraketSimulatorBraketExt.jl b/ext/BraketSimulatorBraketExt/BraketSimulatorBraketExt.jl index 1c15207..e7142cf 100644 --- a/ext/BraketSimulatorBraketExt/BraketSimulatorBraketExt.jl +++ b/ext/BraketSimulatorBraketExt/BraketSimulatorBraketExt.jl @@ -9,7 +9,7 @@ end Braket.name(d::BraketSimulator.AbstractSimulator) = BraketSimulator.name(d) Braket.properties(d::BraketSimulator.AbstractSimulator) = BraketSimulator.properties(d) Braket.simulate(d::BraketSimulator.AbstractSimulator, program::Braket.OpenQasmProgram, args...; kwargs...) = convert(Braket.GateModelTaskResult, BraketSimulator.simulate(d, convert(BraketSimulator.OpenQasmProgram, program), args...; kwargs...)) -Braket.simulate(d::BraketSimulator.AbstractSimulator, program::Braket.Program, args...; kwargs...) = convert(Braket.GateModelTaskResult, BraketSimulator.simulate(d, convert(BraketSimulator.Program, program), args...; kwargs...)) +Braket.simulate(d::BraketSimulator.AbstractSimulator, program::Braket.Program, qubit_count::Int, shots::Int; kwargs...) = convert(Braket.GateModelTaskResult, BraketSimulator.simulate(d, convert(BraketSimulator.Program, program), shots; kwargs...)) Base.convert(::Type{Braket.TaskMetadata}, tm::BraketSimulator.TaskMetadata) = Braket.TaskMetadata(Braket.braketSchemaHeader("braket.task_result.task_metadata", "1"), tm.id, tm.shots, tm.deviceId, tm.deviceParameters, tm.createdAt, tm.endedAt, tm.status, tm.failureReason) Base.convert(::Type{Braket.AdditionalMetadata}, am::BraketSimulator.AdditionalMetadata) = Braket.AdditionalMetadata(convert(Braket.AbstractProgram, am.action), nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing) diff --git a/ext/BraketSimulatorPythonExt/BraketSimulatorPythonExt.jl b/ext/BraketSimulatorPythonExt/BraketSimulatorPythonExt.jl index 089d905..f5c29dc 100644 --- a/ext/BraketSimulatorPythonExt/BraketSimulatorPythonExt.jl +++ b/ext/BraketSimulatorPythonExt/BraketSimulatorPythonExt.jl @@ -15,7 +15,9 @@ function BraketSimulator.simulate(simulator, task_spec::String, inputs::Dict{Str return json end function BraketSimulator.simulate(simulator, task_specs::Vector{String}, inputs::Vector{Dict{String, Any}}, shots::Int; kwargs...) - jl_specs = [BraketSimulator.OpenQasmProgram(BraketSimulator.braketSchemaHeader("braket.ir.openqasm.program", "1"), task_spec, input) for (task_spec, input) in zip(task_specs, inputs)] + jl_specs = map(zip(task_specs, inputs)) do (task_spec, input) + BraketSimulator.OpenQasmProgram(BraketSimulator.braketSchemaHeader("braket.ir.openqasm.program", "1"), task_spec, input) + end jl_results = simulate(simulator, jl_specs, shots; kwargs...) jsons = [JSON3.write(r) for r in jl_results] return jsons diff --git a/src/BraketSimulator.jl b/src/BraketSimulator.jl index d2cdd43..5238844 100644 --- a/src/BraketSimulator.jl +++ b/src/BraketSimulator.jl @@ -53,7 +53,7 @@ function complex_matrix_to_ir(m::Matrix{T}) where {T<:Complex} end return mat end - +complex_matrix_to_ir(m) = m include("raw_schema.jl") @@ -74,7 +74,6 @@ include("noise_kernels.jl") include("Quasar.jl") using .Quasar -const OBS_LIST = (Observables.X(), Observables.Y(), Observables.Z()) const CHUNK_SIZE = 2^10 function _index_to_endian_bits(ix::Int, qubit_count::Int) @@ -86,19 +85,13 @@ function _index_to_endian_bits(ix::Int, qubit_count::Int) return bits end -function _formatted_measurements(simulator::D, measured_qubits::Vector{Int}=collect(0:qubit_count(simulator)-1)) where {D<:AbstractSimulator} +function _formatted_measurements(simulator::D, measured_qubits::Vector{Int}) where {D<:AbstractSimulator} sim_samples = samples(simulator) n_qubits = qubit_count(simulator) - formatted = [_index_to_endian_bits(sample, n_qubits)[measured_qubits .+ 1] for sample in sim_samples] - return formatted + return [_index_to_endian_bits(sample, n_qubits)[measured_qubits .+ 1] for sample in sim_samples] end -function _bundle_results( - results::Vector{ResultTypeValue}, - circuit_ir::Program, - simulator::D, - measured_qubits::Vector{Int} = collect(0:qubit_count(simulator)-1) -) where {D<:AbstractSimulator} +function _build_metadata(simulator, ir) task_mtd = TaskMetadata( braketSchemaHeader("braket.task_result.task_metadata", "1"), string(uuid4()), @@ -111,7 +104,7 @@ function _bundle_results( nothing, ) addl_mtd = AdditionalMetadata( - circuit_ir, + ir, nothing, nothing, nothing, @@ -120,55 +113,28 @@ function _bundle_results( nothing, nothing, ) - formatted_samples = simulator.shots > 0 ? _formatted_measurements(simulator, measured_qubits) : nothing - return GateModelTaskResult( - braketSchemaHeader("braket.task_result.gate_model_task_result", "1"), - formatted_samples, - nothing, - results, - measured_qubits, - task_mtd, - addl_mtd, - ) + return task_mtd, addl_mtd end function _bundle_results( results::Vector{ResultTypeValue}, - circuit_ir::OpenQasmProgram, + circuit_ir, simulator::D, measured_qubits = Set{Int}(0:qubit_count(simulator)-1) ) where {D<:AbstractSimulator} - task_mtd = TaskMetadata( - braketSchemaHeader("braket.task_result.task_metadata", "1"), - string(uuid4()), - simulator.shots, - device_id(simulator), - nothing, - nothing, - nothing, - nothing, - nothing, - ) - addl_mtd = AdditionalMetadata( - circuit_ir, - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - ) sorted_qubits = sort(collect(measured_qubits)) - formatted_samples = simulator.shots > 0 ? _formatted_measurements(simulator, sorted_qubits) : nothing + formatted_samples = if simulator.shots > 0 + _formatted_measurements(simulator, sorted_qubits) + else + nothing + end return GateModelTaskResult( braketSchemaHeader("braket.task_result.gate_model_task_result", "1"), formatted_samples, nothing, results, sorted_qubits, - task_mtd, - addl_mtd, + _build_metadata(simulator, circuit_ir)... ) end @@ -178,11 +144,10 @@ function _generate_results( simulator::D, ) where {D<:AbstractSimulator} result_values = map(result_type -> calculate(result_type, simulator), result_types) - result_values = - [val isa Matrix ? complex_matrix_to_ir(val) : val for val in result_values] final_results = Vector{ResultTypeValue}(undef, length(result_values)) for r_ix in 1:length(final_results) - final_results[r_ix] = ResultTypeValue(results[r_ix], result_values[r_ix]) + final_results[r_ix] = ResultTypeValue(results[r_ix], + complex_matrix_to_ir(result_values[r_ix])) end return final_results end @@ -194,7 +159,7 @@ _translate_result_type(r::IR.StateVector, qc::Int) = StateVector() # stability reasons. Here we take a `nothing` value for `targets` and translate it # to apply to all qubits. _translate_result_type(r::IR.DensityMatrix, qc::Int) = isnothing(r.targets) ? DensityMatrix(collect(0:qc-1)) : DensityMatrix(r.targets) -_translate_result_type(r::IR.Probability, qc::Int) = isnothing(r.targets) ? Probability(collect(0:qc-1)) : Probability(r.targets) +_translate_result_type(r::IR.Probability, qc::Int) = isnothing(r.targets) ? Probability(collect(0:qc-1)) : Probability(r.targets) for (RT, IRT) in ((:Expectation, :(IR.Expectation)), (:Variance, :(IR.Variance)), (:Sample, :(IR.Sample))) @eval begin function _translate_result_type(r::$IRT, qc::Int) @@ -206,46 +171,112 @@ for (RT, IRT) in ((:Expectation, :(IR.Expectation)), (:Variance, :(IR.Variance)) end _translate_result_types(results::Vector{AbstractProgramResult}, qubit_count::Int) = map(result->_translate_result_type(result, qubit_count), results) -function _compute_exact_results(d::AbstractSimulator, program::Program, qc::Int) - result_types = _translate_result_types(program.results, qc) - _validate_result_types_qubits_exist(result_types, qc) +function _compute_exact_results(d::AbstractSimulator, program::Program, qubit_count::Int) + result_types = _translate_result_types(program.results, qubit_count) + _validate_result_types_qubits_exist(result_types, qubit_count) return _generate_results(program.results, result_types, d) end -function _verify_openqasm_shots_observables(circuit::Circuit) - observable_map = Dict() - function _check_observable(observable_map, observable, qubits) - for qubit in qubits, (target, previously_measured) in observable_map - if qubit ∈ target - # must ensure that target is the same - issetequal(target, qubits) || throw(ValidationError("Qubit part of incompatible results targets", "ValueError")) - # must ensure observable is the same - typeof(previously_measured) != typeof(observable) && throw(ValidationError("Conflicting result types applied to a single qubit", "ValueError")) - # including matrix value for Hermitians - observable isa Observables.HermitianObservable && !isapprox(previously_measured.matrix, observable.matrix) && throw(ValidationError("Conflicting result types applied to a single qubit", "ValueError")) - end - end - observable_map[qubits] = observable +""" + _get_measured_qubits(program::Program, qubit_count::Int) -> Vector{Int} + +Get the qubits measured by the program. If [`Measure`](@ref) +instructions are present in the program's instruction list, +their targets are used to form the list of measured qubits. +If not, all qubits from 0 to `qubit_count-1` are measured. +""" +function _get_measured_qubits(program::Program, qubit_count::Int) + measure_inds = findall(ix->ix.operator isa Measure, program.instructions) + isempty(measure_inds) && return collect(0:qubit_count-1) + measure_ixs = program.instructions[measure_inds] + measure_targets = (convert(Vector{Int}, measure.target) for measure in measure_ixs) + measured_qubits = unique(reduce(vcat, measure_targets, init=Int[])) + return measured_qubits +end +""" + _prepare_program(circuit_ir::OpenQasmProgram, inputs::Dict{String, <:Any}, shots::Int) -> (Program, Int) + +Parse the OpenQASM3 source, apply any `inputs` provided for the simulation, and compute +basis rotation instructions if running with non-zero shots. Return the `Program` after +parsing and the qubit count of the circuit. +""" +function _prepare_program(circuit_ir::OpenQasmProgram, inputs::Dict{String, <:Any}, shots::Int) + ir_inputs = isnothing(circuit_ir.inputs) ? Dict{String, Float64}() : circuit_ir.inputs + circuit = Circuit(circuit_ir.source, merge(ir_inputs, inputs)) + n_qubits = qubit_count(circuit) + if shots > 0 + _verify_openqasm_shots_observables(circuit, n_qubits) + basis_rotation_instructions!(circuit) end - for result in filter(rt->rt isa ObservableResult, circuit.result_types) - result.observable isa Observables.I && continue - if result.observable isa Observables.TensorProduct - result_targets = collect(result.targets) - for observable in result.observable.factors - obs_targets = splice!(result_targets, 1:qubit_count(observable)) - _check_observable(observable_map, observable, obs_targets) - end - else - _check_observable(observable_map, result.observable, result.targets) - end + return Program(circuit), n_qubits +end +""" + _prepare_program(circuit_ir::Program, inputs::Dict{String, <:Any}, shots::Int) -> (Program, Int) + +Apply any `inputs` provided for the simulation. Return the `Program` +(with bound parameters) and the qubit count of the circuit. +""" +# nosemgrep +function _prepare_program(circuit_ir::Program, inputs::Dict{String, <:Any}, shots::Int) + operations::Vector{Instruction} = circuit_ir.instructions + symbol_inputs = Dict(Symbol(k) => v for (k, v) in inputs) + operations = [bind_value!(operation, symbol_inputs) for operation in operations] + bound_program = Program(circuit_ir.braketSchemaHeader, operations, circuit_ir.results, circuit_ir.basis_rotation_instructions) + return bound_program, qubit_count(circuit_ir) +end +""" + _combine_operations(program::Program, shots::Int) -> Program + +Combine explicit instructions and basis rotation instructions (if necessary). +Validate that all operations are performed on qubits within `qubit_count`. +""" +function _combine_operations(program::Program, shots::Int) + operations = program.instructions + if shots > 0 && !isempty(program.basis_rotation_instructions) + operations = vcat(operations, program.basis_rotation_instructions) end + _validate_operation_qubits(operations) + return operations +end +""" + _compute_results(::, simulator, program::Program, n_qubits::Int, shots::Int) -> Vector{ResultTypeValue} + +Compute the results once `simulator` has finished applying all the instructions. The results depend on the IR type if `shots>0`: + +- For JAQCD IR (`Program`), the results array is *empty* because the Braket SDK computes the results from the IR directly. +- For OpenQASM IR (`OpenQasmProgram`), the results array is *empty* only if no results are present in the parsed IR. Otherwise, + the results array is populated with the parsed result types (to help the Braket SDK compute them from the sampled measurements) + and a placeholder zero value. +""" +function _compute_results(::Type{OpenQasmProgram}, simulator, program, n_qubits, shots) # nosemgrep + analytic_results = shots == 0 && !isnothing(program.results) && !isempty(program.results) + if analytic_results + return _compute_exact_results(simulator, program, n_qubits) + elseif isnothing(program.results) || isempty(program.results) + return ResultTypeValue[] + else + return ResultTypeValue[ResultTypeValue(result_type, 0.0) for result_type in program.results] + end +end +function _compute_results(::Type{Program}, simulator, program, n_qubits, shots) # nosemgrep + analytic_results = shots == 0 && !isnothing(program.results) && !isempty(program.results) + if analytic_results + return _compute_exact_results(simulator, program, n_qubits) + else + return ResultTypeValue[] + end +end +function _validate_circuit_ir(simulator, circuit_ir::Program, qubit_count::Int, shots::Int) + _validate_ir_results_compatibility(simulator, circuit_ir.results, Val(:JAQCD)) + _validate_ir_instructions_compatibility(simulator, circuit_ir, Val(:JAQCD)) + _validate_shots_and_ir_results(shots, circuit_ir.results, qubit_count) return end """ - simulate(simulator::AbstractSimulator, circuit_ir; shots::Int = 0, kwargs...) -> GateModelTaskResult + simulate(simulator::AbstractSimulator, circuit_ir::Union{OpenQasmProgram, Program}, shots::Int; kwargs...) -> GateModelTaskResult -Simulate the evolution of a statevector or density matrix using the passed in `simulator`. +Simulate the evolution of a state vector or density matrix using the passed-in `simulator`. The instructions to apply (gates and noise channels) and measurements to make are encoded in `circuit_ir`. Supported IR formats are `OpenQASMProgram` (OpenQASM3) and `Program` (JAQCD). Returns a `GateModelTaskResult` containing the individual shot @@ -254,76 +285,66 @@ about the task. """ function simulate( simulator::AbstractSimulator, - circuit_ir::OpenQasmProgram, - shots::Int = 0; + circuit_ir::T, + shots::Int; + inputs = Dict{String, Float64}(), kwargs..., -) - ir_inputs = isnothing(circuit_ir.inputs) || isempty(circuit_ir.inputs) ? Dict{String, Float64}() : circuit_ir.inputs - inputs = get(kwargs, :inputs, ir_inputs) - inputs = isnothing(inputs) || isempty(inputs) ? ir_inputs : Dict{String, Any}(k=>v for (k,v) in inputs) - circuit = Circuit(circuit_ir.source, inputs) - if shots > 0 - _verify_openqasm_shots_observables(circuit) - basis_rotation_instructions!(circuit) - end - measure_ixs = splice!(circuit.instructions, findall(ix->ix.operator isa Measure, circuit.instructions)) - measure_targets = (convert(Vector{Int}, measure.target) for measure in measure_ixs) - measured_qubits = convert(Vector{Int}, unique(reduce(vcat, measure_targets, init=Int[])))::Vector{Int} - program = Program(circuit) - program_qc = qubit_count(program) - n_qubits = max(program_qc, max(measured_qubits..., 0)+1)::Int - _validate_ir_results_compatibility(simulator, program.results, Val(:JAQCD)) - _validate_ir_instructions_compatibility(simulator, program, Val(:JAQCD)) - _validate_shots_and_ir_results(shots, program.results, n_qubits) - operations = program.instructions - if shots > 0 && !isempty(program.basis_rotation_instructions) - operations = vcat(operations, program.basis_rotation_instructions) - end - _validate_operation_qubits(vcat(operations, measure_ixs)) +) where {T<:Union{OpenQasmProgram, Program}} + program, n_qubits = _prepare_program(circuit_ir, inputs, shots) + _validate_circuit_ir(simulator, program, n_qubits, shots) + operations = _combine_operations(program, shots) reinit!(simulator, n_qubits, shots) - simulator = evolve!(simulator, operations) - analytic_results = shots == 0 && !isnothing(program.results) && !isempty(program.results) - results = if analytic_results - _compute_exact_results(simulator, program, n_qubits) - elseif isnothing(program.results) || isempty(program.results) - ResultTypeValue[] - else - ResultTypeValue[ResultTypeValue(result_type, 0.0) for result_type in program.results] - end - isempty(measured_qubits) && (measured_qubits = collect(0:n_qubits-1)) + simulator = evolve!(simulator, operations) + measured_qubits = _get_measured_qubits(program, n_qubits) + results = _compute_results(T, simulator, program, n_qubits, shots) return _bundle_results(results, circuit_ir, simulator, measured_qubits) end + +""" + simulate(simulator::AbstractSimulator, circuit_irs::Vector{<:Union{Program, OpenQasmProgram}}, shots::Int; max_parallel::Int=min(32, Threads.nthreads()), inputs=Dict{String,Float64}(), kwargs...) -> Vector{GateModelTaskResult} + +Simulate the evolution of a *batch* of state vectors or density matrices using the passed in `simulator`. +The instructions to apply (gates and noise channels) and measurements to make are +encoded in `circuit_irs`. Supported IR formats are `OpenQASMProgram` (OpenQASM3) +and `Program` (JAQCD). + +The simulation of the batch is done in parallel using threads. +The keyword argument `max_parallel` specifies the number of evolutions to simulate in +parallel -- the default value is whichever of `32` and `Threads.nthreads()` is smaller. +This is to avoid overwhelming the thread scheduler with too many small tasks waiting to +run, as each evolution is itself threaded. This value may change in the future. + +The `inputs` keyword argument can be a `Dict{String}` or a `Vector{Dict{String}}`. In +the first case, the same input values are applied to all `circuit_irs`. In the second, +the length of the `inputs` *must* be the same as the length of `circuit_irs`, and the +`n`-th `inputs` is applied to the `n`-th `circuit_irs`. + +Returns a `Vector{GateModelTaskResult}`, each element of which contains the individual shot +measurements (if `shots > 0`), final calculated results, corresponding circuit IR, and metadata +about the task. +""" function simulate(simulator::AbstractSimulator, - task_specs::Vector{<:Union{Program, OpenQasmProgram}}, + circuit_irs::Vector{<:Union{Program, OpenQasmProgram}}, shots::Int=0; - max_parallel::Int=-1, + max_parallel::Int=min(32, Threads.nthreads()), inputs = Dict{String, Float64}(), kwargs... ) - is_single_task = length(task_specs) == 1 + + n_tasks = length(circuit_irs) + is_single_task = n_tasks == 1 is_single_input = inputs isa Dict || length(inputs) == 1 - if is_single_input && is_single_task - if inputs isa Vector - return [simulate(simulator, only(task_specs), shots; inputs=only(inputs), kwargs...)] - else - return [simulate(simulator, only(task_specs), shots; inputs=inputs, kwargs...)] - end - end if is_single_input - if inputs isa Dict - inputs = [deepcopy(inputs) for ix in 1:length(task_specs)] + single_input = inputs isa Vector ? only(inputs) : inputs + if is_single_task + return [simulate(simulator, only(circuit_irs), shots; inputs=single_input, kwargs...)] else - inputs = [deepcopy(only(inputs)) for ix in 1:length(task_specs)] + inputs = [deepcopy(single_input) for ix in 1:n_tasks] end end - !is_single_task && !is_single_input && (length(task_specs) != length(inputs)) && throw(ArgumentError("number of inputs ($(length(inputs))), and task specifications ($(length(task_specs))) must be equal.")) - n_tasks = length(task_specs) - + !is_single_task && !is_single_input && (n_tasks != length(inputs)) && throw(ArgumentError("number of inputs ($(length(inputs))), and circuit IRs ($n_tasks) must be equal.")) todo_tasks_ch = Channel{Int}(ch->foreach(ix->put!(ch, ix), 1:n_tasks), n_tasks) - - max_parallel_threads = max_parallel > 0 ? max_parallel : min(32, Threads.nthreads()) - n_task_threads = min(max_parallel_threads, n_tasks) - + n_task_threads = min(max_parallel, n_tasks) results = Vector{GateModelTaskResult}(undef, n_tasks) function process_work(my_sim) while isready(todo_tasks_ch) @@ -337,17 +358,12 @@ function simulate(simulator::AbstractSimulator, # if my_ix is still -1, the channel is empty and # there's no more work to do my_ix == -1 && break - spec = task_specs[my_ix] - input = inputs[my_ix] - results[my_ix] = simulate(my_sim, spec, shots; inputs=input) + results[my_ix] = simulate(my_sim, circuit_irs[my_ix], shots; inputs=inputs[my_ix]) end return end - tasks = Vector{Task}(undef, n_task_threads) # need sync here to ensure all the spawns launch - @sync for worker in 1:n_task_threads - tasks[worker] = Threads.@spawn process_work(similar(simulator)) - end + tasks = @sync [Threads.@spawn process_work(similar(simulator)) for worker in 1:n_task_threads] # tasks don't return anything so we can wait rather than fetch wait.(tasks) # check to ensure all the results were in fact populated @@ -357,38 +373,6 @@ function simulate(simulator::AbstractSimulator, return results end -function simulate( - simulator::AbstractSimulator, - circuit_ir::Program, - qubit_count::Int, - shots::Int; - kwargs..., -) - _validate_ir_results_compatibility(simulator, circuit_ir.results, Val(:JAQCD)) - _validate_ir_instructions_compatibility(simulator, circuit_ir, Val(:JAQCD)) - _validate_shots_and_ir_results(shots, circuit_ir.results, qubit_count) - operations::Vector{Instruction} = circuit_ir.instructions - if shots > 0 && !isnothing(circuit_ir.basis_rotation_instructions) && !isempty(circuit_ir.basis_rotation_instructions) - operations = vcat(operations, circuit_ir.basis_rotation_instructions) - end - inputs = get(kwargs, :inputs, Dict{String,Float64}()) - symbol_inputs = Dict(Symbol(k) => v for (k, v) in inputs) - operations = [bind_value!(Instruction(operation), symbol_inputs) for operation in operations] - _validate_operation_qubits(operations) - reinit!(simulator, qubit_count, shots) - simulator = evolve!(simulator, operations) - analytic_results = shots == 0 && !isnothing(circuit_ir.results) && !isempty(circuit_ir.results) - results = if analytic_results - _compute_exact_results(simulator, circuit_ir, qubit_count) - else - ResultTypeValue[] - end - measured_qubits = get(kwargs, :measured_qubits, collect(0:qubit_count-1)) - isempty(measured_qubits) && (measured_qubits = collect(0:qubit_count-1)) - return _bundle_results(results, circuit_ir, simulator, measured_qubits) -end -simulate(simulator::AbstractSimulator, circuit_ir::Program, shots::Int; kwargs...) = simulate(simulator, circuit_ir, qubit_count(circuit_ir), shots; kwargs...) - include("result_types.jl") include("properties.jl") include("sv_simulator.jl") @@ -505,6 +489,118 @@ include("dm_simulator.jl") #pragma braket result probability cout #pragma braket result probability b """ + grcs_16_qasm = """ + OPENQASM 3.0; + + qubit[16] q; + + h q; + cz q[0], q[1]; + cz q[6], q[7]; + cz q[8], q[9]; + cz q[14], q[15]; + + t q[2:5]; + t q[10:13]; + + cz q[4], q[8]; + cz q[6], q[10]; + + ry(π/2) q[{0, 1, 14}]; + rx(π/2) q[{7, 9, 15}]; + + cz q[1], q[2]; + cz q[9], q[10]; + + t q[0]; + + ry(π/2) q[4]; + rx(π/2) q[6]; + + t q[7]; + + rx(π/2) q[8]; + + t q[14:15]; + + cz q[0], q[4]; + cz q[9], q[13]; + cz q[2], q[6]; + cz q[11], q[15]; + + ry(π/2) q[1]; + + t q[8]; + + ry(π/2) q[10]; + + cz q[2], q[3]; + cz q[4], q[5]; + cz q[10], q[11]; + cz q[12], q[13]; + + ry(π/2) q[0]; + + t q[1]; + + rx(π/2) q[6]; + ry(π/2) q[{9, 15}]; + + cz q[5], q[9]; + cz q[7], q[11]; + + t q[0]; + + rx(π/2) q[2]; + ry(π/2) q[3:4]; + + t q[6]; + + ry(π/2) q[{10, 12}]; + rx(π/2) q[13]; + + t q[15]; + + cz q[5], q[6]; + cz q[13], q[14]; + + t q[2:4]; + + ry(π/2) q[{7, 9}]; + + t q[10]; + + ry(π/2) q[11]; + + t q[12]; + + cz q[8], q[12]; + cz q[1], q[5]; + cz q[10], q[14]; + cz q[3], q[7]; + + rx(π/2) q[6]; + + t q[{9, 11}]; + + rx(π/2) q[13]; + + cz q[0], q[1]; + cz q[6], q[7]; + cz q[8], q[9]; + cz q[14], q[15]; + + rx(π/2) q[3]; + ry(π/2) q[{5, 10, 12}]; + + t q[13]; + + h q; + + #pragma braket result state_vector + #pragma braket result expectation x(q[0]) + #pragma braket result variance x(q[1]) + """ @compile_workload begin using BraketSimulator, BraketSimulator.Quasar simulator = StateVectorSimulator(5, 0) @@ -524,6 +620,10 @@ include("dm_simulator.jl") simulator = StateVectorSimulator(6, 0) oq3_program = OpenQasmProgram(braketSchemaHeader("braket.ir.openqasm.program", "1"), sv_adder_qasm, Dict("a_in"=>3, "b_in"=>7)) simulate(simulator, oq3_program, 0) + + simulator = StateVectorSimulator(16, 0) + oq3_program = OpenQasmProgram(braketSchemaHeader("braket.ir.openqasm.program", "1"), grcs_16_qasm, nothing) + simulate(simulator, oq3_program, 0) end end end # module BraketSimulator diff --git a/src/Quasar.jl b/src/Quasar.jl index fc65899..9677d3a 100644 --- a/src/Quasar.jl +++ b/src/Quasar.jl @@ -28,6 +28,10 @@ const general_letter = first_letter | re"[0-9]" const prefloat = re"[-+]?([0-9]+\.[0-9]*|[0-9]*\.[0-9]+)" + +const integer = re"[-+]?[0-9]+" +const float = prefloat | ((prefloat | re"[-+]?[0-9]+") * re"[eE][-+]?[0-9]+") + const qasm_tokens = [ :identifier => first_letter * rep(general_letter), :irrational => re"π|pi|τ|tau|ℯ|ℇ|euler", @@ -66,6 +70,10 @@ const qasm_tokens = [ :measure => re"measure", :arrow_token => re"->", :reset_token => re"reset", + :delay_token => re"delay", + :stretch_token => re"stretch", + :barrier_token => re"barrier", + :duration_token => re"duration", :void => re"void", :const_token => re"const", :assignment => re"=|-=|\+=|\*=|/=|^=|&=|\|=|<<=|>>=", @@ -89,8 +97,8 @@ const qasm_tokens = [ :bin => re"(0b|0B)[0-1]+", :hex => re"0x[0-9A-Fa-f]+", :dot => re"\.", - :integer => re"[-+]?[0-9]+", - :float => prefloat | ((prefloat | re"[-+]?[0-9]+") * re"[eE][-+]?[0-9]+"), + :integer_token => integer, + :float_token => float, :include_token => re"include", :continue_token => re"continue", :octal_integer => re"0o([0-7]_?)* [0-7]", @@ -102,8 +110,10 @@ const qasm_tokens = [ :string_token => '"' * rep(re"[ !#-~]" | re"\\\\\"") * '"' | '\'' * rep(re"[ -&(-~]" | ('\\' * re"[ -~]")) * '\'', :newline => re"\r?\n", :spaces => re"[\t ]+", + :durationof_token => re"durationof", # this MUST be lower than duration_token to preempt duration :classical_type => re"bool|uint|int|float|angle|complex|array|bit", - :forbidden_keyword => re"cal|defcal|duration|durationof|stretch|delay|barrier|extern", + :duration_value => (float | integer) * re"ns|µs|us|ms|s", + :forbidden_keyword => re"cal|defcal|extern", ] @eval @enum Token error $(first.(qasm_tokens)...) @@ -507,27 +517,27 @@ function parse_literal(tokens::Vector{Tuple{Int64, Int32, Token}}, stack, start, tokens[1][end] == bin && return parse_bin_literal(popfirst!(tokens), qasm) tokens[1][end] == irrational && return parse_irrational_literal(popfirst!(tokens), qasm) tokens[1][end] == boolean && return parse_boolean_literal(popfirst!(tokens), qasm) - tokens[1][end] == integer && length(tokens) == 1 && return parse_integer_literal(popfirst!(tokens), qasm) - tokens[1][end] == float && length(tokens) == 1 && return parse_float_literal(popfirst!(tokens), qasm) + tokens[1][end] == integer_token && length(tokens) == 1 && return parse_integer_literal(popfirst!(tokens), qasm) + tokens[1][end] == float_token && length(tokens) == 1 && return parse_float_literal(popfirst!(tokens), qasm) - is_float = tokens[1][end] == float + is_float = tokens[1][end] == float_token is_complex = false is_operator = tokens[2][end] == operator is_plusminus = is_operator && parse_identifier(tokens[2], qasm).args[1] ∈ ("+","-") is_terminal = (tokens[2][end] == semicolon || tokens[2][end] == comma || (is_operator && !is_plusminus)) - tokens[1][end] == integer && is_terminal && return parse_integer_literal(popfirst!(tokens), qasm) - tokens[1][end] == float && is_terminal && return parse_float_literal(popfirst!(tokens), qasm) + tokens[1][end] == integer_token && is_terminal && return parse_integer_literal(popfirst!(tokens), qasm) + tokens[1][end] == float_token && is_terminal && return parse_float_literal(popfirst!(tokens), qasm) splice_end = 1 if tokens[2][end] == im_token is_complex = true splice_end = 2 - elseif is_plusminus && tokens[3][end] ∈ (integer, float) && tokens[4][end] == im_token + elseif is_plusminus && tokens[3][end] ∈ (integer_token, float_token) && tokens[4][end] == im_token is_complex = true - is_float |= tokens[3][end] == float + is_float |= tokens[3][end] == float_token splice_end = 4 - elseif tokens[2][end] ∈ (integer, float) && tokens[3][end] == im_token # may have absorbed +/- sign + elseif tokens[2][end] ∈ (integer_token, float_token) && tokens[3][end] == im_token # may have absorbed +/- sign is_complex = true - is_float |= tokens[2][end] == float + is_float |= tokens[2][end] == float_token splice_end = 3 end literal_tokens = splice!(tokens, 1:splice_end) @@ -624,7 +634,7 @@ function parse_expression(tokens::Vector{Tuple{Int64, Int32, Token}}, stack, sta token_name = parse_bracketed_expression(pushfirst!(tokens, start_token), stack, start, qasm) elseif start_token[end] == classical_type token_name = parse_classical_type(pushfirst!(tokens, start_token), stack, start, qasm) - elseif start_token[end] ∈ (string_token, integer, float, hex, oct, bin, irrational, dot, boolean) + elseif start_token[end] ∈ (string_token, integer_token, float_token, hex, oct, bin, irrational, dot, boolean) token_name = parse_literal(pushfirst!(tokens, start_token), stack, start, qasm) elseif start_token[end] ∈ (mutable, readonly, const_token) token_name = parse_identifier(start_token, qasm) @@ -635,10 +645,13 @@ function parse_expression(tokens::Vector{Tuple{Int64, Int32, Token}}, stack, sta end head(token_name) == :empty && throw(QasmParseError("unable to parse line with start token $(start_token[end])", stack, start, qasm)) - next_token = first(tokens) if next_token[end] == semicolon || next_token[end] == comma || start_token[end] ∈ (lbracket, lbrace) expr = token_name + elseif start_token[end] == integer_token && next_token[end] == irrational # this is banned! 2π is not supported, 2*π is. + integer_lit = parse_integer_literal(start_token, qasm).args[1] + irrational_lit = parse_irrational_literal(next_token, qasm).args[1] + throw(QasmParseError("expressions of form $(integer_lit)$(irrational_lit) are not supported -- you must separate the terms with a * operator.", stack, start, qasm)) elseif start_token[end] == operator unary_op_symbol::Symbol = Symbol(token_name.args[1]::String) unary_op_symbol ∈ (:~, :!, :-) || throw(QasmParseError("invalid unary operator $unary_op_symbol.", stack, start, qasm)) @@ -688,7 +701,7 @@ function parse_expression(tokens::Vector{Tuple{Int64, Int32, Token}}, stack, sta elseif next_token[end] == assignment op_token = popfirst!(tokens) next_token = first(tokens) - if next_token[end] ∈ (lparen, lbracket, lbrace, string_token, integer, float, hex, oct, bin) + if next_token[end] ∈ (lparen, lbracket, lbrace, string_token, integer_token, float_token, hex, oct, bin) right_hand_side = parse_expression(tokens, stack, start, qasm) elseif next_token[end] == measure popfirst!(tokens) @@ -996,12 +1009,32 @@ function parse_qasm(clean_tokens::Vector{Tuple{Int64, Int32, Token}}, qasm::Stri box_expr = QasmExpression(:box) parse_block_body(box_expr, clean_tokens, stack, start, qasm) push!(stack, box_expr) - elseif token == reset_token + elseif token == reset_token @warn "reset expression encountered -- currently `reset` is a no-op" eol = findfirst(triplet->triplet[end] == semicolon, clean_tokens) reset_tokens = splice!(clean_tokens, 1:eol) targets = parse_expression(reset_tokens, stack, start, qasm) push!(stack, QasmExpression(:reset, targets)) + elseif token == barrier_token + @warn "barrier expression encountered -- currently `barrier` is a no-op" + eol = findfirst(triplet->triplet[end] == semicolon, clean_tokens) + barrier_tokens = splice!(clean_tokens, 1:eol) + targets = parse_expression(barrier_tokens, stack, start, qasm) + push!(stack, QasmExpression(:barrier, targets)) + elseif token == duration_token + @warn "duration expression encountered -- currently `duration` is a no-op" + eol = findfirst(triplet->triplet[end] == semicolon, clean_tokens) + duration_tokens = splice!(clean_tokens, 1:eol) + # TODO: add proper parsing of duration expressions, including + # support for units and algebraic durations like 2*a. + #dur_expr = parse_expression(duration_tokens, stack, start, qasm) + push!(stack, QasmExpression(:duration)) + elseif token == stretch_token + @warn "stretch expression encountered -- currently `stretch` is a no-op" + eol = findfirst(triplet->triplet[end] == semicolon, clean_tokens) + stretch_tokens = splice!(clean_tokens, 1:eol) + stretch_expr = parse_expression(stretch_tokens, stack, start, qasm) + push!(stack, QasmExpression(:stretch, stretch_expr)) elseif token == end_token push!(stack, QasmExpression(:end)) elseif token == identifier || token == builtin_gate @@ -1268,26 +1301,19 @@ function evaluate(@nospecialize(v::AbstractVisitor), expr::QasmExpression) haskey(classical_defs(v), id_name) && return classical_defs(v)[id_name].val haskey(qubit_mapping(v), id_name) && return evaluate_qubits(v, expr) elseif head(expr) == :indexed_identifier - identifier_name = name(expr) + identifier_name = name(expr) if haskey(classical_defs(v), identifier_name) var = classical_defs(v)[identifier_name] ix = evaluate(v, expr.args[2]::QasmExpression) if ix isa StepRange && ix.step > 0 && ix.stop < ix.start # -1 in place of end - new_stop = if var.type isa SizedNumber || var.type isa SizedBitVector - evaluate(v, var.type.size) - 1 - else - length(var.val) - 1 - end - ix = StepRange(ix.start, ix.step, new_stop) - end - flat_ix = Int[] - for ix_ in ix - append!(flat_ix, Int[ii+1 for ii::Int in ix_]) + new_stop = var.type isa SizedNumber || var.type isa SizedBitVector ? evaluate(v, var.type.size) : length(var.val) + ix = StepRange(ix.start, ix.step, new_stop-1) end + flat_ix = mapreduce(ix_ -> ix_ .+ 1, vcat, ix) if var.type isa SizedInt || var.type isa SizedUInt n_bits::Int = evaluate(v, var.type.size)::Int - int_val = convert(Int, var.val)::Int - values = Int[(int_val >> (n_bits - index)) & 1 for index in flat_ix] + int_val = convert(Int, var.val)::Int + values = Int[(int_val >> (n_bits - index)) & 1 for index in flat_ix] return length(flat_ix) == 1 ? values[1] : values else return length(flat_ix) == 1 ? var.val[only(flat_ix)] : var.val[flat_ix] @@ -1364,7 +1390,7 @@ function evaluate(@nospecialize(v::AbstractVisitor), expr::QasmExpression) return value > 0 # TODO else - throw(QasmVisitorError("unable to evaluate expression $expr")) + throw(QasmVisitorError("unable to evaluate cast expression $expr")) end elseif head(expr) == :measure qubits_to_measure = evaluate_qubits(v, expr.args[1]) @@ -1546,11 +1572,16 @@ function visit_gate_call(v::AbstractVisitor, program_expr::QasmExpression) call_targets::Vector{QasmExpression} = convert(Vector{QasmExpression}, head(raw_call_targets.args[1]) == :array_literal ? raw_call_targets.args[1].args : raw_call_targets.args)::Vector{QasmExpression} provided_args::Vector{QasmExpression} = convert(Vector{QasmExpression}, program_expr.args[2].args)::Vector{QasmExpression} has_modifiers = length(program_expr.args) == 4 - n_called_with = length(call_targets) gate_targets = Vector{Int}[evaluate_qubits(v, call_target)::Vector{Int} for call_target in call_targets] + n_called_with = length(gate_targets) hasgate(v, gate_name) || throw(QasmVisitorError("gate $gate_name not defined!")) gate_def = gate_defs(v)[gate_name] n_defined_with = length(gate_def.qubit_targets) + # cases like `ccnot qs`; + if n_called_with < n_defined_with && length(gate_targets[1]) == n_defined_with + n_called_with = length(gate_targets[1]) + gate_targets = Vector{Int}[[gt] for gt in gate_targets[1]] + end applied_arguments = process_gate_arguments(v, gate_name, gate_def.arguments, provided_args, gate_def.body) control_qubits::Vector{Int} = collect(0:(n_called_with-n_defined_with)-1) mods::Vector{QasmExpression} = length(program_expr.args) == 4 ? convert(Vector{QasmExpression}, program_expr.args[4].args) : QasmExpression[] @@ -1561,7 +1592,7 @@ function visit_gate_call(v::AbstractVisitor, program_expr::QasmExpression) end end applied_arguments = handle_gate_modifiers(applied_arguments, mods, control_qubits, false) - longest, gate_targets = splat_gate_targets(gate_targets) + longest, gate_targets = splat_gate_targets(gate_targets) for splatted_ix in 1:longest target_mapper = Dict{Int, Int}(g_ix=>gate_targets[g_ix+1][splatted_ix] for g_ix in 0:n_called_with-1) for ii in 1:length(applied_arguments) @@ -1597,6 +1628,12 @@ function (v::AbstractVisitor)(program_expr::QasmExpression) return v elseif head(program_expr) == :reset return v + elseif head(program_expr) == :barrier + return v + elseif head(program_expr) == :stretch + return v + elseif head(program_expr) == :duration + return v elseif head(program_expr) == :input var_name = name(program_expr) var_type = program_expr.args[1].args[1] @@ -1877,11 +1914,7 @@ function (v::AbstractVisitor)(program_expr::QasmExpression) if noise_type == "kraus" raw_mats = raw_args.args kraus_matrices = map(raw_mats) do raw_mat - kraus_matrix = similar(raw_mat, ComplexF64) - for ii in eachindex(kraus_matrix) - kraus_matrix[ii] = evaluate(v, raw_mat[ii]) - end - kraus_matrix + return broadcast(expr->convert(ComplexF64, evaluate(v, expr)), raw_mat)::Matrix{ComplexF64} end push!(v, Instruction(BraketSimulator.Kraus(kraus_matrices), targets)) else diff --git a/src/circuit.jl b/src/circuit.jl index 21ff029..e565bbb 100644 --- a/src/circuit.jl +++ b/src/circuit.jl @@ -227,7 +227,11 @@ function add_to_qubit_observable_mapping!(c::Circuit, o::Observables.TensorProdu return c end add_to_qubit_observable_set!(c::Circuit, rt::ObservableResult) = union!(c.qubit_observable_set, Set(rt.targets)) +# exclude AdjointGradient from coverage for now +# as we don't yet implement this, so don't have a test for it +# COV_EXCL_START add_to_qubit_observable_set!(c::Circuit, rt::AdjointGradient) = union!(c.qubit_observable_set, Set(reduce(union, rt.targets))) +# COV_EXCL_STOP add_to_qubit_observable_set!(c::Circuit, rt::Result) = c.qubit_observable_set function _check_if_qubit_measured(c::Circuit, qubit::Int) @@ -245,6 +249,9 @@ function add_instruction!(c::Circuit, ix::Instruction{O}) where {O<:Operator} union!(c.parameters, (param,)) end end + if ix.operator isa Measure + append!(c.measure_targets, ix.target) + end push!(c.instructions, ix) return c end diff --git a/src/dm_simulator.jl b/src/dm_simulator.jl index f55fb31..f2792c4 100644 --- a/src/dm_simulator.jl +++ b/src/dm_simulator.jl @@ -148,6 +148,11 @@ function _evolve_op!( ) where {T<:Complex,S<:AbstractDensityMatrix{T},N<:Noise} apply_noise!(op, dms.density_matrix, target...) end +# Measure operators are no-ops for now as measurement is handled at the end +# of simulation, in the results computation step. If/when mid-circuit +# measurement is supported, this operation will collapse the density +# matrix on the measured qubits. +_evolve_op!(dms::DensityMatrixSimulator{T,S}, m::Measure, args...) where {T<:Complex,S<:AbstractDensityMatrix{T}} = return """ evolve!(dms::DensityMatrixSimulator{T, S<:AbstractMatrix{T}}, operations::Vector{Instruction}) -> DensityMatrixSimulator{T, S} diff --git a/src/gate_kernels.jl b/src/gate_kernels.jl index 6f7b507..0bce3bf 100644 --- a/src/gate_kernels.jl +++ b/src/gate_kernels.jl @@ -279,6 +279,7 @@ apply_gate!(::Val{false}, g::I, state_vec::AbstractStateVector{T}, qubits::Int.. return apply_gate!(::Val{true}, g::I, state_vec::AbstractStateVector{T}, qubits::Int...) where {T<:Complex} = return +apply_gate!(::Measure, state_vec, args...) = return function apply_gate!( g_matrix::Union{SMatrix{2,2,T}, Diagonal{T,SVector{2,T}}}, @@ -514,7 +515,6 @@ for (control_gate, target_gate, n_controls) in ( end end - function apply_gate!( factor::Complex, gate::Control{MultiQubitPhaseShift{N}, B}, diff --git a/src/observables.jl b/src/observables.jl index 1319cdf..ed5bccf 100644 --- a/src/observables.jl +++ b/src/observables.jl @@ -211,9 +211,7 @@ function StructTypes.constructfrom(::Type{Observable}, obj::String) (obj == "h" || obj == "H") && return H() throw(ArgumentError("Observable of type \"$obj\" provided, only \"i\", \"x\", \"y\", \"z\", and \"h\" are valid.")) end -StructTypes.constructfrom(::Type{Observable}, obj::Vector{String}) = length(obj) == 1 ? StructTypes.constructfrom(Observable, first(obj)) : TensorProduct(obj) StructTypes.constructfrom(::Type{Observable}, obj::Vector{Vector{Vector{Float64}}}) = HermitianObservable(obj) -StructTypes.constructfrom(::Type{Observable}, obj::Vector{Vector{Vector{Vector{Float64}}}}) = length(obj) == 1 ? HermitianObservable(obj[1]) : TensorProduct(obj) StructTypes.constructfrom(::Type{Observable}, obj::Vector{T}) where {T} = length(obj) == 1 ? StructTypes.constructfrom(Observable, obj[1]) : TensorProduct([StructTypes.constructfrom(Observable, o) for o in obj]) end diff --git a/src/operators.jl b/src/operators.jl index d9654c2..456b4ab 100644 --- a/src/operators.jl +++ b/src/operators.jl @@ -65,3 +65,4 @@ end Measure() = Measure(-1) Parametrizable(m::Measure) = NonParametrized() qubit_count(::Type{Measure}) = 1 +qubit_count(m::Measure) = qubit_count(Measure) diff --git a/src/result_types.jl b/src/result_types.jl index 20bae8d..75668ed 100644 --- a/src/result_types.jl +++ b/src/result_types.jl @@ -50,7 +50,7 @@ function make_alias_table!( for i = 1:ks @inbounds acceptance_probs[smalls[i]] = 1.0 end - nothing + return end function samples(simulator::AbstractSimulator) diff --git a/src/results.jl b/src/results.jl index f8d3b89..d56331f 100644 --- a/src/results.jl +++ b/src/results.jl @@ -20,25 +20,24 @@ for (typ, ir_typ, label) in ((:Expectation, :(IR.Expectation), "expectation"), ( struct $typ <: Result observable::Observables.Observable targets::QubitSet - $typ(observable::Observables.Observable, targets::QubitSet) = new(observable, targets) + @doc """ + $($typ)(o, targets) -> $($typ) + $($typ)(o) -> $($typ) + + Constructs a $($typ) of an observable `o` on qubits `targets`. + + `o` may be one of: + - Any [`Observable`](@ref Observables.Observable) + - A `String` corresponding to an `Observable` (e.g. `\"x\"``) + - A `Vector{String}` in which each element corresponds to an `Observable` + + `targets` may be one of: + - A [`QubitSet`](@ref) + - A `Vector` of `Int`s and/or [`Qubit`](@ref)s + - An `Int` or `Qubit` + - Absent, in which case the observable `o` will be applied to all qubits provided it is a single qubit observable. + """ $typ(o::Observables.Observable, targets) = new(o, QubitSet(targets)) end - @doc """ - $($typ)(o, targets) -> $($typ) - $($typ)(o) -> $($typ) - - Constructs a $($typ) of an observable `o` on qubits `targets`. - - `o` may be one of: - - Any [`Observable`](@ref Observables.Observable) - - A `String` corresponding to an `Observable` (e.g. `\"x\"``) - - A `Vector{String}` in which each element corresponds to an `Observable` - - `targets` may be one of: - - A [`QubitSet`](@ref) - - A `Vector` of `Int`s and/or [`Qubit`](@ref)s - - An `Int` or `Qubit` - - Absent, in which case the observable `o` will be applied to all qubits provided it is a single qubit observable. - """ $typ(o, targets) = $typ(o, QubitSet(targets)) StructTypes.lower(x::$typ) = $ir_typ(StructTypes.lower(x.observable), (isempty(x.targets) ? nothing : Int.(x.targets)), $label) end end diff --git a/src/schemas.jl b/src/schemas.jl index c130e6b..4ca6544 100644 --- a/src/schemas.jl +++ b/src/schemas.jl @@ -20,9 +20,6 @@ struct Instruction{O<:Operator} target::QubitSet end Instruction(o::O, target) where {O<:Operator} = Instruction{O}(o, QubitSet(target...)) - -Instruction(x::Instruction{O}) where {O<:Operator} = x Base.:(==)(ix1::Instruction{O}, ix2::Instruction{O}) where {O<:Operator} = (ix1.operator == ix2.operator && ix1.target == ix2.target) - bind_value!(ix::Instruction{O}, param_values::Dict{Symbol, <:Real}) where {O<:Operator} = Instruction{O}(bind_value!(ix.operator, param_values), ix.target) remap(@nospecialize(ix::Instruction{O}), mapping::Dict{<:Integer, <:Integer}) where {O} = Instruction{O}(copy(ix.operator), [mapping[q] for q in ix.target]) diff --git a/src/validation.jl b/src/validation.jl index ee4b322..a2d7993 100644 --- a/src/validation.jl +++ b/src/validation.jl @@ -54,6 +54,7 @@ function _validate_shots_and_ir_results(shots::Int, results, qubit_count::Int) )) end end + return end function _validate_input_provided(circuit) for instruction in circuit.instructions @@ -80,13 +81,9 @@ function _validate_ir_instructions_compatibility( circuit_instruction_names = map(ix->replace(lowercase(string(typeof(ix.operator))), "_"=>"", "braketsimulator."=>""), circuit.instructions) supported_instructions = Set(map(op->replace(lowercase(op), "_"=>""), supported_operations)) no_noise = true - for name in circuit_instruction_names - if name in _NOISE_INSTRUCTIONS - no_noise = false - if name ∉ supported_instructions - throw(ValidationError("Noise instructions are not supported by the state vector simulator (by default). You need to use the density matrix simulator: LocalSimulator(\"braket_dm_v2\").", "TypeError")) - end - end + for name in filter(ix_name->ix_name ∈ _NOISE_INSTRUCTIONS, circuit_instruction_names) + no_noise = false + name ∉ supported_instructions && throw(ValidationError("Noise instructions are not supported by the state vector simulator (by default). You need to use the density matrix simulator: LocalSimulator(\"braket_dm_v2\").", "TypeError")) end if no_noise && !isempty(intersect(_NOISE_INSTRUCTIONS, supported_instructions)) @warn "You are running a noise-free circuit on the density matrix simulator. Consider running this circuit on the state vector simulator: LocalSimulator(\"braket_sv_v2\") for a better user experience." @@ -96,7 +93,7 @@ end _validate_ir_instructions_compatibility(simulator::D, circuit::Union{Program,Circuit}, v::Val{V}) where {D<:AbstractSimulator, V} = _validate_ir_instructions_compatibility(circuit, supported_operations(simulator, v)) _validate_result_type_qubits_exist(rt::StateVector, qubit_count::Int) = return -_validate_result_type_qubits_exist(rt::Amplitude, qubit_count::Int) = return +_validate_result_type_qubits_exist(rt::Amplitude, qubit_count::Int) = return # exclude adjoint gradient validation from coverage for now # as we don't yet implement this, so don't have a test for it # COV_EXCL_START @@ -123,7 +120,7 @@ _validate_result_types_qubits_exist(rts::Vector{RT}, qubit_count::Int) where {RT function _validate_operation_qubits(operations::Vector{<:Instruction}) unique_qs = Set{Int}() - max_qc = 0 + max_qc = 0 for operation in operations targets = operation.target max_qc = max(max_qc, targets...) @@ -135,3 +132,38 @@ function _validate_operation_qubits(operations::Vector{<:Instruction}) )) return end + +function _check_observable(observable_map, observable, qubits) + for qubit in qubits, (target, previously_measured) in observable_map + qubit ∉ target && continue + # must ensure that target is the same + issetequal(target, qubits) || throw(ValidationError("Qubit part of incompatible results targets", "ValueError")) + # must ensure observable is the same + typeof(previously_measured) != typeof(observable) && throw(ValidationError("Conflicting result types applied to a single qubit", "ValueError")) + # including matrix value for Hermitians + observable isa Observables.HermitianObservable && !isapprox(previously_measured.matrix, observable.matrix) && throw(ValidationError("Conflicting result types applied to a single qubit", "ValueError")) + end + observable_map[qubits] = observable + return observable_map +end + +function _combine_obs_and_targets(observable::Observables.HermitianObservable, result_targets::Vector{Int}) + obs_qc = qubit_count(observable) + length(result_targets) == obs_qc && return [(observable, result_targets)] + obs_qc == 1 && return [(copy(observable), rt) for rt in result_targets] + throw(ValidationError("only single-qubit Hermitian observables can be applied to all qubits.", "ValueError")) +end +_combine_obs_and_targets(observable::Observables.TensorProduct, result_targets::Vector{Int}) = zip(observable.factors, [splice!(result_targets, 1:qubit_count(f)) for f in observable.factors]) +_combine_obs_and_targets(observable, result_targets::Vector{Int}) = length(result_targets) == 1 ? [(observable, result_targets)] : [(copy(observable), t) for t in result_targets] + +function _verify_openqasm_shots_observables(circuit::Circuit, n_qubits::Int) + observable_map = Dict() + for result in filter(rt->rt isa ObservableResult, circuit.result_types) + result.observable isa Observables.I && continue + result_targets = isempty(result.targets) ? collect(0:n_qubits-1) : collect(result.targets) + for obs_and_target in _combine_obs_and_targets(result.observable, result_targets) + observable_map = _check_observable(observable_map, obs_and_target...) + end + end + return +end diff --git a/test/runtests.jl b/test/runtests.jl index f08737e..bdfeb17 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -6,10 +6,12 @@ dir_list = filter(x-> startswith(x, "test_") && endswith(x, ".jl"), readdir(@__D @testset "BraketSimulator" begin @testset "$test" for test in dir_list + @info "Testing $test" include(test) end @testset "docs" begin - Documenter.DocMeta.setdocmeta!(BraketSimulator, :DocTestSetup, :(using BraketSimulator, BraketSimulator.Observables; using BraketSimulator: Program, Circuit, qubits, CNot, H, Rx, FreeParameter, QubitSet, AdjointGradient, BitFlip, qubit_count, Qubit, StateVector, Measure, Probability, Ry, Amplitude, Instruction, DensityMatrix, add_instruction!); recursive=true) - Documenter.doctest(BraketSimulator) + @info "Testing docs" + Documenter.DocMeta.setdocmeta!(BraketSimulator, :DocTestSetup, :(using BraketSimulator, BraketSimulator.Observables; using BraketSimulator: Program, Circuit, qubits, CNot, H, Rx, FreeParameter, QubitSet, AdjointGradient, BitFlip, qubit_count, Qubit, StateVector, Measure, Probability, Ry, Amplitude, Instruction, DensityMatrix, add_instruction!); recursive=true) + Documenter.doctest(BraketSimulator) end end diff --git a/test/test_braket_integration.jl b/test/test_braket_integration.jl index 3abbd4f..ea4c0cc 100644 --- a/test/test_braket_integration.jl +++ b/test/test_braket_integration.jl @@ -1,7 +1,9 @@ using Test, - Statistics, - LinearAlgebra, - BraketSimulator + Logging, + Statistics, + LinearAlgebra, + BraketSimulator + using Braket using Braket: I, name @@ -19,7 +21,9 @@ using Braket: I, name if sim_type == "braket_sv_v2" Braket.Amplitude(c, ["000", "111"]) end - r = d(c, shots = 0) + with_logger(NullLogger()) do + r = d(c, shots = 0) + end end end @@ -119,464 +123,465 @@ end end @testset "Device $DEVICE, shots $SHOTS" for DEVICE in ALL_DEVICES, SHOTS in SHOT_LIST + with_logger(NullLogger()) do + if SHOTS > 0 + @testset "qubit ordering" begin + device = DEVICE + state_110 = Braket.Circuit([(Braket.X, 0), (Braket.X, 1), (Braket.I, 2)]) + state_001 = Braket.Circuit([(Braket.I, 0), (Braket.I, 1), (Braket.X, 2)]) + @testset for (state, most_com) in + ((state_110, "110"), (state_001, "001")) + tasks = (state, ir(state, Val(:JAQCD)), ir(state, Val(:OpenQASM))) + @testset for task in tasks + res = result(device(task, shots = SHOTS)) + mc = argmax(res.measurement_counts) + @test mc == most_com + end + end + end - if SHOTS > 0 - @testset "qubit ordering" begin - device = DEVICE - state_110 = Braket.Circuit([(Braket.X, 0), (Braket.X, 1), (Braket.I, 2)]) - state_001 = Braket.Circuit([(Braket.I, 0), (Braket.I, 1), (Braket.X, 2)]) - @testset for (state, most_com) in - ((state_110, "110"), (state_001, "001")) - tasks = (state, ir(state, Val(:JAQCD)), ir(state, Val(:OpenQASM))) + @testset "Bell pair nonzero shots" begin + circuit = bell_circ() + circuit(Braket.Expectation, Braket.Observables.H() * Braket.Observables.X(), [0, 1]) + circuit(Braket.Sample, Braket.Observables.H() * Braket.Observables.X(), [0, 1]) + tasks = (circuit, ir(circuit, Val(:JAQCD)), ir(circuit, Val(:OpenQASM))) @testset for task in tasks - res = result(device(task, shots = SHOTS)) - mc = argmax(res.measurement_counts) - @test mc == most_com + device = DEVICE + res = result(device(task; shots = SHOTS)) + @test length(res.result_types) == 2 + @test 0.6 < + res[Braket.Expectation(Braket.Observables.H() * Braket.Observables.X(), [0, 1])] < + 0.8 + @test length( + res[Braket.Sample(Braket.Observables.H() * Braket.Observables.X(), [0, 1])], + ) == SHOTS end end end - - @testset "Bell pair nonzero shots" begin + @testset "Bell pair full probability" begin circuit = bell_circ() - circuit(Braket.Expectation, Braket.Observables.H() * Braket.Observables.X(), [0, 1]) - circuit(Braket.Sample, Braket.Observables.H() * Braket.Observables.X(), [0, 1]) + circuit(Braket.Probability) tasks = (circuit, ir(circuit, Val(:JAQCD)), ir(circuit, Val(:OpenQASM))) + tol = get_tol(SHOTS) @testset for task in tasks device = DEVICE - res = result(device(task; shots = SHOTS)) - @test length(res.result_types) == 2 - @test 0.6 < - res[Braket.Expectation(Braket.Observables.H() * Braket.Observables.X(), [0, 1])] < - 0.8 - @test length( - res[Braket.Sample(Braket.Observables.H() * Braket.Observables.X(), [0, 1])], - ) == SHOTS + res = result(device(task, shots = SHOTS)) + @test length(res.result_types) == 1 + @test isapprox( + res[Probability()], + [0.5, 0.0, 0.0, 0.5], + rtol = tol["rtol"], + atol = tol["atol"], + ) end end - end - @testset "Bell pair full probability" begin - circuit = bell_circ() - circuit(Braket.Probability) - tasks = (circuit, ir(circuit, Val(:JAQCD)), ir(circuit, Val(:OpenQASM))) - tol = get_tol(SHOTS) - @testset for task in tasks - device = DEVICE - res = result(device(task, shots = SHOTS)) - @test length(res.result_types) == 1 - @test isapprox( - res[Probability()], - [0.5, 0.0, 0.0, 0.5], - rtol = tol["rtol"], - atol = tol["atol"], - ) - end - end - @testset "Bell pair marginal probability" begin - circuit = bell_circ() - circuit(Braket.Probability, 0) - tasks = (circuit, ir(circuit, Val(:JAQCD)), ir(circuit, Val(:OpenQASM))) - tol = get_tol(SHOTS) - @testset for task in tasks - device = DEVICE - res = result(device(task, shots = SHOTS)) - @test length(res.result_types) == 1 - @test isapprox( - res[Braket.Probability(0)], - [0.5, 0.5], - rtol = tol["rtol"], - atol = tol["atol"], - ) - end - end - @testset "Result types x x y" begin - θ = 0.432 - ϕ = 0.123 - φ = -0.543 - obs_targets = [0, 2] - expected_mean = sin(θ) * sin(ϕ) * sin(φ) - expected_var = - ( - 8 * sin(θ)^2 * cos(2φ) * sin(ϕ)^2 - cos(2(θ - ϕ)) - cos(2(θ + ϕ)) + - 2 * cos(2θ) + - 2 * cos(2ϕ) + - 14 - ) / 16 - expected_eigs = [-1.0, 1.0] - device = DEVICE - shots = SHOTS - @testset "Obs $obs" for obs in ( - Braket.Observables.X() * Braket.Observables.Y(), - Braket.Observables.HermitianObservable(kron([0 1; 1 0], [0 -im; im 0])), - ) - circuit = three_qubit_circuit(θ, ϕ, φ, obs, obs_targets) - shots > 0 && circuit(Braket.Sample, obs, obs_targets) + @testset "Bell pair marginal probability" begin + circuit = bell_circ() + circuit(Braket.Probability, 0) tasks = (circuit, ir(circuit, Val(:JAQCD)), ir(circuit, Val(:OpenQASM))) - for task in tasks - res = result(device(task, shots = shots)) - variance_expectation_sample_result( - res, - shots, - expected_var, - expected_mean, - expected_eigs, + tol = get_tol(SHOTS) + @testset for task in tasks + device = DEVICE + res = result(device(task, shots = SHOTS)) + @test length(res.result_types) == 1 + @test isapprox( + res[Braket.Probability(0)], + [0.5, 0.5], + rtol = tol["rtol"], + atol = tol["atol"], ) end end - end - @testset "Result types z x h x y" begin - θ = 0.432 - ϕ = 0.123 - φ = -0.543 - obs = Braket.Observables.Z() * Braket.Observables.H() * Braket.Observables.Y() - obs_targets = [0, 1, 2] - circuit = three_qubit_circuit(θ, ϕ, φ, obs, obs_targets) - expected_mean = -(cos(φ) * sin(ϕ) + sin(φ) * cos(θ)) / √2 - expected_var = - ( - 3 + cos(2ϕ) * cos(φ)^2 - cos(2θ) * sin(φ)^2 - - 2 * cos(θ) * sin(ϕ) * sin(2φ) - ) / 4 - expected_eigs = [-1.0, 1.0] - device = DEVICE - shots = SHOTS - @testset for obs in ( - Braket.Observables.Z() * Braket.Observables.H() * Braket.Observables.Y(), - ) - circuit = three_qubit_circuit(θ, ϕ, φ, obs, obs_targets) - shots > 0 && circuit(Braket.Sample, obs, obs_targets) - tasks = (circuit, ir(circuit, Val(:JAQCD)), ir(circuit, Val(:OpenQASM))) - for task in tasks - res = result(device(task, shots = shots)) - variance_expectation_sample_result( - res, - shots, - expected_var, - expected_mean, - expected_eigs, - ) + @testset "Result types x x y" begin + θ = 0.432 + ϕ = 0.123 + φ = -0.543 + obs_targets = [0, 2] + expected_mean = sin(θ) * sin(ϕ) * sin(φ) + expected_var = + ( + 8 * sin(θ)^2 * cos(2φ) * sin(ϕ)^2 - cos(2(θ - ϕ)) - cos(2(θ + ϕ)) + + 2 * cos(2θ) + + 2 * cos(2ϕ) + + 14 + ) / 16 + expected_eigs = [-1.0, 1.0] + device = DEVICE + shots = SHOTS + @testset "Obs $obs" for obs in ( + Braket.Observables.X() * Braket.Observables.Y(), + Braket.Observables.HermitianObservable(kron([0 1; 1 0], [0 -im; im 0])), + ) + circuit = three_qubit_circuit(θ, ϕ, φ, obs, obs_targets) + shots > 0 && circuit(Braket.Sample, obs, obs_targets) + tasks = (circuit, ir(circuit, Val(:JAQCD)), ir(circuit, Val(:OpenQASM))) + for task in tasks + res = result(device(task, shots = shots)) + variance_expectation_sample_result( + res, + shots, + expected_var, + expected_mean, + expected_eigs, + ) + end end end - end - @testset "Result types z x z" begin - θ = 0.432 - ϕ = 0.123 - φ = -0.543 - obs_targets = [0, 2] - expected_mean = 0.849694136476246 - expected_var = 0.27801987443788634 - expected_eigs = [-1.0, 1.0] - device = DEVICE - shots = SHOTS - @testset for obs in ( - Braket.Observables.Z() * Braket.Observables.Z(), - Braket.Observables.HermitianObservable(kron([1 0; 0 -1], [1 0; 0 -1])), - ) + @testset "Result types z x h x y" begin + θ = 0.432 + ϕ = 0.123 + φ = -0.543 + obs = Braket.Observables.Z() * Braket.Observables.H() * Braket.Observables.Y() + obs_targets = [0, 1, 2] circuit = three_qubit_circuit(θ, ϕ, φ, obs, obs_targets) - shots > 0 && circuit(Braket.Sample, obs, obs_targets) - tasks = (circuit, ir(circuit, Val(:JAQCD)), ir(circuit, Val(:OpenQASM))) - @testset for task in tasks - res = result(device(task, shots = shots)) - variance_expectation_sample_result( - res, - shots, - expected_var, - expected_mean, - expected_eigs, - ) + expected_mean = -(cos(φ) * sin(ϕ) + sin(φ) * cos(θ)) / √2 + expected_var = + ( + 3 + cos(2ϕ) * cos(φ)^2 - cos(2θ) * sin(φ)^2 - + 2 * cos(θ) * sin(ϕ) * sin(2φ) + ) / 4 + expected_eigs = [-1.0, 1.0] + device = DEVICE + shots = SHOTS + @testset for obs in ( + Braket.Observables.Z() * Braket.Observables.H() * Braket.Observables.Y(), + ) + circuit = three_qubit_circuit(θ, ϕ, φ, obs, obs_targets) + shots > 0 && circuit(Braket.Sample, obs, obs_targets) + tasks = (circuit, ir(circuit, Val(:JAQCD)), ir(circuit, Val(:OpenQASM))) + for task in tasks + res = result(device(task, shots = shots)) + variance_expectation_sample_result( + res, + shots, + expected_var, + expected_mean, + expected_eigs, + ) + end end end - end - @testset "($DEVICE, $SHOTS) Result types tensor {i,y,z,Hermitian} x Hermitian" begin - θ = 0.432 - ϕ = 0.123 - φ = -0.543 - ho_mat = [ - -6 2+im -3 -5+2im - 2-im 0 2-im -5+4im - -3 2+im 0 -4+3im - -5-2im -5-4im -4-3im -6 - ] - @test ishermitian(ho_mat) - ho = Braket.Observables.HermitianObservable(ComplexF64.(ho_mat)) - ho_mat2 = [1 2; 2 4] - ho2 = Braket.Observables.HermitianObservable(ComplexF64.(ho_mat2)) - ho_mat3 = [-6 2+im; 2-im 0] - ho3 = Braket.Observables.HermitianObservable(ComplexF64.(ho_mat3)) - ho_mat4 = kron([1 0; 0 1], [-6 2+im; 2-im 0]) - ho4 = Braket.Observables.HermitianObservable(ComplexF64.(ho_mat4)) - meani = -5.7267957792059345 - meany = 1.4499810303182408 - meanz = - 0.5 * ( - -6 * cos(θ) * (cos(φ) + 1) - - 2 * sin(φ) * (cos(θ) + sin(ϕ) - 2 * cos(ϕ)) + - 3 * cos(φ) * sin(ϕ) + - sin(ϕ) + @testset "Result types z x z" begin + θ = 0.432 + ϕ = 0.123 + φ = -0.543 + obs_targets = [0, 2] + expected_mean = 0.849694136476246 + expected_var = 0.27801987443788634 + expected_eigs = [-1.0, 1.0] + device = DEVICE + shots = SHOTS + @testset for obs in ( + Braket.Observables.Z() * Braket.Observables.Z(), + Braket.Observables.HermitianObservable(kron([1 0; 0 -1], [1 0; 0 -1])), ) - meanh = -4.30215023196904 - meanii = -5.78059066879935 + circuit = three_qubit_circuit(θ, ϕ, φ, obs, obs_targets) + shots > 0 && circuit(Braket.Sample, obs, obs_targets) + tasks = (circuit, ir(circuit, Val(:JAQCD)), ir(circuit, Val(:OpenQASM))) + @testset for task in tasks + res = result(device(task, shots = shots)) + variance_expectation_sample_result( + res, + shots, + expected_var, + expected_mean, + expected_eigs, + ) + end + end + end + @testset "($DEVICE, $SHOTS) Result types tensor {i,y,z,Hermitian} x Hermitian" begin + θ = 0.432 + ϕ = 0.123 + φ = -0.543 + ho_mat = [ + -6 2+im -3 -5+2im + 2-im 0 2-im -5+4im + -3 2+im 0 -4+3im + -5-2im -5-4im -4-3im -6 + ] + @test ishermitian(ho_mat) + ho = Braket.Observables.HermitianObservable(ComplexF64.(ho_mat)) + ho_mat2 = [1 2; 2 4] + ho2 = Braket.Observables.HermitianObservable(ComplexF64.(ho_mat2)) + ho_mat3 = [-6 2+im; 2-im 0] + ho3 = Braket.Observables.HermitianObservable(ComplexF64.(ho_mat3)) + ho_mat4 = kron([1 0; 0 1], [-6 2+im; 2-im 0]) + ho4 = Braket.Observables.HermitianObservable(ComplexF64.(ho_mat4)) + meani = -5.7267957792059345 + meany = 1.4499810303182408 + meanz = + 0.5 * ( + -6 * cos(θ) * (cos(φ) + 1) - + 2 * sin(φ) * (cos(θ) + sin(ϕ) - 2 * cos(ϕ)) + + 3 * cos(φ) * sin(ϕ) + + sin(ϕ) + ) + meanh = -4.30215023196904 + meanii = -5.78059066879935 - vari = 43.33800156673375 - vary = 74.03174647518193 - varz = - ( - 1057 - cos(2ϕ) + 12 * (27 + cos(2ϕ)) * cos(φ) - - 2 * cos(2φ) * sin(ϕ) * (16 * cos(ϕ) + 21 * sin(ϕ)) + 16 * sin(2ϕ) - - 8 * (-17 + cos(2ϕ) + 2 * sin(2ϕ)) * sin(φ) - - 8 * cos(2θ) * (3 + 3 * cos(φ) + sin(φ))^2 - - 24 * cos(ϕ) * (cos(ϕ) + 2 * sin(ϕ)) * sin(2φ) - - 8 * - cos(θ) * + vari = 43.33800156673375 + vary = 74.03174647518193 + varz = ( - 4 * - cos(ϕ) * - (4 + 8 * cos(φ) + cos(2φ) - (1 + 6 * cos(φ)) * sin(φ)) + - sin(ϕ) * - (15 + 8 * cos(φ) - 11 * cos(2φ) + 42 * sin(φ) + 3 * sin(2φ)) - ) - ) / 16 - varh = 370.71292282796804 - varii = 6.268315532585994 + 1057 - cos(2ϕ) + 12 * (27 + cos(2ϕ)) * cos(φ) - + 2 * cos(2φ) * sin(ϕ) * (16 * cos(ϕ) + 21 * sin(ϕ)) + 16 * sin(2ϕ) - + 8 * (-17 + cos(2ϕ) + 2 * sin(2ϕ)) * sin(φ) - + 8 * cos(2θ) * (3 + 3 * cos(φ) + sin(φ))^2 - + 24 * cos(ϕ) * (cos(ϕ) + 2 * sin(ϕ)) * sin(2φ) - + 8 * + cos(θ) * + ( + 4 * + cos(ϕ) * + (4 + 8 * cos(φ) + cos(2φ) - (1 + 6 * cos(φ)) * sin(φ)) + + sin(ϕ) * + (15 + 8 * cos(φ) - 11 * cos(2φ) + 42 * sin(φ) + 3 * sin(2φ)) + ) + ) / 16 + varh = 370.71292282796804 + varii = 6.268315532585994 - i_array = [1 0; 0 1] - y_array = [0 -im; im 0] - z_array = diagm([1, -1]) - eigsi = eigvals(kron(i_array, ho_mat)) - eigsy = eigvals(kron(y_array, ho_mat)) - eigsz = eigvals(kron(z_array, ho_mat)) - eigsh = [-70.90875406, -31.04969387, 0, 3.26468993, 38.693758] - eigsii = eigvals(kron(i_array, kron(i_array, ho_mat3))) - obs_targets = [0, 1, 2] - @testset "Obs $obs" for (obs, expected_mean, expected_var, expected_eigs) in - [ - (Braket.Observables.I() * ho, meani, vari, eigsi), - (Braket.Observables.Y() * ho, meany, vary, eigsy), - (Braket.Observables.Z() * ho, meanz, varz, eigsz), - (ho2 * ho, meanh, varh, eigsh), - ( - Braket.Observables.HermitianObservable(kron(ho_mat2, ho_mat)), - meanh, - varh, - eigsh, - ), - (Braket.Observables.I() * Braket.Observables.I() * ho3, meanii, varii, eigsii), - (Braket.Observables.I() * ho4, meanii, varii, eigsii), - ] - device = DEVICE - shots = SHOTS - circuit = three_qubit_circuit(θ, ϕ, φ, obs, obs_targets) - shots > 0 && circuit(Braket.Sample, obs, obs_targets) - tasks = (circuit, ir(circuit, Val(:JAQCD)), ir(circuit, Val(:OpenQASM))) - for task in tasks - res = result(device(task, shots = shots)) - variance_expectation_sample_result( - res, - shots, - expected_var, - expected_mean, - expected_eigs, - ) + i_array = [1 0; 0 1] + y_array = [0 -im; im 0] + z_array = diagm([1, -1]) + eigsi = eigvals(kron(i_array, ho_mat)) + eigsy = eigvals(kron(y_array, ho_mat)) + eigsz = eigvals(kron(z_array, ho_mat)) + eigsh = [-70.90875406, -31.04969387, 0, 3.26468993, 38.693758] + eigsii = eigvals(kron(i_array, kron(i_array, ho_mat3))) + obs_targets = [0, 1, 2] + @testset "Obs $obs" for (obs, expected_mean, expected_var, expected_eigs) in + [ + (Braket.Observables.I() * ho, meani, vari, eigsi), + (Braket.Observables.Y() * ho, meany, vary, eigsy), + (Braket.Observables.Z() * ho, meanz, varz, eigsz), + (ho2 * ho, meanh, varh, eigsh), + ( + Braket.Observables.HermitianObservable(kron(ho_mat2, ho_mat)), + meanh, + varh, + eigsh, + ), + (Braket.Observables.I() * Braket.Observables.I() * ho3, meanii, varii, eigsii), + (Braket.Observables.I() * ho4, meanii, varii, eigsii), + ] + device = DEVICE + shots = SHOTS + circuit = three_qubit_circuit(θ, ϕ, φ, obs, obs_targets) + shots > 0 && circuit(Braket.Sample, obs, obs_targets) + tasks = (circuit, ir(circuit, Val(:JAQCD)), ir(circuit, Val(:OpenQASM))) + for task in tasks + res = result(device(task, shots = shots)) + variance_expectation_sample_result( + res, + shots, + expected_var, + expected_mean, + expected_eigs, + ) + end end end - end - @testset "Result types single Hermitian" begin - θ = 0.432 - ϕ = 0.123 - φ = -0.543 - ho_mat = [ - -6 2+im -3 -5+2im - 2-im 0 2-im -5+4im - -3 2+im 0 -4+3im - -5-2im -5-4im -4-3im -6 - ] - @test ishermitian(ho_mat) - ho = Braket.Observables.HermitianObservable(ComplexF64.(ho_mat)) - ho_mat2 = [1 2; 2 4] - ho2 = Braket.Observables.HermitianObservable(ho_mat2) - ho_mat3 = [-6 2+im; 2-im 0] - ho3 = Braket.Observables.HermitianObservable(ho_mat3) - ho_mat4 = kron([1 0; 0 1], [-6 2+im; 2-im 0]) - ho4 = Braket.Observables.HermitianObservable(ho_mat4) - h = Braket.Observables.HermitianObservable(kron(ho_mat2, ho_mat)) - meani = -5.7267957792059345 - meanh = -4.30215023196904 - meanii = -5.78059066879935 + @testset "Result types single Hermitian" begin + θ = 0.432 + ϕ = 0.123 + φ = -0.543 + ho_mat = [ + -6 2+im -3 -5+2im + 2-im 0 2-im -5+4im + -3 2+im 0 -4+3im + -5-2im -5-4im -4-3im -6 + ] + @test ishermitian(ho_mat) + ho = Braket.Observables.HermitianObservable(ComplexF64.(ho_mat)) + ho_mat2 = [1 2; 2 4] + ho2 = Braket.Observables.HermitianObservable(ho_mat2) + ho_mat3 = [-6 2+im; 2-im 0] + ho3 = Braket.Observables.HermitianObservable(ho_mat3) + ho_mat4 = kron([1 0; 0 1], [-6 2+im; 2-im 0]) + ho4 = Braket.Observables.HermitianObservable(ho_mat4) + h = Braket.Observables.HermitianObservable(kron(ho_mat2, ho_mat)) + meani = -5.7267957792059345 + meanh = -4.30215023196904 + meanii = -5.78059066879935 - vari = 43.33800156673375 - varh = 370.71292282796804 - varii = 6.268315532585994 + vari = 43.33800156673375 + varh = 370.71292282796804 + varii = 6.268315532585994 - i_array = [1 0; 0 1] - eigsi = eigvals(kron(i_array, ho_mat)) - eigsh = [-70.90875406, -31.04969387, 0, 3.26468993, 38.693758] - eigsii = eigvals(kron(i_array, kron(i_array, ho_mat3))) - obs_targets = [0, 1, 2] - @testset "Obs $obs" for ( - obs, - targets, - expected_mean, - expected_var, - expected_eigs, - ) in [ - (ho, [1, 2], meani, vari, eigsi), - (h, [0, 1, 2], meanh, varh, eigsh), - (ho3, [2], meanii, varii, eigsii), - (ho4, [1, 2], meanii, varii, eigsii), - ] + i_array = [1 0; 0 1] + eigsi = eigvals(kron(i_array, ho_mat)) + eigsh = [-70.90875406, -31.04969387, 0, 3.26468993, 38.693758] + eigsii = eigvals(kron(i_array, kron(i_array, ho_mat3))) + obs_targets = [0, 1, 2] + @testset "Obs $obs" for ( + obs, + targets, + expected_mean, + expected_var, + expected_eigs, + ) in [ + (ho, [1, 2], meani, vari, eigsi), + (h, [0, 1, 2], meanh, varh, eigsh), + (ho3, [2], meanii, varii, eigsii), + (ho4, [1, 2], meanii, varii, eigsii), + ] + device = DEVICE + shots = SHOTS + circuit = three_qubit_circuit(θ, ϕ, φ, obs, targets) + shots > 0 && circuit(Braket.Sample, obs, targets) + tasks = (circuit, ir(circuit, Val(:JAQCD)), ir(circuit, Val(:OpenQASM))) + for task in tasks + res = result(device(task; shots = shots)) + variance_expectation_sample_result( + res, + shots, + expected_var, + expected_mean, + expected_eigs, + ) + end + end + + end + @testset "Result types all selected" begin + θ = 0.543 + ho_mat = [1 2im; -2im 0] + ho = Braket.Observables.HermitianObservable(ho_mat) + expected_mean = 2 * sin(θ) + 0.5 * cos(θ) + 0.5 + var_ = 0.25 * (sin(θ) - 4 * cos(θ))^2 + expected_var = [var_, var_] + expected_eigs = eigvals(Hermitian(ho_mat)) device = DEVICE shots = SHOTS - circuit = three_qubit_circuit(θ, ϕ, φ, obs, targets) - shots > 0 && circuit(Braket.Sample, obs, targets) + circuit = + Braket.Circuit([(Braket.Rx, 0, θ), (Braket.Rx, 1, θ), (Braket.Variance, ho), (Braket.Expectation, ho, 0)]) + shots > 0 && circuit(Braket.Sample, ho, 1) tasks = (circuit, ir(circuit, Val(:JAQCD)), ir(circuit, Val(:OpenQASM))) - for task in tasks + for task in tasks res = result(device(task; shots = shots)) - variance_expectation_sample_result( - res, - shots, - expected_var, - expected_mean, - expected_eigs, - ) - end - end - - end - @testset "Result types all selected" begin - θ = 0.543 - ho_mat = [1 2im; -2im 0] - ho = Braket.Observables.HermitianObservable(ho_mat) - expected_mean = 2 * sin(θ) + 0.5 * cos(θ) + 0.5 - var_ = 0.25 * (sin(θ) - 4 * cos(θ))^2 - expected_var = [var_, var_] - expected_eigs = eigvals(Hermitian(ho_mat)) - device = DEVICE - shots = SHOTS - circuit = - Braket.Circuit([(Braket.Rx, 0, θ), (Braket.Rx, 1, θ), (Braket.Variance, ho), (Braket.Expectation, ho, 0)]) - shots > 0 && circuit(Braket.Sample, ho, 1) - tasks = (circuit, ir(circuit, Val(:JAQCD)), ir(circuit, Val(:OpenQASM))) - for task in tasks - res = result(device(task; shots = shots)) - tol = get_tol(shots) - variance = res.values[1] - expectation = res.values[2] - if shots > 0 - samples = res.values[3] - @test isapprox( - sort(collect(unique(samples))), - sort(collect(unique(expected_eigs))), - rtol = tol["rtol"], - atol = tol["atol"], - ) + tol = get_tol(shots) + variance = res.values[1] + expectation = res.values[2] + if shots > 0 + samples = res.values[3] + @test isapprox( + sort(collect(unique(samples))), + sort(collect(unique(expected_eigs))), + rtol = tol["rtol"], + atol = tol["atol"], + ) + @test isapprox( + mean(samples), + expected_mean, + rtol = tol["rtol"], + atol = tol["atol"], + ) + @test isapprox( + var(samples), + var_, + rtol = tol["rtol"], + atol = tol["atol"], + ) + end @test isapprox( - mean(samples), + expectation, expected_mean, rtol = tol["rtol"], atol = tol["atol"], ) @test isapprox( - var(samples), - var_, + variance, + expected_var, rtol = tol["rtol"], atol = tol["atol"], ) end - @test isapprox( - expectation, - expected_mean, - rtol = tol["rtol"], - atol = tol["atol"], - ) - @test isapprox( - variance, - expected_var, - rtol = tol["rtol"], - atol = tol["atol"], - ) end - end - @testset "Result types noncommuting" begin - shots = 0 - θ = 0.432 - ϕ = 0.123 - φ = -0.543 - ho_mat = [ - -6 2+im -3 -5+2im - 2-im 0 2-im -5+4im - -3 2+im 0 -4+3im - -5-2im -5-4im -4-3im -6 - ] - obs1 = Braket.Observables.X() * Braket.Observables.Y() - obs1_targets = [0, 2] - obs2 = Braket.Observables.Z() * Braket.Observables.Z() - obs2_targets = [0, 2] - obs3 = Braket.Observables.Y() * Braket.Observables.HermitianObservable(ho_mat) - obs3_targets = [0, 1, 2] - obs3_targets = [0, 1, 2] - circuit = three_qubit_circuit(θ, ϕ, φ, obs1, obs1_targets) - circuit(Braket.Expectation, obs2, obs2_targets) - circuit(Braket.Expectation, obs3, obs3_targets) + @testset "Result types noncommuting" begin + shots = 0 + θ = 0.432 + ϕ = 0.123 + φ = -0.543 + ho_mat = [ + -6 2+im -3 -5+2im + 2-im 0 2-im -5+4im + -3 2+im 0 -4+3im + -5-2im -5-4im -4-3im -6 + ] + obs1 = Braket.Observables.X() * Braket.Observables.Y() + obs1_targets = [0, 2] + obs2 = Braket.Observables.Z() * Braket.Observables.Z() + obs2_targets = [0, 2] + obs3 = Braket.Observables.Y() * Braket.Observables.HermitianObservable(ho_mat) + obs3_targets = [0, 1, 2] + obs3_targets = [0, 1, 2] + circuit = three_qubit_circuit(θ, ϕ, φ, obs1, obs1_targets) + circuit(Braket.Expectation, obs2, obs2_targets) + circuit(Braket.Expectation, obs3, obs3_targets) - expected_mean1 = sin(θ) * sin(ϕ) * sin(φ) - expected_var1 = - ( - 8 * sin(θ)^2 * cos(2φ) * sin(ϕ)^2 - cos(2(θ - ϕ)) - cos(2(θ + ϕ)) + - 2 * cos(2θ) + - 2 * cos(2ϕ) + - 14 - ) / 16 - expected_mean2 = 0.849694136476246 - expected_mean3 = 1.4499810303182408 + expected_mean1 = sin(θ) * sin(ϕ) * sin(φ) + expected_var1 = + ( + 8 * sin(θ)^2 * cos(2φ) * sin(ϕ)^2 - cos(2(θ - ϕ)) - cos(2(θ + ϕ)) + + 2 * cos(2θ) + + 2 * cos(2ϕ) + + 14 + ) / 16 + expected_mean2 = 0.849694136476246 + expected_mean3 = 1.4499810303182408 - tasks = (circuit, ir(circuit, Val(:JAQCD)), ir(circuit, Val(:OpenQASM))) - @testset for task in tasks - device = DEVICE - res = result(device(task, shots = shots)) - @test isapprox(res.values[1], expected_var1) - @test isapprox(res.values[2], expected_mean1) - @test isapprox(res.values[3], expected_mean2) - @test isapprox(res.values[4], expected_mean3) + tasks = (circuit, ir(circuit, Val(:JAQCD)), ir(circuit, Val(:OpenQASM))) + @testset for task in tasks + device = DEVICE + res = result(device(task, shots = shots)) + @test isapprox(res.values[1], expected_var1) + @test isapprox(res.values[2], expected_mean1) + @test isapprox(res.values[3], expected_mean2) + @test isapprox(res.values[4], expected_mean3) + end end - end - @testset "Result types noncommuting flipped targets" begin - circuit = bell_circ() - tp = Braket.Observables.TensorProduct(["h", "x"]) - circuit = Braket.Expectation(circuit, tp, [0, 1]) - circuit = Braket.Expectation(circuit, tp, [1, 0]) - tasks = (circuit, ir(circuit, Val(:JAQCD)), ir(circuit, Val(:OpenQASM))) - @testset for task in tasks - device = DEVICE - res = result(device(task, shots = 0)) - @test isapprox(res.values[1], √2 / 2) - @test isapprox(res.values[2], √2 / 2) + @testset "Result types noncommuting flipped targets" begin + circuit = bell_circ() + tp = Braket.Observables.TensorProduct(["h", "x"]) + circuit = Braket.Expectation(circuit, tp, [0, 1]) + circuit = Braket.Expectation(circuit, tp, [1, 0]) + tasks = (circuit, ir(circuit, Val(:JAQCD)), ir(circuit, Val(:OpenQASM))) + @testset for task in tasks + device = DEVICE + res = result(device(task, shots = 0)) + @test isapprox(res.values[1], √2 / 2) + @test isapprox(res.values[2], √2 / 2) + end end - end - @testset "Result types all noncommuting" begin - circuit = bell_circ() - ho = [1 2im; -2im 0] - circuit(Braket.Expectation, Braket.Observables.HermitianObservable(ho)) - circuit(Braket.Expectation, Braket.Observables.X()) - tasks = (circuit, ir(circuit, Val(:JAQCD)), ir(circuit, Val(:OpenQASM))) - @testset for task in tasks - device = DEVICE - res = result(device(task, shots = 0)) - @test isapprox(res.values[1], [0.5, 0.5]) - @test isapprox(res.values[2], [0, 0]) + @testset "Result types all noncommuting" begin + circuit = bell_circ() + ho = [1 2im; -2im 0] + circuit(Braket.Expectation, Braket.Observables.HermitianObservable(ho)) + circuit(Braket.Expectation, Braket.Observables.X()) + tasks = (circuit, ir(circuit, Val(:JAQCD)), ir(circuit, Val(:OpenQASM))) + @testset for task in tasks + device = DEVICE + res = result(device(task, shots = 0)) + @test isapprox(res.values[1], [0.5, 0.5]) + @test isapprox(res.values[2], [0, 0]) + end end - end - @testset "Result types observable not in instructions" begin - bell = bell_circ() - bell(Braket.Expectation, Braket.Observables.X(), 2) - bell(Braket.Variance, Braket.Observables.Y(), 3) - bell_qasm = ir(bell, Val(:OpenQASM)) - @test qubit_count(bell) == 4 - shots = SHOTS - device = DEVICE - @testset for task in (bell, ir(bell, Val(:JAQCD)), bell_qasm) - tol = get_tol(shots) - res = result(device(task, shots = shots)) - @test isapprox(res.values[1], 0, rtol = tol["rtol"], atol = tol["atol"]) - @test isapprox(res.values[2], 1, rtol = tol["rtol"], atol = tol["atol"]) + @testset "Result types observable not in instructions" begin + bell = bell_circ() + bell(Braket.Expectation, Braket.Observables.X(), 2) + bell(Braket.Variance, Braket.Observables.Y(), 3) + bell_qasm = ir(bell, Val(:OpenQASM)) + @test qubit_count(bell) == 4 + shots = SHOTS + device = DEVICE + @testset for task in (bell, ir(bell, Val(:JAQCD)), bell_qasm) + tol = get_tol(shots) + res = result(device(task, shots = shots)) + @test isapprox(res.values[1], 0, rtol = tol["rtol"], atol = tol["atol"]) + @test isapprox(res.values[2], 1, rtol = tol["rtol"], atol = tol["atol"]) + end end end end diff --git a/test/test_dm_simulator.jl b/test/test_dm_simulator.jl index 85ced26..c76e062 100644 --- a/test/test_dm_simulator.jl +++ b/test/test_dm_simulator.jl @@ -1,6 +1,6 @@ -using Test, LinearAlgebra, BraketSimulator, DataStructures +using Test, Logging, LinearAlgebra, BraketSimulator, DataStructures -LARGE_TESTS = get(ENV, "BRAKET_SV_LARGE_TESTS", false) +LARGE_TESTS = get(ENV, "BRAKET_SIM_LARGE_TESTS", "false") == "true" @testset "Density matrix simulator" begin sx = ComplexF64[0 1; 1 0] @@ -358,7 +358,7 @@ LARGE_TESTS = get(ENV, "BRAKET_SV_LARGE_TESTS", false) end return qft_ops end - max_qc = LARGE_TESTS ? 16 : 10 + max_qc = 10 @testset "Qubit count $qubit_count" for qubit_count in 2:max_qc simulation = DensityMatrixSimulator(qubit_count, 0) operations = qft_circuit_operations(qubit_count) @@ -392,11 +392,14 @@ LARGE_TESTS = get(ENV, "BRAKET_SV_LARGE_TESTS", false) shots = 1000 jl_ghz = [make_ghz(num_qubits) for ix in 1:n_circuits] jl_sim = DensityMatrixSimulator(num_qubits, 0); - results = BraketSimulator.simulate(jl_sim, jl_ghz, shots) - for (r_ix, r) in enumerate(results) - @test length(r.measurements) == shots - @test 400 < count(m->m == fill(0, num_qubits), r.measurements) < 600 - @test 400 < count(m->m == fill(1, num_qubits), r.measurements) < 600 + # suppress warnings about noise + with_logger(NullLogger()) do + results = BraketSimulator.simulate(jl_sim, jl_ghz, shots) + for (r_ix, r) in enumerate(results) + @test length(r.measurements) == shots + @test 400 < count(m->m == fill(0, num_qubits), r.measurements) < 600 + @test 400 < count(m->m == fill(1, num_qubits), r.measurements) < 600 + end end end @testset "similar, copy and copyto!" begin diff --git a/test/test_gates.jl b/test/test_gates.jl index ddedd65..9ccd7b9 100644 --- a/test/test_gates.jl +++ b/test/test_gates.jl @@ -34,6 +34,7 @@ T_mat = round.(reduce(hcat, [[1.0, 0], [0, 0.70710678 + 0.70710678im]]), digits= @test BraketSimulator.Parametrizable(g) == BraketSimulator.NonParametrized() @test BraketSimulator.parameters(g) == BraketSimulator.FreeParameter[] @test BraketSimulator.n_angles(g) == 0 + @test BraketSimulator.angles(g) == () @test BraketSimulator.qubit_count(g) == 1 end @testset for g in (BraketSimulator.CNot(), @@ -73,6 +74,7 @@ T_mat = round.(reduce(hcat, [[1.0, 0], [0, 0.70710678 + 0.70710678im]]), digits= α = BraketSimulator.FreeParameter(:α) β = BraketSimulator.FreeParameter(:β) γ = BraketSimulator.FreeParameter(:γ) + @test copy(α) === α @testset for (angle1, angle2, angle3) in ((rand(), rand(), rand()), (π, rand(), rand())) @testset for g in (BraketSimulator.Rx(angle1), BraketSimulator.Ry(angle1), BraketSimulator.Rz(angle1), BraketSimulator.PhaseShift(angle1)) @test qubit_count(g) == 1 diff --git a/test/test_noises.jl b/test/test_noises.jl index 6f2da32..59bc9d1 100644 --- a/test/test_noises.jl +++ b/test/test_noises.jl @@ -24,6 +24,8 @@ struct CustomNoise <: BraketSimulator.Noise end @test BraketSimulator.qubit_count(n) == 1 ix = BraketSimulator.Instruction(n, 0) @test BraketSimulator.Parametrizable(n) == BraketSimulator.Parametrized() + m = BraketSimulator.MultiQubitPauliChannel{1}(Dict("X"=>0.1, "Y"=>0.3)) + @test !(n == m) end @testset "noise = BraketSimulator.TwoQubitPauliChannel" begin n = BraketSimulator.TwoQubitPauliChannel(Dict("XX"=>0.1, "YY"=>0.2)) @@ -56,5 +58,11 @@ struct CustomNoise <: BraketSimulator.Noise end @test BraketSimulator.parameters(n) == BraketSimulator.FreeParameter[] @test BraketSimulator.Parametrizable(n) == BraketSimulator.NonParametrized() end + @testset "bind_value!" begin + n = BraketSimulator.BitFlip(BraketSimulator.FreeParameter(:p)) + @test BraketSimulator.bind_value!(n, Dict(:p=>0.1)) == BraketSimulator.BitFlip(0.1) + @test BraketSimulator.parameters(n) == BraketSimulator.FreeParameter[BraketSimulator.FreeParameter(:p)] + @test BraketSimulator.Parametrizable(n) == BraketSimulator.Parametrized() + end @test BraketSimulator.StructTypes.StructType(Noise) == BraketSimulator.StructTypes.AbstractType() end diff --git a/test/test_observables.jl b/test/test_observables.jl index 8bd33df..036433f 100644 --- a/test/test_observables.jl +++ b/test/test_observables.jl @@ -8,7 +8,7 @@ single_qubit_tests = [ (Observables.Y(), (BraketSimulator.Z(), BraketSimulator.S(), BraketSimulator.H()), BraketSimulator.PauliEigenvalues(Val(1))), (Observables.Z(), (), BraketSimulator.PauliEigenvalues(Val(1))), (Observables.I(), (), [1, 1]), - (Observables.HermitianObservable([1 1-im; 1+im -1]), (BraketSimulator.Unitary(herm_mat),), [-√3, √3]), + (Observables.HermitianObservable([1.0 1.0-im; 1.0+im -1.0]), (BraketSimulator.Unitary(herm_mat),), [-√3, √3]), ] @testset "Observables" begin @@ -24,11 +24,17 @@ single_qubit_tests = [ @test copy(obs) == obs if !(obs isa Observables.HermitianObservable) @test BraketSimulator.Observables.unscaled(2.0 * obs) == obs + else + scaled = 2.0 * obs + @test scaled.matrix == obs.matrix .* 2.0 end end + @test_throws ArgumentError("Observable of type \"c\" provided, only \"i\", \"x\", \"y\", \"z\", and \"h\" are valid.") BraketSimulator.StructTypes.constructfrom(Observables.Observable, "c") @testset "Tensor product of BraketSimulator.Pauli-like (with eigenvalues ±1) observables" begin tensor = Observables.TensorProduct(["h", "x", "z", "y"]) @test eigvals(tensor) == BraketSimulator.PauliEigenvalues(Val(4)) + @test BraketSimulator.StructTypes.constructfrom(Observables.Observable, ["h", "x", "z", "y"]) == tensor + @test eigvals(tensor)[[1, 2]] == [1, -1] actual_gates = BraketSimulator.basis_rotation_gates(tensor) @test length(actual_gates) == 4 diff --git a/test/test_openqasm3.jl b/test/test_openqasm3.jl index 01278d9..fbb2504 100644 --- a/test/test_openqasm3.jl +++ b/test/test_openqasm3.jl @@ -40,6 +40,9 @@ get_tol(shots::Int) = return ( @test_throws Quasar.QasmVisitorError("cannot visit expression $bad_expression.") visitor(bad_expression) @test_throws Quasar.QasmVisitorError("unable to evaluate expression $bad_expression") Quasar.evaluate(visitor, bad_expression) bad_expression = Quasar.QasmExpression(:invalid_expression, Quasar.QasmExpression(:error)) + @test_throws Quasar.QasmVisitorError("unable to evaluate qubits for expression $bad_expression.") Quasar.evaluate_qubits(visitor, bad_expression) + cast_expression = Quasar.QasmExpression(:cast, Quasar.QasmExpression(:type_expr, Int32), Quasar.QasmExpression(:float_literal, 2.0)) + @test_throws Quasar.QasmVisitorError("unable to evaluate cast expression $cast_expression") Quasar.evaluate(visitor, cast_expression) end @testset "Visitors and parents" begin qasm = """ @@ -88,7 +91,7 @@ get_tol(shots::Int) = return ( end bitvector = Quasar.SizedBitVector(Quasar.QasmExpression(:integer_literal, 10)) @test length(bitvector) == Quasar.QasmExpression(:integer_literal, 10) - @test size(bitvector) == (Quasar.QasmExpression(:integer_literal, 10),) + @test size(bitvector) == (Quasar.QasmExpression(:integer_literal, 10),) end @testset "Adder" begin sv_adder_qasm = """ @@ -200,6 +203,16 @@ get_tol(shots::Int) = return ( BraketSimulator.Instruction(BraketSimulator.Measure(), 0), BraketSimulator.Instruction(BraketSimulator.Measure(), 1)] end + @testset "Bare measure" begin + qasm = """ + qubit[2] q; + measure q; + """ + circuit = BraketSimulator.Circuit(qasm) + @test circuit.instructions == [BraketSimulator.Instruction(BraketSimulator.Measure(), 0), + BraketSimulator.Instruction(BraketSimulator.Measure(), 1)] + @test [qubit_count(ix.operator) for ix in circuit.instructions] == [1, 1] + end @testset "GPhase" begin qasm = """ qubit[2] qs; @@ -231,33 +244,55 @@ get_tol(shots::Int) = return ( @test simulation.state_vector ≈ sv @test circuit.result_types == [BraketSimulator.Amplitude(["00", "01", "10", "11"])] end - @testset "Numbers" begin + @testset "Numbers $qasm_str" for (qasm_str, var_name, output_val) in (("float[32] a = 1.24e-3;", "a", 1.24e-3), + ("complex[float] b = 1-0.23im;", "b", 1-0.23im), + ("const bit c = \"0\";", "c", falses(1)), + ("bool d = false;", "d", false), + ("complex[float] e = -0.23+2im;", "e", -0.23+2im), + ("uint f = 0x123456789abcdef;", "f", 0x123456789abcdef), + ("int g = 0o010;", "g", 0o010), + ("float[64] h = 2*π;", "h", 2π), + ("float[64] i = τ/2;", "i", Float64(π)), + ) + + qasm = """ - float[32] a = 1.24e-3; - complex[float] b = 1-0.23im; - const bit c = "0"; - bool d = false; - complex[float] e = -0.23+2im; - uint f = 0x123456789abcdef; - int g = 0o010; + $qasm_str """ parsed = parse_qasm(qasm) visitor = QasmProgramVisitor() visitor(parsed) - @test visitor.classical_defs["a"].val == 1.24e-3 - @test visitor.classical_defs["b"].val == 1-0.23im - @test visitor.classical_defs["c"].val == falses(1) - @test visitor.classical_defs["d"].val == false - @test visitor.classical_defs["e"].val == -0.23 + 2im - @test visitor.classical_defs["f"].val == 0x123456789abcdef - @test visitor.classical_defs["g"].val == 0o010 + @test visitor.classical_defs[var_name].val == output_val + end + @testset "Integers next to irrationals" begin + qasm = """ + float[64] h = 2π; + """ + @test_throws Quasar.QasmParseError parse_qasm(qasm) + try + parse_qasm(qasm) + catch e + msg = sprint(showerror, e) + @test startswith(msg, "QasmParseError: expressions of form 2π are not supported -- you must separate the terms with a * operator.") + end + end + @testset "Bad classical type" begin + qasm = """ + angle[64] h = 2π; + """ + @test_throws Quasar.QasmParseError parse_qasm(qasm) end @testset "Physical qubits" begin qasm = """ h \$0; cnot \$0, \$1; """ - @test BraketSimulator.Circuit(qasm).instructions == [BraketSimulator.Instruction(BraketSimulator.H(), 0), BraketSimulator.Instruction(BraketSimulator.CNot(), [0, 1])] + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + circ = BraketSimulator.Circuit(visitor) + @test BraketSimulator.qubit_count(circ) == 2 + @test circ.instructions == [BraketSimulator.Instruction(BraketSimulator.H(), 0), BraketSimulator.Instruction(BraketSimulator.CNot(), [0, 1])] end @testset "For loop and subroutines" begin qasm_str = """ @@ -299,7 +334,7 @@ get_tol(shots::Int) = return ( ] end @testset "For Loop" begin - qasm = """ + qasm_with_scope = """ int[8] x = 0; int[8] y = -100; int[8] ten = 10; @@ -312,13 +347,7 @@ get_tol(shots::Int) = return ( y += i; } """ - parsed = parse_qasm(qasm) - visitor = QasmProgramVisitor() - visitor(parsed) - @test visitor.classical_defs["x"].val == sum((0, 2, 4, 6)) - @test visitor.classical_defs["y"].val == sum((-100, 2, 4, 6)) - # without scoping { } - qasm = """ + qasm_no_scope = """ int[8] x = 0; int[8] y = -100; int[8] ten = 10; @@ -326,14 +355,16 @@ get_tol(shots::Int) = return ( for uint[8] i in [0:2:ten - 3] x += i; for int[8] i in {2, 4, 6} y += i; """ - parsed = parse_qasm(qasm) - visitor = QasmProgramVisitor() - visitor(parsed) - @test visitor.classical_defs["x"].val == sum((0, 2, 4, 6)) - @test visitor.classical_defs["y"].val == sum((-100, 2, 4, 6)) + @testset "$label" for (label, qasm) in (("With scoping {}", qasm_with_scope), ("Without scoping {}", qasm_no_scope)) + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.classical_defs["x"].val == sum((0, 2, 4, 6)) + @test visitor.classical_defs["y"].val == sum((-100, 2, 4, 6)) + end end @testset "While Loop" begin - qasm = """ + qasm_with_scope = """ int[8] x = 0; int[8] i = 0; @@ -342,20 +373,17 @@ get_tol(shots::Int) = return ( i += 1; } """ - parsed = parse_qasm(qasm) - visitor = QasmProgramVisitor() - visitor(parsed) - @test visitor.classical_defs["x"].val == sum(0:6) - # without scoping { } - qasm = """ - int[8] i = 0; + qasm_no_scope = """ + int[8] x = 0; - while (i < 7) i += 1; + while (x < 7) x += 1; """ - parsed = parse_qasm(qasm) - visitor = QasmProgramVisitor() - visitor(parsed) - @test visitor.classical_defs["i"].val == 7 + @testset "$label" for (label, qasm, val) in (("With scoping {}", qasm_with_scope, sum(0:6)), ("Without scoping {}", qasm_no_scope, 7)) + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.classical_defs["x"].val == val + end end @testset "Break and continue" begin @testset for str in ("continue;", "{ continue; }") @@ -423,55 +451,41 @@ get_tol(shots::Int) = return ( @test_throws Quasar.QasmVisitorError("$str statement encountered outside a loop.") visitor(parsed) end end + @testset "Builtin functions $fn" for (fn, type, arg, result) in (("arccos", "float[64]", 1, acos(1)), + ("arcsin", "float[64]", 1, asin(1)), + ("arctan", "float[64]", 1, atan(1)), + ("ceiling", "int[64]", "π", 4), + ("floor", "int[64]", "π", 3), + ("mod", "int[64]", "4, 3", 1), + ("mod", "float[64]", "5.2, 2.5", mod(5.2, 2.5)), + ("cos", "float[64]", 1, cos(1)), + ("sin", "float[64]", 1, sin(1)), + ("tan", "float[64]", 1, tan(1)), + ("sqrt", "float[64]", 2, sqrt(2)), + ("exp", "float[64]", 2, exp(2)), + ("log", "float[64]", "ℇ", log(ℯ)), + ("popcount", "int[64]", "true", 1), + ("popcount", "int[64]", "\"1001110\"", 4), + ("popcount", "int[64]", "78", 4), + ("popcount", "int[64]", "0x78", 4), + ) + qasm = """ + const $type $(fn)_result = $fn($arg); + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.classical_defs["$(fn)_result"].val == result + end @testset "Builtin functions" begin qasm = """ - const float[64] arccos_result = arccos(1); - const float[64] arcsin_result = arcsin(1); - const float[64] arctan_result = arctan(1); - const int[64] ceiling_result = ceiling(π); - const float[64] cos_result = cos(1); - const float[64] exp_result = exp(2); - const int[64] floor_result = floor(π); - const float[64] log_result = log(ℇ); - const int[64] mod_int_result = mod(4, 3); - const float[64] mod_float_result = mod(5.2, 2.5); - const int[64] popcount_bool_result = popcount(true); - const int[64] popcount_bit_result = popcount("1001110"); - const int[64] popcount_int_result = popcount(78); - const int[64] popcount_uint_result = popcount(0x78); - bit[2] bitvec; - bitvec = "11"; + bit[2] bitvec = "11"; const int[64] popcount_bitvec_result = popcount(bitvec); - // parser gets confused by pow - // const int[64] pow_int_result = pow(3, 3); - // const float[64] pow_float_result = pow(2.5, 2.5); - // add rotl, rotr - const float[64] sin_result = sin(1); - const float[64] sqrt_result = sqrt(2); - const float[64] tan_result = tan(1); """ parsed = parse_qasm(qasm) visitor = QasmProgramVisitor() visitor(parsed) - @test visitor.classical_defs["arccos_result"].val == acos(1) - @test visitor.classical_defs["arcsin_result"].val == asin(1) - @test visitor.classical_defs["arctan_result"].val == atan(1) - @test visitor.classical_defs["ceiling_result"].val == 4 - @test visitor.classical_defs["cos_result"].val == cos(1) - @test visitor.classical_defs["exp_result"].val == exp(2) - @test visitor.classical_defs["floor_result"].val == 3 - @test visitor.classical_defs["log_result"].val == 1.0 - @test visitor.classical_defs["mod_int_result"].val == 1 - @test visitor.classical_defs["mod_float_result"].val == mod(5.2, 2.5) - @test visitor.classical_defs["popcount_bool_result"].val == 1 - @test visitor.classical_defs["popcount_bit_result"].val == 4 - @test visitor.classical_defs["popcount_int_result"].val == 4 - @test visitor.classical_defs["popcount_uint_result"].val == 4 @test visitor.classical_defs["popcount_bitvec_result"].val == 2 - @test visitor.classical_defs["sin_result"].val == sin(1) - @test visitor.classical_defs["sqrt_result"].val == sqrt(2) - @test visitor.classical_defs["tan_result"].val == tan(1) - @testset "Symbolic" begin qasm = """ input float x; @@ -513,6 +527,11 @@ get_tol(shots::Int) = return ( end @test parsed_circ.instructions == c.instructions end + @testset "popcount!" begin + @test Quasar.popcount() == 0 + @test Quasar.popcount(vcat(trues(3), falses(2))) == 3 + @test Quasar.popcount("01010101") == 4 + end end @testset "Bad pragma" begin qasm = """ @@ -521,6 +540,26 @@ get_tol(shots::Int) = return ( """ @test_throws Quasar.QasmParseError parse_qasm(qasm) end + @testset "Barrier" begin + qasm = """ + qubit[4] q; + x q[0]; + barrier q[0]; + """ + @test_warn "barrier expression encountered -- currently `barrier` is a no-op" parse_qasm(qasm) + end + @testset "Stretch" begin + qasm = """ + stretch a; + """ + @test_warn "stretch expression encountered -- currently `stretch` is a no-op" parse_qasm(qasm) + end + @testset "Duration" begin + qasm = """ + duration a = 10μs; + """ + @test_warn "duration expression encountered -- currently `duration` is a no-op" parse_qasm(qasm) + end @testset "Reset" begin qasm = """ qubit[4] q; @@ -545,95 +584,104 @@ get_tol(shots::Int) = return ( visitor = QasmProgramVisitor() @test_throws Quasar.QasmVisitorError("gate x does not accept arguments but arguments were provided.") visitor(parsed) end - #=@testset "Adjoint Gradient pragma" begin + @testset "Adjoint Gradient pragma" begin qasm = """ input float theta; qubit[4] q; rx(theta) q[0]; #pragma braket result adjoint_gradient expectation(-6 * y(q[0]) @ i(q[1]) + 0.75 * y(q[2]) @ z(q[3])) theta """ - circuit = BraketSimulator.Circuit(qasm, Dict("theta"=>0.1)) + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor(Dict("theta"=>0.1)) + @test_throws Quasar.QasmVisitorError("Result type adjoint_gradient is not supported.", "TypeError") visitor(parsed) + #= circuit = BraketSimulator.Circuit(qasm, Dict("theta"=>0.1)) θ = FreeParameter("theta") obs = -2 * BraketSimulator.Observables.Y() * (3 * BraketSimulator.Observables.I()) + 0.75 * BraketSimulator.Observables.Y() * BraketSimulator.Observables.Z() @test circuit.result_types[1].observable == obs @test circuit.result_types[1].targets == [QubitSet([0, 1]), QubitSet([2, 3])] - @test circuit.result_types[1].parameters == ["theta"] - end=# + @test circuit.result_types[1].parameters == ["theta"]=# + end @testset "Assignment operators" begin - qasm = """ - int[16] x; - bit[4] xs; + @testset "Op: $op" for (op, initial, arg, final) in (("+", 0, 1, 1), ("*", 1, 2, 2), ("/", 2, 2, 1), ("-", 1, 5, -4)) + qasm = """ + int[16] x; + x = $initial; + x $op= $arg; + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.classical_defs["x"].val == final + end + @testset "Op: $op" for (op, initial, arg, final) in (("|", "0000", "11", BitVector([0,0,1,1])), + ("&", "0010", "11", BitVector([0,0,1,0])), + ("^", "0001", "10", BitVector([0,0,1,1])), + ) + qasm = """ + bit[4] xs = \"$initial\"; + xs[2:] $op= \"$arg\"; + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.classical_defs["xs"].val == final + end + end + @testset "Binary bit operators $op" for (op, result) in (("&", BitVector([0,1,0,0])), + ("|", BitVector([1,1,0,1])), + ("^", BitVector([1,0,0,1])), + (">", false), + ("<", true), + (">=", false), + ("<=", true), + ("==", false), + ("!=", true), + ) - x = 0; - xs = "0000"; + qasm = """ + bit[4] x = "0101"; + bit[4] y = "1100"; - x += 1; // 1 - x *= 2; // 2 - x /= 2; // 1 - x -= 5; // -4 + bit[4] result = x $op y; + """ + parsed = parse_qasm(qasm) + visitor = QasmProgramVisitor() + visitor(parsed) + @test visitor.classical_defs["x"].val == BitVector([0,1,0,1]) + @test visitor.classical_defs["y"].val == BitVector([1,1,0,0]) + @test visitor.classical_defs["result"].val == result + end + @testset "Binary bit operators $op" for (op, arg1, arg2, result) in (("<<", "x", 2, BitVector([0,1,0,0])), + (">>", "y", 2, BitVector([0,0,1,1])), + ) + qasm = """ + bit[4] x = "0101"; + bit[4] y = "1100"; - xs[2:] |= "11"; + bit[4] result = $arg1 $op $arg2; """ parsed = parse_qasm(qasm) visitor = QasmProgramVisitor() visitor(parsed) - @test visitor.classical_defs["x"].val == -4 - @test visitor.classical_defs["xs"].val == BitVector([0,0,1,1]) + @test visitor.classical_defs["result"].val == result end - @testset "Bit operators" begin + @testset "Unary bit operators $op" for (op, typ, arg, result) in (("~", "bit[4]", "x", BitVector([1,0,1,0])), + ("!", "bit", "x", false), + ("!", "bit", "x << 4", true) + ) + qasm = """ - bit[4] and; - bit[4] or; - bit[4] xor; - bit[4] lshift; - bit[4] rshift; - bit[4] flip; - bit gt; - bit lt; - bit ge; - bit le; - bit eq; - bit neq; bit not; bit not_zero; bit[4] x = "0101"; - bit[4] y = "1100"; - - and = x & y; - or = x | y; - xor = x ^ y; - lshift = x << 2; - rshift = y >> 2; - flip = ~x; - gt = x > y; - lt = x < y; - ge = x >= y; - le = x <= y; - eq = x == y; - neq = x != y; - not = !x; - not_zero = !(x << 4); + $typ result = $(op)$(arg); """ parsed = parse_qasm(qasm) visitor = QasmProgramVisitor() visitor(parsed) @test visitor.classical_defs["x"].val == BitVector([0,1,0,1]) - @test visitor.classical_defs["y"].val == BitVector([1,1,0,0]) - @test visitor.classical_defs["and"].val == BitVector([0,1,0,0]) - @test visitor.classical_defs["or"].val == BitVector([1,1,0,1]) - @test visitor.classical_defs["xor"].val == BitVector([1,0,0,1]) - @test visitor.classical_defs["lshift"].val == BitVector([0,1,0,0]) - @test visitor.classical_defs["rshift"].val == BitVector([0,0,1,1]) - @test visitor.classical_defs["flip"].val == BitVector([1,0,1,0]) - @test visitor.classical_defs["gt"].val == false - @test visitor.classical_defs["lt"].val == true - @test visitor.classical_defs["ge"].val == false - @test visitor.classical_defs["le"].val == true - @test visitor.classical_defs["eq"].val == false - @test visitor.classical_defs["neq"].val == true - @test visitor.classical_defs["not"].val == false - @test visitor.classical_defs["not_zero"].val == true + @test visitor.classical_defs["result"].val == result end @testset "End statement" begin qasm = """ @@ -757,14 +805,16 @@ get_tol(shots::Int) = return ( BraketSimulator.evolve!(simulation, circuit.instructions) @test simulation.state_vector ≈ [0.5, 0.5, 0.5, 0.5im] end - @testset "Simple Pow" begin + @testset "Simple Pow" for (orig_gate, sqrt_gate, sqrt_ix) in (("z", "s", BraketSimulator.S()), + ("x", "v", BraketSimulator.V()), + ) custom_qasm = """ qubit q1; qubit q2; h q1; h q2; - pow(1/2) @ z q1; // s + pow(1/2) @ $orig_gate q1; """ standard_qasm = """ qubit q1; @@ -772,9 +822,11 @@ get_tol(shots::Int) = return ( h q1; h q2; - s q1; // s + $sqrt_gate q1; """ - canonical_ixs = [BraketSimulator.Instruction(BraketSimulator.H(), 0), BraketSimulator.Instruction(BraketSimulator.H(), 1), BraketSimulator.Instruction(BraketSimulator.S(), 0)] + canonical_ixs = [BraketSimulator.Instruction(BraketSimulator.H(), 0), + BraketSimulator.Instruction(BraketSimulator.H(), 1), + BraketSimulator.Instruction(sqrt_ix, 0)] canonical_simulation = BraketSimulator.StateVectorSimulator(2, 0) BraketSimulator.evolve!(canonical_simulation, canonical_ixs) @testset "$title" for (title, qasm) in (("standard", standard_qasm), ("custom", custom_qasm)) @@ -1014,40 +1066,42 @@ get_tol(shots::Int) = return ( collapsed = prod(BraketSimulator.matrix_rep(ix.operator) for ix in circuit.instructions) @test collapsed ≈ diagm(ones(ComplexF64, 2^BraketSimulator.qubit_count(circuit))) end - @testset "Noise" begin + @testset "Noise pragmas $noise, $qubits" for (noise, arg, qubits, ix) in (("bit_flip", ".5", "qs[1]", BraketSimulator.Instruction(BraketSimulator.BitFlip(0.5), 1)), + ("phase_flip", ".5", "qs[0]", BraketSimulator.Instruction(BraketSimulator.PhaseFlip(0.5), 0)), + ("pauli_channel", ".1, .2, .3", "qs[0]", BraketSimulator.Instruction(BraketSimulator.PauliChannel(0.1, 0.2, 0.3), 0)), + ("depolarizing", ".5", "qs[0]", BraketSimulator.Instruction(BraketSimulator.Depolarizing(0.5), 0)), + ("two_qubit_depolarizing", ".9", "qs", BraketSimulator.Instruction(BraketSimulator.TwoQubitDepolarizing(0.9), [0, 1])), + ("two_qubit_depolarizing", ".7", "qs[1], qs[0]", BraketSimulator.Instruction(BraketSimulator.TwoQubitDepolarizing(0.7), [1, 0])), + ("two_qubit_dephasing", ".6", "qs", BraketSimulator.Instruction(BraketSimulator.TwoQubitDephasing(0.6), [0, 1])), + ("amplitude_damping", ".2", "qs[0]", BraketSimulator.Instruction(BraketSimulator.AmplitudeDamping(0.2), 0)), + ("generalized_amplitude_damping", ".2, .3", "qs[1]", BraketSimulator.Instruction(BraketSimulator.GeneralizedAmplitudeDamping(0.2, 0.3), 1)), + ("phase_damping", ".4", "qs[0]", BraketSimulator.Instruction(BraketSimulator.PhaseDamping(0.4), 0)), + ("kraus", "[[0.9486833im, 0], [0, 0.9486833im]], [[0, 0.31622777], [0.31622777, 0]]", "qs[0]", BraketSimulator.Instruction(BraketSimulator.Kraus([[0.9486833im 0; 0 0.9486833im], [0 0.31622777; 0.31622777 0]]), 0)), + ("kraus", "[[0.9486832980505138, 0, 0, 0], [0, 0.9486832980505138, 0, 0], [0, 0, 0.9486832980505138, 0], [0, 0, 0, 0.9486832980505138]], [[0, 0.31622776601683794, 0, 0], [0.31622776601683794, 0, 0, 0], [0, 0, 0, 0.31622776601683794], [0, 0, 0.31622776601683794, 0]]", "qs[{1, 0}]", BraketSimulator.Instruction(BraketSimulator.Kraus([[0.9486832980505138 0 0 0; 0 0.9486832980505138 0 0; 0 0 0.9486832980505138 0; 0 0 0 0.9486832980505138], [0 0.31622776601683794 0 0; 0.31622776601683794 0 0 0; 0 0 0 0.31622776601683794; 0 0 0.31622776601683794 0]]), [1, 0])), + ) + + qasm = """ qubit[2] qs; - #pragma braket noise bit_flip(.5) qs[1] - #pragma braket noise phase_flip(.5) qs[0] - #pragma braket noise pauli_channel(.1, .2, .3) qs[0] - #pragma braket noise depolarizing(.5) qs[0] - #pragma braket noise two_qubit_depolarizing(.9) qs - #pragma braket noise two_qubit_depolarizing(.7) qs[1], qs[0] - #pragma braket noise two_qubit_dephasing(.6) qs - #pragma braket noise amplitude_damping(.2) qs[0] - #pragma braket noise generalized_amplitude_damping(.2, .3) qs[1] - #pragma braket noise phase_damping(.4) qs[0] - #pragma braket noise kraus([[0.9486833im, 0], [0, 0.9486833im]], [[0, 0.31622777], [0.31622777, 0]]) qs[0] - #pragma braket noise kraus([[0.9486832980505138, 0, 0, 0], [0, 0.9486832980505138, 0, 0], [0, 0, 0.9486832980505138, 0], [0, 0, 0, 0.9486832980505138]], [[0, 0.31622776601683794, 0, 0], [0.31622776601683794, 0, 0, 0], [0, 0, 0, 0.31622776601683794], [0, 0, 0.31622776601683794, 0]]) qs[{1, 0}] + #pragma braket noise $noise($arg) $qubits """ circuit = BraketSimulator.Circuit(qasm) - inst_list = [BraketSimulator.Instruction(BraketSimulator.BitFlip(0.5), [1]), - BraketSimulator.Instruction(BraketSimulator.PhaseFlip(0.5), [0]), - BraketSimulator.Instruction(BraketSimulator.PauliChannel(0.1, 0.2, 0.3), [0]), - BraketSimulator.Instruction(BraketSimulator.Depolarizing(0.5), [0]), - BraketSimulator.Instruction(BraketSimulator.TwoQubitDepolarizing(0.9), [0, 1]), - BraketSimulator.Instruction(BraketSimulator.TwoQubitDepolarizing(0.7), [1, 0]), - BraketSimulator.Instruction(BraketSimulator.TwoQubitDephasing(0.6), [0, 1]), - BraketSimulator.Instruction(BraketSimulator.AmplitudeDamping(0.2), [0]), - BraketSimulator.Instruction(BraketSimulator.GeneralizedAmplitudeDamping(0.2, 0.3), [1]), - BraketSimulator.Instruction(BraketSimulator.PhaseDamping(0.4), [0]), - BraketSimulator.Instruction(BraketSimulator.Kraus([[0.9486833im 0; 0 0.9486833im], [0 0.31622777; 0.31622777 0]]), [0]), - BraketSimulator.Instruction(BraketSimulator.Kraus([diagm(fill(√0.9, 4)), √0.1*kron([1.0 0.0; 0.0 1.0], [0.0 1.0; 1.0 0.0])]), [1, 0]), - ] - @testset "Operator $(typeof(ix.operator)), target $(ix.target)" for (cix, ix) in zip(circuit.instructions, inst_list) - @test cix.operator == ix.operator - @test cix.target == ix.target + @test only(circuit.instructions) == ix + end + @testset "StandardObservable/target mismatch" begin + @testset "Observable $obs, result type $rt, qubits $qubits" for obs in ("h", "x", "y", "z"), + rt in ("expectation", "variance", "sample"), + qubits in ("q[1:2]", "q[0:1]", "q[{0, 2}]") + qasm = """ + qubit[3] q; + i q; + + #pragma braket result $rt $obs($qubits) + """ + parsed = parse_qasm(qasm) + visitor = Quasar.QasmProgramVisitor() + @test_throws Quasar.QasmVisitorError("Standard observable target must be exactly 1 qubit.", "ValueError") visitor(parsed) end end @testset "Basis rotations" begin @@ -1112,7 +1166,21 @@ get_tol(shots::Int) = return ( end end @testset "Unitary pragma" begin - qasm = """ + standard_qasm = """ + qubit[3] q; + + x q[0]; + h q[1]; + + t q[0]; + ti q[0]; + + h q[1]; + h q[1]; + + ccnot q; + """ + unitary_qasm = """ qubit[3] q; x q[0]; @@ -1130,10 +1198,13 @@ get_tol(shots::Int) = return ( // unitary pragma for ccnot gate #pragma braket unitary([[1.0, 0, 0, 0, 0, 0, 0, 0], [0, 1.0, 0, 0, 0, 0, 0, 0], [0, 0, 1.0, 0, 0, 0, 0, 0], [0, 0, 0, 1.0, 0, 0, 0, 0], [0, 0, 0, 0, 1.0, 0, 0, 0], [0, 0, 0, 0, 0, 1.0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1.0], [0, 0, 0, 0, 0, 0, 1.0, 0]]) q """ - circuit = BraketSimulator.Circuit(qasm) - simulation = BraketSimulator.StateVectorSimulator(3, 0) + circuit = BraketSimulator.Circuit(unitary_qasm) + ref_circuit = BraketSimulator.Circuit(standard_qasm) + simulation = BraketSimulator.StateVectorSimulator(3, 0) + ref_sim = BraketSimulator.StateVectorSimulator(3, 0) BraketSimulator.evolve!(simulation, circuit.instructions) - @test simulation.state_vector ≈ [0, 0, 0, 0, 0.70710678, 0, 0, 0.70710678] + BraketSimulator.evolve!(ref_sim, ref_circuit.instructions) + @test simulation.state_vector ≈ ref_sim.state_vector end @testset "Output" begin qasm = """ @@ -1166,6 +1237,27 @@ get_tol(shots::Int) = return ( qubit[3] qs; qubit q; + x qs[{0, 2}]; + h q; + cphaseshift(1) qs, q; + phaseshift(-2) q; + ccnot qs; + """ + circuit = BraketSimulator.Circuit(qasm) + @test circuit.instructions[1] == BraketSimulator.Instruction(BraketSimulator.X(), 0) + @test circuit.instructions[2] == BraketSimulator.Instruction(BraketSimulator.X(), 2) + @test circuit.instructions[3] == BraketSimulator.Instruction(BraketSimulator.H(), 3) + @test circuit.instructions[4] == BraketSimulator.Instruction(BraketSimulator.CPhaseShift(1), [0, 3]) + @test circuit.instructions[5] == BraketSimulator.Instruction(BraketSimulator.CPhaseShift(1), [1, 3]) + @test circuit.instructions[6] == BraketSimulator.Instruction(BraketSimulator.CPhaseShift(1), [2, 3]) + @test circuit.instructions[7] == BraketSimulator.Instruction(BraketSimulator.PhaseShift(-2), 3) + @test circuit.instructions[8] == BraketSimulator.Instruction(BraketSimulator.CCNot(), [0, 1, 2]) + end + @testset "Gate on qubit registers" begin + qasm = """ + qubit[3] qs; + qubit q; + x qs[{0, 2}]; h q; cphaseshift(1) qs, q; @@ -1370,7 +1462,7 @@ get_tol(shots::Int) = return ( circuit = BraketSimulator.Circuit(qasm) program = BraketSimulator.Program(circuit) simulation = BraketSimulator.StateVectorSimulator(2, 0) - @test_throws BraketSimulator.ValidationError BraketSimulator.simulate(simulation, program, 2, 0) + @test_throws BraketSimulator.ValidationError BraketSimulator.simulate(simulation, program, 0) end @testset "Referencing invalid qubit(s)" begin source = """ diff --git a/test/test_python_ext.jl b/test/test_python_ext.jl index 13b5ff6..4a2befe 100644 --- a/test/test_python_ext.jl +++ b/test/test_python_ext.jl @@ -70,6 +70,26 @@ using Test, JSON3, PythonCall, BraketSimulator for oq3_result in oq3_results @test oq3_result isa String end + simple_bell_qasm = """ + h \$0; + cy \$0, \$1; + #pragma braket result amplitude '00', '01', '10', '11' + """ + sv_simulator = StateVectorSimulator(2, 0) + oq3_results = BraketSimulator.simulate(sv_simulator, [simple_bell_qasm], [Dict{String, Any}()], 0) + for oq3_result in oq3_results + @test oq3_result isa String + end + simple_bell_qasm = """ + h \$0; + cy \$0, \$1; + #pragma braket result expectation z(\$0) + """ + sv_simulator = StateVectorSimulator(2, 0) + oq3_results = BraketSimulator.simulate(sv_simulator, [simple_bell_qasm], [Dict{String, Any}()], 0) + for oq3_result in oq3_results + @test oq3_result isa String + end end end end diff --git a/test/test_result_types.jl b/test/test_result_types.jl index 1dde577..bb1cfe0 100644 --- a/test/test_result_types.jl +++ b/test/test_result_types.jl @@ -2,6 +2,8 @@ using Test, BraketSimulator, LinearAlgebra const NUM_SAMPLES = 1000 +LARGE_TESTS = get(ENV, "BRAKET_SIM_LARGE_TESTS", "false") == "true" + observables_testdata = [ ( BraketSimulator.Observables.TensorProduct([BraketSimulator.Observables.X(), BraketSimulator.Observables.H()]), @@ -215,4 +217,13 @@ all_qubit_observables_testdata = [ dm = BraketSimulator.calculate(BraketSimulator.DensityMatrix(reverse(0:3)), sim) @test dm ≈ kron(adjoint(state_vector()), state_vector()) end + @testset "Direct sampling" begin + if LARGE_TESTS + sim = BraketSimulator.StateVectorSimulator(30, 10) + sim.state_vector[1] = 1/√2 + sim.state_vector[end] = 1/√2 + shot_results = BraketSimulator.samples(sim) + @test all(s->s ∈ [0, 2^30-1], shot_results) + end + end end diff --git a/test/test_sv_simulator.jl b/test/test_sv_simulator.jl index 1047f6b..e12314f 100644 --- a/test/test_sv_simulator.jl +++ b/test/test_sv_simulator.jl @@ -1,6 +1,6 @@ using Test, BraketSimulator, DataStructures -LARGE_TESTS = get(ENV, "BRAKET_SV_LARGE_TESTS", false) +LARGE_TESTS = get(ENV, "BRAKET_SIM_LARGE_TESTS", "false") == "true" @testset "State vector simulator" begin @testset "Simple circuits nq: $qubit_count $instructions" for ( @@ -323,7 +323,7 @@ LARGE_TESTS = get(ENV, "BRAKET_SV_LARGE_TESTS", false) end return qft_ops end - max_qc = LARGE_TESTS ? 32 : 20 + max_qc = 20 @testset "Qubit count $qubit_count" for qubit_count in 4:max_qc simulation = StateVectorSimulator(qubit_count, 0) operations = qft_circuit_operations(qubit_count) diff --git a/test/test_validation.jl b/test/test_validation.jl index 64ee61e..b4ac9cc 100644 --- a/test/test_validation.jl +++ b/test/test_validation.jl @@ -20,4 +20,1366 @@ using Test, BraketSimulator BraketSimulator.add_instruction!(c, BraketSimulator.Instruction(BraketSimulator.BitFlip(0.1), 0)) @test_throws ValidationError BraketSimulator._validate_ir_instructions_compatibility(sim, c, Val(:OpenQASM)) @test_throws ValidationError BraketSimulator._validate_ir_instructions_compatibility(sim, c, Val(:JAQCD)) + + mat = rand(4, 4) + obs = BraketSimulator.Observables.HermitianObservable(mat + mat') + @test_throws ValidationError("only single-qubit Hermitian observables can be applied to all qubits.", "ValueError") BraketSimulator._combine_obs_and_targets(obs, collect(0:12)) + @testset "Observable compatibility for shots>0 OpenQASM" begin + @testset "compatible observables" begin + sim = StateVectorSimulator(2, 0) + c = BraketSimulator.Circuit() + BraketSimulator.add_instruction!(c, BraketSimulator.Instruction(BraketSimulator.H(), 0)) + BraketSimulator.add_instruction!(c, BraketSimulator.Instruction(BraketSimulator.H(), 1)) + push!(c.result_types, BraketSimulator.Expectation(BraketSimulator.Observables.Z() * BraketSimulator.Observables.X(), [0, 1])) + push!(c.result_types, BraketSimulator.Variance(BraketSimulator.Observables.Y(), 0)) + @test_throws ValidationError BraketSimulator._verify_openqasm_shots_observables(c, 2) + c = BraketSimulator.Circuit() + BraketSimulator.add_instruction!(c, BraketSimulator.Instruction(BraketSimulator.H(), 0)) + BraketSimulator.add_instruction!(c, BraketSimulator.Instruction(BraketSimulator.H(), 1)) + push!(c.result_types, BraketSimulator.Expectation(BraketSimulator.Observables.Z() * BraketSimulator.Observables.X(), [0, 1])) + push!(c.result_types, BraketSimulator.Variance(BraketSimulator.Observables.Z(), 0)) + @test isnothing(BraketSimulator._verify_openqasm_shots_observables(c, 2)) + end + @testset "full PennyLane example" begin + # from a PennyLane VQE workload -- good example of a lot of + # compatible observables! + n_params = 146 + inputs = Dict("p_$pix"=>0.0 for pix in 0:n_params-1) + source = """ + OPENQASM 3.0; + input float p_0; + input float p_31; + input float p_117; + input float p_52; + input float p_103; + input float p_87; + input float p_46; + input float p_34; + input float p_49; + input float p_75; + input float p_35; + input float p_123; + input float p_1; + input float p_15; + input float p_73; + input float p_25; + input float p_93; + input float p_116; + input float p_13; + input float p_86; + input float p_23; + input float p_4; + input float p_32; + input float p_107; + input float p_119; + input float p_63; + input float p_89; + input float p_5; + input float p_111; + input float p_127; + input float p_77; + input float p_53; + input float p_97; + input float p_137; + input float p_48; + input float p_114; + input float p_17; + input float p_42; + input float p_40; + input float p_67; + input float p_126; + input float p_118; + input float p_96; + input float p_112; + input float p_136; + input float p_88; + input float p_33; + input float p_57; + input float p_82; + input float p_110; + input float p_129; + input float p_41; + input float p_80; + input float p_99; + input float p_139; + input float p_66; + input float p_44; + input float p_121; + input float p_69; + input float p_91; + input float p_131; + input float p_3; + input float p_140; + input float p_45; + input float p_81; + input float p_108; + input float p_94; + input float p_134; + input float p_37; + input float p_61; + input float p_100; + input float p_106; + input float p_27; + input float p_84; + input float p_109; + input float p_115; + input float p_141; + input float p_128; + input float p_85; + input float p_122; + input float p_78; + input float p_98; + input float p_138; + input float p_120; + input float p_92; + input float p_7; + input float p_132; + input float p_144; + input float p_18; + input float p_76; + input float p_90; + input float p_101; + input float p_130; + input float p_36; + input float p_16; + input float p_68; + input float p_26; + input float p_145; + input float p_79; + input float p_62; + input float p_104; + input float p_60; + input float p_19; + input float p_29; + input float p_105; + input float p_142; + input float p_71; + input float p_6; + input float p_58; + input float p_124; + input float p_11; + input float p_21; + input float p_50; + input float p_56; + input float p_9; + input float p_74; + input float p_43; + input float p_102; + input float p_125; + input float p_14; + input float p_59; + input float p_24; + input float p_95; + input float p_135; + input float p_64; + input float p_133; + input float p_113; + input float p_51; + input float p_65; + input float p_38; + input float p_72; + input float p_83; + input float p_70; + input float p_28; + input float p_12; + input float p_30; + input float p_22; + input float p_47; + input float p_54; + input float p_10; + input float p_20; + input float p_39; + input float p_55; + input float p_143; + input float p_8; + input float p_2; + qubit[8] q; + x q[0]; + x q[1]; + x q[2]; + x q[3]; + cnot q[4], q[5]; + cnot q[0], q[4]; + h q[5]; + h q[0]; + cnot q[4], q[5]; + cnot q[0], q[1]; + ry(p_0) q[1]; + ry(p_1) q[0]; + cnot q[0], q[5]; + h q[5]; + cnot q[5], q[1]; + ry(p_2) q[1]; + ry(p_3) q[0]; + cnot q[4], q[1]; + cnot q[4], q[0]; + ry(p_4) q[1]; + ry(p_5) q[0]; + cnot q[5], q[1]; + h q[5]; + cnot q[0], q[5]; + ry(p_6) q[1]; + ry(p_7) q[0]; + cnot q[0], q[1]; + cnot q[4], q[0]; + h q[0]; + h q[5]; + cnot q[0], q[4]; + cnot q[4], q[5]; + cnot q[4], q[7]; + cnot q[0], q[4]; + h q[7]; + h q[0]; + cnot q[4], q[7]; + cnot q[0], q[1]; + ry(p_8) q[1]; + ry(p_9) q[0]; + cnot q[0], q[7]; + h q[7]; + cnot q[7], q[1]; + ry(p_10) q[1]; + ry(p_11) q[0]; + cnot q[4], q[1]; + cnot q[4], q[0]; + ry(p_12) q[1]; + ry(p_13) q[0]; + cnot q[7], q[1]; + h q[7]; + cnot q[0], q[7]; + ry(p_14) q[1]; + ry(p_15) q[0]; + cnot q[0], q[1]; + cnot q[4], q[0]; + h q[0]; + h q[7]; + cnot q[0], q[4]; + cnot q[4], q[7]; + cnot q[5], q[6]; + cnot q[0], q[5]; + h q[6]; + h q[0]; + cnot q[5], q[6]; + cnot q[0], q[1]; + ry(p_16) q[1]; + ry(p_17) q[0]; + cnot q[0], q[6]; + h q[6]; + cnot q[6], q[1]; + ry(p_18) q[1]; + ry(p_19) q[0]; + cnot q[5], q[1]; + cnot q[5], q[0]; + ry(p_20) q[1]; + ry(p_21) q[0]; + cnot q[6], q[1]; + h q[6]; + cnot q[0], q[6]; + ry(p_22) q[1]; + ry(p_23) q[0]; + cnot q[0], q[1]; + cnot q[5], q[0]; + h q[0]; + h q[6]; + cnot q[0], q[5]; + cnot q[5], q[6]; + cnot q[6], q[7]; + cnot q[0], q[6]; + h q[7]; + h q[0]; + cnot q[6], q[7]; + cnot q[0], q[1]; + ry(p_24) q[1]; + ry(p_25) q[0]; + cnot q[0], q[7]; + h q[7]; + cnot q[7], q[1]; + ry(p_26) q[1]; + ry(p_27) q[0]; + cnot q[6], q[1]; + cnot q[6], q[0]; + ry(p_28) q[1]; + ry(p_29) q[0]; + cnot q[7], q[1]; + h q[7]; + cnot q[0], q[7]; + ry(p_30) q[1]; + ry(p_31) q[0]; + cnot q[0], q[1]; + cnot q[6], q[0]; + h q[0]; + h q[7]; + cnot q[0], q[6]; + cnot q[6], q[7]; + cnot q[4], q[6]; + cnot q[0], q[4]; + h q[6]; + h q[0]; + cnot q[4], q[6]; + cnot q[0], q[2]; + ry(p_32) q[2]; + ry(p_33) q[0]; + cnot q[0], q[6]; + h q[6]; + cnot q[6], q[2]; + ry(p_34) q[2]; + ry(p_35) q[0]; + cnot q[4], q[2]; + cnot q[4], q[0]; + ry(p_36) q[2]; + ry(p_37) q[0]; + cnot q[6], q[2]; + h q[6]; + cnot q[0], q[6]; + ry(p_38) q[2]; + ry(p_39) q[0]; + cnot q[0], q[2]; + cnot q[4], q[0]; + h q[0]; + h q[6]; + cnot q[0], q[4]; + cnot q[4], q[6]; + cnot q[4], q[5]; + cnot q[0], q[4]; + h q[5]; + h q[0]; + cnot q[4], q[5]; + cnot q[0], q[3]; + ry(p_40) q[3]; + ry(p_41) q[0]; + cnot q[0], q[5]; + h q[5]; + cnot q[5], q[3]; + ry(p_42) q[3]; + ry(p_43) q[0]; + cnot q[4], q[3]; + cnot q[4], q[0]; + ry(p_44) q[3]; + ry(p_45) q[0]; + cnot q[5], q[3]; + h q[5]; + cnot q[0], q[5]; + ry(p_46) q[3]; + ry(p_47) q[0]; + cnot q[0], q[3]; + cnot q[4], q[0]; + h q[0]; + h q[5]; + cnot q[0], q[4]; + cnot q[4], q[5]; + cnot q[4], q[7]; + cnot q[0], q[4]; + h q[7]; + h q[0]; + cnot q[4], q[7]; + cnot q[0], q[3]; + ry(p_48) q[3]; + ry(p_49) q[0]; + cnot q[0], q[7]; + h q[7]; + cnot q[7], q[3]; + ry(p_50) q[3]; + ry(p_51) q[0]; + cnot q[4], q[3]; + cnot q[4], q[0]; + ry(p_52) q[3]; + ry(p_53) q[0]; + cnot q[7], q[3]; + h q[7]; + cnot q[0], q[7]; + ry(p_54) q[3]; + ry(p_55) q[0]; + cnot q[0], q[3]; + cnot q[4], q[0]; + h q[0]; + h q[7]; + cnot q[0], q[4]; + cnot q[4], q[7]; + cnot q[5], q[6]; + cnot q[0], q[5]; + h q[6]; + h q[0]; + cnot q[5], q[6]; + cnot q[0], q[3]; + ry(p_56) q[3]; + ry(p_57) q[0]; + cnot q[0], q[6]; + h q[6]; + cnot q[6], q[3]; + ry(p_58) q[3]; + ry(p_59) q[0]; + cnot q[5], q[3]; + cnot q[5], q[0]; + ry(p_60) q[3]; + ry(p_61) q[0]; + cnot q[6], q[3]; + h q[6]; + cnot q[0], q[6]; + ry(p_62) q[3]; + ry(p_63) q[0]; + cnot q[0], q[3]; + cnot q[5], q[0]; + h q[0]; + h q[6]; + cnot q[0], q[5]; + cnot q[5], q[6]; + cnot q[6], q[7]; + cnot q[0], q[6]; + h q[7]; + h q[0]; + cnot q[6], q[7]; + cnot q[0], q[3]; + ry(p_64) q[3]; + ry(p_65) q[0]; + cnot q[0], q[7]; + h q[7]; + cnot q[7], q[3]; + ry(p_66) q[3]; + ry(p_67) q[0]; + cnot q[6], q[3]; + cnot q[6], q[0]; + ry(p_68) q[3]; + ry(p_69) q[0]; + cnot q[7], q[3]; + h q[7]; + cnot q[0], q[7]; + ry(p_70) q[3]; + ry(p_71) q[0]; + cnot q[0], q[3]; + cnot q[6], q[0]; + h q[0]; + h q[7]; + cnot q[0], q[6]; + cnot q[6], q[7]; + cnot q[4], q[5]; + cnot q[1], q[4]; + h q[5]; + h q[1]; + cnot q[4], q[5]; + cnot q[1], q[2]; + ry(p_72) q[2]; + ry(p_73) q[1]; + cnot q[1], q[5]; + h q[5]; + cnot q[5], q[2]; + ry(p_74) q[2]; + ry(p_75) q[1]; + cnot q[4], q[2]; + cnot q[4], q[1]; + ry(p_76) q[2]; + ry(p_77) q[1]; + cnot q[5], q[2]; + h q[5]; + cnot q[1], q[5]; + ry(p_78) q[2]; + ry(p_79) q[1]; + cnot q[1], q[2]; + cnot q[4], q[1]; + h q[1]; + h q[5]; + cnot q[1], q[4]; + cnot q[4], q[5]; + cnot q[4], q[7]; + cnot q[1], q[4]; + h q[7]; + h q[1]; + cnot q[4], q[7]; + cnot q[1], q[2]; + ry(p_80) q[2]; + ry(p_81) q[1]; + cnot q[1], q[7]; + h q[7]; + cnot q[7], q[2]; + ry(p_82) q[2]; + ry(p_83) q[1]; + cnot q[4], q[2]; + cnot q[4], q[1]; + ry(p_84) q[2]; + ry(p_85) q[1]; + cnot q[7], q[2]; + h q[7]; + cnot q[1], q[7]; + ry(p_86) q[2]; + ry(p_87) q[1]; + cnot q[1], q[2]; + cnot q[4], q[1]; + h q[1]; + h q[7]; + cnot q[1], q[4]; + cnot q[4], q[7]; + cnot q[5], q[6]; + cnot q[1], q[5]; + h q[6]; + h q[1]; + cnot q[5], q[6]; + cnot q[1], q[2]; + ry(p_88) q[2]; + ry(p_89) q[1]; + cnot q[1], q[6]; + h q[6]; + cnot q[6], q[2]; + ry(p_90) q[2]; + ry(p_91) q[1]; + cnot q[5], q[2]; + cnot q[5], q[1]; + ry(p_92) q[2]; + ry(p_93) q[1]; + cnot q[6], q[2]; + h q[6]; + cnot q[1], q[6]; + ry(p_94) q[2]; + ry(p_95) q[1]; + cnot q[1], q[2]; + cnot q[5], q[1]; + h q[1]; + h q[6]; + cnot q[1], q[5]; + cnot q[5], q[6]; + cnot q[6], q[7]; + cnot q[1], q[6]; + h q[7]; + h q[1]; + cnot q[6], q[7]; + cnot q[1], q[2]; + ry(p_96) q[2]; + ry(p_97) q[1]; + cnot q[1], q[7]; + h q[7]; + cnot q[7], q[2]; + ry(p_98) q[2]; + ry(p_99) q[1]; + cnot q[6], q[2]; + cnot q[6], q[1]; + ry(p_100) q[2]; + ry(p_101) q[1]; + cnot q[7], q[2]; + h q[7]; + cnot q[1], q[7]; + ry(p_102) q[2]; + ry(p_103) q[1]; + cnot q[1], q[2]; + cnot q[6], q[1]; + h q[1]; + h q[7]; + cnot q[1], q[6]; + cnot q[6], q[7]; + cnot q[5], q[7]; + cnot q[1], q[5]; + h q[7]; + h q[1]; + cnot q[5], q[7]; + cnot q[1], q[3]; + ry(p_104) q[3]; + ry(p_105) q[1]; + cnot q[1], q[7]; + h q[7]; + cnot q[7], q[3]; + ry(p_106) q[3]; + ry(p_107) q[1]; + cnot q[5], q[3]; + cnot q[5], q[1]; + ry(p_108) q[3]; + ry(p_109) q[1]; + cnot q[7], q[3]; + h q[7]; + cnot q[1], q[7]; + ry(p_110) q[3]; + ry(p_111) q[1]; + cnot q[1], q[3]; + cnot q[5], q[1]; + h q[1]; + h q[7]; + cnot q[1], q[5]; + cnot q[5], q[7]; + cnot q[4], q[5]; + cnot q[2], q[4]; + h q[5]; + h q[2]; + cnot q[4], q[5]; + cnot q[2], q[3]; + ry(p_112) q[3]; + ry(p_113) q[2]; + cnot q[2], q[5]; + h q[5]; + cnot q[5], q[3]; + ry(p_114) q[3]; + ry(p_115) q[2]; + cnot q[4], q[3]; + cnot q[4], q[2]; + ry(p_116) q[3]; + ry(p_117) q[2]; + cnot q[5], q[3]; + h q[5]; + cnot q[2], q[5]; + ry(p_118) q[3]; + ry(p_119) q[2]; + cnot q[2], q[3]; + cnot q[4], q[2]; + h q[2]; + h q[5]; + cnot q[2], q[4]; + cnot q[4], q[5]; + cnot q[4], q[7]; + cnot q[2], q[4]; + h q[7]; + h q[2]; + cnot q[4], q[7]; + cnot q[2], q[3]; + ry(p_120) q[3]; + ry(p_121) q[2]; + cnot q[2], q[7]; + h q[7]; + cnot q[7], q[3]; + ry(p_122) q[3]; + ry(p_123) q[2]; + cnot q[4], q[3]; + cnot q[4], q[2]; + ry(p_124) q[3]; + ry(p_125) q[2]; + cnot q[7], q[3]; + h q[7]; + cnot q[2], q[7]; + ry(p_126) q[3]; + ry(p_127) q[2]; + cnot q[2], q[3]; + cnot q[4], q[2]; + h q[2]; + h q[7]; + cnot q[2], q[4]; + cnot q[4], q[7]; + cnot q[5], q[6]; + cnot q[2], q[5]; + h q[6]; + h q[2]; + cnot q[5], q[6]; + cnot q[2], q[3]; + ry(p_128) q[3]; + ry(p_129) q[2]; + cnot q[2], q[6]; + h q[6]; + cnot q[6], q[3]; + ry(p_130) q[3]; + ry(p_131) q[2]; + cnot q[5], q[3]; + cnot q[5], q[2]; + ry(p_132) q[3]; + ry(p_133) q[2]; + cnot q[6], q[3]; + h q[6]; + cnot q[2], q[6]; + ry(p_134) q[3]; + ry(p_135) q[2]; + cnot q[2], q[3]; + cnot q[5], q[2]; + h q[2]; + h q[6]; + cnot q[2], q[5]; + cnot q[5], q[6]; + cnot q[6], q[7]; + cnot q[2], q[6]; + h q[7]; + h q[2]; + cnot q[6], q[7]; + cnot q[2], q[3]; + ry(p_136) q[3]; + ry(p_137) q[2]; + cnot q[2], q[7]; + h q[7]; + cnot q[7], q[3]; + ry(p_138) q[3]; + ry(p_139) q[2]; + cnot q[6], q[3]; + cnot q[6], q[2]; + ry(p_140) q[3]; + ry(p_141) q[2]; + cnot q[7], q[3]; + h q[7]; + cnot q[2], q[7]; + ry(p_142) q[3]; + ry(p_143) q[2]; + cnot q[2], q[3]; + cnot q[6], q[2]; + h q[2]; + h q[7]; + cnot q[2], q[6]; + cnot q[6], q[7]; + ry(p_144) q[3]; + ry(p_145) q[7]; + #pragma braket result expectation z(q[3]) @ z(q[5]) @ z(q[6]) @ z(q[7]) + #pragma braket result expectation z(q[1]) @ z(q[3]) @ z(q[4]) @ z(q[5]) @ z(q[6]) @ z(q[7]) + #pragma braket result expectation z(q[2]) @ z(q[3]) @ z(q[4]) @ z(q[5]) @ z(q[6]) @ z(q[7]) + #pragma braket result expectation z(q[3]) @ z(q[4]) @ z(q[5]) @ z(q[6]) @ z(q[7]) + #pragma braket result expectation z(q[0]) @ z(q[3]) @ z(q[4]) @ z(q[5]) @ z(q[6]) @ z(q[7]) + """ + circ = BraketSimulator.Circuit(source, inputs) + @test isnothing(BraketSimulator._verify_openqasm_shots_observables(circ, 8)) + # differs from source above by change of one Z to X in observables list + bad_source = """ + OPENQASM 3.0; + input float p_0; + input float p_31; + input float p_117; + input float p_52; + input float p_103; + input float p_87; + input float p_46; + input float p_34; + input float p_49; + input float p_75; + input float p_35; + input float p_123; + input float p_1; + input float p_15; + input float p_73; + input float p_25; + input float p_93; + input float p_116; + input float p_13; + input float p_86; + input float p_23; + input float p_4; + input float p_32; + input float p_107; + input float p_119; + input float p_63; + input float p_89; + input float p_5; + input float p_111; + input float p_127; + input float p_77; + input float p_53; + input float p_97; + input float p_137; + input float p_48; + input float p_114; + input float p_17; + input float p_42; + input float p_40; + input float p_67; + input float p_126; + input float p_118; + input float p_96; + input float p_112; + input float p_136; + input float p_88; + input float p_33; + input float p_57; + input float p_82; + input float p_110; + input float p_129; + input float p_41; + input float p_80; + input float p_99; + input float p_139; + input float p_66; + input float p_44; + input float p_121; + input float p_69; + input float p_91; + input float p_131; + input float p_3; + input float p_140; + input float p_45; + input float p_81; + input float p_108; + input float p_94; + input float p_134; + input float p_37; + input float p_61; + input float p_100; + input float p_106; + input float p_27; + input float p_84; + input float p_109; + input float p_115; + input float p_141; + input float p_128; + input float p_85; + input float p_122; + input float p_78; + input float p_98; + input float p_138; + input float p_120; + input float p_92; + input float p_7; + input float p_132; + input float p_144; + input float p_18; + input float p_76; + input float p_90; + input float p_101; + input float p_130; + input float p_36; + input float p_16; + input float p_68; + input float p_26; + input float p_145; + input float p_79; + input float p_62; + input float p_104; + input float p_60; + input float p_19; + input float p_29; + input float p_105; + input float p_142; + input float p_71; + input float p_6; + input float p_58; + input float p_124; + input float p_11; + input float p_21; + input float p_50; + input float p_56; + input float p_9; + input float p_74; + input float p_43; + input float p_102; + input float p_125; + input float p_14; + input float p_59; + input float p_24; + input float p_95; + input float p_135; + input float p_64; + input float p_133; + input float p_113; + input float p_51; + input float p_65; + input float p_38; + input float p_72; + input float p_83; + input float p_70; + input float p_28; + input float p_12; + input float p_30; + input float p_22; + input float p_47; + input float p_54; + input float p_10; + input float p_20; + input float p_39; + input float p_55; + input float p_143; + input float p_8; + input float p_2; + qubit[8] q; + x q[0]; + x q[1]; + x q[2]; + x q[3]; + cnot q[4], q[5]; + cnot q[0], q[4]; + h q[5]; + h q[0]; + cnot q[4], q[5]; + cnot q[0], q[1]; + ry(p_0) q[1]; + ry(p_1) q[0]; + cnot q[0], q[5]; + h q[5]; + cnot q[5], q[1]; + ry(p_2) q[1]; + ry(p_3) q[0]; + cnot q[4], q[1]; + cnot q[4], q[0]; + ry(p_4) q[1]; + ry(p_5) q[0]; + cnot q[5], q[1]; + h q[5]; + cnot q[0], q[5]; + ry(p_6) q[1]; + ry(p_7) q[0]; + cnot q[0], q[1]; + cnot q[4], q[0]; + h q[0]; + h q[5]; + cnot q[0], q[4]; + cnot q[4], q[5]; + cnot q[4], q[7]; + cnot q[0], q[4]; + h q[7]; + h q[0]; + cnot q[4], q[7]; + cnot q[0], q[1]; + ry(p_8) q[1]; + ry(p_9) q[0]; + cnot q[0], q[7]; + h q[7]; + cnot q[7], q[1]; + ry(p_10) q[1]; + ry(p_11) q[0]; + cnot q[4], q[1]; + cnot q[4], q[0]; + ry(p_12) q[1]; + ry(p_13) q[0]; + cnot q[7], q[1]; + h q[7]; + cnot q[0], q[7]; + ry(p_14) q[1]; + ry(p_15) q[0]; + cnot q[0], q[1]; + cnot q[4], q[0]; + h q[0]; + h q[7]; + cnot q[0], q[4]; + cnot q[4], q[7]; + cnot q[5], q[6]; + cnot q[0], q[5]; + h q[6]; + h q[0]; + cnot q[5], q[6]; + cnot q[0], q[1]; + ry(p_16) q[1]; + ry(p_17) q[0]; + cnot q[0], q[6]; + h q[6]; + cnot q[6], q[1]; + ry(p_18) q[1]; + ry(p_19) q[0]; + cnot q[5], q[1]; + cnot q[5], q[0]; + ry(p_20) q[1]; + ry(p_21) q[0]; + cnot q[6], q[1]; + h q[6]; + cnot q[0], q[6]; + ry(p_22) q[1]; + ry(p_23) q[0]; + cnot q[0], q[1]; + cnot q[5], q[0]; + h q[0]; + h q[6]; + cnot q[0], q[5]; + cnot q[5], q[6]; + cnot q[6], q[7]; + cnot q[0], q[6]; + h q[7]; + h q[0]; + cnot q[6], q[7]; + cnot q[0], q[1]; + ry(p_24) q[1]; + ry(p_25) q[0]; + cnot q[0], q[7]; + h q[7]; + cnot q[7], q[1]; + ry(p_26) q[1]; + ry(p_27) q[0]; + cnot q[6], q[1]; + cnot q[6], q[0]; + ry(p_28) q[1]; + ry(p_29) q[0]; + cnot q[7], q[1]; + h q[7]; + cnot q[0], q[7]; + ry(p_30) q[1]; + ry(p_31) q[0]; + cnot q[0], q[1]; + cnot q[6], q[0]; + h q[0]; + h q[7]; + cnot q[0], q[6]; + cnot q[6], q[7]; + cnot q[4], q[6]; + cnot q[0], q[4]; + h q[6]; + h q[0]; + cnot q[4], q[6]; + cnot q[0], q[2]; + ry(p_32) q[2]; + ry(p_33) q[0]; + cnot q[0], q[6]; + h q[6]; + cnot q[6], q[2]; + ry(p_34) q[2]; + ry(p_35) q[0]; + cnot q[4], q[2]; + cnot q[4], q[0]; + ry(p_36) q[2]; + ry(p_37) q[0]; + cnot q[6], q[2]; + h q[6]; + cnot q[0], q[6]; + ry(p_38) q[2]; + ry(p_39) q[0]; + cnot q[0], q[2]; + cnot q[4], q[0]; + h q[0]; + h q[6]; + cnot q[0], q[4]; + cnot q[4], q[6]; + cnot q[4], q[5]; + cnot q[0], q[4]; + h q[5]; + h q[0]; + cnot q[4], q[5]; + cnot q[0], q[3]; + ry(p_40) q[3]; + ry(p_41) q[0]; + cnot q[0], q[5]; + h q[5]; + cnot q[5], q[3]; + ry(p_42) q[3]; + ry(p_43) q[0]; + cnot q[4], q[3]; + cnot q[4], q[0]; + ry(p_44) q[3]; + ry(p_45) q[0]; + cnot q[5], q[3]; + h q[5]; + cnot q[0], q[5]; + ry(p_46) q[3]; + ry(p_47) q[0]; + cnot q[0], q[3]; + cnot q[4], q[0]; + h q[0]; + h q[5]; + cnot q[0], q[4]; + cnot q[4], q[5]; + cnot q[4], q[7]; + cnot q[0], q[4]; + h q[7]; + h q[0]; + cnot q[4], q[7]; + cnot q[0], q[3]; + ry(p_48) q[3]; + ry(p_49) q[0]; + cnot q[0], q[7]; + h q[7]; + cnot q[7], q[3]; + ry(p_50) q[3]; + ry(p_51) q[0]; + cnot q[4], q[3]; + cnot q[4], q[0]; + ry(p_52) q[3]; + ry(p_53) q[0]; + cnot q[7], q[3]; + h q[7]; + cnot q[0], q[7]; + ry(p_54) q[3]; + ry(p_55) q[0]; + cnot q[0], q[3]; + cnot q[4], q[0]; + h q[0]; + h q[7]; + cnot q[0], q[4]; + cnot q[4], q[7]; + cnot q[5], q[6]; + cnot q[0], q[5]; + h q[6]; + h q[0]; + cnot q[5], q[6]; + cnot q[0], q[3]; + ry(p_56) q[3]; + ry(p_57) q[0]; + cnot q[0], q[6]; + h q[6]; + cnot q[6], q[3]; + ry(p_58) q[3]; + ry(p_59) q[0]; + cnot q[5], q[3]; + cnot q[5], q[0]; + ry(p_60) q[3]; + ry(p_61) q[0]; + cnot q[6], q[3]; + h q[6]; + cnot q[0], q[6]; + ry(p_62) q[3]; + ry(p_63) q[0]; + cnot q[0], q[3]; + cnot q[5], q[0]; + h q[0]; + h q[6]; + cnot q[0], q[5]; + cnot q[5], q[6]; + cnot q[6], q[7]; + cnot q[0], q[6]; + h q[7]; + h q[0]; + cnot q[6], q[7]; + cnot q[0], q[3]; + ry(p_64) q[3]; + ry(p_65) q[0]; + cnot q[0], q[7]; + h q[7]; + cnot q[7], q[3]; + ry(p_66) q[3]; + ry(p_67) q[0]; + cnot q[6], q[3]; + cnot q[6], q[0]; + ry(p_68) q[3]; + ry(p_69) q[0]; + cnot q[7], q[3]; + h q[7]; + cnot q[0], q[7]; + ry(p_70) q[3]; + ry(p_71) q[0]; + cnot q[0], q[3]; + cnot q[6], q[0]; + h q[0]; + h q[7]; + cnot q[0], q[6]; + cnot q[6], q[7]; + cnot q[4], q[5]; + cnot q[1], q[4]; + h q[5]; + h q[1]; + cnot q[4], q[5]; + cnot q[1], q[2]; + ry(p_72) q[2]; + ry(p_73) q[1]; + cnot q[1], q[5]; + h q[5]; + cnot q[5], q[2]; + ry(p_74) q[2]; + ry(p_75) q[1]; + cnot q[4], q[2]; + cnot q[4], q[1]; + ry(p_76) q[2]; + ry(p_77) q[1]; + cnot q[5], q[2]; + h q[5]; + cnot q[1], q[5]; + ry(p_78) q[2]; + ry(p_79) q[1]; + cnot q[1], q[2]; + cnot q[4], q[1]; + h q[1]; + h q[5]; + cnot q[1], q[4]; + cnot q[4], q[5]; + cnot q[4], q[7]; + cnot q[1], q[4]; + h q[7]; + h q[1]; + cnot q[4], q[7]; + cnot q[1], q[2]; + ry(p_80) q[2]; + ry(p_81) q[1]; + cnot q[1], q[7]; + h q[7]; + cnot q[7], q[2]; + ry(p_82) q[2]; + ry(p_83) q[1]; + cnot q[4], q[2]; + cnot q[4], q[1]; + ry(p_84) q[2]; + ry(p_85) q[1]; + cnot q[7], q[2]; + h q[7]; + cnot q[1], q[7]; + ry(p_86) q[2]; + ry(p_87) q[1]; + cnot q[1], q[2]; + cnot q[4], q[1]; + h q[1]; + h q[7]; + cnot q[1], q[4]; + cnot q[4], q[7]; + cnot q[5], q[6]; + cnot q[1], q[5]; + h q[6]; + h q[1]; + cnot q[5], q[6]; + cnot q[1], q[2]; + ry(p_88) q[2]; + ry(p_89) q[1]; + cnot q[1], q[6]; + h q[6]; + cnot q[6], q[2]; + ry(p_90) q[2]; + ry(p_91) q[1]; + cnot q[5], q[2]; + cnot q[5], q[1]; + ry(p_92) q[2]; + ry(p_93) q[1]; + cnot q[6], q[2]; + h q[6]; + cnot q[1], q[6]; + ry(p_94) q[2]; + ry(p_95) q[1]; + cnot q[1], q[2]; + cnot q[5], q[1]; + h q[1]; + h q[6]; + cnot q[1], q[5]; + cnot q[5], q[6]; + cnot q[6], q[7]; + cnot q[1], q[6]; + h q[7]; + h q[1]; + cnot q[6], q[7]; + cnot q[1], q[2]; + ry(p_96) q[2]; + ry(p_97) q[1]; + cnot q[1], q[7]; + h q[7]; + cnot q[7], q[2]; + ry(p_98) q[2]; + ry(p_99) q[1]; + cnot q[6], q[2]; + cnot q[6], q[1]; + ry(p_100) q[2]; + ry(p_101) q[1]; + cnot q[7], q[2]; + h q[7]; + cnot q[1], q[7]; + ry(p_102) q[2]; + ry(p_103) q[1]; + cnot q[1], q[2]; + cnot q[6], q[1]; + h q[1]; + h q[7]; + cnot q[1], q[6]; + cnot q[6], q[7]; + cnot q[5], q[7]; + cnot q[1], q[5]; + h q[7]; + h q[1]; + cnot q[5], q[7]; + cnot q[1], q[3]; + ry(p_104) q[3]; + ry(p_105) q[1]; + cnot q[1], q[7]; + h q[7]; + cnot q[7], q[3]; + ry(p_106) q[3]; + ry(p_107) q[1]; + cnot q[5], q[3]; + cnot q[5], q[1]; + ry(p_108) q[3]; + ry(p_109) q[1]; + cnot q[7], q[3]; + h q[7]; + cnot q[1], q[7]; + ry(p_110) q[3]; + ry(p_111) q[1]; + cnot q[1], q[3]; + cnot q[5], q[1]; + h q[1]; + h q[7]; + cnot q[1], q[5]; + cnot q[5], q[7]; + cnot q[4], q[5]; + cnot q[2], q[4]; + h q[5]; + h q[2]; + cnot q[4], q[5]; + cnot q[2], q[3]; + ry(p_112) q[3]; + ry(p_113) q[2]; + cnot q[2], q[5]; + h q[5]; + cnot q[5], q[3]; + ry(p_114) q[3]; + ry(p_115) q[2]; + cnot q[4], q[3]; + cnot q[4], q[2]; + ry(p_116) q[3]; + ry(p_117) q[2]; + cnot q[5], q[3]; + h q[5]; + cnot q[2], q[5]; + ry(p_118) q[3]; + ry(p_119) q[2]; + cnot q[2], q[3]; + cnot q[4], q[2]; + h q[2]; + h q[5]; + cnot q[2], q[4]; + cnot q[4], q[5]; + cnot q[4], q[7]; + cnot q[2], q[4]; + h q[7]; + h q[2]; + cnot q[4], q[7]; + cnot q[2], q[3]; + ry(p_120) q[3]; + ry(p_121) q[2]; + cnot q[2], q[7]; + h q[7]; + cnot q[7], q[3]; + ry(p_122) q[3]; + ry(p_123) q[2]; + cnot q[4], q[3]; + cnot q[4], q[2]; + ry(p_124) q[3]; + ry(p_125) q[2]; + cnot q[7], q[3]; + h q[7]; + cnot q[2], q[7]; + ry(p_126) q[3]; + ry(p_127) q[2]; + cnot q[2], q[3]; + cnot q[4], q[2]; + h q[2]; + h q[7]; + cnot q[2], q[4]; + cnot q[4], q[7]; + cnot q[5], q[6]; + cnot q[2], q[5]; + h q[6]; + h q[2]; + cnot q[5], q[6]; + cnot q[2], q[3]; + ry(p_128) q[3]; + ry(p_129) q[2]; + cnot q[2], q[6]; + h q[6]; + cnot q[6], q[3]; + ry(p_130) q[3]; + ry(p_131) q[2]; + cnot q[5], q[3]; + cnot q[5], q[2]; + ry(p_132) q[3]; + ry(p_133) q[2]; + cnot q[6], q[3]; + h q[6]; + cnot q[2], q[6]; + ry(p_134) q[3]; + ry(p_135) q[2]; + cnot q[2], q[3]; + cnot q[5], q[2]; + h q[2]; + h q[6]; + cnot q[2], q[5]; + cnot q[5], q[6]; + cnot q[6], q[7]; + cnot q[2], q[6]; + h q[7]; + h q[2]; + cnot q[6], q[7]; + cnot q[2], q[3]; + ry(p_136) q[3]; + ry(p_137) q[2]; + cnot q[2], q[7]; + h q[7]; + cnot q[7], q[3]; + ry(p_138) q[3]; + ry(p_139) q[2]; + cnot q[6], q[3]; + cnot q[6], q[2]; + ry(p_140) q[3]; + ry(p_141) q[2]; + cnot q[7], q[3]; + h q[7]; + cnot q[2], q[7]; + ry(p_142) q[3]; + ry(p_143) q[2]; + cnot q[2], q[3]; + cnot q[6], q[2]; + h q[2]; + h q[7]; + cnot q[2], q[6]; + cnot q[6], q[7]; + ry(p_144) q[3]; + ry(p_145) q[7]; + #pragma braket result expectation z(q[3]) @ z(q[5]) @ z(q[6]) @ z(q[7]) + #pragma braket result expectation z(q[1]) @ z(q[3]) @ z(q[4]) @ z(q[5]) @ z(q[6]) @ z(q[7]) + #pragma braket result expectation z(q[2]) @ z(q[3]) @ z(q[4]) @ z(q[5]) @ z(q[6]) @ z(q[7]) + #pragma braket result expectation z(q[3]) @ z(q[4]) @ z(q[5]) @ z(q[6]) @ z(q[7]) + #pragma braket result expectation z(q[0]) @ z(q[3]) @ z(q[4]) @ z(q[5]) @ x(q[6]) @ z(q[7]) + """ + circ = BraketSimulator.Circuit(bad_source, inputs) + @test_throws ValidationError BraketSimulator._verify_openqasm_shots_observables(circ, 8) + end + end end