Skip to content

Commit

Permalink
Merge pull request #3 from psrenergy/pr/update
Browse files Browse the repository at this point in the history
Update for Qiskit 1.x
  • Loading branch information
pedroripper authored May 29, 2024
2 parents 063363c + 49c1b54 commit cbd73f4
Show file tree
Hide file tree
Showing 7 changed files with 323 additions and 207 deletions.
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 <pedroxavier@psr-inc.com>", "pedroripper <pedroripper@psr-inc.com>"]
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)
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

2 comments on commit cbd73f4

@pedroripper
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/107848

Tip: Release Notes

Did you know you can add release notes too? Just add markdown formatted text underneath the comment after the text
"Release notes:" and it will be added to the registry PR, and if TagBot is installed it will also be added to the
release that TagBot creates. i.e.

@JuliaRegistrator register

Release notes:

## Breaking changes

- blah

To add them here just re-invoke and the PR will be updated.

Tagging

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.3.0 -m "<description of version>" cbd73f4cd50f636bc3f85eaf22bcc5567f1b49bd
git push origin v0.3.0

Please sign in to comment.