Source code for lambeq.training.tket_model

# Copyright 2021-2023 Cambridge Quantum Computing Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Tket Model
==========
Module based on a quantum backend, using `tket`.

"""
from __future__ import annotations

from collections.abc import Callable
from typing import Any

from discopy.quantum import Circuit, Id, Measure
from discopy.tensor import Diagram
import numpy as np

from lambeq.training.quantum_model import QuantumModel


[docs]class TketModel(QuantumModel): """Model based on `tket`. This can run either shot-based simulations of a quantum pipeline or experiments run on quantum hardware using `tket`. """
[docs] def __init__(self, backend_config: dict[str, Any]) -> None: """Initialise TketModel based on the `t|ket>` backend. Other Parameters ---------------- backend_config : dict Dictionary containing the backend configuration. Must include the fields `backend`, `compilation` and `shots`. Raises ------ KeyError If `backend_config` is not provided or has missing fields. """ super().__init__() fields = ('backend', 'compilation', 'shots') missing_fields = [f for f in fields if f not in backend_config] if missing_fields: raise KeyError('Missing arguments in backend configuation. ' f'Missing arguments: {missing_fields}.') self.backend_config = backend_config
def _make_lambda(self, diagram: Diagram) -> Callable[..., Any]: """Measure and lambdify diagrams.""" measured = diagram >> Id().tensor(*[Measure()] * len(diagram.cod)) ret: Callable = measured.lambdify(*self.symbols) return ret def _randint(self, low: int = -1 << 63, high: int = (1 << 63)-1) -> int: return np.random.randint(low, high, dtype=np.int64)
[docs] def get_diagram_output(self, diagrams: list[Diagram]) -> np.ndarray: """Return the prediction for each diagram using t|ket>. Parameters ---------- diagrams : list of :py:class:`~discopy.tensor.Diagram` The :py:class:`Circuits <discopy.quantum.circuit.Circuit>` to be evaluated. Raises ------ ValueError If `model.weights` or `model.symbols` are not initialised. Returns ------- np.ndarray Resulting array. """ if len(self.weights) == 0 or not self.symbols: raise ValueError('Weights and/or symbols not initialised. ' 'Instantiate through ' '`TketModel.from_diagrams()` first, ' 'then call `initialise_weights()`, or load ' 'from pre-trained checkpoint.') lambdified_diagrams = [self._make_lambda(d) for d in diagrams] tensors = Circuit.eval( *[diag_f(*self.weights) for diag_f in lambdified_diagrams], **self.backend_config, seed=self._randint() ) self.backend_config['backend'].empty_cache() # discopy evals a single diagram into a single result # and not a list of results if len(diagrams) == 1: result = self._normalise_vector(tensors.array) return result.reshape(1, *result.shape) return np.array([self._normalise_vector(t.array) for t in tensors])
[docs] def forward(self, x: list[Diagram]) -> np.ndarray: """Perform default forward pass of a lambeq quantum model. In case of a different datapoint (e.g. list of tuple) or additional computational steps, please override this method. Parameters ---------- x : list of :py:class:`~discopy.tensor.Diagram` The :py:class:`Circuits <discopy.quantum.circuit.Circuit>` to be evaluated. Returns ------- np.ndarray Array containing model's prediction. """ return self.get_diagram_output(x)