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