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.

job_type

problem_config key

Required file ID

sample-hamiltonian

normalized_qudit_hamiltonian_optimization

exactly one of polynomial_file_id or hamiltonian_file_id

sample-hamiltonian-integer

qudit_hamiltonian_optimization

exactly one of polynomial_file_id or hamiltonian_file_id

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>'

Note

hamiltonian_file_id is deprecated for the Hamiltonian job types. Prefer polynomial_file_id.

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 device_type

job_type

Submitted device_config key

dirac-3

sample-hamiltonian

dirac-3_normalized_qudit

dirac-3

sample-hamiltonian-integer

dirac-3_qudit

dirac-3s

either

dirac-3s

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-hamiltonian only.

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 raises AssertionError: 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.

Note

The client validates that parameter names are recognized and that the job type and device are compatible. It does not range-check numeric values. The ranges above are enforced by the API, which rejects out-of-range submissions with an requests.HTTPError. Unrecognized keys are dropped from the job body without warning, so inspect the built body when a parameter appears to have no effect.

Warning

The solution_precision parameter is no longer supported. Passing it to build_job_body emits a UserWarning and the value is ignored; use client-side postprocessing to distill continuous solutions instead.

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.