Skip to content

Quantum Layer

The Classiq engine exports the QLayer object, which inherits from torch.nn.Module (like most objects in the torch.nn namespace), and it acts like one.

The QLayer object is defined like this:

class QLayer(nn.Module):
    def __init__(
        self,
        quantum_program: SerializedQuantumProgram,
        execute: ExecuteFunction,
        post_process: PostProcessFunction,
    ) -> None: ...

The first parameter, quantum_program, is the result of synthesizing a quantum model. Note that the parameters are assumed to follow the API stated in qnn.

The second parameter is a callable which is responsible for executing the quantum program, usually with execute_qnn. It takes a SerializedQuantumProgram and MultipleArguments (a list of arguments sets to assign to the quantum program parameters) as inputs, and returns a ResultsCollection.

The third parameter is a callable which is responsible for post-processing each execution result. It takes a SavedResult as input, process it and returns a Tensor.

An example of such callables:

import torch

from classiq.applications.qnn.types import (
    MultipleArguments,
    SavedResult,
    ResultsCollection,
)

from classiq.execution import execute_qnn
from classiq.synthesis import SerializedQuantumProgram


def execute(
    quantum_program: SerializedQuantumProgram, arguments: MultipleArguments
) -> ResultsCollection:
    return execute_qnn(quantum_program, arguments)


def post_process(result: SavedResult) -> torch.Tensor:
    # for example, post-processing can take some value out of `result.value.counts`, which is a `dict`
    value = _post_process_result(result)
    return torch.tensor(value)

Execution

To facilitate the execution of your quantum layer, we supply the utility function execute_qnn. It enables you to easily execute a batch of input arguments, and instruct whether you want the sample results or the estimation results according to specific observables.

The inputs for execute_qnn are:

  • quantum_program of type SerializedQuantumProgram
  • arguments of type MultipleArguments
  • (optionally) observables of type PauliOperators.

The function returns a ResultsCollection, which is a list of SavedResult objects (see Execution Results for more information).

The type of each SavedResult depends on the observables input:

  • If no observables were given, the type would be ExecutionDetails.
  • Otherwise, the type would be EstimationResult.
Note

If only one observable was given, execute_qnn will estimate the execution of all batched arguments with this observable.

If more than one observable was given, their number should match the number of batched arguments, and each execution with a set of arguments will be estimated with the matching observable.

Examples

# Execute and return the sample results
def execute(
    quantum_program: SerializedQuantumProgram, arguments: MultipleArguments
) -> ResultsCollection:
    return execute_qnn(quantum_program, arguments)


# Execute and return the estimation results according to a specific observable
def execute(
    quantum_program: SerializedQuantumProgram, arguments: MultipleArguments
) -> ResultsCollection:
    return execute_qnn(
        quantum_program,
        arguments,
        observables=PauliOperators(
            operators=[
                PauliOperator(
                    pauli_list=[("II", 1 / 2), ("IZ", -1 / 2), ("ZI", -1 / 2)]
                )
            ]
        ),
    )

Behind the Scenes

Behind the scenes, the QLayer handles the following actions:

  • Processing of the PQC
  • Initializing and tracking of parameters
  • Passing the inputs and weights (as multi-dimensional tensors) to the execution function
  • Passing the results from the execution function to the post-processing function
  • Gradient calculation on the PQC