Dirac-3S (dirac-3s)
dirac-3s is QCi’s second generation Dirac-3 device. It runs the same two qudit problem types as
dirac-3, namely the normalized-qudit (continuous) and qudit (integer) Hamiltonian
optimizations, and is selected by name in the job parameters.
Selecting the Dirac-3S Device
Pass device_type="dirac-3s" in the job_params dictionary given to
build_job_body—
- job_body = client.build_job_body(
- job_type="sample-hamiltonian",
- job_params={"device_type": "dirac-3s", "sum_constraint": 1},
- polynomial_file_id=file_id,
- )
The device_type key is required for every job type. Omitting it raises—
- ValueError: no 'device_type' specified in job_params, must be one of ('dirac-1',
- 'dirac-3', 'dirac-3s', 'dirac-3_normalized_qudit', 'dirac-3_qudit')
An unrecognized device name raises a ValueError from
qci_client.optimization.enum.DeviceType.
Supported Job Types on Dirac-3S
dirac-3s accepts both qudit job types.
|
| Required file ID |
|---|---|---|
|
| exactly one of |
|
| exactly one of |
Use sample-hamiltonian for continuous variables subject to a sum constraint, and
sample-hamiltonian-integer for integer variables with a bounded number of levels per
variable.
Supplying both polynomial_file_id and hamiltonian_file_id, or neither, raises—
- AssertionError: exactly one of hamiltonian_file_id or polynomial_file_id must be
- specified for job_type='<job_type>'
Every other job type is restricted to qubit devices. Pairing one with dirac-3s
raises ValueError: <job_type> not supported on dirac-3s.
Device-Name Remapping and Job Metrics
dirac-3 is a convenience alias. When it is requested, the client rewrites the
device_config key to the specific device name that the API expects, based on the job
type. dirac-3s is passed through literally and is never rewritten.
Requested |
| Submitted |
|---|---|---|
|
|
|
|
|
|
| either |
|
So a job body built for dirac-3s keys its device configuration exactly as
written—
- {
- "job_submission": {
- "problem_config": {
- "normalized_qudit_hamiltonian_optimization": {
- "polynomial_file_id": "<file id>"
- }
- },
- "device_config": {
- "dirac-3s": {
- "num_samples": 5,
- "relaxation_schedule": 1,
- "sum_constraint": 10
- }
- }
- }
- }
This matters when reading job metrics, which are keyed by the device name that was actually submitted rather than the one that was requested. Read the device block without hardcoding the name—
- metrics = client.get_job_metrics(job_id=job_id)
- device = next(iter(metrics["job_metrics"]["time_ns"]["device"].values()))
Dirac-3S Device Configuration Parameters
job_params is a flat dictionary. build_job_body sorts its keys into
problem_config or device_config. The parameters below are the ones routed to
device_config for the two job types that dirac-3s supports.
num_samples
How many samples the stochastic solver draws.
- Range: 1 to 100, inclusive.
- Default: 1.
- Accepted by both job types.
relaxation_schedule
Tunes a group of device parameters as a preset. Higher schedules give better solution quality at the cost of longer evolution time.
- Range: 1 to 4, inclusive.
- Default: 1.
- Accepted by both job types.
sum_constraint
The value that the solution variables must sum to. This is what makes a normalized-qudit problem normalized: the solution lives on a simplex of the given size.
- Range: 1 to 10000, inclusive.
- Accepted by
sample-hamiltonianonly.
num_levels
The number of discrete levels available to each variable, as a list of integers. A
variable with k levels ranges over 0 through k - 1.
- Required by
sample-hamiltonian-integer. Omitting it raisesAssertionError: num_levels is a required field.
mean_photon_number
Advanced. Overrides the value that relaxation_schedule would otherwise set
implicitly.
- Range: 0.0000667 to 0.0066666, inclusive.
- Accepted by both job types.
quantum_fluctuation_coefficient
Advanced. Also overrides a value implied by relaxation_schedule.
- Range: 1 to 100, inclusive.
- Accepted by both job types.
Continuous Example on Dirac-3S
This minimizes the polynomial H = -x1^2 + 2*x1*x2 - x2^2 subject to x1 + x2 = 1
with both variables non-negative. The constraint is expressed through
sum_constraint rather than through a constraints file, which is what the
normalized-qudit problem type is for. The optimum is H = -1, reached when one
variable takes the whole budget and the other is zero.
In the polynomial file, each entry of data is one term: idx names the
variables in that term and val is its coefficient. Variables are numbered from
1, so [1, 1] is x1^2 and [1, 2] is x1*x2. Every idx list has
length max_degree, and all three terms here are of degree 2.
- from qci_client import QciClient
- client = QciClient()
- polynomial = {
- "file_name": "dirac-3s-continuous-example",
- "file_config": {
- "polynomial": {
- "num_variables": 2,
- "min_degree": 2,
- "max_degree": 2,
- "data": [
- {"idx": [1, 1], "val": -1.0},
- {"idx": [1, 2], "val": 2.0},
- {"idx": [2, 2], "val": -1.0},
- ],
- }
- },
- }
- file_id = client.upload_file(file=polynomial)["file_id"]
- job_body = client.build_job_body(
- job_type="sample-hamiltonian",
- job_name="dirac-3s-continuous-example",
- job_tags=["quickstart"],
- job_params={
- "device_type": "dirac-3s",
- "relaxation_schedule": 1,
- "sum_constraint": 1,
- },
- polynomial_file_id=file_id,
- )
- response = client.process_job(job_body=job_body)
- if response["status"] != "COMPLETED":
- raise RuntimeError(f"job did not complete: {response['status']}")
- print(f"x = {response['results']['solutions'][0]}")
- print(f"H = {response['results']['energies'][0]}")
process_job blocks until the job reaches a terminal status and, because verbose
defaults to True, logs its progress—
- 2026-08-17 10:14:02 - Dirac allocation balance = 600.0 s
- 2026-08-17 10:14:03 - Job submitted: job_id='6534d9d1e4b0a1f2c3d40001'
- 2026-08-17 10:14:03 - QUEUED
- 2026-08-17 10:14:11 - RUNNING
- 2026-08-17 10:14:19 - COMPLETED
- 2026-08-17 10:14:19 - Dirac allocation balance = 599.0 s
- x = [1.0, 0.0]
- H = -1.0
Integer Example on Dirac-3S
This runs an integer problem on dirac-3s and drives the polling loop by hand
instead of using process_job. The pattern is useful when other work should
proceed while the job runs, or when a failed job is to be handled without an
exception.
The polynomial spans degrees 2 through 4 over two variables, so every idx list
has length 4 and lower-degree terms are left-padded with 0. The index 0 is
padding, not a variable: [0, 0, 1, 1] is x1^2, [0, 1, 1, 1] is x1^3,
and [1, 1, 1, 1] is x1^4. Indices must be non-decreasing from left to right,
so each term has exactly one representation.
The num_levels entry gives one level count per variable and is required for integer
jobs.
- from qci_client import QciClient, JobStatus, JOB_STATUSES_FINAL
- client = QciClient()
- polynomial = {
- "file_name": "dirac-3s-integer-example",
- "file_config": {
- "polynomial": {
- "num_variables": 2,
- "min_degree": 2,
- "max_degree": 4,
- "data": [
- {"idx": [0, 0, 1, 1], "val": 1.0},
- {"idx": [0, 1, 1, 1], "val": -2.0},
- {"idx": [1, 1, 1, 1], "val": 1.0},
- ],
- }
- },
- }
- file_id = client.upload_file(file=polynomial)["file_id"]
- job_body = client.build_job_body(
- job_type="sample-hamiltonian-integer",
- job_name="dirac-3s-integer-example",
- job_params={
- "device_type": "dirac-3s",
- "num_levels": [3, 4],
- "num_samples": 2,
- "relaxation_schedule": 2,
- },
- polynomial_file_id=file_id,
- )
- assert list(job_body["job_submission"]["device_config"]) == ["dirac-3s"]
- job_id = client.submit_job(job_body=job_body)["job_id"]
- print(f"submitted {job_id}")
- status = JobStatus.SUBMITTED
- while status not in JOB_STATUSES_FINAL:
- status = JobStatus(client.get_job_status(job_id=job_id)["status"])
- response = client.get_job_results(job_id=job_id)
- if status is not JobStatus.COMPLETED:
- print(f"job finished as {status.value}")
- print(response["job_info"])
- else:
- results = response["results"]
- for solution, energy, count in zip(
- results["solutions"], results["energies"], results["counts"]
- ):
- print(f"x = {solution} H = {energy} seen {count}x")
- submitted 6534d9d1e4b0a1f2c3d40002
- x = [1, 0] H = 0.0 seen 1x
- x = [0, 2] H = 0.0 seen 1x
The assertion above holds because dirac-3s is never remapped, unlike dirac-3.
Poll against JOB_STATUSES_FINAL rather than against JobStatus.COMPLETED,
otherwise a failed job loops forever. ERRORED and CANCELLED are terminal
too, and leave results as None, so always check status before indexing
into results.
This polynomial factors as x1^2 * (x1 - 1)^2, so it reaches zero at x1 = 0 and
x1 = 1. Note that x2 does not appear in any term: it is declared by
num_variables and given a level count, but the objective does not constrain it,
so its value varies freely between samples.