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.
QLayer object is defined like this:
class QLayer(nn.Module): def __init__( self, quantum_program: SerializedQuantumProgram, execute: ExecuteFunction, post_process: PostProcessFunction, ) -> None: ...
The second parameter is a callable which is responsible for executing the quantum program, usually with
It takes a
MultipleArguments (a list of arguments sets to assign to the quantum program parameters) as inputs, and returns a
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
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)
To facilitate the execution of your quantum layer, we supply the utility function
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
PauliOperators: each observable should match the model output port if present, or the whole quantum program otherwise (estimation for models with multiple output ports is currently not supported).
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
- If no
observableswere given, the type would be
- Otherwise, the type would be
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.
# 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