This notebook is the Julia counterpart to
notebooks_py/6-QCi_python.ipynb.
It teaches the QUBO workflow supported by
QCIOpt.jl through JuMP, while
identifying the continuous and constrained eqc-models examples that do not
have direct Julia equivalents.
The default path is deterministic, credential-free, and service-free. QCI cloud submission is a separate explicit opt-in. QCIOpt.jl is a community wrapper and is not officially supported by Quantum Computing Inc.
Google Colab¶
Select a Julia runtime, run the bootstrap cell once, and continue in order.
Package installation belongs to the shared notebooks_jl environment; there
are no notebook-local Pkg.add calls.
Notebook Cell
function load_qubonotebooks_bootstrap()
candidates = (
joinpath(pwd(), "scripts", "notebook_bootstrap.jl"),
joinpath(pwd(), "..", "scripts", "notebook_bootstrap.jl"),
joinpath(pwd(), "QUBONotebooks", "scripts", "notebook_bootstrap.jl"),
joinpath("/content", "QUBONotebooks", "scripts", "notebook_bootstrap.jl"),
)
for candidate in candidates
if isfile(candidate)
include(candidate)
return nothing
end
end
in_colab = haskey(ENV, "COLAB_RELEASE_TAG") || haskey(ENV, "COLAB_JUPYTER_IP") || isdir(joinpath("/content", "sample_data"))
if in_colab
repo_dir = get(ENV, "QUBONOTEBOOKS_REPO_DIR", joinpath(pwd(), "QUBONotebooks"))
if !isdir(repo_dir)
println("[bootstrap] Cloning JuliaQUBO/QUBONotebooks into $repo_dir")
run(Cmd(["git", "clone", "--quiet", "--depth", "1", "https://github.com/JuliaQUBO/QUBONotebooks.git", repo_dir]))
end
include(joinpath(repo_dir, "scripts", "notebook_bootstrap.jl"))
return nothing
end
error("Could not locate scripts/notebook_bootstrap.jl from $(pwd()).")
end
load_qubonotebooks_bootstrap()
BOOTSTRAP = Base.invokelatest(QUBONotebooksBootstrap.bootstrap_notebook, "6-QCi")
QUBONOTEBOOKS_REPO_DIR = BOOTSTRAP.repo_dir
JULIA_NOTEBOOKS_DIR = BOOTSTRAP.notebooks_dir
JULIA_PROJECT_DIR = BOOTSTRAP.project_dir
IN_COLAB = BOOTSTRAP.in_colab;Notebook Cell
import Pkg
python_warning_filter = "ignore:invalid escape sequence:SyntaxWarning"
python_warning_filters = String.(filter(!isempty, split(get(ENV, "PYTHONWARNINGS", ""), ",")))
if python_warning_filter ∉ python_warning_filters
push!(python_warning_filters, python_warning_filter)
ENV["PYTHONWARNINGS"] = join(python_warning_filters, ",")
end
if @isdefined(JULIA_PROJECT_DIR)
Pkg.activate(JULIA_PROJECT_DIR; io = devnull)
else
Pkg.activate(@__DIR__; io = devnull)
end
Pkg.instantiate(; io = devnull, allow_autoprecomp = false)Learning objectives¶
By the end of this notebook you will be able to:
construct a binary quadratic JuMP model with
Model(QCIOpt.Optimizer);establish a small QUBO’s optimum by independent enumeration before any provider call;
configure the QCI device, sample count, and token through supported optimizer attributes;
gate cloud execution on both an explicit opt-in and
QCI_TOKEN;enumerate live results and independently check their binary values and reported energies; and
distinguish direct QCIOpt workflows from Python-only or manually reformulated
eqc-modelsexamples.
Prerequisites¶
Prior notebooks: Notebook 2’s QUBO and Ising introduction is recommended, but the exact example below is self-contained.
Basic Julia and JuMP syntax.
The distinction between a constrained optimization model and a QUBO, whose constraints must already be represented in a quadratic objective.
For the optional cloud section only: a QCI account, an API token stored in
QCI_TOKEN, and permission to submit to the selected device.
No token is needed for setup, model construction, enumeration, exercises, or default verification.
# QUBONOTEBOOKS_COLAB_IMPORT_CELL
Base.invokelatest(
QUBONotebooksBootstrap.warm_notebook_packages!,
"6-QCi",
);
@assert QCIOpt.qci_default_token() === nothing
println("Loaded JuMP and QCIOpt without configuring cloud credentials.")
const QCI_OPT_REVIEWED_REVISION = "30a6074fdd5bd75c3f1cf965329edd01c67e63fe"
manifest_text = read(joinpath(JULIA_PROJECT_DIR, "Manifest.toml"), String)
@assert pkgversion(QCIOpt) == v"0.1.0"
@assert occursin("repo-url = \"https://github.com/SECQUOIA/QCIOpt.jl\"", manifest_text)
@assert occursin("repo-rev = \"$QCI_OPT_REVIEWED_REVISION\"", manifest_text)
println("QCIOpt URL-only revision: ", QCI_OPT_REVIEWED_REVISION)QCIOpt URL-only revision: 30a6074fdd5bd75c3f1cf965329edd01c67e63fe
A small QUBO with an independent exact check¶
We will minimize
The square penalizes selecting zero, two, or three items. Exactly one selected
item has energy zero. The instance is deliberately small enough to enumerate,
so the reference result does not depend on QCIOpt or a cloud response. The
binary variables and objective energies are dimensionless quantities [-].
qci_model = Model(QCIOpt.Optimizer)
@variable(qci_model, x[1:3], Bin)
@objective(qci_model, Min, (sum(x) - 1)^2)
@assert num_variables(qci_model) == 3
@assert objective_sense(qci_model) == MOI.MIN_SENSE
@assert termination_status(qci_model) == MOI.OPTIMIZE_NOT_CALLED
qci_modelA JuMP Model
├ solver: QCI Optimizer (dirac-3)
├ objective_sense: MIN_SENSE
│ └ objective_function_type: QuadExpr
├ num_variables: 3
├ num_constraints: 3
│ └ VariableRef in MOI.ZeroOne: 3
└ Names registered in the model
└ :xall_binary_states(n::Integer) = [
Int[bits...] for bits in Iterators.product(ntuple(_ -> (0, 1), n)...)
]
small_qubo_energy(bits) = (sum(bits) - 1)^2
function validated_binary_bits(raw_values; atol = 1e-8)
@assert all(
value ->
isapprox(value, 0.0; atol = atol, rtol = 0.0) ||
isapprox(value, 1.0; atol = atol, rtol = 0.0),
raw_values,
) "QCI returned a non-binary sample outside atol=$atol."
return round.(Int, raw_values)
end
@assert validated_binary_bits([0.0, 1.0, 0.0]) == [0, 1, 0]
fractional_values_rejected = try
validated_binary_bits([0.4, 0.6, 0.0])
false
catch error
error isa AssertionError
end
@assert fractional_values_rejected
exact_energy_table = [
(bits = bits, energy = small_qubo_energy(bits))
for bits in all_binary_states(3)
]
exact_best_energy = minimum(row.energy for row in exact_energy_table)
exact_best_states = [
row.bits for row in exact_energy_table if row.energy == exact_best_energy
]
@assert exact_best_energy == 0
@assert length(exact_best_states) == 3
@assert all(bits -> sum(bits) == 1, exact_best_states)
@assert all(row -> row.energy >= exact_best_energy, exact_energy_table)
println("Independent exact optimum: energy = $exact_best_energy")
println("Optimal bit vectors: ", exact_best_states)Independent exact optimum: energy = 0
Optimal bit vectors: [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
The three optima are the three one-hot vectors. This exact table is the contract for the optional live response: a returned bit vector must be binary, and its independently recomputed energy must agree with the provider value. The table does not predict which optimum will be sampled most often.
QCIOpt attributes and optional cloud execution¶
The pinned QCIOpt revision exposes DeviceType() on the JuMP optimizer.
Its current optimizer implementation represents the sample count and API token
as MathOptInterface raw attributes named num_samples and api_token.
The upstream README shows QCIOpt.NumberOfReads(), but that spelling is not
exported by the pinned optimizer revision. The additive
QCIOpt.DiracSampler interface instead calls its count
NumberOfSamples(). Here we stay with the issue’s requested
Model(QCIOpt.Optimizer) interface and use the attributes its source and tests
actually support.
set_attribute(qci_model, QCIOpt.DeviceType(), "dirac-3")
set_attribute(qci_model, MOI.RawOptimizerAttribute("num_samples"), 10)
set_silent(qci_model)
@assert get_attribute(qci_model, QCIOpt.DeviceType()) == "dirac-3"
@assert get_attribute(qci_model, MOI.RawOptimizerAttribute("num_samples")) == 10
qci_status_after_attribute_config = termination_status(qci_model)
@assert qci_status_after_attribute_config == MOI.OPTIMIZE_NOT_CALLED
println("Configured DIRAC-3 with 10 requested samples; no job has been submitted.")Configured DIRAC-3 with 10 requested samples; no job has been submitted.
Explicit cloud guard¶
Cloud execution requires both:
QUBONOTEBOOKS_QCI_ENABLE_CLOUD=1, an explicit acknowledgement that the next cell may submit a provider job; anda nonempty
QCI_TOKENsupplied through the process environment or an approved hosted-notebook secret store.
The default verification target forces the opt-in off. A separate live target
also sets QUBONOTEBOOKS_QCI_REQUIRE_CLOUD=1, so it fails if submission did not
occur. Never paste a token into a cell or save it in notebook output.
qci_cloud_requested =
get(ENV, "QUBONOTEBOOKS_QCI_ENABLE_CLOUD", "0") == "1"
qci_cloud_required =
get(ENV, "QUBONOTEBOOKS_QCI_REQUIRE_CLOUD", "0") == "1"
function require_qci_credentials()
token = get(ENV, "QCI_TOKEN", "")
isempty(strip(token)) && error(
"QCI cloud execution was requested, but QCI_TOKEN is empty. " *
"Set it through the process environment or an approved secret store.",
)
return token
end
if qci_cloud_requested
println("QCI cloud execution requested; credentials will be checked in the submission cell.")
else
println("QCI cloud execution is disabled. Set QUBONOTEBOOKS_QCI_ENABLE_CLOUD=1 to opt in.")
endQCI cloud execution requested; credentials will be checked in the submission cell.
qci_cloud_submitted = false
qci_live_results = NamedTuple[]
if qci_cloud_requested
token = require_qci_credentials()
set_attribute(
qci_model,
MOI.RawOptimizerAttribute("api_token"),
token,
)
# QCIOpt's Dirac-3 level check currently consults its package default
# token while querying the current allocation. Bridge the explicitly
# supplied token only for the submission, then restore the default.
previous_default_token = QCIOpt.qci_default_token()
QCIOpt.qci_default_token!(token)
try
optimize!(qci_model)
finally
QCIOpt.qci_default_token!(previous_default_token)
end
qci_cloud_submitted = true
@assert result_count(qci_model) >= 1
for result_index in 1:result_count(qci_model)
raw_values = value.(x; result=result_index)
bits = validated_binary_bits(raw_values)
reported_energy = objective_value(qci_model; result=result_index)
recomputed_energy = small_qubo_energy(bits)
multiplicity = MOI.get(
qci_model,
QCIOpt.ResultMultiplicity(result_index),
)
@assert isapprox(recomputed_energy, reported_energy; atol=1e-8)
@assert multiplicity >= 1
push!(
qci_live_results,
(
bits = Tuple(bits),
energy = reported_energy,
multiplicity = multiplicity,
),
)
end
println(
"Validated $(length(qci_live_results)) returned solutions; " *
"best energy = $(minimum(row.energy for row in qci_live_results)).",
)
for (result_index, row) in enumerate(qci_live_results)
println(
" result $result_index: bits=$(collect(row.bits)), " *
"energy=$(row.energy), multiplicity=$(row.multiplicity)",
)
end
else
println("QCI cloud submission skipped; the exact credential-free result remains available.")
end
if qci_cloud_required && !qci_cloud_submitted
error("QCI cloud verification was required, but no job was submitted.")
end
nothingValidated 3 returned solutions; best energy = 0.0.
result 1: bits=[0, 1, 0], energy=0.0, multiplicity=5
result 2: bits=[0, 0, 1], energy=0.0, multiplicity=3
result 3: bits=[1, 0, 0], energy=0.0, multiplicity=2
Python-to-Julia feature map¶
The Python notebook uses eqc-models, while QCIOpt is a JuMP/MOI wrapper.
Similar provider branding does not make the modeling surfaces interchangeable.
| Python Notebook 6 workflow | QCIOpt.jl status | Julia treatment here |
|---|---|---|
| Continuous constrained Dirac-3 model | Python-only | The reviewed QCIOpt optimizer supports binary/integer variable domains, not the Python continuous constrained API. |
| Bounded integer quadratic objective on Dirac-3 | Direct for supported quadratic JuMP objectives | Use integer variables with bounds and DeviceType() == "dirac-3"; higher-degree nonlinear objectives are not claimed. |
| QUBO through DIRAC-3 | Direct | Use binary JuMP variables and a quadratic objective, as in this notebook. |
| Constrained linear integer model converted to QUBO | Requires manual penalty reformulation | Derive and validate the penalty model before submission; QCIOpt does not perform the Python notebook’s automatic conversion. |
| Explicit constrained polynomial model | Python-only as shown | QCIOpt has no direct equivalent to the eqc-models constrained-polynomial wrapper; a QUBO-compatible case may be manually reformulated, but unsupported constraints are not simulated. |
This boundary is intentional: the notebook preserves the learning objective of modeling, submission, decoding, and validation without pretending unsupported API parity.
Reading an optional live result responsibly¶
A returned minimum on this three-variable exercise demonstrates only that a sample contained an exact optimum. It is not evidence of quantum advantage, solver superiority, or production-scale performance. Provider queues and device behavior can vary. Keep live validation focused on binary values, independently recomputed energies, and multiplicities—not account details, opaque identifiers, or unsanitized provider metadata.
Practice checkpoints¶
Each exercise remains credential-free.
# EXERCISE 1: Generalize the exact check to four variables and predict the
# optimum energy and degeneracy before running the solution cell.Notebook Cell
# SOLUTION (hidden in workshop version):
four_bit_rows = [
(bits = bits, energy = small_qubo_energy(bits))
for bits in all_binary_states(4)
]
four_bit_best = minimum(row.energy for row in four_bit_rows)
four_bit_optima = [row.bits for row in four_bit_rows if row.energy == four_bit_best]
@assert four_bit_best == 0
@assert length(four_bit_optima) == 4
@assert all(bits -> sum(bits) == 1, four_bit_optima)
println("Four-variable one-hot optimum: $four_bit_best with $(length(four_bit_optima)) states.")Four-variable one-hot optimum: 0 with 4 states.
# EXERCISE 2: Query the configured device and sample count. Explain why setting
# these attributes does not itself submit a QCI job.Notebook Cell
# SOLUTION (hidden in workshop version):
exercise_device = get_attribute(qci_model, QCIOpt.DeviceType())
exercise_samples = get_attribute(
qci_model,
MOI.RawOptimizerAttribute("num_samples"),
)
@assert exercise_device == "dirac-3"
@assert exercise_samples == 10
@assert qci_status_after_attribute_config == MOI.OPTIMIZE_NOT_CALLED
println("device=$exercise_device, samples=$exercise_samples; attribute setup itself did not call optimize!.")device=dirac-3, samples=10; attribute setup itself did not call optimize!.
# EXERCISE 3: Classify these configurations without contacting QCI:
# (requested=false, token=""), (requested=true, token=""), and
# (requested=true, token="secret supplied outside the notebook").Notebook Cell
# SOLUTION (hidden in workshop version):
cloud_gate_state(requested, token) =
!requested ? :skipped : isempty(strip(token)) ? :blocked : :ready
@assert cloud_gate_state(false, "") == :skipped
@assert cloud_gate_state(true, "") == :blocked
@assert cloud_gate_state(true, "secret supplied outside the notebook") == :ready
println("The guard states are skipped, blocked, and ready, respectively.")The guard states are skipped, blocked, and ready, respectively.
Summary¶
Learning objectives met:
Built a binary quadratic model with
Model(QCIOpt.Optimizer)without contacting QCI.Established the exact energy and optimal states by independent enumeration.
Configured the current supported device and sample-count attributes.
Gated token injection and cloud submission behind explicit environment controls.
Added result decoding and independent energy checks for the optional live path.
Mapped direct, manually reformulated, and Python-only feature boundaries.
Next steps: change the one-hot model, recompute its exact reference table, and only then consider the opt-in live target. For constrained applications, derive and test the penalty formulation independently before submitting it.
Further reading:
Review the pinned QCIOpt source and offline tests to track its current JuMP contract.
Continue with Notebook 7 for three canonical QUBO formulations and exhaustive validation patterns.
References¶
QCIOpt.jl repository, reviewed at merge commit
30a6074fdd5bd75c3f1cf965329edd01c67e63fe.The repository’s Python QCi notebook, used only to map learning objectives and supported feature boundaries.