Metadata-Version: 2.4
Name: torch-pennylane-vqc
Version: 0.1.0
Summary: A torch addition package that adds parametrized quantum circuits to use in hybrid quantum-classical machine learning models.
Author-email: Andrey Belov <andrey.belov.4203@gmail.com>
License-Expression: MIT
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# torch-quantum

## Purpose
This package serves as an addition to Pytorch that supplements it with layers
based on variational quantum circuits allowing to create hybrid quantum-classical
machine learning models.

## Installation
```commandline
pip install torch-quantum
```

## Usage examples
### Using models
```python
from torch.utils.data import DataLoader
from torchqml.models import RotationalClassifier

train_loader = ... # load dataset

model = RotationalClassifier(n_qubits=4)

optimizer, criterion = ... # train entities

model.train()
for data, target in train_loader:
    optimizer.zero_grad()
    output = model(data)
    loss = criterion(output, target)
    loss.backward()
    optimizer.step()
```
### Creating models
Model creation completely replicates Pytorch's approach.
```python
import torch.nn as nn
from torchqml.layers import RotationLayer


class MyModel(nn.Module):
    def __init__(self, n_inputs, n_qubits, n_outputs):
        super().__init__()
        self.init_layer = nn.Linear(n_inputs, n_qubits)
        self.q_layer = RotationLayer(n_qubits)
        self.out_layer = nn.Linear(n_qubits, n_outputs)
    
    def forward(self, x):
        x = self.init_layer(x)
        x = self.q_layer(x)
        x = x.float()
        res = self.out_layer(x)
        return res
```
### Creating layers
When creating a layer derived from QuantumLayer make sure to initialize weights
and qnode attributes.
```python
import torch
import torch.nn as nn
import pennylane as qml
from torchqml.layers import QuantumLayer


class MyQuantumLayer(QuantumLayer):
    def __init__(self, n_inputs, encoding='rx', device='default.qubit'):
        super().__init__(n_inputs, encoding, device)
        weight_shape = (self.n_qubits,)
        self.weights = nn.Parameter(torch.randn(weight_shape) * 0.1)
        self.qnode = self.make_qnode()

    def make_qnode(self):
        @qml.qnode(self.device, interface='torch', diff_method='backprop')
        def qnode(inputs):
            self.encode(inputs, self.n_qubits)
            for i in range(self.n_qubits):
                self.ansatz(self.weights[i], wires=[i])
            return [qml.expval(qml.PauliZ(wires=i)) for i in range(self.n_qubits)]
        return qnode
```
