Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update for Qiskit 1.x #3

Merged
merged 20 commits into from
May 29, 2024
12 changes: 10 additions & 2 deletions CondaPkg.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
channels = ["anaconda", "conda-forge"]

[deps]
python = ">=3.7,<=3.11"

[pip.deps]
qiskit = "==0.39.4"
qiskit-optimization = "==0.4.0"
qiskit = "==1.1.0"
qiskit-optimization = "==0.6.1"
qiskit-ibm-runtime = "==0.23.0"
qiskit-algorithms = "==0.3.0"
qiskit-aer = "==0.14.1"
scipy = "==1.13.0"
numpy = "==1.26.0"
11 changes: 6 additions & 5 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
name = "QiskitOpt"
uuid = "81b20daf-e62b-4502-a0d1-aa084de80e33"
authors = ["pedromxavier <[email protected]>", "pedroripper <[email protected]>"]
version = "0.2.0"
version = "0.3.0"

[deps]
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
PythonCall = "6099a3de-0909-46bc-b1f4-468b9a2dfc0d"
QUBODrivers = "a3f166f7-2cd3-47b6-9e1e-6fbfe0449eb0"
QUBO = "ce8c2e91-a970-4681-856b-16178c24a30c"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[compat]
PythonCall = "0.9.12"
QUBODrivers = "0.1"
julia = "1.6"
PythonCall = "0.9.20"
julia = "1.9"
QUBO = "0.3.0"
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ end
To access IBM's Quantum Computers, it is necessary to create an account at [IBM Q](https://quantum-computing.ibm.com/) to obtain an API Token and run the following python code:

```python
from qiskit import IBMQ
from qiskit_ibm_runtime import QiskitRuntimeService

IBMQ.save_account("YOUR_TOKEN_HERE")
QiskitRuntimeService.save_account(channel='ibm_quantum', token="YOUR_TOKEN_HERE")
```

Another option is to set the `IBMQ_API_TOKEN` environment variable before loading `QiskitOpt.jl`:
Expand Down
215 changes: 140 additions & 75 deletions src/QAOA.jl
Original file line number Diff line number Diff line change
@@ -1,113 +1,178 @@
module QAOA

using Random
using PythonCall: pyconvert
using LinearAlgebra
using PythonCall: pyconvert, pylist, pydict, pyint, pytuple, pybool, @pyexec
using ..QiskitOpt:
qiskit,
qiskit_optimization_algorithms,
qiskit_optimization_runtime,
quadratic_program
import QUBODrivers:
MOI,
QUBODrivers,
QUBOTools,
Sample,
SampleSet,
@setup,
sample

@setup Optimizer begin
name = "IBM Qiskit QAOA"
sense = :min
domain = :bool
version = v"0.4.0"
qiskit_ibm_runtime,
qiskit_algorithms,
qiskit_aer,
quadratic_program,
scipy,
numpy

using QUBO
MOI = QUBODrivers.MOI
Sample = QUBODrivers.Sample
SampleSet = QUBODrivers.SampleSet

QUBODrivers.@setup Optimizer begin
name = "QAOA @ IBMQ"
attributes = begin
NumberOfReads["num_reads"]::Integer = 1_000
RandomSeed["seed"]::Union{Integer,Nothing} = nothing
IBMBackend["ibm_backend"]::String = "ibmq_qasm_simulator"
MaximumIterations["max_iter"]::Integer = 15
NumberOfReads["num_reads"]::Integer = 100
NumberOfLayers["num_layers"]::Integer = 1
InitialParameters["initial_parameters"]::Union{Vector{Float64}, Nothing} = nothing
IBMFakeBackend["ibm_fake_backend"] = qiskit_ibm_runtime.fake_provider.FakeAlgiers
IBMBackend["ibm_backend"]::Union{String, Nothing} = nothing
IsLocal["is_local"]::Bool = false
Entanglement["entanglement"]::String = "linear"
Channel["channel"]::String = "ibm_quantum"
Instance["instance"]::String = "ibm-q/open/main"
end
end

function sample(sampler::Optimizer{T}) where {T}
function QUBODrivers.sample(sampler::Optimizer{T}) where {T}
# -*- Retrieve Attributes - *-
seed = MOI.get(sampler, QAOA.RandomSeed())
num_reads = MOI.get(sampler, QAOA.NumberOfReads())
n, L, Q, α, β = QUBOTools.qubo(sampler, :dense)
ibm_backend = MOI.get(sampler, QAOA.IBMBackend())

# -*- Retrieve Model -*- #
qp, α, β = quadratic_program(sampler)

# -*- Instantiate Random Generator -*- #
rng = Random.Xoshiro(seed)

# Results vector
samples = Vector{Sample{T,Int}}(undef, num_reads)
samples = QUBOTools.Sample{T,Int}[]

# Timing Information
# Extra Information
metadata = Dict{String,Any}(
"origin" => "IBMQ QAOA @ $(ibm_backend)",
"time" => Dict{String,Any}(),
"time" => Dict{String,Any}(),
"evals" => Vector{Float64}(),
)

# Connect to IBMQ and get backend
connect(sampler) do client
qaoa = qiskit_optimization_algorithms.MinimumEigenOptimizer(client)
results = qaoa.solve(qp)

Ψ = Vector{Int}[]
ρ = Float64[]
Λ = T[]
retrieve(sampler) do result, sample_results
if MOI.get(sampler, MOI.ObjectiveSense()) == MOI.MAX_SENSE
α = -α
end

for sample in results.samples
for key in sample_results.keys()
state = reverse(parse.(Int,split(pyconvert.(String, key),"")))
sample = QUBOTools.Sample{T,Int}(
# state:
push!(Ψ, pyconvert.(Int, sample.x))
state,
# energy:
α * (state'* (Q+Diagonal(L)) * state + β),
# reads:
push!(ρ, pyconvert(Float64, sample.probability))
# value:
push!(Λ, α * (pyconvert(T, sample.fval) + β))
pyconvert(Int, sample_results[key])
)
push!(samples, sample)
end

P = cumsum(ρ)

for i = 1:num_reads
p = rand(rng)
j = first(searchsorted(P, p))

samples[i] = Sample{T}(Ψ[j], Λ[j])
end

metadata["time"]["effective"] = pyconvert(
Float64,
results.min_eigen_solver_result.optimizer_time,
)

return nothing
end

return SampleSet{T}(samples, metadata)
end

function connect(
function retrieve(
callback::Function,
sampler::Optimizer,
)
sampler::Optimizer{T},
) where {T}
# -*- Retrieve Attributes -*- #
ibm_backend = MOI.get(sampler, QAOA.IBMBackend())
max_iter = MOI.get(sampler, QAOA.MaximumIterations())
num_reads = MOI.get(sampler, QAOA.NumberOfReads())
num_layers = MOI.get(sampler, QAOA.NumberOfLayers())
ibm_backend = MOI.get(sampler, QAOA.IBMBackend())
ibm_fake_backend = MOI.get(sampler, QAOA.IBMFakeBackend())
channel = MOI.get(sampler, QAOA.Channel())
instance = MOI.get(sampler, QAOA.Instance())
initial_parameters = MOI.get(sampler, QAOA.InitialParameters())
is_local = MOI.get(sampler, QAOA.IsLocal())

@pyexec """
def cost_function(params, ansatz, hamiltonian, estimator):
pub = (ansatz, [hamiltonian], [params])
result = estimator.run(pubs=[pub]).result()
energy = result[0].data.evs[0]
return energy
""" => cost_function

service = qiskit_ibm_runtime.QiskitRuntimeService(
channel = channel,
instance = instance,
)

backend = if !isnothing(ibm_backend)
_backend = service.get_backend(ibm_backend)
if is_local && ibm_backend != "ibmq_qasm_simulator"
qiskit_aer.AerSimulator.from_backend(_backend)
else
_backend
end
else
_backend = ibm_fake_backend()
is_local = true
ibm_backend = _backend.backend_name
_backend
end

# -*- Load Credentials -*- #
qiskit.IBMQ.load_account()
if is_local && ibm_backend != "ibmq_qasm_simulator"
backend = qiskit_aer.AerSimulator.from_backend(backend)
end

# -*- Connect to provider -*- #
provider = qiskit.IBMQ.get_provider()
backend = provider.get_backend(ibm_backend)
ising_qp = quadratic_program(sampler)
ising_hamiltonian = ising_qp[0]
ansatz = qiskit.circuit.library.QAOAAnsatz(
ising_hamiltonian,
reps = num_layers
)

# pass manager for the quantum circuit (optimize the circuit for the target device)
pass_manager = qiskit.transpiler.preset_passmanagers.generate_preset_pass_manager(
target = backend.target,
optimization_level = 3
)


# Ansatz and Hamiltonian to ISA (Instruction Set Architecture)
ansatz_isa = pass_manager.run(ansatz)
ising_hamiltonian = ising_hamiltonian.apply_layout(layout = ansatz_isa.layout)


if isnothing(initial_parameters)
initial_parameters = numpy.empty([ansatz_isa.num_parameters])
for i in 1:pyconvert(Int, ansatz_isa.num_parameters)
initial_parameters[i-1] = numpy.random.rand()
end
end

estimator = if is_local || ibm_backend == "ibmq_qasm_simulator"
qiskit_ibm_runtime.EstimatorV2(backend = backend)
else
session = qiskit_ibm_runtime.Session(service=service, backend=backend)
qiskit_ibm_runtime.EstimatorV2(session=session)
end
if !is_local
estimator.options.default_shots = num_reads
end

# -*- Setup QAOA Client -*- #
client = qiskit_optimization_runtime.QAOAClient(
provider = provider,
backend = backend,
println("Running QAOA on $(ibm_backend)...")
scipy_options = pydict()
scipy_options["maxiter"] = max_iter
result = scipy.optimize.minimize(
cost_function,
initial_parameters,
args = (ansatz_isa, ising_hamiltonian, estimator),
method = "cobyla",
options = scipy_options
)

callback(client)
qc = ansatz.assign_parameters(result.x)
qc.measure_all()
optimized_qc = pass_manager.run(qc)

qiskit_sampler = qiskit.primitives.StatevectorSampler(default_shots = pyint(num_reads))
sampling_result = qiskit_sampler.run(pylist([optimized_qc])).result()[0]
samples = sampling_result.data.meas.get_counts()

callback(result, samples)

return nothing
end
Expand Down
52 changes: 27 additions & 25 deletions src/QiskitOpt.jl
Original file line number Diff line number Diff line change
@@ -1,50 +1,52 @@
module QiskitOpt

using PythonCall
import QUBODrivers: MOI, QUBODrivers, QUBOTools
using QUBO
MOI = QUBODrivers.MOI

# :: Python Qiskit Modules ::
const qiskit = PythonCall.pynew()
const qiskit_algorithms = PythonCall.pynew()
const qiskit_optimization = PythonCall.pynew()
const qiskit_optimization_algorithms = PythonCall.pynew()
const qiskit_optimization_runtime = PythonCall.pynew()
const qiskit_utils = PythonCall.pynew()
const qiskit = PythonCall.pynew()
const qiskit_optimization = PythonCall.pynew()
const qiskit_ibm_runtime = PythonCall.pynew()
const qiskit_algorithms = PythonCall.pynew()
const qiskit_aer = PythonCall.pynew()
const scipy = PythonCall.pynew()
const numpy = PythonCall.pynew()

function __init__()
# Load Python Packages
PythonCall.pycopy!(qiskit, pyimport("qiskit"))
PythonCall.pycopy!(qiskit_algorithms, pyimport("qiskit.algorithms"))
PythonCall.pycopy!(qiskit_optimization, pyimport("qiskit_optimization"))
PythonCall.pycopy!(
qiskit_optimization_algorithms,
pyimport("qiskit_optimization.algorithms"),
)
PythonCall.pycopy!(qiskit_optimization_runtime, pyimport("qiskit_optimization.runtime"))
PythonCall.pycopy!(qiskit_utils, pyimport("qiskit.utils"))
PythonCall.pycopy!(qiskit_ibm_runtime, pyimport("qiskit_ibm_runtime"))
PythonCall.pycopy!(qiskit_algorithms, pyimport("qiskit_algorithms"))
PythonCall.pycopy!(qiskit_aer, pyimport("qiskit_aer"))
PythonCall.pycopy!(scipy, pyimport("scipy"))
PythonCall.pycopy!(numpy, pyimport("numpy"))

# IBMQ Credentials
IBMQ_API_TOKEN = get(ENV, "IBMQ_API_TOKEN", nothing)
pedroripper marked this conversation as resolved.
Show resolved Hide resolved
IBMQ_INSTANCE = get(ENV, "IBMQ_INSTANCE", "ibm-q/open/main")

if !isnothing(IBMQ_API_TOKEN)
qiskit.IBMQ.save_account(IBMQ_API_TOKEN)
qiskit_ibm_runtime.QiskitRuntimeService.save_account(channel=pystr("ibm_quantum"), instance = pystr(IBMQ_INSTANCE), token=pystr(IBMQ_API_TOKEN))
end
end

function quadratic_program(sampler::QUBODrivers.AbstractSampler{T}) where {T}
# Retrieve Model
Q, α, β = QUBODrivers.qubo(sampler, Dict)
n, L, Q, α, β = QUBOTools.qubo(sampler, :dense)

# Build Qiskit Model
linear = PythonCall.pydict()
quadratic = PythonCall.pydict()

for ((i, j), q) in Q
if i == j
linear[string(i)] = q
else
quadratic[string(i), string(j)] = q
end
sense = MOI.get(sampler, MOI.ObjectiveSense())

for i in 1:n
linear[string(i)] = L[i] * (sense == MOI.MIN_SENSE ? 1 : -1)
end
for i in 1:n, j in 1:n
quadratic[string(i), string(j)] = Q[i,j] * (sense == MOI.MIN_SENSE ? 1 : -1)
end

qp = qiskit_optimization.QuadraticProgram()
Expand All @@ -54,11 +56,11 @@ function quadratic_program(sampler::QUBODrivers.AbstractSampler{T}) where {T}
end

qp.minimize(linear = linear, quadratic = quadratic)

return (qp, α, β)
return qp.to_ising()
end

export QAOA, VQE
export VQE, QAOA

include("QAOA.jl")
include("VQE.jl")
Expand Down
Loading
Loading