Skip to content

Commit

Permalink
Unitary hack (#281)
Browse files Browse the repository at this point in the history
* chore: remove unnecessary import

* fix: fix dep warns

* fix: fix more depwarns and remove job_monitor

* fix: bump up qiskit version

* fix, lint: fix type annotation error for py38, fix lint

* fix: fix cnot error

* fix: fix examples

* ci: update workflow

* test: relax assertion threshold

* Create QGAN.Py

* Added QCBM algorithm with example

* Remove unused imports

* Updated init py following best practices

* Add files via upload

* rm density matrix for now

* Updated with argparse

* bump ibm runtime

Co-authored-by: Kazuki Tsuoka <[email protected]>

* bump qiskit aer

Co-authored-by: Kazuki Tsuoka <[email protected]>

* [fix] revive paramnum

* change: remove unnessesary cloning

* Added qcbm gaussian mixture notebook

* support parameter expression in qiskit2tq

* fix tab

Co-authored-by: GenericP3rson <[email protected]>

* fix tab

Co-authored-by: GenericP3rson <[email protected]>

* fix spacing

Co-authored-by: GenericP3rson <[email protected]>

* fix tab

Co-authored-by: GenericP3rson <[email protected]>

* fix tab

Co-authored-by: GenericP3rson <[email protected]>

* Update torchquantum/operator/standard_gates/qubit_unitary.py

Co-authored-by: GenericP3rson <[email protected]>

* black formatted

* added QGan notebook

* test: add test for qiskit2tq

* change: print

* change: remove comments

* Create QGan.py

* Delete examples/Newfolder/QuantumGAN/README.md directory

* Create QGan.py

* Create Readme.md

* Add files via upload

* Update Readme.md

* Add files via upload

* Delete qgan_notebook.ipynb

* Delete QGAN.Py

* fix: fix test

* Create quantum_pulse_simulation.py

* fix: fix type annotations

* Delete torchQuantumpulse.ipynb

* Rename QGANtorch (2).ipynb to qgan_generated.ipynb

* Rename QGANPng.png to qgan_generated.png

* Rename QGANPng2.png to qgan_image.png

* Update QGan.py

* Rename Readme.md to  README.md

* Update  README.md

* Update  README.md

* Rename qgan_image.png to qgan_latent_dim.png

* Update quantum_pulse_simulation.py

---------

Co-authored-by: Kazuki Tsuoka <[email protected]>
Co-authored-by: Chanandellar Bong <[email protected]>
Co-authored-by: Gopal Dahale <[email protected]>
Co-authored-by: Gopal Ramesh Dahale <[email protected]>
Co-authored-by: Hanrui Wang <[email protected]>
  • Loading branch information
6 people authored Jul 17, 2024
1 parent c6f8e8b commit 8555b83
Show file tree
Hide file tree
Showing 49 changed files with 2,662 additions and 1,796 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/functional_tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
name: Python package

on:
push:
push:
pull_request:

jobs:
Expand All @@ -17,16 +17,16 @@ jobs:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
python -m pip install flake8 pytest qiskit-aer qiskit_ibm_runtime
python -m pip install flake8 pytest
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup Python 3.8
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Update pip and install lint utilities
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/pull_request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- uses: pre-commit/[email protected]
42 changes: 42 additions & 0 deletions examples/QCBM/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Quantum Circuit Born Machine
(Implementation by: [Gopal Ramesh Dahale](https://github.com/Gopal-Dahale))

Quantum Circuit Born Machine (QCBM) [1] is a generative modeling algorithm which uses Born rule from quantum mechanics to sample from a quantum state $|\psi \rangle$ learned by training an ansatz $U(\theta)$ [1][2]. In this tutorial we show how `torchquantum` can be used to model a Gaussian mixture with QCBM.

## Setup

Below is the usage of `qcbm_gaussian_mixture.py` which can be obtained by running `python qcbm_gaussian_mixture.py -h`.

```
usage: qcbm_gaussian_mixture.py [-h] [--n_wires N_WIRES] [--epochs EPOCHS] [--n_blocks N_BLOCKS] [--n_layers_per_block N_LAYERS_PER_BLOCK] [--plot] [--optimizer OPTIMIZER] [--lr LR]
options:
-h, --help show this help message and exit
--n_wires N_WIRES Number of wires used in the circuit
--epochs EPOCHS Number of training epochs
--n_blocks N_BLOCKS Number of blocks in ansatz
--n_layers_per_block N_LAYERS_PER_BLOCK
Number of layers per block in ansatz
--plot Visualize the predicted probability distribution
--optimizer OPTIMIZER
optimizer class from torch.optim
--lr LR
```

For example:

```
python qcbm_gaussian_mixture.py --plot --epochs 100 --optimizer RMSprop --lr 0.01 --n_blocks 6 --n_layers_per_block 2 --n_wires 6
```

Using the command above gives an output similar to the plot below.

<p align="center">
<img src ='./assets/sample_output.png' width-500 alt='sample output of QCBM'>
</p>


## References

1. Liu, Jin-Guo, and Lei Wang. “Differentiable learning of quantum circuit born machines.” Physical Review A 98.6 (2018): 062324.
2. Gili, Kaitlin, et al. "Do quantum circuit born machines generalize?." Quantum Science and Technology 8.3 (2023): 035021.
Binary file added examples/QCBM/assets/sample_output.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
255 changes: 255 additions & 0 deletions examples/QCBM/qcbm_gaussian_mixture.ipynb

Large diffs are not rendered by default.

129 changes: 129 additions & 0 deletions examples/QCBM/qcbm_gaussian_mixture.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import matplotlib.pyplot as plt
import numpy as np
import torch
from torchquantum.algorithm import QCBM, MMDLoss
import torchquantum as tq
import argparse
import os
from pprint import pprint


# Reproducibility
def set_seed(seed: int = 42) -> None:
np.random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed(seed)
# When running on the CuDNN backend, two further options must be set
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False
# Set a fixed value for the hash seed
os.environ["PYTHONHASHSEED"] = str(seed)
print(f"Random seed set as {seed}")


def _setup_parser():
parser = argparse.ArgumentParser()
parser.add_argument(
"--n_wires", type=int, default=6, help="Number of wires used in the circuit"
)
parser.add_argument(
"--epochs", type=int, default=10, help="Number of training epochs"
)
parser.add_argument(
"--n_blocks", type=int, default=6, help="Number of blocks in ansatz"
)
parser.add_argument(
"--n_layers_per_block",
type=int,
default=1,
help="Number of layers per block in ansatz",
)
parser.add_argument(
"--plot",
action="store_true",
help="Visualize the predicted probability distribution",
)
parser.add_argument(
"--optimizer", type=str, default="Adam", help="optimizer class from torch.optim"
)
parser.add_argument("--lr", type=float, default=1e-2)
return parser


# Function to create a gaussian mixture
def gaussian_mixture_pdf(x, mus, sigmas):
mus, sigmas = np.array(mus), np.array(sigmas)
vars = sigmas**2
values = [
(1 / np.sqrt(2 * np.pi * v)) * np.exp(-((x - m) ** 2) / (2 * v))
for m, v in zip(mus, vars)
]
values = np.sum([val / sum(val) for val in values], axis=0)
return values / np.sum(values)


def main():
set_seed()
parser = _setup_parser()
args = parser.parse_args()

print("Configuration:")
pprint(vars(args))

# Create a gaussian mixture
n_wires = args.n_wires
assert n_wires >= 1, "Number of wires must be at least 1"

x_max = 2**n_wires
x_input = np.arange(x_max)
mus = [(2 / 8) * x_max, (5 / 8) * x_max]
sigmas = [x_max / 10] * 2
data = gaussian_mixture_pdf(x_input, mus, sigmas)

# This is the target distribution that the QCBM will learn
target_probs = torch.tensor(data, dtype=torch.float32)

# Ansatz
layers = tq.RXYZCXLayer0(
{
"n_blocks": args.n_blocks,
"n_wires": n_wires,
"n_layers_per_block": args.n_layers_per_block,
}
)

qcbm = QCBM(n_wires, layers)

# To train QCBMs, we use MMDLoss with radial basis function kernel.
bandwidth = torch.tensor([0.25, 60])
space = torch.arange(2**n_wires)
mmd = MMDLoss(bandwidth, space)

# Optimization
optimizer_class = getattr(torch.optim, args.optimizer)
optimizer = optimizer_class(qcbm.parameters(), lr=args.lr)

for i in range(args.epochs):
optimizer.zero_grad(set_to_none=True)
pred_probs = qcbm()
loss = mmd(pred_probs, target_probs)
loss.backward()
optimizer.step()
print(i, loss.item())

# Visualize the results
if args.plot:
with torch.no_grad():
pred_probs = qcbm()

plt.plot(x_input, target_probs, linestyle="-.", label=r"$\pi(x)$")
plt.bar(x_input, pred_probs, color="green", alpha=0.5, label="samples")
plt.xlabel("Samples")
plt.ylabel("Prob. Distribution")

plt.legend()
plt.show()


if __name__ == "__main__":
main()
74 changes: 74 additions & 0 deletions examples/QuantumGan/ README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Quantum Generative Adversarial Network (QGAN) Example

This repository contains an example implementation of a Quantum Generative Adversarial Network (QGAN) using PyTorch and TorchQuantum. The example is provided in a Jupyter Notebook for interactive exploration.

## Overview

A QGAN consists of two main components:

1. **Generator:** This network generates fake quantum data samples.
2. **Discriminator:** This network tries to distinguish between real and fake quantum data samples.

The goal is to train the generator to produce quantum data that is indistinguishable from real data, according to the discriminator. This is achieved through an adversarial training process, where the generator and discriminator are trained simultaneously in a competitive manner.

## Repository Contents

- `qgan_notebook.ipynb`: Jupyter Notebook demonstrating the QGAN implementation.
- `qgan_script.py`: Python script containing the QGAN model and a main function for initializing the model with command-line arguments.

## Installation

To run the examples, you need to have the following dependencies installed:

- Python 3
- PyTorch
- TorchQuantum
- Jupyter Notebook
- ipywidgets

You can install the required Python packages using pip:

```bash
pip install torch torchquantum jupyter ipywidgets
```


Running the Examples
Jupyter Notebook
Open the qgan_notebook.ipynb file in Jupyter Notebook.
Execute the notebook cells to see the QGAN model in action.
Python Script
You can also run the QGAN model using the Python script. The script uses argparse to handle command-line arguments.

bash
Copy code
python qgan_script.py <n_qubits> <latent_dim>
Replace <n_qubits> and <latent_dim> with the desired number of qubits and latent dimensions.

Notebook Details
The Jupyter Notebook is structured as follows:

Introduction: Provides an overview of the QGAN and its components.
Import Libraries: Imports the necessary libraries, including PyTorch and TorchQuantum.
Generator Class: Defines the quantum generator model.
Discriminator Class: Defines the quantum discriminator model.
QGAN Class: Combines the generator and discriminator into a single QGAN model.
Main Function: Initializes the QGAN model and prints its structure.
Interactive Model Creation: Uses ipywidgets to create an interactive interface for adjusting the number of qubits and latent dimensions.
Understanding QGANs
QGANs are a type of Generative Adversarial Network (GAN) that operate in the quantum domain. They leverage quantum circuits to generate and evaluate data samples. The adversarial training process involves two competing networks:

The Generator creates fake quantum data samples from a latent space.
The Discriminator attempts to distinguish these fake samples from real quantum data.
Through training, the generator improves its ability to create realistic quantum data, while the discriminator enhances its ability to identify fake data. This process results in a generator that can produce high-quality quantum data samples.


## QGAN Implementation for CIFAR-10 Dataset
This implementation trains a QGAN on the CIFAR-10 dataset to generate fake images. It follows a similar structure to the TorchQuantum QGAN, with the addition of data loading and processing specific to the CIFAR-10 dataset.
Generated images can be seen in the folder

This `README.md` file explains the purpose of the repository, the structure of the notebook, and how to run the examples, along with a brief overview of the QGAN concept for those unfamiliar with it.


## Reference
- [ ] https://arxiv.org/abs/2312.09939
84 changes: 84 additions & 0 deletions examples/QuantumGan/QGan.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import argparse
import torch
import torch.nn as nn
import torch.optim as optim
import torchquantum as tq

class Generator(nn.Module):
def __init__(self, n_qubits: int, latent_dim: int):
super().__init__()
self.n_qubits = n_qubits
self.latent_dim = latent_dim

# Quantum encoder
self.encoder = tq.GeneralEncoder([
{'input_idx': [i], 'func': 'rx', 'wires': [i]}
for i in range(self.n_qubits)
])

# RX gates
self.rxs = nn.ModuleList([
tq.RX(has_params=True, trainable=True) for _ in range(self.n_qubits)
])

def forward(self, x):
qdev = tq.QuantumDevice(n_wires=self.n_qubits, bsz=x.shape[0], device=x.device)
self.encoder(qdev, x)

for i in range(self.n_qubits):
self.rxs[i](qdev, wires=i)

return tq.measure(qdev)

class Discriminator(nn.Module):
def __init__(self, n_qubits: int):
super().__init__()
self.n_qubits = n_qubits

# Quantum encoder
self.encoder = tq.GeneralEncoder([
{'input_idx': [i], 'func': 'rx', 'wires': [i]}
for i in range(self.n_qubits)
])

# RX gates
self.rxs = nn.ModuleList([
tq.RX(has_params=True, trainable=True) for _ in range(self.n_qubits)
])

# Quantum measurement
self.measure = tq.MeasureAll(tq.PauliZ)

def forward(self, x):
qdev = tq.QuantumDevice(n_wires=self.n_qubits, bsz=x.shape[0], device=x.device)
self.encoder(qdev, x)

for i in range(self.n_qubits):
self.rxs[i](qdev, wires=i)

return self.measure(qdev)

class QGAN(nn.Module):
def __init__(self, n_qubits: int, latent_dim: int):
super().__init__()
self.generator = Generator(n_qubits, latent_dim)
self.discriminator = Discriminator(n_qubits)

def forward(self, z):
fake_data = self.generator(z)
fake_output = self.discriminator(fake_data)
return fake_output

def main(n_qubits, latent_dim):
model = QGAN(n_qubits, latent_dim)
print(model)

if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Quantum Generative Adversarial Network (QGAN) Example")
parser.add_argument('n_qubits', type=int, help='Number of qubits')
parser.add_argument('latent_dim', type=int, help='Dimension of the latent space')

args = parser.parse_args()

main(args.n_qubits, args.latent_dim)

Loading

0 comments on commit 8555b83

Please sign in to comment.