Submitting and managing tasks

Once authenticated, you can submit a task to the system to add your quantum program to the queue of your chosen Quantum Processing Unit (QPU). You can see a list of QPUs available to you at https://cloud.oqc.app/qpu. You will need the QPU ID for your chose QPU to submit tasks to.

Language specifications

Programs can be submitted in any of the following languages:

Task creation

A quantum program must be transformed into a QPUTask object before you submit it to the cloud. See the following example for the quantum Hello World written in OPENQasm2.0. You can find more about this circuit and the family of circuits it belongs to in the Qiskit textbook.

from qcaas_client.client import OQCClient, QPUTask

client = OQCClient(
url = <oqc_cloud_url>,
authentication_token = <access_token>
)

hello_world = """
OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
creg c[2];
h q;
cx q[0], q[1];
measure q->c;
"""

task = QPUTask(program=hello_world)

task_results = client.schedule_tasks(task,qpu_id=<qpu_id>)

Compiler and runtime configuration

You can include configuration settings when generating the QPUTask object. This allows you to tailor the processing of your program on the QPU. We encourage you to experiment with the different compiler and runtime configuration settings as introduced below (for more information, see https://github.com/oqc-community/qat).

The task configuration is represented by the CompilerConfig object, which contains the following fields:

  • repeats. The number of times the circuit is run during this task, sometimes referred to as shots. The hardware can only store 10000 shots at a time, but you can submit many more than that with OQC Cloud then automatically batching the shots. The default value is 1000.

  • repetition_period. The time that the hardware waits between shots. Our QPU does not have an active reset so it is necessary to provide time to allow the system to relax to reinitialise to a simple fiducial state. The default value is 200e-6 seconds. Note: we strongly recommended that you do not provide a custom value unless you have a good understanding of the potential impact.

  • results_format. The format of the returned results. This can be passed in as a QuantumResultsFormat object. There are two settings: Raw or Binary.

    • Binary. This discriminates the raw values into a 0 or a 1. This can be set using QuantumResultsFormat().binary_count().

    • Raw. This will output the absolute magnitude of the averaged IQ values as measured at readout. This can be set using res_format = QuantumResultsFormat().raw().

  • metrics. The type of metrics that you would like to be recorded (e.g. optimised circuit and its instruction count, different processing times). The default is that all metrics are returned. This can be set using the MetricsType object such as MetricsType.OptimizedCircuit to return the optimised circuit. Default value is MetricsType.Default (i.e. all metrics are returned)

  • optimisations. The optimizations you want to be performed on the input program. The compiler carries out a number of optimisation passes on the program using Qiskit or Tket, with the tKet optimisation compiler integrated into our own compiler pipeline. There are three levels of optimisation offered:

    • 0 (minimum in code): Routes the qubits for the QPU architecture and converts all gates to TK1 and CNOT.

    • 1: Applies Synthesise Tket, a computationally efficient optimisation

    • 2: Applies Full peephole optimise, a more extensive optimisation

    Note that:

    • Levels 1 and 2 squash one qubit rotations (e.g. the gate sequence XZZX would evaluate to the identity operator I).

    The default values are:

    • For QASM2.0 programs, TketOptimizations.One is applied.

    • For QIR programs, no optimisations are applied.

Here, we provide a summary of the different optimisations which you may want to utilise, along with code examples.

# Some standard optimisations:

# In the case you only want to map the qubits onto the hardware
# and convert the gates into native gates
optimisations = Tket(TketOptimizations.One)

# To attain maximum optimisations without consideration for
# hardware specifications
optimisations = Tket(TketOptimizations.Two)

# Optimisation settings are switched off by default, but this can
# be done explicitly
optimisations = Tket()
optimisations.disable()

# The following examples are some notable settings:

# Do nothing
optimisations = Tket(TketOptimizations.Empty)

# Map your circuit onto the physical topology of our system
optimisations = Tket(TketOptimizations.DefaultMappingPass)

# Maximum optimisation, classically inspired by full peephole
# compiler pass
optimisations = Tket(TketOptimizations.FullPeepholeOptimise)

# Perform some context specific simplifications (e.g. we know
# we start in the 0 state, so Z gate has no effect)
optimisations = Tket(TketOptimizations.ContextSimp)

# Compiles the two qubit gates to conform to the directions
# allowed by our hardware
optimisations = Tket(TketOptimizations.DirectionalCXGates)

User-defined configuration

Here is a complete example of a user-defined CompilerConfig object and its inclusion in the generation of a QPUTask object.

# Setup a customised compiler configuration

shot_count = 1000
rep_period = 90e-6
res_format = QuantumResultsFormat().binary_count()
allowed_metrics = MetricsType.OptimizedInstructionCount
optim = Tket()
optim.tket_optimizations = TketOptimizations.DefaultMappingPass

custom_config = CompilerConfig(repeats = shot_count,
                        repetition_period = rep_period,
                        results_format = res_format,
                        metrics = allowed_metrics,
                        optimizations = optim)

Experimental compiler configurations

As part of experimental features, we have two different readout mitigations, namely matrix mitigation and linear mitigation.

For a deep dive into the theory of matrix mitigation the qiskit textbook includes a notebook going through this:

Note:
  • MatrixMitigation has an exponential calibration and runtime overhead and is only potentially available for the Lucy generation of QPU.

  • Only LinearMitigation is enabled now for Toshiko devices. We will update when other options are available.

Matrix mitigation makes use of C_output the counts obtained from the hardware, C_perfect the ideal noiseless result, and M a noisy transformation matrix associated with readout errors. Then Matrix mitigation starts by noting C_output = M*C_perfect, where M is the transfer matrix that takes us away from the theoretical perfect results to C_output. Multiplying both sides by the inverse of M, M^{-1}, gets us, M^{-1}*C_output = C_perfect. We can compute the elements of M_{i,j} = p(i|j), where i is the measured bit string from the input bitstring, j. Note that this is modelled as a purely classical process that we can undo with post-processing

LinearMitigation is a simplification of the above that requires constant number of circuits for calibration. We measure the “all zero” state and “all one” state. From this, for each qubit we are able to extract p(0/1|0/1). We can then for each measured bitstring on each qubit invert the measured bitstring up to a first order correction. This is a much simplified version of the full MatrixMitigation and will only correct for uncorrelated readout errors. Here is a complete example of a user-customised CompilerConfig object and its inclusion in the generation of a QPUTask object.

# To use readout mitigation,
# setup a customised compiler configuration with below results_format and error_mitigation
custom_config = CompilerConfig(
                        results_format=QuantumResultsFormat().binary_count(),
                        error_mitigation=ErrorMitigationConfig.LinearMitigation)

Task submission

When the QPUTask object has been created, you can then be submit it to the system. A list of QPUTasks can also be submitted. For this, there are two modes of execution available using the Client.

  • Scheduling. This submits a task and then returns a list of the QPUTask that have been submitted, including their task ids. The task id of a task can then be used to monitor the task, and to fetch the results upon completion.

task = QPUTask(program = hello_world, config = custom_config)

# schedule_tasks function returns a list of QPUTasks. The following
# code shows how to access the task_id for the first task for
# monitoring purposes (refer to the Task monitoring section)

task_id = client.schedule_tasks(task,qpu_id=<qpu_id>)[0].task_id
task_results = client.get_task_results(task_id,qpu_id=<qpu_id>)
  • Execution. This will submit a task and then continue to monitor the state of the task until a result is returned. Typically, you would only use this feature when you know that a job will be processed promptly (i.e. the job is submitted to an active window) or that a long wait time for the task to complete is acceptable to you.

task = QPUTask(program = hello_world, config = custom_config)
task_results = client.execute_tasks(task, qpu_id=<qpu_id>)

Task cancellation

If a task has not completed or is currently running, you can cancel it. Either a single task or a list of tasks can be cancelled at once. Note, a task cannot be cancelled once it has started.

client.cancel_task(task_id,qpu_id=<qpu_id>) # To cancel one task
client.cancel_task([task1_id, task2_id, task3_id, ...],qpu_id=<qpu_id>) # To cancel many tasks

Task monitoring

You can monitor the status of your submitted tasks at any time. The following examples show how you can track the progress of tasks, and the form of expected responses.

Task timings

The get_task_timings method returns the timings of events a task undergoes as it progresses through OQC Cloud. The events are described in Table 1.

Table 1: Event descriptions

Event

Description

SERVER_RECEIVED

The server has received the task

SERVER_ENQUEUED

The server has pushed the task to our task queue system

COMPILER_DEQUEUED

The compiler, which generates QPU specific instructions for the submitted program using the Quantum Assembly Toolchain (QAT), has pulled the task from the task queue system

COMPILER_ENQUEUED

The compiler has pushed the task to our task queue system with the compiled instructions.

EXECUTOR_DEQUEUED

The executor, which executes the task on the QPU and performs post-porcessing using QAT, has pulled the task from the task queue system

EXECUTOR_ENQUEUED

The executor has pushed the completed task to the task queue system

SERVER_DEQUEUED

The server has pulled from the task queue system queue and stores the results

The command and expected output is presented in the following code.

command: client.get_task_timings(task_id,qpu_id=<qpu_id>)
example response:
{'COMPILER_DEQUEUED': '2024-07-11 09:56:08.074425+00:00',
 'COMPILER_ENQUEUED': '2024-07-11 09:56:09.099431+00:00',
 'EXECUTOR_DEQUEUED': '2024-07-11 09:56:09.574102+00:00',
 'EXECUTOR_ENQUEUED': '2024-07-11 09:56:10.829953+00:00',
 'SERVER_DEQUEUED': '2024-07-11 09:56:11.590405+00:00',
 'SERVER_ENQUEUED': '2024-07-11 09:55:58.327529+00:00',
 'SERVER_RECEIVED': '2024-07-11 09:55:58.268346+00:00'}

Note: all times are in UTC

Task status

The get_task_status function allows you to obtain the status of your task. The possible statuses are described in Table 2.

Table 2: Status descriptions

Status

Description

CREATED

The task has been created and has entered our system

SUBMITTED

The task has been submitted to our task queue system

RUNNING

The task is undergoing compilation and execution on the QPU

COMPLETED

The task has been successfully completed

FAILED

The task has failed to complete. In this event, error information will be available (refer to Task error for more information on how to handle failed tasks)

CANCELLED

The task has been cancelled (refer to the Task cancellation for more details on how to cancel tasks)

UNKNOWN

In the event that a task has this status, you should resubmit the task. If this is repeated, contact OQC Cloud support

EXPIRED

The task is older than 7 days and has been purged from the system

The command and expected output is presented in the following code

command: client.get_task_status(task_id,qpu_id=<qpu_id>)
example response: COMPLETED

Task metrics

The metrics recorded for a given task can be defined in the compiler configuration. In the following, the command and expected output is presented for the case the optimised circuit and instruction count are recorded.

command: client.get_task_metrics(task_id,qpu_id=<qpu_id>)
example response: {'optimized_circuit': '\nOPENQASM 2.0;\ninclude "qelib1.inc";\nqreg q[2];\nh q;\ncreg
c[2];\nmeasure q->c;\n', 'optimized_instruction_count': 50}

Task metadata

This function will return the task metadata which includes the compiler configuration and the task creation time.

command: client.get_task_metadata(task_id,qpu_id=<qpu_id>)
example response: {'config': '{"$type": "<class \'qcaas_client.compiler_config.CompilerConfig\'>", "$data": {"repeats": 1000,
"repetition_period": 2e-05, "results_format": {"$type": "<class \'qcaas_client.compiler_config.QuantumResultsFormat\'>",
"$data": {"format": {"$type": "<enum \'qcaas_client.compiler_config.InlineResultsProcessing\'>", "$value": 1}, "transforms":
{"$type": "<enum \'qcaas_client.compiler_config.ResultsFormatting\'>", "$value": 3}}}, "metrics": {"$type":
"<enum \'qcaas_client.compiler_config.MetricsType\'>", "$value": 4}, "active_calibrations": [], "optimizations": {"$type":
"<enum \'qcaas_client.compiler_config.TketOptimizations\'>", "$value": 2}}}', 'created_at': 'Tue, 17 Oct 2023 11:24:35 GMT',
'id': '0f41e535-222c-4ce2-ade0-1a4c57ad53d8'}

Task results

If the task has completed, you can obtain the results of their task using the following code. In this example, the results are in attribute ‘c’ as defined in your output measure q->c;

command: client.get_task_results(task_id,qpu_id=<qpu_id>).result
example response: {'c': {'00': 1000}}

Task error

If the task has failed, you can obtain information about the error using the following code.

command: client.get_task_errors(task_id,qpu_id=<qpu_id>).error_code
example response: 101
command: client.get_task_errors(task_id,qpu_id=<qpu_id>).error_message
example response: local variable 'decoded_program' referenced before assignment

Execution estimation

You can obtain execution estimates for tasks already submitted or for tasks you intend to submit.

Task execution estimates

If you want to know when your submitted tasks are likely to be executed, you can supply a list of the task ids you would like to query. For each valid task id, you will receive an estimated start time, queue position, and a list of upcoming windows.

command: client.get_task_execution_estimates([task_id_1],qpu_id=<qpu_id>)
example response:
{
    'task_wait_times':
        [
            {
                'estimated_start_time': '2023-08-16T11:18:01.680Z',
                'position_in_queue': 10,
                'task_id': '3fa85f64-5717-4562-b3fc-2c963f66afa6',
                'timestamp': '2023-08-16T11:18:01.680Z',
                'windows':
                    [
                        {
                            'end_time': '2023-08-16T11:18:01.680Z',
                            'start_time': '2023-08-16T11:18:01.680Z',
                            'window_description': 'CURRENT'
                        }
                    ]
            }
        ]
}

 

Fermioniq Emulators

 

Task Submission

Submitting jobs to the Toshiko emulators works similarly to our existing QPUs. Once authenticated, you can submit a task to an emulator by specifying its QPU ID during task scheduling.

We offer two Toshiko emulators:

  • Toshiko CPU Emulator with QPU ID qpu:uk:3:sim:ferm:1:ef19ecf6

  • Toshiko GPU Emulator with QPU ID qpu:uk:3:sim:ferm:2:2881bdfa

These are both powered by the Fermioniq Ava Emulator, which you can learn more about at https://www.fermioniq.com/ava.

To submit the previously defined hello_world task to the Toshiko GPU Emulator, you simply need to provide qpu_id='qpu:uk:3:sim:ferm:2:2881bdfa to schedule_tasks. Note: it is recommended to use schedule_tasks (and get_task_results to fetch the result) rather than execute_tasks as the latter will be blocking for a longer duration.

from qcaas_client.client import OQCClient, QPUTask

client = OQCClient(
  url = <oqc_cloud_url>,
  authentication_token = <access_token>
)
client.authenticate()

qpu_id = 'qpu:uk:3:sim:ferm:2:2881bdfa' # Ensure you set the correct emulator QPU ID here!

hello_world = """
OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
creg c[2];
h q;
cx q[0], q[1];
measure q->c;
"""

task = QPUTask(program=hello_world, qpu_id=qpu_id)

scheduled_task = client.schedule_tasks(task, qpu_id=qpu_id)

Note

Only OPENQASM2 programs are currently supported for the Toshiko emulators. This means that rolled or conditional logic is not supported at present.

 

Task Configuration

To specify configuration settings for an emulator task, use the ExperimentalConfig object from the client (instead of CompilerConfig which defines settings for live hardware tasks).

An example of a customised ExperimentalConfig object and its inclusion in the generation of a QPUTask object is provided below:

from qcaas_client.client import OQCClient, QPUTask, ExperimentalConfig, QuantumResultsFormat

qpu_id = 'qpu:uk:3:sim:ferm:1:ef19ecf6'
shot_count = 1000
res_format = QuantumResultsFormat().binary_count()
noise = False
bond_dimension = 50

custom_config = ExperimentalConfig(
            repeats=shot_count,
            results_format=res_format,
            noise=noise,
            bond_dimension=bond_dimension
          )

task = QPUTask(program=hello_world, qpu_id=qpu_id, config=custom_config)

scheduled_task = client.schedule_tasks(task, qpu_id=qpu_id)

This script configures the emulator to run 1000 noiseless hello_world executions times on the CPU backend (using DMRG emulation with bond dimension 50). Results are then returned in binary count format, and are retrievable with get_task_results as described in Task results.

The complete set of configurable parameters for ExperimentalConfig is summarised in Table 3 below.

 

Table 3: Configuration settings for Toshiko emulators

Setting

Description

Notes

Repeats

Number of times the quantum circuit is executed.

  • Range of validity: 1-100,000.

  • Default: 1000.

Noise

Enables or disables noisy emulation.

  • Boolean: True or False.

  • If True, the default Toshiko noise model is applied (see note section below).

  • Defaults to False.

Bond Dimension (D)

Sets the maximum bond dimension (D) for tensor-network (DMRG) mode. Higher D increases simulation accuracy, at the cost of compute.

  • Range of validity: 10-4000. For D > 100, the GPU backend should be used.

  • Defaults:
    • Noiseless emulation: D = 50 (CPU Emulator), D = 100 (GPU Emulator).

    • Noisy emulation: fixed values of D = 32 (CPU Emulator), D = 256 (GPU Emulator).

Results Format

Defines the format of returned results. This can be passed in as a QuantumResultsFormat object (just like with our other QPUs).

  • There are two options:

    • Binary count. Example: {‘00’: 499, ‘11’: 501}.

    • Raw: probability associated with each sampled bit string. Example: {‘00’: 0.4998, ‘11’: 0.5002}. This is the closest analogue to raw IQ values that Fermioniq offers.

  • Default: binary count.

Noise Model

Specifies the noise model applied when noise is enabled.

  • Default: Toshiko noise model.

  • See Advanced Usage section below for more details on this setting.

Note

Default Toshiko noise model: this default model is applied to your circuit when you set noise to True in your task configuration. It simulates noise characteristics specific to our 32-qubit Toshiko device. This includes:

  • Readout errors: defined by error probabilities (1-F₀₀, 1-F₁₁).

  • Single-qubit depolarizing channel: with probability p = 2·(1-FRB /100) applied individually to each qubit.

  • Two-qubit depolarizing channel: applied to each valid CX pair with probability p = 4/3·(1-FCX /100).

 

Quickstart example

The example below defines a basic 5-qubit GHZ circuit and schedules 3000 noiseless shots on the Toshiko CPU Emulator in DMRG mode, with bond dimension set to 30. For more details on emulator modes, see the following section on Emulator Behaviour.

Noiseless 5-qubit GHZ circuit on Toshiko CPU Emulator

from qcaas_client.client import (
    OQCClient,
    QPUTask,
    ExperimentalConfig,
    QuantumResultsFormat
)
from time import sleep

client = OQCClient(
    url="<oqc_cloud_url>",
    authentication_token="<access_token>"
)
client.authenticate()

qpu_id = "qpu:uk:3:sim:ferm:1:ef19ecf6"     # Toshiko CPU emulator

# OPENQASM 2 GHZ-5 program
ghz_5 = """
OPENQASM 2.0;
include "qelib1.inc";
qreg q[5];
creg c[5];
h q[0];
cx q[0], q[1];
cx q[0], q[2];
cx q[0], q[3];
cx q[0], q[4];
measure q -> c;
"""

# example config
config = ExperimentalConfig(
    repeats=3000,
    results_format=QuantumResultsFormat().binary_count(),
    noise=False,     # set True to use the default Toshiko noise model
    bond_dimension=30   # experiment - note if D > 100, use GPU backend!
)

# schedule the job (non-blocking)
task = QPUTask(program=ghz_5, qpu_id=qpu_id, config=config)
scheduled = client.schedule_tasks(task, qpu_id=qpu_id)
task_id = scheduled[0].task_id

# poll until completed, then fetch results
while client.get_task_status(task_id, qpu_id=qpu_id) != "COMPLETED":
    sleep(0.5)
results = client.get_task_results(task_id, qpu_id=qpu_id).result

print(results)
# Example output: {'00000': 1500, '11111': 1500}

Tip

Try experimenting with the two different backends (CPU and GPU emulator), the number of qubits in the GHZ state, the bond dimension D, and the noise setting. Note that for larger, more complex circuits, or when D > 100, the GPU backend should be used.

 

Emulator Behaviour

The default emulator behaviour depends on the chosen backend, as well as the noise setting and the qubit count of the submitted circuit. Table 4 summarises this default behaviour, and the special exceptions are noted below. Note: to learn more about emulator modes and noise settings, a brief helper box is provided at the end of this subsection.

Table 4: Emulation mode defaults

Mode

Backend

Default Emulator Mode

Default Bond Dimension (D)

Noiseless

CPU

DMRG

D = 50

GPU

Statevector (≤ 32 qubits) DMRG (> 32 qubits)

D = 100 (only relevant for DMRG mode)

Noisy

CPU

DMRG

Fixed at D = 32

GPU

DMRG

Fixed at D = 256

Special cases:

  • Circuits with qubit count ≥ 32: Both CPU and GPU backends default to DMRG mode due to computational efficiency.

  • Single-qubit circuits (qubit count = 1): Both CPU and GPU backends default to statevector mode, as DMRG is not supported for a single tensor (MPS length of 1).

Caution

Running a noisy circuit with > 32 qubits will result in error as the default Toshiko noise model is a 32-qubit model. For larger circuits, define a custom noise model as detailed in the Advanced Usage section.

 

Understanding Emulator modes

  • Emulation mode: determines the method used for quantum state simulation.

    • DMRG (Density Matrix Renormalization Group) mode: compresses the quantum state into a tensor-network representation. This allows for efficient state approximation of circuits with higher qubit counts. Increasing the bond dimension D raises the maximum Schmidt rank and therefore the accuracy of the approximation.

    • Statevector mode: represents quantum states exactly, by storing every amplitude explicitly as a 2N -element vector. Provides exact simulation but memory usage grows exponentially with qubit count, making it ideal only for small circuits. Note bond dimension D is not relevant in this case.

  • Noise Setting: controls whether the emulator simulates realistic quantum hardware noise.

    • Noisy: applies a realistic noise model (e.g., the Toshiko noise model or a custom one). This could include readout errors, one-qubit or two-qubit gate noise, or custom gate noise.

    • Noiseless: runs idealized circuits without any decoherence or gate noise.

For further reading, the Fermioniq docs are highly recommended.

 

Task Monitoring

Task status and results

To check the status of a task, you can use the get_task_status method as before. All possible task statuses are detailed in Task status (see Table 2). When the status of a task is COMPLETED, you can retrieve the results using the get_task_results method, as outlined in Task results.

Example usage:

status = client.get_task_status(task_id, qpu_id=<qpu_id>)
result = client.get_task_results(task_id, qpu_id=<qpu_id>).result
# Example response:
# 'COMPLETED'
# {'00': 244, '01': 247, '10': 268, '11': 241}

Task cancellation

You can also cancel tasks scheduled on Toshiko emulators if they have not completed . Ensure that you supply the correct emulator qpu_id which the task was scheduled on.

Example usage:

# Cancel one or many tasks
client.cancel_task(task_id, qpu_id=<qpu_id>)
client.cancel_task([task1_id, task2_id, task3_id, ...], qpu_id=<qpu_id>)

Task errors

If a task finishes with a FAILED status, you can use the get_task_errors method to inspect the issue.

Example usage:

# Inspect task error
err = client.get_task_errors(task_id, qpu_id=<qpu_id>)
print(err.error_code, err.error_message)
# Example response
# 400 Fermioniq backend only supports OpenQASM programs.

For further assistance with task errors, please contact support at support@oqc.tech.

Task metrics, metadata, and timings

The helper methods get_task_metadata(), get_task_metrics(), and get_task_timings() function identically for the Toshiko emulators as our others QPUs. These methods allow you to retrieve job metadata, execution metadata, and server timings respectively.

Example usage and outputs are provided below:

metrics = client.get_task_metrics(task_id, qpu_id=qpu_id)
timings = client.get_task_timings(task_id, qpu_id=qpu_id)
metadata = client.get_task_metadata(task_id, qpu_id=qpu_id)

# Example metrics output
{'extrapolated_2qubit_gate_fidelity': 1.0, 'fidelity_product': 1.0, 'num_subcircuits': 1,
'number_1qubit_gates': 16, 'number_2qubit_gates': 0, 'output_metadata': {'samples':
{'time_taken': 1.782407138030976}}, 'runtime': 2.3731323732063174, 'status': 'Completed'}

# Example timings output
{'SERVER_DEQUEUED': '2025-04-23 10:35:02.521224+00:00',
'SERVER_ENQUEUED': '2025-04-23 10:34:51.026767+00:00',
'SERVER_RECEIVED': '2025-04-23 10:34:49.537897+00:00'}

# Example metadata output
{'allow_support_access': False, 'config': [EmulatorConfig(qubits=['q_0','q_1'], grouping=None, group_size=2, physical_dimensions=(2, 2), initial_state=0, mode='dmrg', ignore_swaps=False, noise=NoiseConfig(enabled=False,
validate_model=True), tebd=TEBDConfig(max_D=2, svd_cutoff=1e-08), dmrg=DMRGConfig(D=42, init_mode='ket', convergence_window_size=32, convergence_threshold=1e-05, target_fidelity=1.0, max_sweeps=10000, max_subcircuit_rows=1,
mpo_bond_dim=4, regular_grid=True, truncate_rows=True), statevector=StateVectorConfig(), output=OutputConfig(amplitudes=AmplitudesOutputConfig(enabled=False, bitstrings=[0]), sampling=SampleOutputConfig(enabled=True,
n_shots=1000, return_probabilities=False), mps=MpsOutputConfig(enabled=False), expectation_values=ExpectationValuesOutputConfig(enabled=False, observables=[])), optimizer=OptimizerConfig(enabled=False, observable=None,
optimizer_name='spsa', optimizer_settings={}, initial_param_values={}, initial_param_noise=0.1), trajectory=TrajectoryConfig(n_shots=1, target_probability_tol=0.001, search_method='dfs'))]",
'created_at': 'Wed, 23 Apr 2025 10:34:49 GMT', 'id': '7040bb-fdbf-4b47-8327-5576f0ffa218', 'qpu_id': 'qpu:uk:3:sim:ferm:2:2881bdfa', 'tag': 'Example'}

 

Unsupported methods

Table 5 lists client methods which are not supported for Toshiko emulators. Invoking any of these methods with an emulator qpu_id returns HTTP 405 Method Not Allowed.

Table 5: Unsupported methods for Toshiko emulators

Method

Notes

get_calibrations()

Only available for real hardware devices.

get_task_execution_estimates()

Not supported by Fermioniq emulator backend.

get_qpu_execution_estimates()

Not supported by Fermioniq emulator backend.

 

Advanced Usage

The default noise model for the emulator is the Toshiko noise model, which is only applicable for circuits with up to 32 qubits. If you need to execute noisy circuits with a greater number of qubits, emulate larger hardware, or precisely configure your noise settings, you can provide a customised noise model.

To do this, you need to install the experimental extra package:

Install oqc-qcaas-client[experimental]

The noise model supplied must be either a Fermioniq NoiseModel object, or its serialised JSON form. This noise model can then be included in the ExperimentalConfig noise_model argument, and will be applied to any job submitted with this config. For detailed information about NoiseModel objects and their configurable options, see the Noisy Emulation section of the Fermioniq documentation.

Example: defining a basic noise model in Python

from qcshared.noise_models.model import NoiseModel, ANY
from qcshared.noise_models.channel import DepolarizingChannel, PhaseAmplitudeDampingChannel
from qiskit import QuantumCircuit, qasm2
from qcaas_client.client import OQCClient, QPUTask, ExperimentalConfig, QuantumResultsFormat

def qiskit_bell_pair():
    circuit = QuantumCircuit(2)
    circuit.h(0)
    circuit.cx(0, 1)
    return circuit

bell_pair_circuit = qiskit_bell_pair()
bell_pair_program = qasm2.dumps(bell_pair_circuit)

custom_noise_model = NoiseModel(
    qubits=bell_pair_circuit.qubits,
    name="Example-noise-model",
    description="An example noise model for 2 qubits",
)

# Create channel instances (see more info in Fermioniq class definitions)
d = DepolarizingChannel(p=0.3, num_qubits=2)    # p = depolarizing parameter (0-1)
pad = PhaseAmplitudeDampingChannel(gamma_pd=0.3, gamma_ad=0.3, excited_state_population=0.1)   # gamma_ad = amplitude dampling paramater (0-1), gamma_pd = phase damping parameter (0-1)

# Add readout error to all qubits - prob of misreading 0 is 0.1, and 1 is 0.2
custom_noise_model.add_readout_error(qubit=ANY, p0=0.1, p1=0.2)

# Add 2-qubit gate noise: depolarizing channel applied to both qubits
custom_noise_model.add(gate_name=ANY, gate_qubits=2, channel=d, channel_qubits="same")

# Add H gate (single-qubit) noise: phase-amplitude channel applied to qubit
custom_noise_model.add(gate_name="h", gate_qubits=1, channel=pad, channel_qubits="same")

# Include custom_noise_model in task configuration - note noise must be True!
config = ExperimentalConfig(
  repeats=1000,
  results_format=QuantumResultsFormat().binary_count(),
  noise=True,
  bond_dimension=33,
  noise_model=custom_noise_model
)

# create task with qasm2 program and config for CPU emulator
task = QPUTask(program=bell_pair_program, qpu_id='qpu:uk:3:sim:ferm:1:ef19ecf6', config=config)

If you would like to store your noise model for later use, turn it into a JSON-serialisable dictionary with qcshared.json.encode.jsonify and then save it to a file. You can also pass this serialised noise model directly into ExperimentalConfig.

Example: saving and loading a serialised noise model

from pathlib import Path
import json
from qcshared.json.encode import jsonify

serialised_nm = jsonify(custom_noise_model)

# Optionally, save the serialised noise model to a file
model_path = Path("example_model.json")
model_path.write_text(json.dumps(serialised_nm, indent=2))

# Load a serialised noise model from file
nm_from_file = json.loads(model_path.read_text())

# Include serialised noise model in config
config = ExperimentalConfig(
    repeats        = shot_count,
    results_format = res_format,
    noise          = True,
    bond_dimension = 33,
    noise_model    = nm_from_file,
)

Caution

The noise model must be defined to match the program circuit you intend to apply it on. Defining a noise model for fewer qubits than used by the circuit will cause errors.