Source code for qailab.torch.layers.regression

 1"""Attempts at regression compatible layers"""
 2from qiskit import QuantumCircuit
 3from quantum_launcher.routines.qiskit_routines import QiskitBackend
 4import torch
 5
 6from qailab.torch.qlayer import QLayer
 7from qailab.torch.autograd import ArgMax
 8
 9
[docs] 10class ExpectedValueQLayer(QLayer): 11 """ 12 This layer returns the expected value of a bitstring sampled from the underlying quantum circuit. 13 The value is a floating point number in range <0, 2^num_measured_qubits - 1> 14 """ 15 16 def __init__( 17 self, 18 circuit: QuantumCircuit, 19 *, 20 backend: QiskitBackend | None = None, 21 shots: int = 1024, 22 rescale_output: tuple[float, float] | None = None 23 ) -> None: 24 """ 25 Args: 26 circuit (QuantumCircuit): Circuit to run. 27 backend (QiskitBackend | None, optional): Backend to use. If None a local QiskitBackend is created. Defaults to None. 28 shots (int, optional): Number of times to sample the circuit. Defaults to 1024. 29 rescale_output (tuple[float,float] | None, optional): 30 Tuple of (low, high) representing a range to rescale output to. 31 If None the output will be in range <0, 2^num_measured_qubits - 1>. Defaults to None. 32 """ 33 super().__init__(circuit, backend=backend, shots=shots) 34 self._max_expected_out_value = self.out_features - 1 35 self.out_features = 1 36 self._rescale_output_range = rescale_output 37 38 def _rescale_out(self, x: torch.Tensor): 39 if self._rescale_output_range is None: 40 return x 41 42 out_min, out_max = self._rescale_output_range 43 x_zero_one = x / max(self._max_expected_out_value, 1) 44 x_scale = x_zero_one * (out_max - out_min) + out_min 45 return x_scale 46
[docs] 47 def forward(self, input_tensor: torch.Tensor) -> torch.Tensor: 48 out_distribution = super().forward(input_tensor) 49 50 def make_vals(l): 51 return torch.tensor( 52 list(range(l)), 53 dtype=out_distribution.dtype, 54 requires_grad=out_distribution.requires_grad 55 ) 56 57 # Unbatched input 58 if len(out_distribution.shape) == 1: 59 values = make_vals(out_distribution.shape[0]) 60 return torch.sum(out_distribution * values) 61 # Batched input 62 values = torch.stack([make_vals(out_distribution.shape[1])] * out_distribution.shape[0]) 63 summed = torch.sum(out_distribution * values, dim=-1) 64 return self._rescale_out(summed)
65 66
[docs] 67class ArgmaxQLayer(QLayer): 68 """ 69 This layer returns the most common bitstring sampled from the underlying quantum circuit. 70 The value is a whole number in range <0, 2^num_measured_qubits - 1> 71 """ 72 73 def __init__(self, circuit: QuantumCircuit, *, backend: QiskitBackend | None = None, shots: int = 1024) -> None: 74 super().__init__(circuit, backend=backend, shots=shots) 75 self.out_features = 1 76
[docs] 77 def forward(self, input_tensor: torch.Tensor) -> torch.Tensor: 78 out_distribution = super().forward(input_tensor) 79 argmax = ArgMax.apply(out_distribution) 80 if not isinstance(argmax, torch.Tensor): 81 raise ValueError(f"Argmax output error. {type(argmax)} is not torch.Tensor") 82 return argmax