import math
import numpy as np
import torch
from .abstract_models import QuantumModel
from .quantum_utils import measure_vqc_output, validate_model
[docs]class VQC(QuantumModel):
"""
.. todo:: TODO: Write description of VQC
.. list-table:: Valid combinations of `num_quantum_params` and `num_qubits`
:widths: 15 15
:header-rows: 1
* - :code:`num_quantum_params`
- :code:`num_qubits`
* - 6
- 2
* - 9
- 2
* - 12
- 4
* - 15
- 2
* - 18
- 6
* - 30
- 4
* - 39
- 4
* - 45
- 6
* - 57
- 4
:param num_quantum_params: Number of quantum parameters/angles to use in the model.
:type num_quantum_params: int
:param num_qubits: Number of qubits to use in the model.
:type num_qubits: int
"""
def __init__(self, num_quantum_params: int, num_qubits: int, *args, **kwargs):
self.grad = np.empty(0)
num_u2, num_u4 = validate_model(num_quantum_params, num_qubits)
self.vqc_params = [num_quantum_params, num_qubits, num_u2, num_u4]
self.sigmoid = torch.nn.Sigmoid()
[docs] def clip(self, x: torch.Tensor, *args) -> torch.Tensor:
"""
Scales inputs, using sigmoid, to :math:`[0, 2\pi]`, which is required by VQC forward method.
"""
x = 2.0 * math.pi * self.sigmoid(x)
return x
def forward(self, x: np.ndarray, *args):
if len(x.shape) == 1: # One element per batch
result, self.grad = measure_vqc_output(x, self.vqc_params, do_grad=True)
elif len(x.shape) == 2: # Multiple elements per batch
# Go through every sample and calculate result and gradient for it.
_result = []
_grad = []
for x_sample in x:
result_sample, grad_sample = measure_vqc_output(
x_sample, self.vqc_params, do_grad=True
)
_grad.append(grad_sample)
_result.append(result_sample)
# Switch to numpy arrays to be compatible with everything else
result = np.array(_result)
self.grad = np.array(_grad)
return result
def backward(self, prev_gradient: np.ndarray) -> np.ndarray:
return np.matmul(prev_gradient, self.grad)