Skip to main content

Haiqu.run(circuits, parameters=None, shots=1000, observables=None, device=None, device_id=None, options=None, use_mitigation=False, use_packing=False, pack_size=None, job_name=None, job_description=None, dry_run=False)

Run quantum circuits on the selected backend. This flexible method supports multiple execution scenarios, with different combinations of circuits, parameters, and observables. When multiple values are provided for any of them, the results are returned as nested lists with up to 3 layers, ordered by circuits, then observables, and finally parameters.
  • Parameters:
    • circuits (QuantumCircuit | list *[*QuantumCircuit ] | CircuitModel | list *[*CircuitModel ]) — The quantum circuit(s) to execute. Can be a single circuit or a list of circuits.
    • parameters (list | None) — The parameters for the circuits. Can be a single set of parameters or nested lists of parameter sets. For multiple circuits, must be a list where each element corresponds to parameters for that circuit. Defaults to None, in which case the circuits must not have any parameters.
    • shots (int) — The number of shots for each circuit execution. Defaults to 1000.
    • observables (SparsePauliOp | list *[*SparsePauliOp ] | list *[*list *[*SparsePauliOp ] ] | None) — The observable(s) to measure. The order of Pauli terms in a single string follows the Qiskit reversed-order convention (e.g., "IZ" measures qubit 0 in the Z basis). Defaults to None, in which case the circuits must include their own measurements. Accepted shapes:
      • Single circuit: a single SparsePauliOp, the nested form [[op1, op2, ...]], or a bare list [op1, op2, ...].
      • Multiple circuits: a list of length num_circuits, where each element is independently either a single SparsePauliOp (one observable on that circuit) or a list of SparsePauliOp (multiple observables on that circuit). Mixing is allowed — [[op1, op2], op3] for two circuits is valid.
      The fully-nested form is the unambiguous canonical shape and is recommended when the same code path handles both single and multi-circuit submissions.
    • device (DeviceModel | None) — The device to run the circuits on. If specified, device_id is ignored.
    • device_id (str | None) — The ID of the device to run the circuits on. Defaults to None.
    • options (dict | None) — Options to pass to the device. Supports an optional "error_mitigation_options" key with a dictionary of boolean flags to control individual error mitigation components when use_mitigation=True. Supported keys:
      • "dynamical_decoupling" (bool): Toggle dynamical decoupling. Defaults to True.
      • "readout_mitigation" (bool): Toggle readout error mitigation. Defaults to True.
      • "noise_tailoring" (bool): Toggle noise tailoring via Pauli twirling. Defaults to False.
      • "advanced_mitigation" (bool): Toggle advanced mitigation. Defaults to True.
      To use MPS simulation on aer_simulator, pass {"method": "matrix_product_state"}. Simulator qubit limits (enforced server-side):
      • Statevector (aer_simulator default): up to 24 qubits.
      • MPS (aer_simulator with method="matrix_product_state"): no strict qubit limit.
      • Noisy simulation (fake devices such as fake_kyiv, or aer_simulator with a noise_model): up to 12 qubits.
      See the run reference for full details.
    • use_mitigation (bool) — Whether to use error mitigation techniques. Defaults to False.
    • use_packing (bool) — Whether to use circuit packing for efficient device utilization. Defaults to False. Warning: Experimental — packing replicates circuits on unused device qubits to run multiple copies in parallel, which may increase errors for deeper input circuits. For example, a 4-qubit circuit with pack_size=2 and 1000 shots runs two copies in parallel with 500 shots each, yielding 1000 shots of results while only paying for 500 shot executions on the QPU — a 2x cost saving.
    • pack_size (int | None) — Number of circuit copies to pack onto the device. Must be >= 2. Only valid when use_packing=True. If None (default), the backend will pack into at most 2/3 of the device qubits.
    • job_name (str | None) — The name for the job. If None (default), a name will be automatically generated.
    • job_description (str | None) — The description for the job.
    • dry_run (bool) — Whether to stop just prior to backend execution for QPU cost estimation. Defaults to False. When True, the job result will be empty since execution on the device is skipped. The estimated QPU cost is then available via job.estimated_qpu_cost.
  • Returns: The Run job that will execute the circuit. : Call job.result() to retrieve the execution results as a nested list ordered by circuits → observables → parameters:
    • Without observables: list of measurement distributions (dict[bitstring, quasi-probability]), one per circuit, in Qiskit bit-order.
    • With observables, no parameter sweep: 2D list of expectation values, indexed [circuit][observable].
    • With observables and a parameter sweep: 3D list of expectation values, indexed [circuit][observable][parameter].

    When dry_run=True, result() is empty; use job.estimated_qpu_cost instead. job.info exposes auxiliary metadata (uncertainty when observables are supplied, qpu_cost). Run help(job.result) for the full description of result and info contents.
  • Return type: RunJobModel

Examples

Single circuit, no parameters, no observables:
>>> from qiskit import QuantumCircuit
>>> qc = QuantumCircuit(2)
>>> qc.h(0)
>>> qc.cx(0, 1)
>>> qc.measure_all()
>>> job = haiqu.run(circuits=qc, device_id="aer_simulator")
>>> job.result()  # Returns: [dist_c1] (bitstrings in Qiskit convention)
[{'00': 0.504, '11': 0.496}]
Single circuit, multiple parameters, no observables:
>>> from qiskit import QuantumCircuit
>>> from qiskit.circuit import Parameter
>>> theta = Parameter('θ')
>>> qc = QuantumCircuit(2)
>>> qc.ry(theta, 0)
>>> qc.cx(0, 1)
>>> qc.measure_all()
>>> job = haiqu.run(
...     circuits=qc,
...     parameters=[[0.5], [1.0]],
...     device_id="aer_simulator",
... )
>>> job.result()  # Returns: [[dist_c1_p1, dist_c1_p2]]
[[{'00': 0.934, '11': 0.066}, {'00': 0.802, '11': 0.198}]]
Single circuit, no parameters, multiple observables:
>>> from qiskit import QuantumCircuit
>>> from qiskit.quantum_info import SparsePauliOp
>>> qc = QuantumCircuit(2)
>>> qc.h(0)
>>> qc.cx(0, 1)
>>> obs = [SparsePauliOp("ZZ"), SparsePauliOp("XY")]
>>> job = haiqu.run(
...     circuits=qc,
...     observables=obs,
...     device_id="aer_simulator",
... )
>>> job.result()  # Returns: [[exp_c1_obs1, exp_c1_obs2]]
[[1.0, 0.018000000000000016]]
Single circuit, multiple parameters, multiple observables:
>>> from qiskit import QuantumCircuit
>>> from qiskit.circuit import Parameter
>>> from qiskit.quantum_info import SparsePauliOp
>>> theta = Parameter('θ')
>>> qc = QuantumCircuit(2)
>>> qc.ry(theta, 0)
>>> qc.cx(0, 1)
>>> params = [[0.5], [1.0]]
>>> obs = [SparsePauliOp("ZZ"), SparsePauliOp("XX")]
>>> job = haiqu.run(
...     circuits=qc,
...     parameters=params,
...     observables=obs,
...     device_id="aer_simulator",
... )
>>> job.result()  # Returns: [[[exp_c1_obs1_p1, exp_c1_obs1_p2], [exp_c1_obs2_p1, exp_c1_obs2_p2]]]
[[[1.0, 1.0], [0.49, 0.846]]]
Multiple circuits, no parameters, no observables:
>>> from qiskit import QuantumCircuit
>>> qc1 = QuantumCircuit(2)
>>> qc1.h(0)
>>> qc1.cx(0, 1)
>>> qc1.measure_all()
>>> qc2 = QuantumCircuit(2)
>>> qc2.x(0)
>>> qc2.cx(0, 1)
>>> qc2.measure_all()
>>> circuits = [qc1, qc2]
>>> job = haiqu.run(circuits=circuits, device_id="aer_simulator")
>>> job.result()  # Returns: [dist_c1, dist_c2]
[{'11': 0.524, '00': 0.476}, {'11': 1.0}]
Multiple circuits, multiple parameters, no observables:
>>> from qiskit import QuantumCircuit
>>> from qiskit.circuit import Parameter
>>> theta = Parameter('θ')
>>> qc1 = QuantumCircuit(2)
>>> qc1.ry(theta, 0)
>>> qc1.cx(0, 1)
>>> qc1.measure_all()
>>> qc2 = QuantumCircuit(2)
>>> qc2.rx(theta, 0)
>>> qc2.cz(0, 1)
>>> qc2.measure_all()
>>> circuits = [qc1, qc2]
>>> params = [[[0.5], [1.0]], [[0.3], [0.7]]]  # Parameters for each circuit
>>> job = haiqu.run(
...     circuits=circuits,
...     parameters=params,
...     device_id="aer_simulator",
... )
>>> job.result()  # Returns: [[dist_c1_p1, dist_c1_p2], [dist_c2_p1, dist_c2_p2]]
[[{'00': 0.955, '11': 0.045}, {'00': 0.783, '11': 0.217}],
 [{'00': 0.982, '01': 0.018}, {'00': 0.882, '01': 0.118}]]
Multiple circuits, no parameters, multiple observables:
>>> from qiskit import QuantumCircuit
>>> from qiskit.quantum_info import SparsePauliOp
>>> qc1 = QuantumCircuit(2)
>>> qc1.h(0)
>>> qc1.cx(0, 1)
>>> qc2 = QuantumCircuit(2)
>>> qc2.x(0)
>>> qc2.cx(0, 1)
>>> circuits = [qc1, qc2]
>>> obs = [[SparsePauliOp("ZZ"), SparsePauliOp("XX")],
...        [SparsePauliOp("YY"), SparsePauliOp("ZX")]]  # Observables for each circuit
>>> job = haiqu.run(
...     circuits=circuits,
...     observables=obs,
...     device_id="aer_simulator",
... )
>>> job.result()  # Returns: [[exp_c1_obs1, exp_c1_obs2], [exp_c2_obs1, exp_c2_obs2]]
[[1.0, 1.0], [0.0, -0.0020000000000000018]]
Multiple circuits, multiple parameters, multiple observables:
>>> from qiskit import QuantumCircuit
>>> from qiskit.circuit import Parameter
>>> from qiskit.quantum_info import SparsePauliOp
>>> theta = Parameter('θ')
>>> qc1 = QuantumCircuit(2)
>>> qc1.ry(theta, 0)
>>> qc1.cx(0, 1)
>>> qc2 = QuantumCircuit(2)
>>> qc2.rx(theta, 0)
>>> qc2.cz(0, 1)
>>> circuits = [qc1, qc2]
>>> params = [[[0.5], [1.0]], [[0.3], [0.7]]]  # Parameters for each circuit
>>> obs = [[SparsePauliOp("ZZ"), SparsePauliOp("XX")],
...        [SparsePauliOp("YY"), SparsePauliOp("ZX")]]  # Observables for each circuit
>>> job = haiqu.run(
...     circuits=circuits,
...     parameters=params,
...     observables=obs,
...     device_id="aer_simulator",
... )
>>> job.result()
# Returns: [
#     [[exp_c1_obs1_p1, exp_c1_obs1_p2], [exp_c1_obs2_p1, exp_c1_obs2_p2]],
#     [[exp_c2_obs1_p1, exp_c2_obs1_p2], [exp_c2_obs2_p1, exp_c2_obs2_p2]],
# ]
[[[1.0, 1.0], [0.482, 0.8280000000000001]],
 [[-0.016000000000000014, 0.003999999999999963],
  [-0.02400000000000002, 0.008000000000000007]]]
Example of using Matrix Product State (MPS) simulator for larger scale experiments:
>>> from qiskit.circuit.random import random_circuit
>>> circuit = random_circuit(num_qubits=20, depth=3, max_operands=3, seed=2025, measure=True)
>>> job = haiqu.run(circuit, device_id="aer_simulator", options={
...                 "method": "matrix_product_state",  # set method to MPS
...                 "matrix_product_state_max_bond_dimension": 16,  # preferably, limit the bonds
...               })