Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ __pycache__/
*.egg-info/
*.eggs
*.DS_Store
/.vscode/*
timer.dat
tmp/*

/build/
/dist/
Expand All @@ -13,4 +16,5 @@ __pycache__/
/docuenv/
/eccvenv/
/python311venv/
/multiverseenv/
/multiverseenv/
/notebooks/
11 changes: 6 additions & 5 deletions src/qrisp/circuit/quantum_circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

TO_GATE_COUNTER = np.zeros(1)


class QuantumCircuit:
"""
This class describes quantum circuits. Many of the attribute and method names are
Expand Down Expand Up @@ -208,7 +209,7 @@ class QuantumCircuit:
clbit_index_counter = np.zeros(1, dtype=int)
xla_mode = 0

def __init__(self, num_qubits=0, num_clbits=0, name=None):
def __init__(self, num_qubits=0, num_clbits=0):
object.__setattr__(self, "data", [])
object.__setattr__(self, "qubits", [])
object.__setattr__(self, "clbits", [])
Expand Down Expand Up @@ -340,7 +341,7 @@ def to_op(self, name=None):
"""

if name is None:
#name = "circuit" + str(id(self))[:5]
# name = "circuit" + str(id(self))[:5]
name = "circuit" + str(int(TO_GATE_COUNTER[0]))[:7].zfill(7)

TO_GATE_COUNTER[0] += 1
Expand Down Expand Up @@ -1073,13 +1074,13 @@ def to_qasm2(self, formatted=False, filename=None, encoding=None):
try:
return qiskit_qc.qasm(formatted, filename, encoding)
except:
from qiskit.qasm2 import dumps, QASM2ExportError
from qiskit.qasm2 import QASM2ExportError, dumps

try:
return dumps(qiskit_qc)
except (QASM2ExportError, TypeError):
from qiskit.qasm3 import dumps
from qiskit import transpile
from qiskit.qasm3 import dumps

transpiled_qiskit_qc = transpile(
qiskit_qc,
Expand Down Expand Up @@ -2328,7 +2329,7 @@ def u3(self, theta, phi, lam, qubits):

"""
self.append(ops.u3Gate(theta, phi, lam), [qubits])

def r(self, phi, theta, qubits):
self.append(ops.RGate(phi, theta), [qubits])

Expand Down
207 changes: 100 additions & 107 deletions src/qrisp/interface/converter/pennylane_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,105 +19,120 @@
import types

import numpy as np
import pennylane as qml
import sympy

from qrisp.circuit import ControlledOperation
from qrisp.circuit import ControlledOperation, QuantumCircuit, Operation

"""
TODO:
TODO:

-- Change the structure of the mapping
-- Update mapping with more gates (ideally all the currently supported Qrisp gates)
-- Add support for all gates in the mapping
-- Polish the code with pylint/black standards
-- Improve efficiency if possible

-- clbit interaction with measurements
-- conditional environment, i.e. mid-circuit measurement is possible, have to create this
--> otherwise no clbit interaction?

BUGS:

"""


def create_qml_instruction(op):
import pennylane as qml

# create basic, straight forward instructions
if op.name == "rxx":
qml_ins = qml.IsingXX
elif op.name == "ryy":
qml_ins = qml.IsingYY
elif op.name == "rzz":
qml_ins = qml.IsingZZ

elif op.name == "p":
qml_ins = qml.RZ
elif op.name == "rx":
qml_ins = qml.RX
elif op.name == "ry":
qml_ins = qml.RY
elif op.name == "rz":
qml_ins = qml.RZ
elif op.name == "u1":
qml_ins = qml.U1

elif op.name == "u3":
qml_ins = qml.U3
elif op.name == "xxyy":
qml_ins = qml.IsingXY
elif op.name == "swap":
qml_ins = qml.SWAP
elif op.name == "h":
qml_ins = qml.Hadamard
elif op.name == "x":
qml_ins = qml.PauliX
elif op.name == "y":
qml_ins = qml.PauliY
elif op.name == "z":
qml_ins = qml.PauliZ
elif op.name == "s" or op.name == "s_dg":
qml_ins = qml.S
elif op.name == "t" or op.name == "t_dg":
qml_ins = qml.T
elif op.name == "sx" or op.name == "sx_dg":
qml_ins = qml.SX
# Mapping from Qrisp operation names to PennyLane gates
mapping = {
"rxx": qml.IsingXX,
"ryy": qml.IsingYY,
"rzz": qml.IsingZZ,
"p": qml.RZ,
"rx": qml.RX,
"ry": qml.RY,
"rz": qml.RZ,
"u1": qml.U1,
"u3": qml.U3,
"xxyy": qml.IsingXY,
"swap": qml.SWAP,
"h": qml.Hadamard,
"x": qml.PauliX,
"y": qml.PauliY,
"z": qml.PauliZ,
"sx": qml.SX,
"sx_dg": qml.SX,
"s": qml.S,
"s_dg": qml.S,
"t": qml.T,
"t_dg": qml.T,
"cx": qml.CNOT,
"cy": qml.CY,
"cz": qml.CZ,
"cp": qml.ControlledPhaseShift,
}


def _create_qml_instruction(op: Operation) -> types.FunctionType:
"""Create a PennyLane instruction from a Qrisp operation."""

if op.name in mapping:
return mapping[op.name]

# complex gate, with subcircuit definition we create a representative subcircuit
elif op.definition:
circ = qml_converter(op.definition, circ=True)
return circ
else:
Exception("Cannot convert")
return qml_ins
if op.definition is not None:
return qml_converter(op.definition, circ=True)

raise NotImplementedError(
f"Operation {op.name} not implemented in PennyLane converter."
)

import sympy

def qml_converter(qc: QuantumCircuit, circ: bool = False) -> types.FunctionType:
"""
Convert a Qrisp quantum circuit to a PennyLane quantum function.

def qml_converter(qc, circ=False):
import pennylane as qml
Parameters
----------
qc : QuantumCircuit
The Qrisp quantum circuit to be converted.
circ : bool, optional
If True, the returned function is a PennyLane quantum function. Default is False.

def circuit(*args, wires=None):
Returns
-------
types.FunctionType
A PennyLane quantum function representing the Qrisp circuit.

"""

circFlag = False
def circuit(*args, wires=None):

circ_flag = False
qbit_dic = {}
# add qubits

# Map Qrisp qubit identifiers to PennyLane wires
if wires is None:
for qubit in qc.qubits:
qbit_dic[qubit.identifier] = qubit.identifier
else:
for i in range(len(qc.qubits)):
qbit_dic[qc.qubits[i].identifier] = wires[i]
for i, qubit in enumerate(qc.qubits):
qbit_dic[qubit.identifier] = wires[i]

# save measurements
measurelist = []
symbols = list(qc.abstract_params)

for index in range(len(qc.data)):
for idx, data in enumerate(qc.data):

op = qc.data[index].op
op = data.op
params = list(op.params)
for i in range(len(params)):
if isinstance(params[i], sympy.Symbol):
params[i] = args[symbols.index(params[i])]

qubit_list = [qbit_dic[qubit.identifier] for qubit in qc.data[index].qubits]
op_qubits = qc.data[index].qubits
# If the operation has symbolic parameters, substitute them with the provided arguments.
# Otherwise, convert them to float64 (with float128 there is a bug in PennyLane)
for i, param in enumerate(params):
if isinstance(param, sympy.Symbol):
params[i] = args[symbols.index(param)]
else:
params[i] = np.float64(param)

qubit_list = [qbit_dic[qubit.identifier] for qubit in data.qubits]
op_qubits = data.qubits

if op.name in ["qb_alloc", "qb_dealloc"]:
continue
Expand All @@ -137,41 +152,19 @@ def circuit(*args, wires=None):
# deffed as RXXYY with angle pi
params = [np.pi]

if op.name == "cx":
# maybe adjustment necessary here
if hasattr(op, "ctrl_state"):
qml_ins = qml.CNOT([qubit_list[0], qubit_list[1]])
else:
qml_ins = qml.CNOT([qubit_list[0], qubit_list[1]])

elif op.name == "cy":
if hasattr(op, "ctrl_state"):
qml_ins = qml.CY([qubit_list[0], qubit_list[1]])
else:
qml_ins = qml.CY([qubit_list[0], qubit_list[1]])

elif op.name == "cz":
if hasattr(op, "ctrl_state"):
qml_ins = qml.CZ([qubit_list[0], qubit_list[1]])
else:
qml_ins = qml.CZ([qubit_list[0], qubit_list[1]])
# TODO: this can certainly be optimized/simplified
if op.name in ("cx", "cy", "cz"):
qml_ins = mapping[op.name](wires=qubit_list[:2])

elif op.name == "cp":
if hasattr(op, "ctrl_state"):
qml_ins = qml.ControlledPhaseShift(
*params, [qubit_list[0], qubit_list[1]]
)
else:
qml_ins = qml.ControlledPhaseShift(
*params, [qubit_list[0], qubit_list[1]]
)
qml_ins = mapping[op.name](params[0], wires=qubit_list[:2])

# return the controlled instruction or the nested circuit
elif isinstance(op, ControlledOperation):
qml_trafo = create_qml_instruction(op)
qml_trafo = _create_qml_instruction(op)
if isinstance(qml_trafo, types.FunctionType):
circFlag = True
wire_map = dict()
circ_flag = True
wire_map = {}
for i in range(len(op_qubits)):
# wire_map[i] = op_qubits[i].identifier
wire_map[op.definition.qubits[i].identifier] = op_qubits[
Expand All @@ -186,15 +179,15 @@ def circuit(*args, wires=None):

# return the instruction or the nested circuit
else:
qml_trafo = create_qml_instruction(op)
qml_trafo = _create_qml_instruction(op)
if isinstance(qml_trafo, types.FunctionType):
circFlag = True
wire_map = dict()
for i in range(len(op_qubits)):
circ_flag = True
wire_map = {}
for idx, qubit in enumerate(op_qubits):
# map the wires as given for the nested circuit
wire_map[op.definition.qubits[i].identifier] = op_qubits[
i
].identifier
wire_map[op.definition.qubits[idx].identifier] = (
qubit.identifier
)
mapped_quantum_function = qml.map_wires(qml_trafo, wire_map)

else:
Expand All @@ -205,13 +198,13 @@ def circuit(*args, wires=None):

if not isinstance(circ, bool):
circ()
elif not circFlag:
elif not circ_flag:
qml_ins
else:
mapped_quantum_function()

# add measurements at the end, i.e. return a count object
if len(measurelist):
if len(measurelist) > 0:
return qml.counts(wires=measurelist)

return circuit
Loading