From 3268d6ea9acf50a1ad7fa30d3b7cd1de32f7c261 Mon Sep 17 00:00:00 2001 From: Milan <30416311+krneta@users.noreply.github.com> Date: Tue, 30 Apr 2024 13:31:36 -0700 Subject: [PATCH] update: adding codescan analysis (#3) --- .github/workflows/codescan-analysis.yml | 25 ++ .../BraketSimulatorPythonExt.jl | 4 +- ext/BraketSimulatorPythonExt/translation.jl | 115 ++++---- src/BraketSimulator.jl | 17 +- src/custom_gates.jl | 106 ++++---- src/dm_simulator.jl | 47 ++-- src/gate_kernels.jl | 250 +++++++++--------- src/inverted_gates.jl | 28 +- src/result_types.jl | 10 + src/sv_simulator.jl | 42 ++- src/validation.jl | 71 ++--- 11 files changed, 338 insertions(+), 377 deletions(-) create mode 100644 .github/workflows/codescan-analysis.yml diff --git a/.github/workflows/codescan-analysis.yml b/.github/workflows/codescan-analysis.yml new file mode 100644 index 0000000..4d7843b --- /dev/null +++ b/.github/workflows/codescan-analysis.yml @@ -0,0 +1,25 @@ +name: Semgrep Codescan + +on: + pull_request: + branches: + - main + - feature/** + +jobs: + semgrep-codescan: + name: Semgrep Codescan + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - uses: actions/setup-python@v5 + with: + python-version: '3.10' + - name: Install Semgrep + run: python3 -m pip install semgrep + - name: Get rules from JuliaComputing + run: git clone https://github.com/JuliaComputing/semgrep-rules-julia.git + - name: Run Semgrep Julia rules + run: semgrep --error --config semgrep-rules-julia/rules . diff --git a/ext/BraketSimulatorPythonExt/BraketSimulatorPythonExt.jl b/ext/BraketSimulatorPythonExt/BraketSimulatorPythonExt.jl index f3d3f6a..336c998 100644 --- a/ext/BraketSimulatorPythonExt/BraketSimulatorPythonExt.jl +++ b/ext/BraketSimulatorPythonExt/BraketSimulatorPythonExt.jl @@ -254,7 +254,9 @@ function BraketSimulator.parse_program(simulator::D, program::OpenQasmProgram, s if shots > 0 py_circ.instructions += py_circ.basis_rotation_instructions end - return ir(pyconvert(Circuit, py_circ), Val(:JAQCD)) + circ = pyconvert(Circuit, py_circ) + @assert qubit_count(circ) <= properties(simulator).paradigm.qubitCount "parsed circuit's qubit count $(qubit_count(circ)) is larger than maximum ($(properties(simulator).paradigm.qubitCount)) for simulator type $D." + return ir(circ, Val(:JAQCD)) end function simulate( diff --git a/ext/BraketSimulatorPythonExt/translation.jl b/ext/BraketSimulatorPythonExt/translation.jl index 08c432f..7208c2b 100644 --- a/ext/BraketSimulatorPythonExt/translation.jl +++ b/ext/BraketSimulatorPythonExt/translation.jl @@ -30,16 +30,16 @@ for (rt, brt, fn) in ((:(Braket.IR.Sample), :(Braket.Sample), :jl_convert_sample (:(Braket.IR.Variance), :(Braket.Variance), :jl_convert_variance), ) @eval begin - function $fn(::Type{$rt}, x::Py) + function $fn(t::Type{$rt}, x::Py) jl_obs = convert_ir_obs(x.observable) jl_ts = convert_ir_target(x.targets) - jl_rt = $rt(jl_obs, jl_ts, pyconvert(String, x.type)) + jl_rt = t(jl_obs, jl_ts, pyconvert(String, x.type)) PythonCall.pyconvert_return(jl_rt) end $fn(::Type{AbstractProgramResult}, x::Py) = $fn($rt, x) - function $fn(::Type{$brt}, x::Py) + function $fn(t::Type{$brt}, x::Py) jl_obs = Braket.StructTypes.constructfrom(Braket.Observables.Observable, convert_ir_obs(x.observable)) - PythonCall.pyconvert_return($brt(jl_obs, convert_ir_target(x.targets))) + PythonCall.pyconvert_return(t(jl_obs, convert_ir_target(x.targets))) end $fn(::Type{Braket.Result}, x::Py) = $fn($brt, x::Py) end @@ -49,9 +49,9 @@ for (rt, brt, fn) in ((:(Braket.IR.Probability), :(Braket.Probability), :jl_conv (:(Braket.IR.DensityMatrix), :(Braket.DensityMatrix), :jl_convert_densitymatrix), ) @eval begin - function $fn(::Type{$rt}, x::Py) + function $fn(t::Type{$rt}, x::Py) jl_ts = convert_ir_target(x.targets) - jl_rt = $rt(jl_ts, pyconvert(String, x.type)) + jl_rt = t(jl_ts, pyconvert(String, x.type)) PythonCall.pyconvert_return(jl_rt) end $fn(::Type{AbstractProgramResult}, x::Py) = $fn($rt, x) @@ -73,27 +73,28 @@ function inputs_to_jl(x) return Dict(jl_inputs) end end -function jl_convert_oqprogram(::Type{OpenQasmProgram}, x::Py) +function jl_convert_oqprogram(t::Type{OpenQasmProgram}, x::Py) bsh = pyconvert(Braket.braketSchemaHeader, x.braketSchemaHeader) source = pyconvert(String, x.source) inputs = inputs_to_jl(x.inputs) - PythonCall.pyconvert_return(OpenQasmProgram(bsh, source, inputs)) + PythonCall.pyconvert_return(t(bsh, source, inputs)) end -function jl_convert_amplitude(::Type{Braket.IR.Amplitude}, x::Py) - jl_rt = Braket.IR.Amplitude([pyconvert(String, s) for s in x.states], pyconvert(String, x.type)) +function jl_convert_amplitude(t::Type{Braket.IR.Amplitude}, x::Py) + states = map(state->pyconvert(String, state), x.states) + jl_rt = t(states, pyconvert(String, x.type)) PythonCall.pyconvert_return(jl_rt) end jl_convert_amplitude(::Type{AbstractProgramResult}, x::Py) = jl_convert_amplitude(Braket.IR.Amplitude, x::Py) -function jl_convert_amplitude(::Type{Braket.Amplitude}, x::Py) - jl_rt = Braket.Amplitude([pyconvert(String, s) for s in x.states]) - PythonCall.pyconvert_return(jl_rt) +function jl_convert_amplitude(t::Type{Braket.Amplitude}, x::Py) + states = map(state->pyconvert(String, state), x.states) + PythonCall.pyconvert_return(t(states)) end jl_convert_amplitude(::Type{Braket.Result}, x::Py) = jl_convert_amplitude(Braket.Amplitude, x::Py) -function jl_convert_statevector(::Type{Braket.IR.StateVector}, x::Py) - jl_rt = Braket.IR.StateVector(pyconvert(String, x.type)) +function jl_convert_statevector(t::Type{Braket.IR.StateVector}, x::Py) + jl_rt = t(pyconvert(String, x.type)) PythonCall.pyconvert_return(jl_rt) end jl_convert_statevector(::Type{AbstractProgramResult}, x::Py) = jl_convert_statevector(Braket.IR.StateVector, x::Py) @@ -103,11 +104,11 @@ jl_convert_statevector(::Type{Braket.Result}, x::Py) = jl_convert_statevector(Br # exclude adjoint gradient translation from coverage for now # as we don't yet implement this, so don't have a test for it # COV_EXCL_START -function jl_convert_adjointgradient(::Type{Braket.IR.AdjointGradient}, x::Py) +function jl_convert_adjointgradient(t::Type{Braket.IR.AdjointGradient}, x::Py) jl_targets = pyis(x.targets, pybuiltins.None) ? nothing : [convert_targets(t_) for t_ in t] jl_params = pyis(x.targets, pybuiltins.None) ? nothing : [pyconvert(String, p) for p in x.parameters] jl_obs = pyisinstance(x.observable, pybuiltins.str) ? pyconvert(String, x.observable) : [convert_ir_obs(o) for o in x.observable] - jl_rt = Braket.IR.AdjointGradient(jl_params, jl_obs, jl_targets, pyconvert(String, x.type)) + jl_rt = t(jl_params, jl_obs, jl_targets, pyconvert(String, x.type)) PythonCall.pyconvert_return(jl_rt) end jl_convert_adjointgradient(::Type{AbstractProgramResult}, x::Py) = jl_convert_adjointgradient(Braket.IR.AdjointGradient, x::Py) @@ -237,7 +238,7 @@ for (g, fn) in ((:CPhaseShift, :jl_convert_cphaseshift), end end -function jl_convert_ix(::Type{Instruction}, x::Py)::Instruction +function jl_convert_ix(t::Type{Instruction}, x::Py)::Instruction # extract targets full_targets = QubitSet() for attr ∈ ("control", "target") # single-qubit @@ -256,14 +257,14 @@ function jl_convert_ix(::Type{Instruction}, x::Py)::Instruction py_name = pyconvert(String, pygetattr(x, "type")) jl_type = sts[Symbol(py_name)] # bad, can we come up with something more generic - ix = Instruction(PythonCall.pyconvert(jl_type, x), full_targets) + ix = t(PythonCall.pyconvert(jl_type, x), full_targets) PythonCall.pyconvert_return(ix) end -function jl_convert_bsh(::Type{Braket.braketSchemaHeader}, x::Py) +function jl_convert_bsh(t::Type{Braket.braketSchemaHeader}, x::Py) name = pyconvert(String, x.name) version = pyconvert(String, x.version) - PythonCall.pyconvert_return(Braket.braketSchemaHeader(name, version)) + PythonCall.pyconvert_return(t(name, version)) end for (fn, jl_typ) in ((:jl_convert_sim_identity, :(Braket.Observables.I)), @@ -311,9 +312,9 @@ for (fn, jl_typ) in ((:jl_convert_sim_identity, :(Braket.I)), (:jl_convert_sim_ccnot, :CCNot), ) @eval begin - function $fn(::Type{Braket.Instruction}, x::Py) + function $fn(t::Type{Braket.Instruction}, x::Py) jl_operation = _handle_modifiers($jl_typ(), x) - PythonCall.pyconvert_return(Instruction(jl_operation, convert_targets(x.targets))) + PythonCall.pyconvert_return(t(jl_operation, convert_targets(x.targets))) end end end @@ -334,9 +335,9 @@ for (fn, jl_typ) in ((:jl_convert_sim_gpi, :GPi), (:jl_convert_sim_zz, :ZZ), ) @eval begin - function $fn(::Type{Braket.Instruction}, x::Py) + function $fn(t::Type{Braket.Instruction}, x::Py) jl_operation = _handle_modifiers($jl_typ(pyconvert(Union{Float64, FreeParameter}, x._angle)), x) - PythonCall.pyconvert_return(Instruction(jl_operation, convert_targets(x.targets))) + PythonCall.pyconvert_return(t(jl_operation, convert_targets(x.targets))) end end end @@ -344,19 +345,19 @@ for (fn, jl_typ, a1, a2, a3) in ((:jl_convert_sim_ms, :MS, "_angle_1", "_angle_2 (:jl_convert_sim_u, :U, "_theta", "_phi", "_lambda"), ) @eval begin - function $fn(::Type{Braket.Instruction}, x::Py) + function $fn(t::Type{Braket.Instruction}, x::Py) angle = (pyconvert(Union{Float64, FreeParameter}, getproperty(x, Symbol($a1))), pyconvert(Union{Float64, FreeParameter}, getproperty(x, Symbol($a2))), pyconvert(Union{Float64, FreeParameter}, getproperty(x, Symbol($a3)))) jl_operation = _handle_modifiers($jl_typ(angle), x) - PythonCall.pyconvert_return(Instruction(jl_operation, convert_targets(x.targets))) + PythonCall.pyconvert_return(t(jl_operation, convert_targets(x.targets))) end end end -function jl_convert_sim_unitary(::Type{Braket.Instruction}, x::Py) +function jl_convert_sim_unitary(t::Type{Braket.Instruction}, x::Py) jl_matrix = pyconvert(Matrix{ComplexF64}, x._matrix) jl_operation = _handle_modifiers(Unitary(jl_matrix), x) - PythonCall.pyconvert_return(Instruction(jl_operation, convert_targets(x.targets))) + PythonCall.pyconvert_return(t(jl_operation, convert_targets(x.targets))) end for (fn, jl_typ) in ((:jl_convert_sim_bitflip, :BitFlip), (:jl_convert_sim_phaseflip, :PhaseFlip), @@ -365,9 +366,9 @@ for (fn, jl_typ) in ((:jl_convert_sim_bitflip, :BitFlip), (:jl_convert_sim_twoqubitdephasing, :TwoQubitDephasing), ) @eval begin - function $fn(::Type{Braket.Instruction}, x::Py) + function $fn(t::Type{Braket.Instruction}, x::Py) jl_op = $jl_typ(pyconvert(Union{Float64, FreeParameter}, x._probability)) - PythonCall.pyconvert_return(Instruction(jl_op, convert_targets(x.targets))) + PythonCall.pyconvert_return(t(jl_op, convert_targets(x.targets))) end end end @@ -376,45 +377,41 @@ for (fn, jl_typ) in ((:jl_convert_sim_amplitudedamping, :AmplitudeDamping), (:jl_convert_sim_phasedamping, :PhaseDamping), ) @eval begin - function $fn(::Type{Braket.Instruction}, x::Py) + function $fn(t::Type{Braket.Instruction}, x::Py) jl_op = $jl_typ(pyconvert(Union{Float64, FreeParameter}, x._gamma)) - PythonCall.pyconvert_return(Instruction(jl_op, convert_targets(x.targets))) + PythonCall.pyconvert_return(t(jl_op, convert_targets(x.targets))) end end end -function jl_convert_sim_paulichannel(::Type{Braket.Instruction}, x::Py) +function jl_convert_sim_paulichannel(t::Type{Braket.Instruction}, x::Py) jl_op = PauliChannel(pyconvert(Union{Float64, FreeParameter}, x._probX), pyconvert(Union{Float64, FreeParameter}, x._probY), pyconvert(Union{Float64, FreeParameter}, x._probZ) ) - PythonCall.pyconvert_return(Instruction(jl_op, convert_targets(x.targets))) + PythonCall.pyconvert_return(t(jl_op, convert_targets(x.targets))) end -function jl_convert_sim_generalizedamplitudedamping(::Type{Braket.Instruction}, x::Py) +function jl_convert_sim_generalizedamplitudedamping(t::Type{Braket.Instruction}, x::Py) jl_op = GeneralizedAmplitudeDamping(pyconvert(Union{Float64, FreeParameter}, x._probability), pyconvert(Union{Float64, FreeParameter}, x._gamma), ) - PythonCall.pyconvert_return(Instruction(jl_op, convert_targets(x.targets))) + PythonCall.pyconvert_return(t(jl_op, convert_targets(x.targets))) end jl_convert_sympy_Pi(::Type{Float64}, x::Py) = PythonCall.pyconvert_return(convert(Float64, π)) jl_convert_sympy_E(::Type{Float64}, x::Py) = PythonCall.pyconvert_return(convert(Float64, ℯ)) -function jl_convert_sympy_Mul(::Type{Float64}, x::Py) - jl_args = [pyconvert(Float64, a) for a in x.args] - val = prod(jl_args, init=1.0) - PythonCall.pyconvert_return(val) -end +jl_convert_sympy_Mul(::Type{Float64}, x::Py) = PythonCall.pyconvert_return(mapreduce(arg->pyconvert(Float64, arg), *, x.args, init=1.0)) -function jl_convert_sim_gphase(::Type{Braket.Instruction}, x::Py) +function jl_convert_sim_gphase(t::Type{Braket.Instruction}, x::Py) angle = pyconvert(Union{FreeParameter, Float64}, x._angle) jl_targets = convert_targets(x.targets) jl_op = MultiQubitPhaseShift{length(jl_targets)}(angle) - PythonCall.pyconvert_return(Instruction(jl_op, jl_targets)) + PythonCall.pyconvert_return(t(jl_op, jl_targets)) end -function jl_convert_sim_kraus(::Type{Braket.Instruction}, x::Py) +function jl_convert_sim_kraus(t::Type{Braket.Instruction}, x::Py) jl_op = Kraus([pyconvert(Matrix{ComplexF64}, m) for m in x._matrices]) - PythonCall.pyconvert_return(Instruction(jl_op, convert_targets(x.targets))) + PythonCall.pyconvert_return(t(jl_op, convert_targets(x.targets))) end for (rt, fn) in ((:(Braket.Sample), :jl_convert_sim_sample), @@ -422,9 +419,9 @@ for (rt, fn) in ((:(Braket.Sample), :jl_convert_sim_sample), (:(Braket.Variance), :jl_convert_sim_variance), ) @eval begin - function $fn(::Type{$rt}, x::Py) + function $fn(t::Type{$rt}, x::Py) jl_obs = pyconvert(Braket.Observables.Observable, x._observable) - PythonCall.pyconvert_return($rt(jl_obs, convert_ir_target(x._targets))) + PythonCall.pyconvert_return(t(jl_obs, convert_ir_target(x._targets))) end end end @@ -437,9 +434,9 @@ for (rt, fn) in ((:(Braket.Probability), :jl_convert_sim_probability), end end -function jl_convert_sim_amplitude(::Type{Braket.Amplitude}, x::Py) - jl_rt = Braket.Amplitude([pyconvert(String, s) for s in x._states]) - PythonCall.pyconvert_return(jl_rt) +function jl_convert_sim_amplitude(t::Type{Braket.Amplitude}, x::Py) + states = map(state->pyconvert(String, state), x._states) + PythonCall.pyconvert_return(t(states)) end # exclude adjoint gradient translation from coverage for now @@ -450,20 +447,20 @@ jl_convert_sim_adjointgradient(::Type{Braket.Result}, x::Py) = error("not implem # COV_EXCL_STOP jl_convert_sim_statevector(::Type{Braket.StateVector}, x::Py) = PythonCall.pyconvert_return(Braket.StateVector()) -function jl_convert_sim_circuit(::Type{Braket.Circuit}, x) - instructions = [pyconvert(Instruction, ix) for ix in x.instructions] - results = [pyconvert(Result, rt) for rt in x.results] - prog = Braket.Circuit() +function jl_convert_sim_circuit(t::Type{Braket.Circuit}, x) + instructions = map(ix->pyconvert(Instruction, ix), x.instructions) + results = map(rt->pyconvert(Result, rt), x.results) + prog = t() foreach(ix->Braket.add_instruction!(prog, ix), instructions) foreach(rt->push!(prog.result_types, rt), results) PythonCall.pyconvert_return(prog) end -function jl_convert_program(::Type{Braket.IR.Program}, x_jaqcd) - instructions = [pyconvert(Instruction, ix) for ix in x_jaqcd.instructions] +function jl_convert_program(t::Type{Braket.IR.Program}, x_jaqcd) + instructions = map(ix->pyconvert(Instruction, ix), x_jaqcd.instructions) results = pyisinstance(x_jaqcd.results, PythonCall.pybuiltins.list) ? [pyconvert(AbstractProgramResult, rt) for rt in x_jaqcd.results] : AbstractProgramResult[] bris = pyisinstance(x_jaqcd.basis_rotation_instructions, PythonCall.pybuiltins.list) ? [pyconvert(Instruction, ix) for ix in x_jaqcd.basis_rotation_instructions] : Instruction[] - prog = Braket.Program(Braket.header_dict[Braket.Program], instructions, results, bris) + prog = t(Braket.header_dict[Braket.Program], instructions, results, bris) PythonCall.pyconvert_return(prog) end jl_convert_circuit(::Type{Braket.IR.Program}, x) = jl_convert_program(Braket.IR.Program, x._to_jaqcd()) @@ -579,6 +576,6 @@ function Py(r::GateModelTaskResult, action) py_qubits = !isnothing(r.measuredQubits) ? pylist(r.measuredQubits) : PythonCall.pybuiltins.None py_results = pylist(Py(rtv) for rtv in r.resultTypes) py_task_mtd = braket[].task_result.task_metadata_v1.TaskMetadata(id=pystr(r.taskMetadata.id), shots=Py(r.taskMetadata.shots), deviceId=pystr(r.taskMetadata.deviceId)) - py_addl_mtd = braket[].task_result.additional_metadata.AdditionalMetadata(action=braket[].ir.openqasm.program_v1.Program(source="")) + py_addl_mtd = braket[].task_result.additional_metadata.AdditionalMetadata(action=action) return braket[].task_result.GateModelTaskResult(measurements=py_measurements, measurementProbabilities=py_probabilities, resultTypes=py_results, measuredQubits=py_qubits, taskMetadata=py_task_mtd, additionalMetadata=py_addl_mtd) end diff --git a/src/BraketSimulator.jl b/src/BraketSimulator.jl index 5ee251f..b970208 100644 --- a/src/BraketSimulator.jl +++ b/src/BraketSimulator.jl @@ -145,13 +145,12 @@ function _generate_results( result_types::Vector, simulator::D, ) where {D<:AbstractSimulator} - result_values = [calculate(result_type, simulator) for result_type in result_types] + result_values = map(result_type -> calculate(result_type, simulator), result_types) result_values = [val isa Matrix ? Braket.complex_matrix_to_ir(val) : val for val in result_values] - return [ - Braket.ResultTypeValue(result, result_value) for - (result, result_value) in zip(results, result_values) - ] + return map(zip(results, result_values)) do (result, result_value) + Braket.ResultTypeValue(result, result_value) + end end _translate_result_type(r::Braket.IR.Amplitude, qc::Int) = Braket.Amplitude(r.states) @@ -182,10 +181,10 @@ function _translate_result_types( results::Vector{Braket.AbstractProgramResult}, qubit_count::Int, ) - return [_translate_result_type(r, qubit_count) for r in results] + return map(result->_translate_result_type(result, qubit_count), results) end -function _compute_exact_results(d::AbstractSimulator, program::Program, qc::Int, inputs::Dict{String, Float64}) +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) return _generate_results(program.results, result_types, d) @@ -225,7 +224,7 @@ function simulate( simulator = evolve!(simulator, operations) end @debug "Time for evolution: $(stats.time)" - results = shots == 0 && !isempty(program.results) ? _compute_exact_results(simulator, program, n_qubits, inputs) : [Braket.ResultTypeValue(result_type, 0.0) for result_type in program.results] + results = shots == 0 && !isempty(program.results) ? _compute_exact_results(simulator, program, n_qubits) : [Braket.ResultTypeValue(result_type, 0.0) for result_type in program.results] measured_qubits = get(kwargs, :measured_qubits, collect(0:n_qubits-1)) isempty(measured_qubits) && (measured_qubits = collect(0:n_qubits-1)) stats = @timed _bundle_results(results, circuit_ir, simulator; measured_qubits=measured_qubits) @@ -257,7 +256,7 @@ function simulate( simulator = evolve!(simulator, operations) end @debug "Time for evolution: $(stats.time)" - results = shots == 0 && !isempty(circuit_ir.results) ? _compute_exact_results(simulator, circuit_ir, qubit_count, inputs) : Braket.ResultTypeValue[] + results = shots == 0 && !isempty(circuit_ir.results) ? _compute_exact_results(simulator, circuit_ir, qubit_count) : Braket.ResultTypeValue[] measured_qubits = get(kwargs, :measured_qubits, collect(0:qubit_count-1)) isempty(measured_qubits) && (measured_qubits = collect(0:qubit_count-1)) stats = @timed _bundle_results(results, circuit_ir, simulator; measured_qubits=measured_qubits) diff --git a/src/custom_gates.jl b/src/custom_gates.jl index eea6e24..5873563 100644 --- a/src/custom_gates.jl +++ b/src/custom_gates.jl @@ -62,6 +62,23 @@ Braket.chars(::Type{MultiQubitPhaseShift}) = "GPhase(ang)" Braket.qubit_count(g::MultiQubitPhaseShift{N}) where {N} = N Base.inv(g::MultiQubitPhaseShift{N}) where {N} = MultiQubitPhaseShift{N}((-g.angle[1],)) +function apply_gate!( + factor::ComplexF64, + diagonal::Braket.PauliEigenvalues{N}, + state_vec::StateVector{T}, + ts::Vararg{Int,N}, +) where {T<:Complex,N} + g_mat = Diagonal(SVector{2^N,ComplexF64}(exp(factor * diagonal[i]) for i = 1:2^N)) + apply_gate!(g_mat, state_vec, ts...) +end +function apply_gate!( + factor::ComplexF64, + state_vec::StateVector{T}, + ts::Vararg{Int,N}, +) where {T<:Complex,N} + g_mat = Diagonal(SVector{2^N,ComplexF64}(factor for i = 1:2^N)) + apply_gate!(g_mat, state_vec, ts...) +end for (V, f) in ((true, :conj), (false, :identity)) @eval begin apply_gate!( @@ -77,77 +94,44 @@ for (V, f) in ((true, :conj), (false, :identity)) t1::Int, t2::Int, ) where {T<:Complex} = apply_gate!(Val($V), ZZ(g.angle), state_vec, t1, t2) - function apply_gate!( + apply_gate!( ::Val{$V}, g::MultiRZ, state_vec::StateVector{T}, ts::Vararg{Int,N}, - ) where {T<:Complex,N} - factor = -im * g.angle[1] / 2.0 - r_mat = Braket.PauliEigenvalues(Val(N)) - g_mat = Diagonal($f(SVector{2^N,ComplexF64}(exp(factor * r_mat[i]) for i = 1:2^N))) - apply_gate!(g_mat, state_vec, ts...) - end - function apply_gate!( - ::Val{$V}, - g::MultiQubitPhaseShift{1}, - state_vec::StateVector{T}, - t::Int, - ) where {T<:Complex} - g_mat = $f(Diagonal(SVector{2, ComplexF64}(exp(im*g.angle[1]), exp(im*g.angle[1])))) - return apply_gate!(g_mat, state_vec, t) - end - function apply_gate!( - ::Val{$V}, - g::MultiQubitPhaseShift{N}, - state_vec::StateVector{T}, - ts::Vararg{Int,N}, - ) where {T<:Complex,N} - n_amps, endian_ts = get_amps_and_qubits(state_vec, ts...) - ordered_ts = sort(collect(endian_ts)) - flip_list = map(0:2^N-1) do t - f_vals = Bool[(((1 << f_ix) & t) >> f_ix) for f_ix = 0:N-1] - return ordered_ts[f_vals] - end - factor = im * g.angle[1] - r_mat = ones(Float64, 2^N) - g_mat = Diagonal($f(SVector{2^N,ComplexF64}(exp(factor) * r_mat[i] for i = 1:2^N))) - apply_gate!(g_mat, state_vec, ts...) - end + ) where {T<:Complex,N} = apply_gate!($f(-im * g.angle[1] / 2.0), Braket.PauliEigenvalues(Val(N)), state_vec, ts...) + apply_gate!(::Val{$V}, g::MultiQubitPhaseShift{N}, state_vec::StateVector{T}, ts::Vararg{Int,N}) where {T<:Complex, N} = apply_gate!($f(exp(im*g.angle[1])), state_vec, ts...) end end -for V in (false, true) - @eval begin - function apply_gate!( - ::Val{$V}, - g::DoubleExcitation, - state_vec::StateVector{T}, - t1::Int, - t2::Int, - t3::Int, - t4::Int, - ) where {T<:Complex} - n_amps, endian_ts = get_amps_and_qubits(state_vec, t1, t2, t3, t4) - ordered_ts = sort(collect(endian_ts)) - cosϕ = cos(g.angle[1] / 2.0) - sinϕ = sin(g.angle[1] / 2.0) - e_t1, e_t2, e_t3, e_t4 = endian_ts - Threads.@threads for ix = 0:div(n_amps, 2^4)-1 - padded_ix = pad_bits(ix, ordered_ts) - i0011 = flip_bits(padded_ix, (e_t3, e_t4)) + 1 - i1100 = flip_bits(padded_ix, (e_t1, e_t2)) + 1 - @inbounds begin - amp0011 = state_vec[i0011] - amp1100 = state_vec[i1100] - state_vec[i0011] = cosϕ * amp0011 - sinϕ * amp1100 - state_vec[i1100] = sinϕ * amp0011 + cosϕ * amp1100 - end - end - return +function apply_gate!( + g::DoubleExcitation, + state_vec::StateVector{T}, + t1::Int, + t2::Int, + t3::Int, + t4::Int, +) where {T<:Complex} + n_amps, endian_ts = get_amps_and_qubits(state_vec, t1, t2, t3, t4) + ordered_ts = sort(collect(endian_ts)) + cosϕ = cos(g.angle[1] / 2.0) + sinϕ = sin(g.angle[1] / 2.0) + e_t1, e_t2, e_t3, e_t4 = endian_ts + Threads.@threads for ix = 0:div(n_amps, 2^4)-1 + padded_ix = pad_bits(ix, ordered_ts) + i0011 = flip_bits(padded_ix, (e_t3, e_t4)) + 1 + i1100 = flip_bits(padded_ix, (e_t1, e_t2)) + 1 + @inbounds begin + amp0011 = state_vec[i0011] + amp1100 = state_vec[i1100] + state_vec[i0011] = cosϕ * amp0011 - sinϕ * amp1100 + state_vec[i1100] = sinϕ * amp0011 + cosϕ * amp1100 end end + return end +apply_gate!(::Val{true}, g::DoubleExcitation, state_vec::StateVector{T}, t1::Int, t2::Int, t3::Int, t4::Int) where {T<:Complex} = apply_gate!(g, state_vec, t1, t2, t3, t4) +apply_gate!(::Val{false}, g::DoubleExcitation, state_vec::StateVector{T}, t1::Int, t2::Int, t3::Int, t4::Int) where {T<:Complex} = apply_gate!(g, state_vec, t1, t2, t3, t4) struct Control{G<:Gate, B} <: Gate g::G diff --git a/src/dm_simulator.jl b/src/dm_simulator.jl index 68f7ff7..a1bb1cc 100644 --- a/src/dm_simulator.jl +++ b/src/dm_simulator.jl @@ -39,10 +39,10 @@ mutable struct DensityMatrixSimulator{T,S} <: end end function init( - ::Type{DensityMatrixSimulator{T,S}}, + t::Type{S}, qubit_count::Int, -) where {T,S<:AbstractMatrix{T}} - dm = S(undef, 2^qubit_count, 2^qubit_count) +) where {T<:Complex,S<:AbstractMatrix{T}} + dm = t(undef, 2^qubit_count, 2^qubit_count) fill!(dm, zero(T)) dm[1, 1] = one(T) return dm @@ -51,7 +51,7 @@ function DensityMatrixSimulator{T,S}( qubit_count::Int, shots::Int, ) where {T,S<:AbstractDensityMatrix{T}} - dm = init(DensityMatrixSimulator{T,S}, qubit_count) + dm = init(S, qubit_count) return DensityMatrixSimulator{T,S}(dm, qubit_count, shots) end """ @@ -70,10 +70,12 @@ Braket.qubit_count(dms::DensityMatrixSimulator) = dms.qubit_count Query the properties and capabilities of a `DensityMatrixSimulator`, including which gates and result types are supported and the minimum and maximum shot and qubit counts. """ Braket.properties(d::DensityMatrixSimulator) = dm_props -supported_operations(d::DensityMatrixSimulator) = - dm_props.action["braket.ir.openqasm.program"].supportedOperations -supported_result_types(d::DensityMatrixSimulator) = - dm_props.action["braket.ir.openqasm.program"].supportedResultTypes +supported_operations(d::DensityMatrixSimulator, ::Val{:OpenQASM}) = dm_props.action["braket.ir.openqasm.program"].supportedOperations +supported_operations(d::DensityMatrixSimulator, ::Val{:JAQCD}) = dm_props.action["braket.ir.jaqcd.program"].supportedOperations +supported_operations(d::DensityMatrixSimulator) = supported_operations(d::DensityMatrixSimulator, Val(:OpenQASM)) +supported_result_types(d::DensityMatrixSimulator, ::Val{:OpenQASM}) = dm_props.action["braket.ir.openqasm.program"].supportedResultTypes +supported_result_types(d::DensityMatrixSimulator, ::Val{:JAQCD}) = dm_props.action["braket.ir.jaqcd.program"].supportedResultTypes +supported_result_types(d::DensityMatrixSimulator) = supported_result_types(d::DensityMatrixSimulator, Val(:OpenQASM)) Braket.device_id(dms::DensityMatrixSimulator) = "braket_dm_v2" Braket.name(dms::DensityMatrixSimulator) = "DensityMatrixSimulator" Base.show(io::IO, dms::DensityMatrixSimulator) = @@ -169,24 +171,14 @@ function evolve!( return dms end -for (gate, obs) in ( - (:X, :(Braket.Observables.X)), - (:Y, :(Braket.Observables.Y)), - (:Z, :(Braket.Observables.Z)), - (:I, :(Braket.Observables.I)), - (:H, :(Braket.Observables.H)), -) - @eval begin - function apply_observable!( - observable::$obs, - dm::S, - targets, - ) where {T<:Complex,S<:AbstractDensityMatrix{T}} - reshaped_dm = reshape(dm, length(dm)) - foreach(target->apply_gate!($gate(), reshaped_dm, target), targets) - return dm - end - end +function apply_observable!( + gate::G, + dm::S, + targets, +) where {T<:Complex,S<:AbstractDensityMatrix{T}, G<:Gate} + reshaped_dm = reshape(dm, length(dm)) + foreach(target->apply_gate!(gate, reshaped_dm, target), targets) + return dm end function apply_observable!( observable::Braket.Observables.HermitianObservable, @@ -233,8 +225,7 @@ end function apply_observables!(dms::DensityMatrixSimulator, observables) !isempty(dms._density_matrix_after_observables) && error("observables have already been applied.") - diag_gates = [diagonalizing_gates(observable...) for observable in observables] - operations = reduce(vcat, diag_gates) + operations = mapreduce(obs->diagonalizing_gates(obs...), vcat, observables) dms._density_matrix_after_observables = deepcopy(dms.density_matrix) reshaped_dm = reshape(dms._density_matrix_after_observables, length(dms.density_matrix)) for operation in operations diff --git a/src/gate_kernels.jl b/src/gate_kernels.jl index c7311e3..39ae323 100644 --- a/src/gate_kernels.jl +++ b/src/gate_kernels.jl @@ -194,34 +194,19 @@ end for (V, f) in ((false, :identity), (true, :conj)) @eval begin - function apply_gate!( - ::Val{$V}, - g::G, - state_vec::StateVector{T}, - qubits::Int..., - ) where {G<:Gate,T<:Complex} - g_mat = $f(matrix_rep(g)) - return apply_gate!(g_mat, state_vec, qubits...) - end - function apply_gate!( - ::Val{$V}, - g::Unitary, - state_vec::StateVector{T}, - qubit::Int, - ) where {T<:Complex} - g_mat = SMatrix{2,2,T}($f(matrix_rep(g))) - return apply_gate!(g_mat, state_vec, qubit) - end - function apply_gate!( - ::Val{$V}, - g::Unitary, - state_vec::StateVector{T}, - q1::Int, - q2::Int, - ) where {T<:Complex} - g_mat = SMatrix{4,4,T}($f(matrix_rep(g))) - return apply_gate!(g_mat, state_vec, q2, q1) - end + apply_gate!(::Val{$V}, + g::G, + state_vec::StateVector{T}, + qubits::Int...) where {G<:Gate,T<:Complex} = apply_gate!($f(matrix_rep(g)), state_vec, qubits...) + apply_gate!(::Val{$V}, + g::Unitary, + state_vec::StateVector{T}, + qubit::Int) where {T<:Complex} = apply_gate!(SMatrix{2,2,T}($f(matrix_rep(g))), state_vec, qubit) + apply_gate!(::Val{$V}, + g::Unitary, + state_vec::StateVector{T}, + q1::Int, + q2::Int) where {T<:Complex} = apply_gate!(SMatrix{4,4,T}($f(matrix_rep(g))), state_vec, q2, q1) end end @@ -247,9 +232,98 @@ for (sw, factor) in ((:Swap, 1.0), (:ISwap, im), (:PSwap, :(exp(im * g.angle[1]) end end +function apply_controlled_gate!( + g_matrix::SMatrix{2, 2, ComplexF64}, + c_bit::Bool, + state_vec::StateVector{T}, + control::Int, + target::Int, +) where {T<:Complex} + n_amps, (endian_control, endian_target) = + get_amps_and_qubits(state_vec, control, target) + + small_t, big_t = minmax(endian_control, endian_target) + g_00, g_10, g_01, g_11 = g_matrix + Threads.@threads for ix = 0:div(n_amps, 4)-1 + ix_00 = pad_bit(pad_bit(ix, small_t), big_t) + ix_10 = flip_bit(ix_00, endian_control) + ix_01 = flip_bit(ix_00, endian_target) + ix_11 = flip_bit(ix_01, endian_control) + lower_ix = c_bit ? ix_10 + 1 : ix_00 + 1 + higher_ix = c_bit ? ix_11 + 1 : ix_01 + 1 + @inbounds begin + lower_amp = state_vec[lower_ix] + higher_amp = state_vec[higher_ix] + state_vec[lower_ix] = g_00 * lower_amp + g_01 * higher_amp + state_vec[higher_ix] = g_10 * lower_amp + g_11 * higher_amp + end + end + return +end +function apply_controlled_gate!( + g_matrix::SMatrix{4, 4, ComplexF64}, + c_bit::Bool, + state_vec::StateVector{T}, + control::Int, + t1::Int, + t2::Int, +) where {T<:Complex} + n_amps, (endian_control, endian_t1, endian_t2) = + get_amps_and_qubits(state_vec, control, t1, t2) + small_t, mid_t, big_t = sort([endian_control, endian_t1, endian_t2]) + Threads.@threads for ix = 0:div(n_amps, 8)-1 + ix_00 = pad_bits(ix, (small_t, mid_t, big_t)) + if c_bit + ix_00 = flip_bit(ix_00, endian_control) + end + ix_10 = flip_bit(ix_00, endian_t2) + ix_01 = flip_bit(ix_00, endian_t1) + ix_11 = flip_bit(ix_01, endian_t2) + ix_vec = SVector{4,Int}(ix_00 + 1, ix_01 + 1, ix_10 + 1, ix_11 + 1) + @views @inbounds begin + amps = SVector{4,T}(state_vec[ix_vec]) + state_vec[ix_vec] = g_matrix * amps + end + end + return +end +# doubly controlled unitaries +function apply_controlled_gate!( + g_matrix::SMatrix{2, 2, ComplexF64}, + c1_bit::Bool, + c2_bit::Bool, + state_vec::StateVector{T}, + c1::Int, + c2::Int, + t::Int, +) where {T<:Complex} + n_amps, (endian_c1, endian_c2, endian_target) = + get_amps_and_qubits(state_vec, c1, c2, t) + small_t, mid_t, big_t = sort([endian_c1, endian_c2, endian_target]) + g_00, g_10, g_01, g_11 = g_matrix + Threads.@threads for ix = 0:div(n_amps, 8)-1 + # insert 0 at c1, 0 at c2, 0 at target + padded_ix = pad_bits(ix, [small_t, mid_t, big_t]) + # flip c1 and c2 if needed + lower_ix = padded_ix + for (c_val, c_bit) in zip((c1_bit, c2_bit), (endian_c1, endian_c2)) + c_val == 1 && (lower_ix = flip_bit(lower_ix, c_bit)) + end + # flip target + higher_ix = flip_bit(lower_ix, endian_target) + 1 + lower_ix += 1 + @inbounds begin + lower_amp = state_vec[lower_ix] + higher_amp = state_vec[higher_ix] + state_vec[lower_ix] = g_00 * lower_amp + g_01 * higher_amp + state_vec[higher_ix] = g_10 * lower_amp + g_11 * higher_amp + end + end + return +end for (V, f) in ((true, :conj), (false, :identity)) @eval begin - function apply_controlled_gate!( + apply_controlled_gate!( ::Val{$V}, ::Val{1}, g::G, @@ -258,31 +332,8 @@ for (V, f) in ((true, :conj), (false, :identity)) control_bits::NTuple{1,Int}, control::Int, target::Int, - ) where {G<:Gate,TG<:Gate,T<:Complex} - n_amps, (endian_control, endian_target) = - get_amps_and_qubits(state_vec, control, target) - - small_t, big_t = minmax(endian_control, endian_target) - g_mat = $f(matrix_rep(tg)) - c_bit = control_bits[1] == 1 - g_00, g_10, g_01, g_11 = g_mat - Threads.@threads for ix = 0:div(n_amps, 4)-1 - ix_00 = pad_bit(pad_bit(ix, small_t), big_t) - ix_10 = flip_bit(ix_00, endian_control) - ix_01 = flip_bit(ix_00, endian_target) - ix_11 = flip_bit(ix_01, endian_control) - lower_ix = c_bit ? ix_10 + 1 : ix_00 + 1 - higher_ix = c_bit ? ix_11 + 1 : ix_01 + 1 - @inbounds begin - lower_amp = state_vec[lower_ix] - higher_amp = state_vec[higher_ix] - state_vec[lower_ix] = g_00 * lower_amp + g_01 * higher_amp - state_vec[higher_ix] = g_10 * lower_amp + g_11 * higher_amp - end - end - return - end - function apply_controlled_gate!( + ) where {G<:Gate,TG<:Gate,T<:Complex} = apply_controlled_gate!($f(matrix_rep(tg)), control_bits[1] == 1, state_vec, control, target) + apply_controlled_gate!( ::Val{$V}, ::Val{1}, g::G, @@ -292,32 +343,9 @@ for (V, f) in ((true, :conj), (false, :identity)) control::Int, t1::Int, t2::Int, - control_bit::Int=1, - ) where {G<:Gate,TG<:Gate,T<:Complex} - n_amps, (endian_control, endian_t1, endian_t2) = - get_amps_and_qubits(state_vec, control, t1, t2) - small_t, mid_t, big_t = sort([endian_control, endian_t1, endian_t2]) - g_mat = $f(matrix_rep(tg)) - c_bit = control_bits[1] == 1 - Threads.@threads for ix = 0:div(n_amps, 8)-1 - ix_00 = pad_bits(ix, (small_t, mid_t, big_t)) - if c_bit - ix_00 = flip_bit(ix_00, endian_control) - end - ix_10 = flip_bit(ix_00, endian_t2) - ix_01 = flip_bit(ix_00, endian_t1) - ix_11 = flip_bit(ix_01, endian_t2) - ix_vec = SVector{4,Int}(ix_00 + 1, ix_01 + 1, ix_10 + 1, ix_11 + 1) - @views @inbounds begin - amps = SVector{4,T}(state_vec[ix_vec]) - state_vec[ix_vec] = g_mat * amps - end - end - return - end - + ) where {G<:Gate,TG<:Gate,T<:Complex} = apply_controlled_gate!($f(matrix_rep(tg)), control_bits[1] == 1, state_vec, control, t1, t2) # doubly controlled unitaries - function apply_controlled_gate!( + apply_controlled_gate!( ::Val{$V}, ::Val{2}, g::G, @@ -327,32 +355,7 @@ for (V, f) in ((true, :conj), (false, :identity)) c1::Int, c2::Int, t::Int, - ) where {G<:Gate,TG<:Gate,T<:Complex} - n_amps, (endian_c1, endian_c2, endian_target) = - get_amps_and_qubits(state_vec, c1, c2, t) - small_t, mid_t, big_t = sort([endian_c1, endian_c2, endian_target]) - g_mat = $f(matrix_rep(tg)) - g_00, g_10, g_01, g_11 = g_mat - Threads.@threads for ix = 0:div(n_amps, 8)-1 - # insert 0 at c1, 0 at c2, 0 at target - padded_ix = pad_bits(ix, [small_t, mid_t, big_t]) - # flip c1 and c2 if needed - lower_ix = padded_ix - for (c_val, c_bit) in zip(control_bits, (endian_c1, endian_c2)) - c_val == 1 && (lower_ix = flip_bit(lower_ix, c_bit)) - end - # flip target - higher_ix = flip_bit(lower_ix, endian_target) + 1 - lower_ix += 1 - @inbounds begin - lower_amp = state_vec[lower_ix] - higher_amp = state_vec[higher_ix] - state_vec[lower_ix] = g_00 * lower_amp + g_01 * higher_amp - state_vec[higher_ix] = g_10 * lower_amp + g_11 * higher_amp - end - end - return - end + ) where {G<:Gate,TG<:Gate,T<:Complex} = apply_controlled_gate!($f(matrix_rep(tg)), control_bits[1] == 1, control_bits[2] == 1, state_vec, c1, c2, t) end end @@ -377,6 +380,24 @@ for (cg, tg, nc) in ( end end + +function apply_gate!( + factor::Complex, + g::Control{MultiQubitPhaseShift{N}, B}, + state_vec::StateVector{T}, + qubits::Int..., +) where {T<:Complex, N, B} + bvs = g.bitvals + g_mat = ones(ComplexF64, 2^N) + ix = 0 + for (bi, bv) in enumerate(bvs) + ix += bv << (bi - 1) + end + ix += 1 + g_mat[ix] = exp(factor*g.g.angle[1]) + apply_gate!(Diagonal(SVector{2^N, ComplexF64}(g_mat)), state_vec, qubits...) +end + # arbitrary number of targets for (V, f) in ((false, :identity), (true, :conj)) @eval begin @@ -387,30 +408,19 @@ for (V, f) in ((false, :identity), (true, :conj)) qubits::Int..., ) where {T<:Complex, G<:Gate, B} = apply_controlled_gate!(Val($V), Val(B), g, g.g, state_vec, g.bitvals, qubits...) - function apply_gate!( + apply_gate!( ::Val{$V}, g::Control{MultiQubitPhaseShift{N}, B}, state_vec::StateVector{T}, qubits::Int..., - ) where {T<:Complex, N, B} - bvs = g.bitvals - g_mat = ones(ComplexF64, 2^N) - ix = 0 - for (bi, bv) in enumerate(bvs) - ix += bv << (bi - 1) - end - ix += 1 - g_mat[ix] = $f(exp(im*g.g.angle[1])) - apply_gate!(Diagonal(SVector{2^N, ComplexF64}(g_mat)), state_vec, qubits...) - end - function apply_gate!( + ) where {T<:Complex, N, B} = apply_gate!($f(im), g, state_vec, qubits...) + apply_gate!( ::Val{$V}, g::Unitary, state_vec::StateVector{T}, ts::Vararg{Int,NQ}, - ) where {T<:Complex,NQ} + ) where {T<:Complex,NQ} = apply_gate!($f(SMatrix{2^NQ, 2^NQ, ComplexF64}(matrix_rep(g))), state_vec, ts...) - end end end function apply_gate!( diff --git a/src/inverted_gates.jl b/src/inverted_gates.jl index d2ef959..3a54e8f 100644 --- a/src/inverted_gates.jl +++ b/src/inverted_gates.jl @@ -28,21 +28,13 @@ Base.inv(g::Unitary) = Unitary(inv(g.matrix)) Base.inv(g::GPi2) = GPi2(g.angle[1] + π) Base.inv(g::MS) = MS(g.angle[1] + π, g.angle[2], g.angle[3]) -function Base.inv(g::ISwap) - u_mat = zeros(ComplexF64, 4, 4) - u_mat[1, 1] = 1.0 - u_mat[4, 4] = 1.0 - u_mat[2, 3] = -im - u_mat[3, 2] = -im - return Unitary(u_mat) -end -function Base.inv(g::CV) - u_mat = zeros(ComplexF64, 4, 4) - u_mat[1, 1] = 1.0 - u_mat[2, 2] = 1.0 - u_mat[3, 3] = 0.5-0.5im - u_mat[3, 4] = 0.5+0.5im - u_mat[4, 3] = 0.5+0.5im - u_mat[4, 4] = 0.5-0.5im - return Unitary(u_mat) -end +const iswap_inv = ComplexF64[1.0 0.0 0.0 0.0; + 0.0 0.0 -im 0.0; + 0.0 -im 0.0 0.0; + 0.0 0.0 0.0 1.0] +Base.inv(::ISwap) = Unitary(iswap_inv) +const cv_inv = ComplexF64[1.0 0.0 0.0 0.0; + 0.0 1.0 0.0 0.0; + 0.0 0.0 0.5-0.5im 0.5+0.5im; + 0.0 0.0 0.5+0.5im 0.5-0.5im] +Base.inv(::CV) = Unitary(cv_inv) diff --git a/src/result_types.jl b/src/result_types.jl index 6110aa7..d0fcebd 100644 --- a/src/result_types.jl +++ b/src/result_types.jl @@ -193,6 +193,16 @@ function expectation_op_squared( ) end +for (gate, obs) in ( + (:X, :(Braket.Observables.X)), + (:Y, :(Braket.Observables.Y)), + (:Z, :(Braket.Observables.Z)), + (:I, :(Braket.Observables.I)), + (:H, :(Braket.Observables.H)), +) + @eval apply_observable!(::$obs, sv_or_dm::T, targets) where {T<:AbstractVecOrMat{<:Complex}} = apply_observable!($gate(), sv_or_dm, targets) +end + function apply_observable!( observable::Braket.Observables.TensorProduct, sv_or_dm::T, diff --git a/src/sv_simulator.jl b/src/sv_simulator.jl index d85c455..c460f8d 100644 --- a/src/sv_simulator.jl +++ b/src/sv_simulator.jl @@ -41,10 +41,10 @@ mutable struct StateVectorSimulator{T,S<:AbstractVector{T}} <: AbstractSimulator end end function init( - ::Type{StateVectorSimulator{T,S}}, + t::Type{S}, qubit_count::Int, ) where {T,S<:AbstractVector{T}} - sv = S(undef, 2^qubit_count) + sv = t(undef, 2^qubit_count) fill!(sv, zero(T)) sv[1] = one(T) return sv @@ -54,7 +54,7 @@ function StateVectorSimulator{T,S}( qubit_count::Int, shots::Int, ) where {T,S<:AbstractVector{T}} - sv = init(StateVectorSimulator{T,S}, qubit_count) + sv = init(S, qubit_count) return StateVectorSimulator{T,S}(sv, qubit_count, shots) end """ @@ -73,10 +73,12 @@ Braket.qubit_count(svs::StateVectorSimulator) = svs.qubit_count Query the properties and capabilities of a `StateVectorSimulator`, including which gates and result types are supported and the minimum and maximum shot and qubit counts. """ Braket.properties(svs::StateVectorSimulator) = sv_props -supported_operations(svs::StateVectorSimulator) = - sv_props.action["braket.ir.openqasm.program"].supportedOperations -supported_result_types(svs::StateVectorSimulator) = - sv_props.action["braket.ir.openqasm.program"].supportedResultTypes +supported_operations(svs::StateVectorSimulator, ::Val{:OpenQASM}) = sv_props.action["braket.ir.openqasm.program"].supportedOperations +supported_operations(svs::StateVectorSimulator) = supported_operations(svs::StateVectorSimulator, Val(:OpenQASM)) +supported_operations(svs::StateVectorSimulator, ::Val{:JAQCD}) = sv_props.action["braket.ir.jaqcd.program"].supportedOperations +supported_result_types(svs::StateVectorSimulator, ::Val{:OpenQASM}) = sv_props.action["braket.ir.openqasm.program"].supportedResultTypes +supported_result_types(svs::StateVectorSimulator, ::Val{:JAQCD}) = sv_props.action["braket.ir.jaqcd.program"].supportedResultTypes +supported_result_types(svs::StateVectorSimulator) = supported_result_types(svs::StateVectorSimulator, Val(:OpenQASM)) Braket.device_id(svs::StateVectorSimulator) = "braket_sv_v2" Braket.name(svs::StateVectorSimulator) = "StateVectorSimulator" Base.show(io::IO, svs::StateVectorSimulator) = @@ -151,23 +153,13 @@ state_vector(svs::StateVectorSimulator) = svs.state_vector density_matrix(svs::StateVectorSimulator) = kron(svs.state_vector, adjoint(svs.state_vector)) -for (gate, obs) in ( - (:X, :(Braket.Observables.X)), - (:Y, :(Braket.Observables.Y)), - (:Z, :(Braket.Observables.Z)), - (:I, :(Braket.Observables.I)), - (:H, :(Braket.Observables.H)), -) - @eval begin - function apply_observable!( - observable::$obs, - sv::S, - target::Int, - ) where {S<:AbstractVector{<:Complex}} - apply_gate!($gate(), sv, target) - return sv - end - end +function apply_observable!( + gate::G, + sv::S, + target::Int, +) where {S<:AbstractVector{<:Complex}, G<:Gate} + apply_gate!(gate, sv, target) + return sv end function apply_observable!( observable::Braket.Observables.HermitianObservable, @@ -271,7 +263,7 @@ function apply_observables!(svs::StateVectorSimulator, observables) !isempty(svs._state_vector_after_observables) && error("observables have already been applied.") svs._state_vector_after_observables = deepcopy(svs.state_vector) - operations = reduce(vcat, diagonalizing_gates(obs...) for obs in observables) + operations = mapreduce(obs->diagonalizing_gates(obs...), vcat, observables) for operation in operations apply_gate!(operation.operator, svs._state_vector_after_observables, operation.target...) end diff --git a/src/validation.jl b/src/validation.jl index fc522fc..0b69e7c 100644 --- a/src/validation.jl +++ b/src/validation.jl @@ -23,39 +23,20 @@ function _validate_amplitude_states(states::Vector{String}, qubit_count::Int) end function _validate_ir_results_compatibility( - d::D, + simulator::D, results, - ::Val{:OpenQASM}, + supported_result_types, ) where {D<:AbstractSimulator} isempty(results) && return - circuit_result_types_name = [rt.type for rt in results] - supported_result_types = properties(d).action["braket.ir.openqasm.program"].supportedResultTypes - supported_result_types_names = [lowercase(string(srt.name)) for srt in supported_result_types] + supported_result_types_names = map(supported_rt->lowercase(string(supported_rt.name)), supported_result_types) for name in circuit_result_types_name name ∉ supported_result_types_names && - throw(ErrorException("result type $name not supported by $D")) - end - return -end - -function _validate_ir_results_compatibility( - d::D, - results, - ::Val{:JAQCD}, -) where {D<:AbstractSimulator} - isempty(results) && return - - circuit_result_types_name = [rt.type for rt in results] - supported_result_types = - properties(d).action["braket.ir.jaqcd.program"].supportedResultTypes - supported_result_types_names = [lowercase(srt.name) for srt in supported_result_types] - for name in circuit_result_types_name - name ∉ supported_result_types_names && - throw(ErrorException("result type $name not supported by $D")) + throw(ErrorException("result type $name not supported by $simulator")) end return end +_validate_ir_results_compatibility(simulator::D, results, ::Val{V}) where {D<:AbstractSimulator, V} = _validate_ir_results_compatibility(simulator, results, supported_result_types(simulator, Val(V))) function _validate_shots_and_ir_results(shots::Int, results, qubit_count::Int) if shots == 0 @@ -91,18 +72,18 @@ function _validate_input_provided(circuit) end function _validate_ir_instructions_compatibility( - d::D, + simulator::D, circuit::Union{Program,Circuit}, - ::Val{:JAQCD}, + supported_operations, ) where {D<:AbstractSimulator} - circuit_instruction_names = [replace(lowercase(string(typeof(ix.operator))), "_"=>"") for ix in circuit.instructions] - supported_instructions = Set(replace(lowercase(op), "_"=>"") for op in properties(d).action["braket.ir.jaqcd.program"].supportedOperations) + circuit_instruction_names = map(ix->replace(lowercase(string(typeof(ix.operator))), "_"=>""), 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(ErrorException("Noise instructions are not supported by the state vector simulator (by default). You need to use the density matrix simulator: LocalSimulator(\"braket_dm_v2\").")) + throw(ErrorException("Noise instructions are not supported by $simulator (by default). You need to use the density matrix simulator: LocalSimulator(\"braket_dm_v2\").")) end end end @@ -111,29 +92,7 @@ function _validate_ir_instructions_compatibility( end return end - -function _validate_ir_instructions_compatibility( - d::D, - circuit::Union{Program,Circuit}, - ::Val{:OpenQASM}, -) where {D<:AbstractSimulator} - circuit_instruction_names = [replace(lowercase(string(typeof(ix.operator))), "_"=>"") for ix in circuit.instructions] - supported_instructions = Set(replace(lowercase(op), "_"=>"") for op in properties(d).action["braket.ir.openqasm.program"].supportedOperations) - no_noise = true - for name in circuit_instruction_names - if name in _NOISE_INSTRUCTIONS - no_noise = false - if name ∉ supported_instructions - throw(ErrorException("Noise instructions are not supported by the state vector simulator (by default). You need to use the density matrix simulator: LocalSimulator(\"braket_dm_v2\").")) - end - end - 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." - end - return -end - +_validate_ir_instructions_compatibility(simulator::D, circuit::Union{Program,Circuit}, v::Val{V}) where {D<:AbstractSimulator, V} = _validate_ir_instructions_compatibility(simulator, circuit, supported_operations(simulator, v)) _validate_result_type_qubits_exist(rt::Braket.StateVector, qubit_count::Int) = return _validate_result_type_qubits_exist(rt::Braket.Amplitude, qubit_count::Int) = return @@ -158,12 +117,12 @@ end _validate_result_types_qubits_exist(rts::Vector{RT}, qubit_count::Int) where {RT<:Result} = foreach(rt->_validate_result_type_qubits_exist(rt, qubit_count), rts) function _validate_operation_qubits(operations::Vector{<:Instruction}) - targs = (ix.target for ix in operations) unique_qs = Set{Int}() max_qc = 0 - for t in targs - max_qc = max(max_qc, t...) - union!(unique_qs, t) + for operation in operations + targets = operation.target + max_qc = max(max_qc, targets...) + union!(unique_qs, targets) end max_qc >= length(unique_qs) && throw( "Non-contiguous qubit indices supplied; qubit indices in a circuit must be contiguous. Qubits referenced: $unique_qs",