I am trying to make an encoder that embeds classical data into relative phases of computational basis states (Phase Encoding). I finished coding the circuitry, I just do not know how to format it so that it will work with batches of input. I tried returning a list of tapes when implementing it into my broader machine learning model, but this was unsuccessful. Is there a recommended way to go about this? I am exporting the circuit as a TorchLayer in my actual code.
import pennylane as qml
from pennylane.operation import Operation, AnyWires
import torch
import math
import numpy as np
class PhaseEncoder(Operation):
num_params = 0
num_wires = AnyWires
par_domain = None
def __init__(self, wires=None):
super().__init__(wires=wires)
self.inputs = None
# Not sure of the best way to pass non-trainable params, so this was what I came up with
def set_inputs(self, inputs, max, num_wires):
inputs = inputs*2*np.pi/max
self.inputs = inputs.detach().cpu().numpy()
self.num_wires = num_wires
def expand(self):
if self.inputs is None:
raise ValueError("No inputs given to phase encoder")
with qml.tape.QuantumTape() as tape:
# Phase shift will target last qubit so only two X gates are required
first_target_wire_flip = False
second_target_wire_flip = False
for i in range(self.num_wires):
qml.Hadamard(wires=i)
# Iterate through the inputs
for count, i in enumerate(self.inputs):
binary_string = format(count, f'0{self.num_wires}b')
binary_list = [int(bit) for bit in binary_string]
if any(x > 0 for x in self.inputs[:2**(self.num_wires-1)]) and first_target_wire_flip == False:
first_target_wire_flip = True
qml.PauliX(wires=self.num_wires-1)
if count >= 2**(self.num_wires-1) and second_target_wire_flip==False:
second_target_wire_flip = True
qml.PauliX(wires=self.num_wires-1)
# Apply nothing if the input is 0
if i == 0:
continue
qml.ctrl(op=qml.PhaseShift(phi=i,wires=self.num_wires-1), control=[_ for _ in range(0, self.num_wires-1)],control_values=binary_list[::-1][:-1])
return tape
######
#Test#
######
scale_to = 13
x = torch.tensor([0,0,3,4,0,1,3,3,0,1])
num_wires = math.ceil((math.log2(x.shape[-1])))
dev = qml.device('default.qubit', wires=num_wires)
@qml.qnode(dev)
def circuit():
PhaseEncoder(wires=dev.wires).set_inputs(x,scale_to,num_wires)
return [qml.expval(qml.PauliZ(wire)) for wire in dev.wires]
drawer = qml.draw(circuit, expansion_strategy="device")
print(drawer())
Version Information:
Platform info: Linux-5.15.90.1-microsoft-standard-WSL2-x86_64-with-glibc2.35
Python version: 3.9.0
Numpy version: 1.23.5
Scipy version: 1.9.1
Installed devices:
- default.gaussian (PennyLane-0.31.1)
- default.mixed (PennyLane-0.31.1)
- default.qubit (PennyLane-0.31.1)
- default.qubit.autograd (PennyLane-0.31.1)
- default.qubit.jax (PennyLane-0.31.1)
- default.qubit.tf (PennyLane-0.31.1)
- default.qubit.torch (PennyLane-0.31.1)
- default.qutrit (PennyLane-0.31.1)
- null.qubit (PennyLane-0.31.1)
- lightning.qubit (PennyLane-Lightning-0.31.0)
- qiskit.aer (PennyLane-qiskit-0.30.1)
- qiskit.basicaer (PennyLane-qiskit-0.30.1)
- qiskit.ibmq (PennyLane-qiskit-0.30.1)
- qiskit.ibmq.circuit_runner (PennyLane-qiskit-0.30.1)
- qiskit.ibmq.sampler (PennyLane-qiskit-0.30.1)
- lightning.gpu (PennyLane-Lightning-GPU-0.30.0)
- cirq.mixedsimulator (PennyLane-Cirq-0.29.0)
- cirq.pasqal (PennyLane-Cirq-0.29.0)
- cirq.qsim (PennyLane-Cirq-0.29.0)
- cirq.qsimh (PennyLane-Cirq-0.29.0)
- cirq.simulator (PennyLane-Cirq-0.29.0)