Trouble implementing parallel operations

Hi all, I am working on an experiment in which I am trying to implement parallel operations for a quantum-classical hybrid network that uses the qiskit.aer device instantiated with a noise model.
In my approach to parallel operations, I am trying to distribute part of the process over four individual GPUs. The code for my approach is adapted from the quantum GAN demo:

import math
import random
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import pennylane as qml

# Pytorch imports
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import Dataset, DataLoader
from qiskit_aer import AerSimulator
from qiskit_aer.noise import (NoiseModel, QuantumError, ReadoutError,
    pauli_error, depolarizing_error, thermal_relaxation_error)
from copy import deepcopy

# Set the random seed for reproducibility
seed = 42
torch.manual_seed(seed)
np.random.seed(seed)
random.seed(seed)

device_list = [torch.device("cuda:" + str(i)) for i in range(torch.cuda.device_count())]
n_devs = len(device_list)


class DigitsDataset(Dataset):
    """Pytorch dataloader for the Optical Recognition of Handwritten Digits Data Set"""

    def __init__(self, csv_file, label=0, transform=None):
        """
        Args:
            csv_file (string): Path to the csv file with annotations.
            root_dir (string): Directory with all the images.
            transform (callable, optional): Optional transform to be applied
                on a sample.
        """
        self.csv_file = csv_file
        self.transform = transform
        self.df = self.filter_by_label(label)

    def filter_by_label(self, label):
        # Use pandas to return a dataframe of only zeros
        df = pd.read_csv(self.csv_file)
        df = df.loc[df.iloc[:, -1] == label]
        return df

    def __len__(self):
        return len(self.df)

    def __getitem__(self, idx):
        if torch.is_tensor(idx):
            idx = idx.tolist()

        image = self.df.iloc[idx, :-1] / 16
        image = np.array(image)
        image = image.astype(np.float32).reshape(8, 8)

        if self.transform:
            image = self.transform(image)

        # Return image and label
        return image, 0


image_size = 8  # Height / width of the square images
batch_size = 1

transform = transforms.Compose([transforms.ToTensor()])
dataset = DigitsDataset(csv_file="optdigits.tra", transform=transform)
dataloader = torch.utils.data.DataLoader(
    dataset, batch_size=batch_size, shuffle=True, drop_last=True
)

plt.figure(figsize=(8,2))

for i in range(8):
    image = dataset[i][0].reshape(image_size,image_size)
    plt.subplot(1,8,i+1)
    plt.axis('off')
    plt.imshow(image.numpy(), cmap='gray')

plt.show()



class Discriminator(nn.Module):
    """Fully connected classical discriminator"""

    def __init__(self):
        super().__init__()

        self.model = nn.Sequential(
            # Inputs to first hidden layer (num_input_features -> 64)
            nn.Linear(image_size * image_size, 64),
            nn.ReLU(),
            # First hidden layer (64 -> 16)
            nn.Linear(64, 16),
            nn.ReLU(),
            # Second hidden layer (16 -> output)
            nn.Linear(16, 1),
            nn.Sigmoid(),
        )

    def forward(self, x):
        return self.model(x)



# Quantum variables
n_qubits = 5  # Total number of qubits / N
n_a_qubits = 1  # Number of ancillary qubits / N_A
q_depth = 6  # Depth of the parameterised quantum circuit / D
n_generators = 4  # Number of subgenerators for the patch method / N_G
# Quantum simulator
#dev = qml.device('qiskit.aer', wires=n_qubits, noise_model=None, method="statevector_gpu", analytic=True)
# Enable CUDA device if available
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")




def quantum_circuit(noise, weights):

    weights = weights.reshape(q_depth, n_qubits)

    # Initialise latent vectors
    for i in range(n_qubits):
        qml.RY(noise[i], wires=i)

    # Repeated layer
    for i in range(q_depth):
        # Parameterised layer
        for y in range(n_qubits):
            qml.RY(weights[i][y], wires=y)

        # Control Z gates
        for y in range(n_qubits - 1):
            qml.CZ(wires=[y, y + 1])

    return qml.probs(wires=list(range(n_qubits)))


# For further info on how the non-linear transform is implemented in Pennylane
# https://discuss.pennylane.ai/t/ancillary-subsystem-measurement-then-trace-out/1532
def partial_measure(probs):
    # Non-linear Transform
    #probs = quantum_circuit(noise, weights)
    probsgiven0 = probs[: (2 ** (n_qubits - n_a_qubits))]
    probsgiven0 /= torch.sum(probs)

    # Post-Processing
    probsgiven = probsgiven0 / torch.max(probsgiven0)
    return probsgiven


class PatchQuantumGenerator(nn.Module):
    """Quantum generator class for the patch method"""

    def __init__(self, n_generators, q_delta=1):
        """
        Args:
            n_generators (int): Number of sub-generators to be used in the patch method.
            q_delta (float, optional): Spread of the random distribution for parameter initialisation.
        """

        super().__init__()

        self.q_params = nn.ParameterList(
            [
                nn.Parameter(q_delta * torch.rand(q_depth * n_qubits), requires_grad=True)
                for _ in range(n_generators)
            ]
        )
        self.n_generators = n_generators
        self.torch_device = None
        self.dev = qml.device('qiskit.aer', wires=n_qubits, noise_model=None, method="statevector_gpu", analytic=True)
        self.dev.backend.set_options(blocking_enable=True)
        self.dev.backend.set_options(shot_branching_enable=True)
        self.dev.backend.set_options(shot_branching_sampling_enable=True)
        self.dev.backend.set_options(num_threads_per_device=512)
        new_noise_model = NoiseModel()
        error_prob = np.random.uniform(0, 1)
        noise_prob = depolarizing_error(error_prob, 1)
        new_noise_model.add_all_qubit_quantum_error(noise_prob, ['ry'])
        self.dev.backend.set_options(noise_model=new_noise_model)
        self.node = qml.QNode(quantum_circuit, self.dev)

    def forward(self, x):
        # Size of each sub-generator output
        patch_size = 2 ** (n_qubits - n_a_qubits)

        # Create a Tensor to 'catch' a batch of images from the for loop. x.size(0) is the batch size.
        images = torch.Tensor(x.size(0), 0).to(self.torch_device if self.torch_device is not None else device)

        # Iterate over all sub-generators
        for params in self.q_params:

            # Create a Tensor to 'catch' a batch of the patches from a single sub-generator
            patches = torch.Tensor(0, patch_size).to(self.torch_device if self.torch_device is not None else device)
            for elem in x:
                probs = self.node(elem, params)
                q_out = partial_measure(probs).float().unsqueeze(0)
                patches = torch.cat((patches, q_out))

            # Each batch of patches is concatenated with each other to create a batch of images
            images = torch.cat((images, patches), 1)

        return images
    def set_torch_device(self, torch_device):
        self.torch_device = torch_device


lrG = 0.3  # Learning rate for the generator
lrD = 0.01  # Learning rate for the discriminator
num_iter = 500  # Number of training iterations


discriminator = Discriminator().to(device)
generator = PatchQuantumGenerator(n_generators).to(device)
gen_clones = nn.parallel.replicate(generator, device_list)
for i in range(n_devs):
	gen_clones[i].set_torch_device(device_list[i])

# Binary cross entropy
criterion = nn.BCELoss()

# Optimisers
optD = optim.SGD(discriminator.parameters(), lr=lrD)
optG = optim.SGD(generator.parameters(), lr=lrG)

real_labels = torch.full((batch_size,), 1.0, dtype=torch.float, device=device)
fake_labels = torch.full((batch_size,), 0.0, dtype=torch.float, device=device)

# Fixed noise allows us to visually track the generated images throughout training
fixed_noise = torch.rand(8, n_qubits, device=device) * math.pi / 2

# Iteration counter
counter = 0

# Collect images for plotting later
results = []

while True:
    for i, (data, _) in enumerate(dataloader):

        # Data for training the discriminator
        data = data.reshape(-1, image_size * image_size)
        real_data = data.to(device) 

        # Noise follwing a uniform distribution in range [0,pi/2)
        noise = torch.rand(batch_size, n_qubits, device=device) * math.pi / 2
        noise_clones = [noise.clone() for i in range(n_devs)]
        noise_data = torch.stack(noise_clones)
        noise_inputs = list(nn.parallel.scatter(noise_data, device_list))
        for i in range(n_devs):
            noise_inputs[i] = noise_inputs[i].view(noise.shape)
        fake_data_list = nn.parallel.parallel_apply(gen_clones, noise_inputs)
        print("Parallel apply done")
        fake_data_gather = nn.parallel.gather(fake_data_list, device_list[0])
        fake_data = torch.sum(fake_data_gather) / n_devs
        
        #fake_data = generator(noise)

        # Training the discriminator
        discriminator.zero_grad()
        outD_real = discriminator(real_data).view(-1)
        outD_fake = discriminator(fake_data.detach()).view(-1)

        errD_real = criterion(outD_real, real_labels)
        errD_fake = criterion(outD_fake, fake_labels)
        # Propagate gradients
        errD_real.backward()
        errD_fake.backward()

        errD = errD_real + errD_fake
        optD.step()

        # Training the generator
        generator.zero_grad()
        outD_fake = discriminator(fake_data).view(-1)
        errG = criterion(outD_fake, real_labels)
        errG.backward()
        optG.step()

        counter += 1

        # Show loss values
        if counter % 10 == 0:
            print(f'Iteration: {counter}, Discriminator Loss: {errD:0.3f}, Generator Loss: {errG:0.3f}')
            test_images = generator(fixed_noise).view(8,1,image_size,image_size).cpu().detach()

            # Save images every 50 iterations
            if counter % 50 == 0:
                results.append(test_images)

        if counter == num_iter:
            break
    if counter == num_iter:
        break


fig = plt.figure(figsize=(10, 5))
outer = gridspec.GridSpec(5, 2, wspace=0.1)

for i, images in enumerate(results):
    inner = gridspec.GridSpecFromSubplotSpec(1, images.size(0),
                    subplot_spec=outer[i])

    images = torch.squeeze(images, dim=1)
    for j, im in enumerate(images):

        ax = plt.Subplot(fig, inner[j])
        ax.imshow(im.numpy(), cmap="gray")
        ax.set_xticks([])
        ax.set_yticks([])
        if j==0:
            ax.set_title(f'Iteration {50+i*50}', loc='left')
        fig.add_subplot(ax)

plt.show()

When I run the code, I get the following error message:

Simulation failed and returned the following error message:
ERROR: Failed to load circuits: Duplicate key "_method_" in save instruction.
Simulation failed and returned the following error message:
ERROR: Failed to load circuits: Duplicate key "_method_" in save instruction.
Traceback (most recent call last):
  File "/N/slate/justsing/CausalTensornet/pennylane_qiskit_toy_example.py", line 263, in <module>
    fake_data_list = nn.parallel.parallel_apply(gen_clones, noise_inputs)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/torch/nn/parallel/parallel_apply.py", line 108, in parallel_apply
    output.reraise()
  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/torch/_utils.py", line 705, in reraise
    raise exception
qiskit.exceptions.QiskitError: 'Caught QiskitError in replica 0 on device 0.\nOriginal Traceback (most recent call last):\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/torch/nn/parallel/parallel_apply.py", line 83, in _worker\n    output = module(*input, **kwargs)\n             ^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/torch/nn/modules/module.py", line 1532, in _wrapped_call_impl\n    return self._call_impl(*args, **kwargs)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/torch/nn/modules/module.py", line 1541, in _call_impl\n    return forward_call(*args, **kwargs)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/N/slate/justsing/CausalTensornet/pennylane_qiskit_toy_example.py", line 207, in forward\n    probs = self.node(elem, params)\n            ^^^^^^^^^^^^^^^^^^^^^^^\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/pennylane/workflow/qnode.py", line 1164, in __call__\n    return self._impl_call(*args, **kwargs)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/pennylane/workflow/qnode.py", line 1150, in _impl_call\n    res = self._execution_component(args, kwargs, override_shots=override_shots)\n          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/pennylane/workflow/qnode.py", line 1103, in _execution_component\n    res = qml.execute(\n          ^^^^^^^^^^^^\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/pennylane/workflow/execution.py", line 835, in execute\n    results = ml_boundary_execute(tapes, execute_fn, jpc, device=device)\n              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/pennylane/workflow/interfaces/torch.py", line 236, in execute\n    return ExecuteTapes.apply(kwargs, *parameters)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/pennylane/workflow/interfaces/torch.py", line 89, in new_apply\n    flat_out = orig_apply(out_struct_holder, *inp)\n               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/torch/autograd/function.py", line 598, in apply\n    return super().apply(*args, **kwargs)  # type: ignore[misc]\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/pennylane/workflow/interfaces/torch.py", line 93, in new_forward\n    out = orig_fw(ctx, *inp)\n          ^^^^^^^^^^^^^^^^^^\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/pennylane/workflow/interfaces/torch.py", line 158, in forward\n    res = tuple(kwargs["execute_fn"](ctx.tapes))\n                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/pennylane/workflow/execution.py", line 316, in inner_execute\n    results = device_execution(transformed_tapes)\n              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/N/soft/rhel8/deeplearning/Python-3.11.5/lib/python3.11/contextlib.py", line 81, in inner\n    return func(*args, **kwds)\n           ^^^^^^^^^^^^^^^^^^^\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/pennylane_qiskit/qiskit_device.py", line 519, in batch_execute\n    self._state = self._get_state(result, experiment=circuit_obj)\n                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/pennylane_qiskit/qiskit_device.py", line 416, in _get_state\n    state = np.asarray(result.get_statevector(experiment))\n                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/qiskit/result/result.py", line 314, in get_statevector\n    self.data(experiment)["statevector"], decimals=decimals\n    ^^^^^^^^^^^^^^^^^^^^^\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/qiskit/result/result.py", line 187, in data\n    return self._get_experiment(experiment).data.to_dict()\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/qiskit/result/result.py", line 380, in _get_experiment\n    raise QiskitError(\'Data for experiment "%s" could not be found.\' % key)\nqiskit.exceptions.QiskitError: \'Data for experiment "circ0" could not be found.\'\n'
[justsing@g4 CausalTensornet]$ ./pennylane_qiskit_run.sh
Simulation failed and returned the following error message:
ERROR: Failed to load circuits: Duplicate key "_method_" in save instruction.
Simulation failed and returned the following error message:
ERROR: Failed to load circuits: Duplicate key "_method_" in save instruction.
Simulation failed and returned the following error message:
ERROR: Failed to load circuits: Duplicate key "_method_" in save instruction.
Traceback (most recent call last):
  File "pennylane_qiskit_toy_example.py", line 263, in <module>
    fake_data_list = nn.parallel.parallel_apply(gen_clones, noise_inputs)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/torch/nn/parallel/parallel_apply.py", line 108, in parallel_apply
    output.reraise()
  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/torch/_utils.py", line 705, in reraise
    raise exception
qiskit.exceptions.QiskitError: 'Caught QiskitError in replica 1 on device 0.\nOriginal Traceback (most recent call last):\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/torch/nn/parallel/parallel_apply.py", line 83, in _worker\n    output = module(*input, **kwargs)\n             ^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/torch/nn/modules/module.py", line 1532, in _wrapped_call_impl\n    return self._call_impl(*args, **kwargs)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/torch/nn/modules/module.py", line 1541, in _call_impl\n    return forward_call(*args, **kwargs)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/N/slate/justsing/CausalTensornet/pennylane_qiskit_toy_example.py", line 207, in forward\n    probs = self.node(elem, params)\n            ^^^^^^^^^^^^^^^^^^^^^^^\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/pennylane/workflow/qnode.py", line 1164, in __call__\n    return self._impl_call(*args, **kwargs)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/pennylane/workflow/qnode.py", line 1150, in _impl_call\n    res = self._execution_component(args, kwargs, override_shots=override_shots)\n          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/pennylane/workflow/qnode.py", line 1103, in _execution_component\n    res = qml.execute(\n          ^^^^^^^^^^^^\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/pennylane/workflow/execution.py", line 835, in execute\n    results = ml_boundary_execute(tapes, execute_fn, jpc, device=device)\n              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/pennylane/workflow/interfaces/torch.py", line 236, in execute\n    return ExecuteTapes.apply(kwargs, *parameters)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/pennylane/workflow/interfaces/torch.py", line 89, in new_apply\n    flat_out = orig_apply(out_struct_holder, *inp)\n               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/torch/autograd/function.py", line 598, in apply\n    return super().apply(*args, **kwargs)  # type: ignore[misc]\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/pennylane/workflow/interfaces/torch.py", line 93, in new_forward\n    out = orig_fw(ctx, *inp)\n          ^^^^^^^^^^^^^^^^^^\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/pennylane/workflow/interfaces/torch.py", line 158, in forward\n    res = tuple(kwargs["execute_fn"](ctx.tapes))\n                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/pennylane/workflow/execution.py", line 316, in inner_execute\n    results = device_execution(transformed_tapes)\n              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/N/soft/rhel8/deeplearning/Python-3.11.5/lib/python3.11/contextlib.py", line 81, in inner\n    return func(*args, **kwds)\n           ^^^^^^^^^^^^^^^^^^^\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/pennylane_qiskit/qiskit_device.py", line 519, in batch_execute\n    self._state = self._get_state(result, experiment=circuit_obj)\n                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/pennylane_qiskit/qiskit_device.py", line 416, in _get_state\n    state = np.asarray(result.get_statevector(experiment))\n                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/qiskit/result/result.py", line 314, in get_statevector\n    self.data(experiment)["statevector"], decimals=decimals\n    ^^^^^^^^^^^^^^^^^^^^^\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/qiskit/result/result.py", line 187, in data\n    return self._get_experiment(experiment).data.to_dict()\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/N/u/justsing/Quartz/.local/lib/python3.11/site-packages/qiskit/result/result.py", line 380, in _get_experiment\n    raise QiskitError(\'Data for experiment "%s" could not be found.\' % key)\nqiskit.exceptions.QiskitError: \'Data for experiment "circ0" could not be found.\'\n'

I am wondering if this issue has to do with a compatability issue between the pennylane-qiskit plugin and the AerSimulator backend, because I noticed that the file aer.py was last updated five months ago, which I think was before the major Qiskit overhaul that happened this year, while the Qiskit Aer backend itself was updated last month. My technical specs are below. Any help that you can provide would be greatly appreciated.

Name: PennyLane
Version: 0.37.0
Summary: PennyLane is a cross-platform Python library for quantum computing, quantum machine learning, and quantum chemistry. Train a quantum computer the same way as a neural network.
Home-page: https://github.com/PennyLaneAI/pennylane
Author: 
Author-email: 
License: Apache License 2.0
Location: /N/u/justsing/Quartz/.local/lib/python3.11/site-packages
Requires: appdirs, autograd, autoray, cachetools, networkx, numpy, packaging, pennylane-lightning, requests, rustworkx, scipy, semantic-version, toml, typing-extensions
Required-by: PennyLane-Cirq, Pennylane-Pyquest, PennyLane-qiskit, pennylane-qulacs, PennyLane-Rigetti, PennyLane_Lightning, PennyLane_Lightning_GPU

Platform info:           Linux-4.18.0-553.8.1.el8_10.x86_64-x86_64-with-glibc2.28
Python version:          3.11.5
Numpy version:           1.26.4
Scipy version:           1.14.0
Installed devices:
- qiskit.aer (PennyLane-qiskit-0.37.0)
- qiskit.basicaer (PennyLane-qiskit-0.37.0)
- qiskit.basicsim (PennyLane-qiskit-0.37.0)
- qiskit.ibmq (PennyLane-qiskit-0.37.0)
- qiskit.ibmq.circuit_runner (PennyLane-qiskit-0.37.0)
- qiskit.ibmq.sampler (PennyLane-qiskit-0.37.0)
- qiskit.remote (PennyLane-qiskit-0.37.0)
- qulacs.simulator (pennylane-qulacs-0.36.0)
- lightning.gpu (PennyLane_Lightning_GPU-0.36.0)
- lightning.qubit (PennyLane_Lightning-0.37.0)
- cirq.mixedsimulator (PennyLane-Cirq-0.36.0)
- cirq.pasqal (PennyLane-Cirq-0.36.0)
- cirq.qsim (PennyLane-Cirq-0.36.0)
- cirq.qsimh (PennyLane-Cirq-0.36.0)
- cirq.simulator (PennyLane-Cirq-0.36.0)
- default.clifford (PennyLane-0.37.0)
- default.gaussian (PennyLane-0.37.0)
- default.mixed (PennyLane-0.37.0)
- default.qubit (PennyLane-0.37.0)
- default.qubit.autograd (PennyLane-0.37.0)
- default.qubit.jax (PennyLane-0.37.0)
- default.qubit.legacy (PennyLane-0.37.0)
- default.qubit.tf (PennyLane-0.37.0)
- default.qubit.torch (PennyLane-0.37.0)
- default.qutrit (PennyLane-0.37.0)
- default.qutrit.mixed (PennyLane-0.37.0)
- default.tensor (PennyLane-0.37.0)
- null.qubit (PennyLane-0.37.0)
- rigetti.numpy_wavefunction (PennyLane-Rigetti-0.36.0)
- rigetti.qpu (PennyLane-Rigetti-0.36.0)
- rigetti.qvm (PennyLane-Rigetti-0.36.0)
- rigetti.wavefunction (PennyLane-Rigetti-0.36.0)
- pyquest.mixed (Pennylane-Pyquest-0.1.0)
- pyquest.pure (Pennylane-Pyquest-0.1.0)

Hi @justin6626 ,

It looks like this is due to Qiskit not being able to find some data.

If you look at the error message this is what’s causing the issue:

raise QiskitError(\'Data for experiment "%s" could not be found.\' % key)\nqiskit.exceptions.QiskitError: \'Data for experiment "circ0" could not be found.

I would recommend that you make a self-contained minimal working example in order to find the source of the issue.

A minimal (but self-contained) working example is the simplest version of the code that reproduces the problem. It should include all necessary imports, data, functions, etc., so that we can copy-paste the code and reproduce the problem. However it shouldn’t contain any unnecessary data, functions, …, for example gates and functions that can be removed to simplify the code.

If you’re not sure what this means then make sure to check out this video.

Thank you very much for getting back to me!

Would the demo Turning quantum nodes into Torch Layers be closer to a minimal example for the purpose of using it as the foundation for the toy example I am trying to build?

Hi @justin6626 ,

Yes, absolutely! You can choose for example the sequential model (the first example in that demo) and then make a comment about every line that you’re modifying with respect to the demo. This will make it much easier to find the source of the problem!

Thank you very much! My apologies for the delay.

Here is the code for the smaller toy example:

import torch
import torch.nn as nn
import matplotlib.pyplot as plt
import numpy as np
from sklearn.datasets import make_moons
import pennylane as qml
########Qiskit imports for toy example########
from qiskit_aer import AerSimulator
from qiskit_aer.noise import (NoiseModel, QuantumError, ReadoutError,
    pauli_error, depolarizing_error, thermal_relaxation_error)
##############################################
    
from copy import deepcopy

device_list = [torch.device("cuda:" + str(i)) for i in range(torch.cuda.device_count())]
n_devs = len(device_list)


# Set random seeds
torch.manual_seed(42)
np.random.seed(42)

X, y = make_moons(n_samples=200, noise=0.1)
y_ = torch.unsqueeze(torch.tensor(y), 1)  # used for one-hot encoded labels
y_hot = torch.scatter(torch.zeros((200, 2)), 1, y_, 1)

c = ["#1f77b4" if y_ == 0 else "#ff7f0e" for y_ in y]  # colours for each class
# plt.axis("off")
# plt.scatter(X[:, 0], X[:, 1], c=c)
# plt.show()


n_qubits = 2
dev = qml.device("default.qubit", wires=n_qubits)

#@qml.qnode(dev)
def qcircuit(inputs, weights):
    qml.AngleEmbedding(inputs, wires=range(n_qubits))
    qml.BasicEntanglerLayers(weights, wires=range(n_qubits))
    return [qml.expval(qml.PauliZ(wires=i)) for i in range(n_qubits)]


n_layers = 6
weight_shapes = {"weights": (n_layers, n_qubits)}





X = torch.tensor(X, requires_grad=True).float()
y_hot = y_hot.float()

batch_size = 5
batches = 200 // batch_size

data_loader = torch.utils.data.DataLoader(
    list(zip(X, y_hot)), batch_size=5, shuffle=True, drop_last=True
)


class HybridModel(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.clayer_1 = nn.Linear(2, 4)
        self.dev = qml.device('qiskit.aer', wires=n_qubits, noise_model=None, method="statevector_gpu", analytic=True)
        self.dev.backend.set_options(blocking_enable=True)
        self.dev.backend.set_options(shot_branching_enable=True)
        self.dev.backend.set_options(shot_branching_sampling_enable=True)
        self.dev.backend.set_options(num_threads_per_device=512)
        new_noise_model = NoiseModel()
        error_prob = np.random.uniform(0, 1)
        noise_prob = depolarizing_error(error_prob, 1)
        new_noise_model.add_all_qubit_quantum_error(noise_prob, ['rx'])
        self.dev.backend.set_options(noise_model=new_noise_model)
        self.node = qml.QNode(qcircuit, self.dev)
        self.qlayer_1 = qml.qnn.TorchLayer(self.node, weight_shapes)
        self.qlayer_2 = qml.qnn.TorchLayer(self.node, weight_shapes)
        self.clayer_2 = nn.Linear(4, 2)
        self.softmax = nn.Softmax(dim=1)
        self.device = None
    
    def set_device(self, device):
        self.device = device

    def forward(self, x):
        x = self.clayer_1(x)
        x_1, x_2 = torch.split(x, 2, dim=1)
        x_1 = self.qlayer_1(x_1)
        x_2 = self.qlayer_2(x_2)
        x = torch.cat([x_1, x_2], axis=1)
        x = x.to(self.device)
        x = self.clayer_2(x)
        return self.softmax(x)

model = HybridModel().to(device_list[0])


opt = torch.optim.SGD(model.parameters(), lr=0.2)
loss = torch.nn.L1Loss()
epochs = 6

for epoch in range(epochs):

    running_loss = 0

    for xs, ys in data_loader:
        ys = ys.to(device_list[0])
        opt.zero_grad()
        replicas = nn.parallel.replicate(model, device_list)
        for i in range(n_devs):
            replicas[i] = replicas[i].to(device_list[i])
            replicas[i].set_device(device_list[i])
        xs_clones = [xs.clone() for i in range(n_devs)]
        xs_series = torch.stack(xs_clones)
        xs_scattered = list(nn.parallel.scatter(xs_series, device_list))
        for i in range(n_devs):
            xs_scattered[i] = xs_scattered[i].view(xs.shape)
        results = nn.parallel.parallel_apply(replicas, xs_scattered)
        xs_out = nn.parallel.gather(results, device_list[0])
        loss_evaluated = loss(torch.sum(nn.parallel.gather(results, device_list[0])) / n_devs, ys)
        loss_evaluated.backward()
        opt.step()
        running_loss += loss_evaluated

    avg_loss = running_loss / batches
    print("Average loss over epoch {}: {:.4f}".format(epoch + 1, avg_loss))

y_pred = model(X)
predictions = torch.argmax(y_pred, axis=1).detach().numpy()

correct = [1 if p == p_true else 0 for p, p_true in zip(predictions, y)]
accuracy = sum(correct) / len(correct)
print(f"Accuracy: {accuracy * 100}%")

When I run it, it produces the same error as before:

Simulation failed and returned the following error message:
ERROR: Failed to load circuits: Duplicate key "_method_" in save instruction.
Traceback (most recent call last):
  File "/N/slate/justsing/CausalTensornet/pennylane_qiskit_toy_example2.py", line 118, in <module>
    results = nn.parallel.parallel_apply(replicas, xs_scattered)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/geode2/home/u040/justsing/Quartz/QCF_Experiment/lib64/python3.11/site-packages/torch/nn/parallel/parallel_apply.py", line 108, in parallel_apply
    output.reraise()
  File "/geode2/home/u040/justsing/Quartz/QCF_Experiment/lib64/python3.11/site-packages/torch/_utils.py", line 706, in reraise
    raise exception
qiskit.exceptions.QiskitError: 'Caught QiskitError in replica 3 on device 0.\nOriginal Traceback (most recent call last):\n  File "/geode2/home/u040/justsing/Quartz/QCF_Experiment/lib64/python3.11/site-packages/torch/nn/parallel/parallel_apply.py", line 83, in _worker\n    output = module(*input, **kwargs)\n             ^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/geode2/home/u040/justsing/Quartz/QCF_Experiment/lib64/python3.11/site-packages/torch/nn/modules/module.py", line 1553, in _wrapped_call_impl\n    return self._call_impl(*args, **kwargs)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/geode2/home/u040/justsing/Quartz/QCF_Experiment/lib64/python3.11/site-packages/torch/nn/modules/module.py", line 1562, in _call_impl\n    return forward_call(*args, **kwargs)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/N/slate/justsing/CausalTensornet/pennylane_qiskit_toy_example2.py", line 88, in forward\n    x_1 = self.qlayer_1(x_1)\n          ^^^^^^^^^^^^^^^^^^\n  File "/geode2/home/u040/justsing/Quartz/QCF_Experiment/lib64/python3.11/site-packages/torch/nn/modules/module.py", line 1553, in _wrapped_call_impl\n    return self._call_impl(*args, **kwargs)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/geode2/home/u040/justsing/Quartz/QCF_Experiment/lib64/python3.11/site-packages/torch/nn/modules/module.py", line 1562, in _call_impl\n    return forward_call(*args, **kwargs)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/geode2/home/u040/justsing/Quartz/QCF_Experiment/lib64/python3.11/site-packages/pennylane/qnn/torch.py", line 404, in forward\n    results = self._evaluate_qnode(inputs)\n              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/geode2/home/u040/justsing/Quartz/QCF_Experiment/lib64/python3.11/site-packages/pennylane/qnn/torch.py", line 430, in _evaluate_qnode\n    res = self.qnode(**kwargs)\n          ^^^^^^^^^^^^^^^^^^^^\n  File "/geode2/home/u040/justsing/Quartz/QCF_Experiment/lib64/python3.11/site-packages/pennylane/workflow/qnode.py", line 1164, in __call__\n    return self._impl_call(*args, **kwargs)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/geode2/home/u040/justsing/Quartz/QCF_Experiment/lib64/python3.11/site-packages/pennylane/workflow/qnode.py", line 1150, in _impl_call\n    res = self._execution_component(args, kwargs, override_shots=override_shots)\n          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/geode2/home/u040/justsing/Quartz/QCF_Experiment/lib64/python3.11/site-packages/pennylane/workflow/qnode.py", line 1103, in _execution_component\n    res = qml.execute(\n          ^^^^^^^^^^^^\n  File "/geode2/home/u040/justsing/Quartz/QCF_Experiment/lib64/python3.11/site-packages/pennylane/workflow/execution.py", line 835, in execute\n    results = ml_boundary_execute(tapes, execute_fn, jpc, device=device)\n              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/geode2/home/u040/justsing/Quartz/QCF_Experiment/lib64/python3.11/site-packages/pennylane/workflow/interfaces/torch.py", line 236, in execute\n    return ExecuteTapes.apply(kwargs, *parameters)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/geode2/home/u040/justsing/Quartz/QCF_Experiment/lib64/python3.11/site-packages/pennylane/workflow/interfaces/torch.py", line 89, in new_apply\n    flat_out = orig_apply(out_struct_holder, *inp)\n               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/geode2/home/u040/justsing/Quartz/QCF_Experiment/lib64/python3.11/site-packages/torch/autograd/function.py", line 574, in apply\n    return super().apply(*args, **kwargs)  # type: ignore[misc]\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/geode2/home/u040/justsing/Quartz/QCF_Experiment/lib64/python3.11/site-packages/pennylane/workflow/interfaces/torch.py", line 93, in new_forward\n    out = orig_fw(ctx, *inp)\n          ^^^^^^^^^^^^^^^^^^\n  File "/geode2/home/u040/justsing/Quartz/QCF_Experiment/lib64/python3.11/site-packages/pennylane/workflow/interfaces/torch.py", line 158, in forward\n    res = tuple(kwargs["execute_fn"](ctx.tapes))\n                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/geode2/home/u040/justsing/Quartz/QCF_Experiment/lib64/python3.11/site-packages/pennylane/workflow/execution.py", line 316, in inner_execute\n    results = device_execution(transformed_tapes)\n              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/usr/lib64/python3.11/contextlib.py", line 81, in inner\n    return func(*args, **kwds)\n           ^^^^^^^^^^^^^^^^^^^\n  File "/geode2/home/u040/justsing/Quartz/QCF_Experiment/lib64/python3.11/site-packages/pennylane_qiskit/qiskit_device.py", line 519, in batch_execute\n    self._state = self._get_state(result, experiment=circuit_obj)\n                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/geode2/home/u040/justsing/Quartz/QCF_Experiment/lib64/python3.11/site-packages/pennylane_qiskit/qiskit_device.py", line 416, in _get_state\n    state = np.asarray(result.get_statevector(experiment))\n                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/geode2/home/u040/justsing/Quartz/QCF_Experiment/lib64/python3.11/site-packages/qiskit/result/result.py", line 314, in get_statevector\n    self.data(experiment)["statevector"], decimals=decimals\n    ^^^^^^^^^^^^^^^^^^^^^\n  File "/geode2/home/u040/justsing/Quartz/QCF_Experiment/lib64/python3.11/site-packages/qiskit/result/result.py", line 187, in data\n    return self._get_experiment(experiment).data.to_dict()\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/geode2/home/u040/justsing/Quartz/QCF_Experiment/lib64/python3.11/site-packages/qiskit/result/result.py", line 380, in _get_experiment\n    raise QiskitError(\'Data for experiment "%s" could not be found.\' % key)\nqiskit.exceptions.QiskitError: \'Data for experiment "circ0" could not be found.\'\n'

Do you have any suggestions for how I might be able to address this issue? I hypothesize that this may be a consequence of a compatibility issue resulting from the major overhaul that was done on Qiskit recently. If so, is there a stopgap measure that I can use until pennylane-qiskit is fully update to accommodate the new Qiskit implementation?

Hi @justin6626 ,

I simplified your code a bit more and noticed a couple of things. On one hand it seems that method="statevector_gpu" is not a valid option so I changed it to method="statevector".

On the other hand it looks like the problem isn’t caused by Qiskit but by your use of the CUDA devices.

If you run the code below you’ll notice that it runs fast with default qubit, slow with qiskit.aer, and impossibly slow when you try to add the Qiskit noise model.


import torch
import torch.nn as nn
import matplotlib.pyplot as plt
import numpy as np
from sklearn.datasets import make_moons
import pennylane as qml
########Qiskit imports for toy example########
from qiskit_aer import AerSimulator
from qiskit_aer.noise import (NoiseModel, QuantumError, ReadoutError,
    pauli_error, depolarizing_error, thermal_relaxation_error)
##############################################


# Set random seeds
torch.manual_seed(42)
np.random.seed(42)

X, y = make_moons(n_samples=200, noise=0.1)
y_ = torch.unsqueeze(torch.tensor(y), 1)  # used for one-hot encoded labels
y_hot = torch.scatter(torch.zeros((200, 2)), 1, y_, 1)

c = ["#1f77b4" if y_ == 0 else "#ff7f0e" for y_ in y]  # colours for each class
# plt.axis("off")
# plt.scatter(X[:, 0], X[:, 1], c=c)
# plt.show()


n_qubits = 2
#dev = qml.device("default.qubit", wires=n_qubits)
dev = qml.device('qiskit.aer', wires=n_qubits)
"""
dev = qml.device('qiskit.aer', wires=n_qubits, noise_model=None, method="statevector", analytic=True)
new_noise_model = NoiseModel()
error_prob = np.random.uniform(0, 1)
noise_prob = depolarizing_error(error_prob, 1)
new_noise_model.add_all_qubit_quantum_error(noise_prob, ['rx'])
dev.backend.set_options(noise_model=new_noise_model)
"""

@qml.qnode(dev) # Uncommented
def qcircuit(inputs, weights):
    qml.AngleEmbedding(inputs, wires=range(n_qubits))
    qml.BasicEntanglerLayers(weights, wires=range(n_qubits))
    return [qml.expval(qml.PauliZ(wires=i)) for i in range(n_qubits)]


n_layers = 4
weight_shapes = {"weights": (n_layers, n_qubits)}

# I simplified this class a lot
class HybridModel(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.clayer_1 = nn.Linear(2, 4)
        self.qlayer_1 = qml.qnn.TorchLayer(qcircuit, weight_shapes)
        self.qlayer_2 = qml.qnn.TorchLayer(qcircuit, weight_shapes)
        self.clayer_2 = nn.Linear(4, 2)
        self.softmax = nn.Softmax(dim=1)
        self.device = None

    def forward(self, x):
        x = self.clayer_1(x)
        x_1, x_2 = torch.split(x, 2, dim=1)
        x_1 = self.qlayer_1(x_1)
        x_2 = self.qlayer_2(x_2)
        x = torch.cat([x_1, x_2], axis=1)
        x = x.to(self.device)
        x = self.clayer_2(x)
        return self.softmax(x)


model = HybridModel()

opt = torch.optim.SGD(model.parameters(), lr=0.2)
loss = torch.nn.L1Loss()


X = torch.tensor(X, requires_grad=True).float()
y_hot = y_hot.float()

batch_size = 5
batches = 200 // batch_size

data_loader = torch.utils.data.DataLoader(
    list(zip(X, y_hot)), batch_size=5, shuffle=True, drop_last=True
)


epochs = 3

for epoch in range(epochs):

    running_loss = 0

    for xs, ys in data_loader:
        ys = ys
        opt.zero_grad()
        loss_evaluated = loss(model(xs), ys) # added
        loss_evaluated.backward()
        opt.step()
        running_loss += loss_evaluated

    avg_loss = running_loss / batches
    print("Average loss over epoch {}: {:.4f}".format(epoch + 1, avg_loss))

y_pred = model(X)
predictions = torch.argmax(y_pred, axis=1).detach().numpy()

correct = [1 if p == p_true else 0 for p, p_true in zip(predictions, y)]
accuracy = sum(correct) / len(correct)
print(f"Accuracy: {accuracy * 100}%")

One alternative is trying to use PennyLane noise models.
You can see examples on how to use them in the latest release blog post and in the docs for qml.noise.

In the example below I added a PennyLane noise model. You’ll notice that it runs slowly but faster than with the Qiskit noise model.


import torch
import torch.nn as nn
import matplotlib.pyplot as plt
import numpy as np
from sklearn.datasets import make_moons
import pennylane as qml
########Qiskit imports for toy example########
from qiskit_aer import AerSimulator
##############################################


# Set random seeds
torch.manual_seed(42)
np.random.seed(42)

X, y = make_moons(n_samples=200, noise=0.1)
y_ = torch.unsqueeze(torch.tensor(y), 1)  # used for one-hot encoded labels
y_hot = torch.scatter(torch.zeros((200, 2)), 1, y_, 1)

c = ["#1f77b4" if y_ == 0 else "#ff7f0e" for y_ in y]  # colours for each class
# plt.axis("off")
# plt.scatter(X[:, 0], X[:, 1], c=c)
# plt.show()


n_qubits = 2
#dev = qml.device("default.qubit", wires=n_qubits)
dev = qml.device('qiskit.aer', wires=n_qubits)

@qml.qnode(dev) # Uncommented
def qcircuit(inputs, weights):
    qml.AngleEmbedding(inputs, wires=range(n_qubits))
    qml.BasicEntanglerLayers(weights, wires=range(n_qubits))
    return [qml.expval(qml.PauliZ(wires=i)) for i in range(n_qubits)]

# -- PennyLane noise model --
@qml.BooleanFn
def c0(op):
    return isinstance(op, qml.RY) and op.parameters[0] < 0.5

def n0(op, **kwargs):
    qml.RY(op.parameters[0] * 0.05, wires=op.wires)

c1 = qml.noise.op_eq(qml.X) & qml.noise.wires_in([0, 1])
n1 = qml.noise.partial_wires(qml.AmplitudeDamping, 0.4)

noise_model = qml.NoiseModel({c0: n0, c1: n1})

qcircuit = qml.add_noise(qcircuit, noise_model)

# -------------------

n_layers = 4
weight_shapes = {"weights": (n_layers, n_qubits)}

# I simplified this class a lot
class HybridModel(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.clayer_1 = nn.Linear(2, 4)
        self.qlayer_1 = qml.qnn.TorchLayer(qcircuit, weight_shapes)
        self.qlayer_2 = qml.qnn.TorchLayer(qcircuit, weight_shapes)
        self.clayer_2 = nn.Linear(4, 2)
        self.softmax = nn.Softmax(dim=1)
        self.device = None

    def forward(self, x):
        x = self.clayer_1(x)
        x_1, x_2 = torch.split(x, 2, dim=1)
        x_1 = self.qlayer_1(x_1)
        x_2 = self.qlayer_2(x_2)
        x = torch.cat([x_1, x_2], axis=1)
        x = x.to(self.device)
        x = self.clayer_2(x)
        return self.softmax(x)


model = HybridModel()

opt = torch.optim.SGD(model.parameters(), lr=0.2)
loss = torch.nn.L1Loss()


X = torch.tensor(X, requires_grad=True).float()
y_hot = y_hot.float()

batch_size = 5
batches = 200 // batch_size

data_loader = torch.utils.data.DataLoader(
    list(zip(X, y_hot)), batch_size=5, shuffle=True, drop_last=True
)


epochs = 3

for epoch in range(epochs):

    running_loss = 0

    for xs, ys in data_loader:
        ys = ys
        opt.zero_grad()
        loss_evaluated = loss(model(xs), ys) # added
        loss_evaluated.backward()
        opt.step()
        running_loss += loss_evaluated

    avg_loss = running_loss / batches
    print("Average loss over epoch {}: {:.4f}".format(epoch + 1, avg_loss))

y_pred = model(X)
predictions = torch.argmax(y_pred, axis=1).detach().numpy()

correct = [1 if p == p_true else 0 for p, p_true in zip(predictions, y)]
accuracy = sum(correct) / len(correct)
print(f"Accuracy: {accuracy * 100}%")

I hope this helps you!

Thank you very much, this helps a lot! Are noise models compatible with all built-in Pennylane devices, including those in the lightning category?

They should indeed be compatible @justin6626 . Let us know if you run into any issues though! Since it’s a very new feature there may be some friction points at the beginning so it helps a lot if you let us know of any issues you may have while using it.

Will do, thank you very much!

1 Like

I tried the Pennylane NoiseModel with the lightning.kokkos and it works great! In machine learning applications I recommend using the adjoint differentiation method for best results. :smiley:

1 Like

That’s great to hear @justin6626 ! Thanks for posting this message :raised_hands: