From d2e31f3044d74b5e74bd3c5800101b6b9eed6396 Mon Sep 17 00:00:00 2001 From: WrathfulSpatula Date: Fri, 31 May 2024 11:05:50 -0400 Subject: [PATCH 01/27] Debug controlled and ISWAP gates --- pennylane_qrack/qrack_device.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pennylane_qrack/qrack_device.cpp b/pennylane_qrack/qrack_device.cpp index 5417f74..9d2b35c 100644 --- a/pennylane_qrack/qrack_device.cpp +++ b/pennylane_qrack/qrack_device.cpp @@ -121,7 +121,11 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { if (wires.size() != 2U) { throw std::invalid_argument("ISWAP must have exactly two target qubits!"); } - qsim->ISwap(wires[0U], wires[1U]); + if (inverse) { + qsim->ISwap(wires[0U], wires[1U]); + } else { + qsim->IISwap(wires[0U], wires[1U]); + } } else if (name == "PSWAP") { if (wires.size() != 2U) { throw std::invalid_argument("PSWAP must have exactly two target qubits!"); @@ -310,9 +314,9 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { } std::vector mcp_wires(control_wires); mcp_wires.push_back(wires[0U]); - qsim->MCPhase(mcp_wires, Qrack::I_CMPLX, Qrack::ONE_CMPLX, wires[1U]); + qsim->MCPhase(mcp_wires, inverse ? -Qrack::I_CMPLX : Qrack::I_CMPLX, Qrack::ONE_CMPLX, wires[1U]); qsim->CSwap(control_wires, wires[0U], wires[1U]); - qsim->MCPhase(mcp_wires, Qrack::I_CMPLX, Qrack::ONE_CMPLX, wires[1U]); + qsim->MCPhase(mcp_wires, inverse ? -Qrack::I_CMPLX : Qrack::I_CMPLX, Qrack::ONE_CMPLX, wires[1U]); for (bitLenInt i = 0U; i < control_wires.size(); ++i) { if (!control_values[i]) { qsim->X(control_wires[i]); @@ -697,6 +701,7 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { || (name == "CZ") || (name == "CSWAP") || (name == "ControlledPhaseShift") + || (name == "CPhase") || (name == "CRX") || (name == "CRY") || (name == "CRZ") From 4c5f6f98e45f7b8a3158ab0754e02ec4b0f1014d Mon Sep 17 00:00:00 2001 From: WrathfulSpatula Date: Fri, 31 May 2024 12:30:01 -0400 Subject: [PATCH 02/27] Debugging and simplifying phase gates --- pennylane_qrack/_version.py | 2 +- pennylane_qrack/qrack_device.cpp | 34 ++------------------------------ 2 files changed, 3 insertions(+), 33 deletions(-) diff --git a/pennylane_qrack/_version.py b/pennylane_qrack/_version.py index a4d0467..b88df1e 100644 --- a/pennylane_qrack/_version.py +++ b/pennylane_qrack/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.6.3" +__version__ = "0.6.4" diff --git a/pennylane_qrack/qrack_device.cpp b/pennylane_qrack/qrack_device.cpp index 9d2b35c..077e2f2 100644 --- a/pennylane_qrack/qrack_device.cpp +++ b/pennylane_qrack/qrack_device.cpp @@ -134,14 +134,7 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { qsim->CU(c, wires[1U], ZERO_R1, ZERO_R1, inverse ? -params[0U] : params[0U]); qsim->Swap(wires[0U], wires[1U]); qsim->CU(c, wires[1U], ZERO_R1, ZERO_R1, inverse ? -params[0U] : params[0U]); - } else if (name == "PhaseShift") { - const Qrack::real1 cosine = (Qrack::real1)cos(inverse ? -params[0U] : params[0U]); - const Qrack::real1 sine = (Qrack::real1)sin(inverse ? -params[0U] : params[0U]); - const Qrack::complex bottomRight(cosine, sine); - for (const bitLenInt& target : wires) { - qsim->Phase(Qrack::ONE_CMPLX, bottomRight, target); - } - } else if (name == "PhaseShift") { + } else if ((name == "PhaseShift") || (name == "U1")) { const Qrack::real1 cosine = (Qrack::real1)cos(inverse ? -params[0U] : params[0U]); const Qrack::real1 sine = (Qrack::real1)sin(inverse ? -params[0U] : params[0U]); const Qrack::complex bottomRight(cosine, sine); @@ -193,14 +186,6 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { qsim->U(target, Qrack::PI_R1 / 2, params[0U], params[1U]); } } - } else if (name == "U1") { - for (const bitLenInt& target : wires) { - if (inverse) { - qsim->U(target, ZERO_R1, ZERO_R1, -params[0U]); - } else { - qsim->U(target, ZERO_R1, ZERO_R1, params[0U]); - } - } } else if (name == "QFT") { const size_t maxLcv = wires.size() >> 1U; const size_t end = wires.size() - 1U; @@ -322,7 +307,7 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { qsim->X(control_wires[i]); } } - } else if ((name == "PhaseShift") || (name == "ControlledPhaseShift") || (name == "CPhase")) { + } else if ((name == "PhaseShift") || (name == "U1") || (name == "ControlledPhaseShift") || (name == "CPhase")) { const Qrack::real1 cosine = (Qrack::real1)cos(inverse ? -params[0U] : params[0U]); const Qrack::real1 sine = (Qrack::real1)sin(inverse ? -params[0U] : params[0U]); const Qrack::complex bottomRight(cosine, sine); @@ -408,21 +393,6 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { for (const bitLenInt& target : wires) { qsim->UCMtrx(control_wires, mtrx, target, controlPerm); } - } else if (name == "U1") { - const Qrack::real1 theta = ZERO_R1; - const Qrack::real1 phi = ZERO_R1; - const Qrack::real1 lambda = inverse ? -params[0U] : params[0U]; - const Qrack::real1 cos0 = (Qrack::real1)cos(theta / 2); - const Qrack::real1 sin0 = (Qrack::real1)sin(theta / 2); - const Qrack::complex mtrx[4U]{ - Qrack::complex(cos0, ZERO_R1), sin0 * Qrack::complex((Qrack::real1)(-cos(lambda)), - (Qrack::real1)(-sin(lambda))), - sin0 * Qrack::complex((Qrack::real1)cos(phi), (Qrack::real1)sin(phi)), - cos0 * Qrack::complex((Qrack::real1)cos(phi + lambda), (Qrack::real1)sin(phi + lambda)) - }; - for (const bitLenInt& target : wires) { - qsim->UCMtrx(control_wires, mtrx, target, controlPerm); - } } else if (name != "Identity") { throw std::domain_error("Unrecognized gate name: " + name); } From 738ca3eb627eb192b76a43f273280d28f2adc4e1 Mon Sep 17 00:00:00 2001 From: WrathfulSpatula Date: Fri, 31 May 2024 12:41:11 -0400 Subject: [PATCH 03/27] Simplify Catalyst control handling --- pennylane_qrack/qrack_device.cpp | 35 ++++++++++++++------------------ 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/pennylane_qrack/qrack_device.cpp b/pennylane_qrack/qrack_device.cpp index 077e2f2..653d3fd 100644 --- a/pennylane_qrack/qrack_device.cpp +++ b/pennylane_qrack/qrack_device.cpp @@ -653,33 +653,28 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { auto &&dev_wires = getDeviceWires(wires); auto &&dev_controlled_wires = getDeviceWires(controlled_wires); std::vector dev_controlled_values(controlled_values); - if (name == "MultiControlledX") { - while (wires.size() > 1U) { - dev_controlled_wires.push_back(dev_wires[0]); - dev_controlled_values.push_back(true); - dev_wires.erase(dev_wires.begin()); - } - } else if (name == "Toffoli") { - dev_controlled_wires.push_back(dev_wires[0]); - dev_controlled_values.push_back(true); - dev_wires.erase(dev_wires.begin()); - dev_controlled_wires.push_back(dev_wires[0]); - dev_controlled_values.push_back(true); - dev_wires.erase(dev_wires.begin()); - } else if ((name == "CNOT") + if ((name == "MultiControlledX") + || (name == "CNOT") || (name == "CY") || (name == "CZ") - || (name == "CSWAP") || (name == "ControlledPhaseShift") || (name == "CPhase") || (name == "CRX") || (name == "CRY") || (name == "CRZ") - || (name == "CRot")) - { - dev_controlled_wires.push_back(dev_wires[0]); - dev_controlled_values.push_back(true); - dev_wires.erase(dev_wires.begin()); + || (name == "CRot") + || (name == "Toffoli")) { + while (dev_wires.size() > 1U) { + dev_controlled_wires.push_back(dev_wires[0]); + dev_controlled_values.push_back(true); + dev_wires.erase(dev_wires.begin()); + } + } else if (name == "CSWAP") { + while (dev_wires.size() > 2U) { + dev_controlled_wires.push_back(dev_wires[0]); + dev_controlled_values.push_back(true); + dev_wires.erase(dev_wires.begin()); + } } // Update the state-vector From 9bcdbd1f84fb719d96992767c1224b748d535e71 Mon Sep 17 00:00:00 2001 From: WrathfulSpatula Date: Fri, 31 May 2024 13:10:58 -0400 Subject: [PATCH 04/27] Debug and generalize PyQrack back end --- pennylane_qrack/qrack_device.py | 137 +++++++++++++++++--------------- 1 file changed, 72 insertions(+), 65 deletions(-) diff --git a/pennylane_qrack/qrack_device.py b/pennylane_qrack/qrack_device.py index 32d8551..33eabd6 100644 --- a/pennylane_qrack/qrack_device.py +++ b/pennylane_qrack/qrack_device.py @@ -409,84 +409,89 @@ def _apply_gate(self, op): elif opname in ["CZ", "CZ.inv", "C(CZ)", "C(CZ).inv"]: self._state.mcz(device_wires.labels[:-1], device_wires.labels[-1]) elif opname == "S": - self._state.s(device_wires.labels[0]) + for label in device_wires.labels: + self._state.s(label) elif opname == "S.inv": - self._state.adjs(device_wires.labels[0]) + for label in device_wires.labels: + self._state.adjs(label) elif opname == "C(S)": self._state.mcs(device_wires.labels[:-1], device_wires.labels[-1]) elif opname == "C(S).inv": self._state.mcadjs(device_wires.labels[:-1], device_wires.labels[-1]) elif opname == "T": - self._state.t(device_wires.labels[0]) + for label in device_wires.labels: + self._state.t(label) elif opname == "T.inv": - self._state.adjt(device_wires.labels[0]) + for label in device_wires.labels: + self._state.adjt(label) elif opname == "C(T)": self._state.mct(device_wires.labels[:-1], device_wires.labels[-1]) elif opname == "C(T).inv": self._state.mcadjt(device_wires.labels[:-1], device_wires.labels[-1]) elif opname == "RX": - self._state.r(Pauli.PauliX, par[0], device_wires.labels[0]) + for label in device_wires.labels: + self._state.r(Pauli.PauliX, par[0], label) elif opname == "RX.inv": - self._state.r(Pauli.PauliX, -par[0], device_wires.labels[0]) + for label in device_wires.labels: + self._state.r(Pauli.PauliX, -par[0], label) elif opname in ["CRX", "C(RX)", "C(CRX)"]: self._state.mcr(Pauli.PauliX, par[0], device_wires.labels[:-1], device_wires.labels[-1]) elif opname in ["CRX.inv", "C(RX).inv", "C(CRX).inv"]: self._state.mcr(Pauli.PauliX, par[0], device_wires.labels[:-1], device_wires.labels[-1]) elif opname == "RY": - self._state.r(Pauli.PauliY, par[0], device_wires.labels[0]) + for label in device_wires.labels: + self._state.r(Pauli.PauliY, par[0], label) elif opname == "RY.inv": - self._state.r(Pauli.PauliY, -par[0], device_wires.labels[0]) + for label in device_wires.labels: + self._state.r(Pauli.PauliY, -par[0], label) elif opname in ["CRY", "C(RY)", "C(CRY)"]: self._state.mcr(Pauli.PauliY, par[0], device_wires.labels[:-1], device_wires.labels[-1]) elif opname in ["CRY.inv", "C(RY).inv", "C(CRY).inv"]: self._state.mcr(Pauli.PauliY, par[0], device_wires.labels[:-1], device_wires.labels[-1]) elif opname == "RZ": - self._state.r(Pauli.PauliZ, par[0], device_wires.labels[0]) + for label in device_wires.labels: + self._state.r(Pauli.PauliZ, par[0], label) elif opname == "RZ.inv": - self._state.r(Pauli.PauliZ, -par[0], device_wires.labels[0]) + for label in device_wires.labels: + self._state.r(Pauli.PauliZ, -par[0], label) elif opname in ["CRZ", "C(RZ)", "C(CRZ)"]: self._state.mcr(Pauli.PauliZ, par[0], device_wires.labels[:-1], device_wires.labels[-1]) elif opname in ["CRZ.inv", "C(RZ).inv", "C(CRZ).inv"]: self._state.mcr(Pauli.PauliY, par[0], device_wires.labels[:-1], device_wires.labels[-1]) elif opname in ["PauliX", "PauliX.inv"]: - self._state.x(device_wires.labels[0]) + for label in device_wires.labels: + self._state.x(label) elif opname in ["PauliY", "PauliY.inv"]: - self._state.y(device_wires.labels[0]) + for label in device_wires.labels: + self._state.y(label) elif opname in ["PauliZ", "PauliZ.inv"]: - self._state.z(device_wires.labels[0]) + for label in device_wires.labels: + self._state.z(label) elif opname in ["Hadamard", "Hadamard.inv"]: - self._state.h(device_wires.labels[0]) + for label in device_wires.labels: + self._state.h(label) elif opname == "SX": - self._state.mtrx( - [(1 + 1j) / 2, (1 - 1j) / 2, (1 - 1j) / 2, (1 + 1j) / 2], - device_wires.labels[0], - ) + sx_mtrx = [(1 + 1j) / 2, (1 - 1j) / 2, (1 - 1j) / 2, (1 + 1j) / 2] + for label in device_wires.labels: + self._state.mtrx(sx_mtrx, label) elif opname == "SX.inv": - self._state.mtrx( - [(1 - 1j) / 2, (1 + 1j) / 2, (1 + 1j) / 2, (1 - 1j) / 2], - device_wires.labels[0], - ) + isx_mtrx = [(1 - 1j) / 2, (1 + 1j) / 2, (1 + 1j) / 2, (1 - 1j) / 2] + for label in device_wires.labels: + self._state.mtrx(isx_mtrx, label) elif opname == "C(SX)": self._state.mcmtrx( device_wires.labels[:-1], [(1 + 1j) / 2, (1 - 1j) / 2, (1 - 1j) / 2, (1 + 1j) / 2], device_wires.labels[-1], ) - elif opname == "SX.inv": - self._state.mtrx( - [(1 - 1j) / 2, (1 + 1j) / 2, (1 + 1j) / 2, (1 - 1j) / 2], - device_wires.labels[0], - ) - elif opname == "C(SX).inc": - self._state.mcmtrx( - device_wires.labels[:-1], - [(1 - 1j) / 2, (1 + 1j) / 2, (1 + 1j) / 2, (1 - 1j) / 2], - device_wires.labels[-1], - ) elif opname in ["PhaseShift", "U1"]: - self._state.mtrx([1, 0, 0, cmath.exp(1j * par[0])], device_wires.labels[0]) + p_mtrx = [1, 0, 0, cmath.exp(1j * par[0])] + for label in device_wires.labels: + self._state.mtrx(p_mtrx, label) elif opname in ["PhaseShift.inv", "U1.inv"]: - self._state.mtrx([1, 0, 0, cmath.exp(1j * -par[0])], device_wires.labels[0]) + ip_mtrx = [1, 0, 0, cmath.exp(1j * -par[0])] + for label in device_wires.labels: + self._state.mtrx(p_mtrx, label) elif opname in ["C(PhaseShift)", "C(U1)"]: self._state.mtrx( device_wires.labels[:-1], @@ -522,25 +527,23 @@ def _apply_gate(self, op): device_wires.labels[-1], ) elif opname == "U2": - self._state.mtrx( - [ - 1, - cmath.exp(1j * par[1]), - cmath.exp(1j * par[0]), - cmath.exp(1j * (par[0] + par[1])), - ], - device_wires.labels[0], - ) + u2_mtrx = [ + 1, + cmath.exp(1j * par[1]), + cmath.exp(1j * par[0]), + cmath.exp(1j * (par[0] + par[1])), + ] + for label in device_wires.labels: + self._state.mtrx(u2_mtrx, label) elif opname == "U2.inv": - self._state.mtrx( - [ - 1, - cmath.exp(1j * -par[1]), - cmath.exp(1j * -par[0]), - cmath.exp(1j * (-par[0] - par[1])), - ], - device_wires.labels[0], - ) + iu2_mtrx = [ + 1, + cmath.exp(1j * -par[1]), + cmath.exp(1j * -par[0]), + cmath.exp(1j * (-par[0] - par[1])), + ] + for label in device_wires.labels: + self._state.mtrx(iu2_mtrx, label) elif opname == "C(U2)": self._state.mcmtrx( device_wires.labels[:-1], @@ -564,17 +567,21 @@ def _apply_gate(self, op): device_wires.labels[-1], ) elif opname == "U3": - self._state.u(device_wires.labels[-1], par[0], par[1], par[2]) + for label in device_wires.labels: + self._state.u(label, par[0], par[1], par[2]) elif opname == "U3.inv": - self._state.u(device_wires.labels[-1], -par[0], -par[1], -par[2]) + for label in device_wires.labels: + self._state.u(label, -par[0], -par[1], -par[2]) elif opname == "Rot": - self._state.r(Pauli.PauliZ, par[0], device_wires.labels[-1]) - self._state.r(Pauli.PauliY, par[1], device_wires.labels[-1]) - self._state.r(Pauli.PauliZ, par[2], device_wires.labels[-1]) + for label in device_wires.labels: + self._state.r(Pauli.PauliZ, par[0], label) + self._state.r(Pauli.PauliY, par[1], label) + self._state.r(Pauli.PauliZ, par[2], label) elif opname == "Rot.inv": - self._state.r(Pauli.PauliZ, -par[2], device_wires.labels[-1]) - self._state.r(Pauli.PauliY, -par[1], device_wires.labels[-1]) - self._state.r(Pauli.PauliZ, -par[0], device_wires.labels[-1]) + for label in device_wires.labels: + self._state.r(Pauli.PauliZ, -par[2], label) + self._state.r(Pauli.PauliY, -par[1], label) + self._state.r(Pauli.PauliZ, -par[0], label) elif opname == "C(U3)": self._state.mcu( device_wires.labels[:-1], @@ -593,11 +600,11 @@ def _apply_gate(self, op): ) elif opname == "QFT": self._state.qft(device_wires.labels) - for i in range(len(wires) >> 1): - self._state.swap(wires[i], wires[-i]) + for i in range(len(device_wires.labels) >> 1): + self._state.swap(device_wires.labels[i], device_wires.labels[-i]) elif opname == "QFT.inv": - for i in range(len(wires) >> 1): - self._state.swap(wires[i], wires[-i]) + for i in range(len(device_wires.labels) >> 1): + self._state.swap(device_wires.labels[i], device_wires.labels[-i]) self._state.iqft(device_wires.labels) elif opname not in [ "Identity", From de6f5e085eae8ac71b16622017fdb9f3fa17e568 Mon Sep 17 00:00:00 2001 From: WrathfulSpatula Date: Fri, 31 May 2024 13:38:40 -0400 Subject: [PATCH 05/27] Decompose QFT --- pennylane_qrack/QrackDeviceConfig.toml | 2 +- pennylane_qrack/qrack_device.cpp | 14 -------------- pennylane_qrack/qrack_device.py | 9 --------- 3 files changed, 1 insertion(+), 24 deletions(-) diff --git a/pennylane_qrack/QrackDeviceConfig.toml b/pennylane_qrack/QrackDeviceConfig.toml index 24ec83d..ca3a67e 100644 --- a/pennylane_qrack/QrackDeviceConfig.toml +++ b/pennylane_qrack/QrackDeviceConfig.toml @@ -48,7 +48,6 @@ U2 = { properties = [ "controllable", "invertible" ] } U1 = { properties = [ "controllable", "invertible" ] } MultiControlledX = { properties = [ "controllable", "invertible" ] } Identity = { properties = [ "controllable", "invertible" ] } -QFT = { properties = [ "invertible" ] } # Operators that should be decomposed according to the algorithm used # by PennyLane's device API. @@ -79,6 +78,7 @@ QFT = { properties = [ "invertible" ] } # IsingYY = {} # IsingZZ = {} # IsingXY = {} +# QFT = {} # Gates which should be translated to QubitUnitary # [operators.gates.matrix] diff --git a/pennylane_qrack/qrack_device.cpp b/pennylane_qrack/qrack_device.cpp index 653d3fd..73780dd 100644 --- a/pennylane_qrack/qrack_device.cpp +++ b/pennylane_qrack/qrack_device.cpp @@ -186,20 +186,6 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { qsim->U(target, Qrack::PI_R1 / 2, params[0U], params[1U]); } } - } else if (name == "QFT") { - const size_t maxLcv = wires.size() >> 1U; - const size_t end = wires.size() - 1U; - if (inverse) { - for (size_t i = 0U; i < maxLcv; ++i) { - qsim->Swap(wires[i], wires[end - i]); - } - qsim->IQFTR(wires); - } else { - qsim->QFTR(wires); - for (size_t i = 0U; i < maxLcv; ++i) { - qsim->Swap(wires[i], wires[end - i]); - } - } } else if (name != "Identity") { throw std::domain_error("Unrecognized gate name: " + name); } diff --git a/pennylane_qrack/qrack_device.py b/pennylane_qrack/qrack_device.py index 33eabd6..6a058e9 100644 --- a/pennylane_qrack/qrack_device.py +++ b/pennylane_qrack/qrack_device.py @@ -154,7 +154,6 @@ class QrackDevice(QubitDevice): "C(CPhase)", "MultiControlledX", "C(MultiControlledX)", - "QFT", } config = pathlib.Path( @@ -598,14 +597,6 @@ def _apply_gate(self, op): -par[1], -par[2], ) - elif opname == "QFT": - self._state.qft(device_wires.labels) - for i in range(len(device_wires.labels) >> 1): - self._state.swap(device_wires.labels[i], device_wires.labels[-i]) - elif opname == "QFT.inv": - for i in range(len(device_wires.labels) >> 1): - self._state.swap(device_wires.labels[i], device_wires.labels[-i]) - self._state.iqft(device_wires.labels) elif opname not in [ "Identity", "Identity.inv", From dc9d6e474f85bad4fb30cd4e3bab04c5ef76aeb2 Mon Sep 17 00:00:00 2001 From: WrathfulSpatula Date: Fri, 31 May 2024 13:51:26 -0400 Subject: [PATCH 06/27] Debugging ControlledPhaseShift --- pennylane_qrack/qrack_device.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/pennylane_qrack/qrack_device.cpp b/pennylane_qrack/qrack_device.cpp index 73780dd..97742f6 100644 --- a/pennylane_qrack/qrack_device.cpp +++ b/pennylane_qrack/qrack_device.cpp @@ -141,11 +141,6 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { for (const bitLenInt& target : wires) { qsim->Phase(Qrack::ONE_CMPLX, bottomRight, target); } - } else if ((name == "ControlledPhaseShift") || (name == "CPhase")) { - const Qrack::real1 cosine = (Qrack::real1)cos(inverse ? -params[0U] : params[0U]); - const Qrack::real1 sine = (Qrack::real1)sin(inverse ? -params[0U] : params[0U]); - const Qrack::complex bottomRight(cosine, sine); - qsim->MCPhase(std::vector(wires.begin(), wires.end() - 1U), Qrack::ONE_CMPLX, bottomRight, wires.back()); } else if (name == "RX") { for (const bitLenInt& target : wires) { qsim->RX(inverse ? -params[0U] : params[0U], target); @@ -664,7 +659,7 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { } // Update the state-vector - if (controlled_wires.empty()) { + if (dev_controlled_wires.empty()) { applyNamedOperation(name, dev_wires, inverse, params); } else { applyNamedOperation(name, dev_controlled_wires, dev_controlled_values, dev_wires, inverse, params); @@ -690,7 +685,7 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { Qrack::inv2x2(mtrx, inv); // Update the state-vector - if (controlled_wires.empty()) { + if (dev_controlled_wires.empty()) { qsim->Mtrx(inverse ? inv : mtrx, wires[0U]); } else { bitCapInt controlPerm = 0U; From 8b47722d4568997298118c4736003fec65fcc98e Mon Sep 17 00:00:00 2001 From: WrathfulSpatula Date: Fri, 31 May 2024 13:56:52 -0400 Subject: [PATCH 07/27] Fix Catalyst control wire order --- pennylane_qrack/qrack_device.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pennylane_qrack/qrack_device.cpp b/pennylane_qrack/qrack_device.cpp index 97742f6..bf8f294 100644 --- a/pennylane_qrack/qrack_device.cpp +++ b/pennylane_qrack/qrack_device.cpp @@ -191,11 +191,10 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { const std::vector &wires, const bool& inverse, const std::vector ¶ms) { - bitCapInt controlPerm = 0U; - for (size_t i = 0U; i < control_values.size(); ++i) { - controlPerm = controlPerm << 1U; + bitCapInt controlPerm = Qrack::ZERO_BCI; + for (bitLenInt i = 0U; i < control_values.size(); ++i) { if (control_values[i]) { - controlPerm = controlPerm | 1U; + controlPerm = controlPerm | Qrack::pow2(i); } } From f1a21b97b1279eecbd6b9ef3feca94cda1a6db70 Mon Sep 17 00:00:00 2001 From: WrathfulSpatula Date: Fri, 31 May 2024 14:20:04 -0400 Subject: [PATCH 08/27] Debug MatrixOperation() --- pennylane_qrack/qrack_device.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pennylane_qrack/qrack_device.cpp b/pennylane_qrack/qrack_device.cpp index bf8f294..73c10ce 100644 --- a/pennylane_qrack/qrack_device.cpp +++ b/pennylane_qrack/qrack_device.cpp @@ -685,16 +685,15 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { // Update the state-vector if (dev_controlled_wires.empty()) { - qsim->Mtrx(inverse ? inv : mtrx, wires[0U]); + qsim->Mtrx(inverse ? inv : mtrx, dev_wires[0U]); } else { - bitCapInt controlPerm = 0U; - for (size_t i = 0U; i < controlled_values.size(); ++i) { - controlPerm = controlPerm << 1U; + bitCapInt controlPerm = Qrack::ZERO_BCI; + for (bitLenInt i = 0U; i < controlled_values.size(); ++i) { if (controlled_values[i]) { - controlPerm = controlPerm | 1U; + controlPerm = controlPerm | Qrack::pow2(i); } } - qsim->UCMtrx(dev_controlled_wires, inverse ? inv : mtrx, wires[0U], controlPerm); + qsim->UCMtrx(dev_controlled_wires, inverse ? inv : mtrx, dev_wires[0U], controlPerm); } } auto Expval(ObsIdType id) -> double override From 1dd3d70da18a00b0e404c1ee5107d57ec7d3fd27 Mon Sep 17 00:00:00 2001 From: WrathfulSpatula Date: Fri, 31 May 2024 14:54:06 -0400 Subject: [PATCH 09/27] Debug Catalyst observables --- pennylane_qrack/qrack_device.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pennylane_qrack/qrack_device.cpp b/pennylane_qrack/qrack_device.cpp index 73c10ce..981e9e8 100644 --- a/pennylane_qrack/qrack_device.cpp +++ b/pennylane_qrack/qrack_device.cpp @@ -541,6 +541,9 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { auto Observable(ObsId id, const std::vector> &matrix, const std::vector &wires) -> ObsIdType override { + RT_FAIL_IF(wires.size() != 1U, "Cannot have observables besides tensor products of Pauli observables"); + auto &&dev_wires = getDeviceWires(wires); + Qrack::Pauli basis = Qrack::PauliI; switch (id) { case ObsId::PauliX: @@ -555,7 +558,7 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { default: break; } - obs_cache.push_back(QrackObservable({ basis }, { (bitLenInt)wires[0U] })); + obs_cache.push_back(QrackObservable({ basis }, { (bitLenInt)dev_wires[0U] })); return obs_cache.size() - 1U; } From df35d6f884abb7fc46f1b24aa2699d2c858a24ae Mon Sep 17 00:00:00 2001 From: WrathfulSpatula Date: Fri, 31 May 2024 15:19:59 -0400 Subject: [PATCH 10/27] Fix tests --- pennylane_qrack/qrack_device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_qrack/qrack_device.py b/pennylane_qrack/qrack_device.py index 6a058e9..035718e 100644 --- a/pennylane_qrack/qrack_device.py +++ b/pennylane_qrack/qrack_device.py @@ -490,7 +490,7 @@ def _apply_gate(self, op): elif opname in ["PhaseShift.inv", "U1.inv"]: ip_mtrx = [1, 0, 0, cmath.exp(1j * -par[0])] for label in device_wires.labels: - self._state.mtrx(p_mtrx, label) + self._state.mtrx(ip_mtrx, label) elif opname in ["C(PhaseShift)", "C(U1)"]: self._state.mtrx( device_wires.labels[:-1], From e9e8326236dfb253ef4e7a8a19488f3baaf52cf8 Mon Sep 17 00:00:00 2001 From: WrathfulSpatula Date: Fri, 31 May 2024 16:22:24 -0400 Subject: [PATCH 11/27] Fix off-by-one --- pennylane_qrack/qrack_device.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_qrack/qrack_device.cpp b/pennylane_qrack/qrack_device.cpp index 981e9e8..d4ad9e9 100644 --- a/pennylane_qrack/qrack_device.cpp +++ b/pennylane_qrack/qrack_device.cpp @@ -442,7 +442,7 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { wires = 0U; size_t q; while ((q = value.find(",")) != std::string::npos) { - const bitLenInt label = stoi(value.substr(0, q)); + const bitLenInt label = stoi(value.substr(0, q)) + 1U; value.erase(0, q + 1U); if (label > wires) { wires = label; From 3f572a856eb814e1315dd01efc3bbb8635e65151 Mon Sep 17 00:00:00 2001 From: WrathfulSpatula Date: Fri, 31 May 2024 16:40:56 -0400 Subject: [PATCH 12/27] Reverse wire order --- pennylane_qrack/qrack_device.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pennylane_qrack/qrack_device.cpp b/pennylane_qrack/qrack_device.cpp index d4ad9e9..dd022fd 100644 --- a/pennylane_qrack/qrack_device.cpp +++ b/pennylane_qrack/qrack_device.cpp @@ -42,7 +42,8 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { { std::vector res; res.reserve(wires.size()); - std::transform(wires.begin(), wires.end(), std::back_inserter(res), [](auto w) { return (bitLenInt)w; }); + const bitLenInt end = qsim->GetQubitCount() - 1U; + std::transform(wires.begin(), wires.end(), std::back_inserter(res), [end](auto w) { return end - (bitLenInt)w; }); return res; } From 9cd319e5b9ae54e4513fcc0229bf5b1e322b7212 Mon Sep 17 00:00:00 2001 From: WrathfulSpatula Date: Fri, 31 May 2024 17:16:47 -0400 Subject: [PATCH 13/27] reverseWires() --- pennylane_qrack/qrack_device.cpp | 33 ++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/pennylane_qrack/qrack_device.cpp b/pennylane_qrack/qrack_device.cpp index dd022fd..66f3a99 100644 --- a/pennylane_qrack/qrack_device.cpp +++ b/pennylane_qrack/qrack_device.cpp @@ -38,6 +38,16 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { static constexpr bool QRACK_RESULT_TRUE_CONST = true; static constexpr bool QRACK_RESULT_FALSE_CONST = false; + inline auto reverseWires() -> void + { + const bitLenInt count = qsim->GetQubitCount(); + const bitLenInt end = count - 1U; + const bitLenInt maxLcv = count >> 1U; + for (bitLenInt i = 0U; i < maxLcv; ++i) { + qsim->Swap(i, end - i); + } + } + inline auto getDeviceWires(const std::vector &wires) -> std::vector { std::vector res; @@ -735,15 +745,16 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { void PartialProbs(DataView &p, const std::vector &wires) override { RT_FAIL_IF((size_t)Qrack::pow2(wires.size()) != p.size(), "Invalid size for the pre-allocated probabilities vector"); - std::vector ids(wires.size()); - std::transform(wires.begin(), wires.end(), ids.end(), [](QubitIdType a) { return (bitLenInt)a; }); + auto &&dev_wires = getDeviceWires(wires); + reverseWires(); #if FPPOW == 6 - qsim->ProbBitsAll(ids, &(*(p.begin()))); + qsim->ProbBitsAll(dev_wires, &(*(p.begin()))); #else std::unique_ptr _p(new Qrack::real1[p.size()]); - qsim->ProbBitsAll(ids, _p.get()); + qsim->ProbBitsAll(dev_wires, _p.get()); std::copy(_p.get(), _p.get() + p.size(), p.begin()); #endif + reverseWires(); } void Sample(DataView &samples, size_t shots) override { @@ -771,11 +782,14 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { // that could be instead implied by the size of "samples." RT_FAIL_IF(samples.size() != shots, "Invalid size for the pre-allocated samples"); - std::vector qPowers(wires.size()); + auto &&dev_wires = getDeviceWires(wires); + std::vector qPowers(dev_wires.size()); for (size_t i = 0U; i < qPowers.size(); ++i) { - qPowers[i] = Qrack::pow2((bitLenInt)wires[i]); + qPowers[i] = Qrack::pow2((bitLenInt)dev_wires[i]); } + reverseWires(); auto q_samples = qsim->MultiShotMeasureMask(qPowers, shots); + reverseWires(); auto samplesIter = samples.begin(); for (size_t shot = 0U; shot < shots; ++shot) { @@ -827,11 +841,14 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { RT_FAIL_IF(eigvals.size() != numElements || counts.size() != numElements, "Invalid size for the pre-allocated counts"); - std::vector qPowers(wires.size()); + auto &&dev_wires = getDeviceWires(wires); + std::vector qPowers(dev_wires.size()); for (size_t i = 0U; i < qPowers.size(); ++i) { - qPowers[i] = Qrack::pow2(wires[i]); + qPowers[i] = Qrack::pow2(dev_wires[i]); } + reverseWires(); auto q_samples = qsim->MultiShotMeasureMask(qPowers, shots); + reverseWires(); std::iota(eigvals.begin(), eigvals.end(), 0); std::fill(counts.begin(), counts.end(), 0); From 0ea3c8088b33a69ffac841661042ddc181634f15 Mon Sep 17 00:00:00 2001 From: WrathfulSpatula Date: Fri, 31 May 2024 17:27:21 -0400 Subject: [PATCH 14/27] Revert "Decompose QFT" This reverts commit de6f5e085eae8ac71b16622017fdb9f3fa17e568. --- pennylane_qrack/QrackDeviceConfig.toml | 2 +- pennylane_qrack/qrack_device.cpp | 14 ++++++++++++++ pennylane_qrack/qrack_device.py | 9 +++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/pennylane_qrack/QrackDeviceConfig.toml b/pennylane_qrack/QrackDeviceConfig.toml index ca3a67e..24ec83d 100644 --- a/pennylane_qrack/QrackDeviceConfig.toml +++ b/pennylane_qrack/QrackDeviceConfig.toml @@ -48,6 +48,7 @@ U2 = { properties = [ "controllable", "invertible" ] } U1 = { properties = [ "controllable", "invertible" ] } MultiControlledX = { properties = [ "controllable", "invertible" ] } Identity = { properties = [ "controllable", "invertible" ] } +QFT = { properties = [ "invertible" ] } # Operators that should be decomposed according to the algorithm used # by PennyLane's device API. @@ -78,7 +79,6 @@ Identity = { properties = [ "controllable", "invertible" ] } # IsingYY = {} # IsingZZ = {} # IsingXY = {} -# QFT = {} # Gates which should be translated to QubitUnitary # [operators.gates.matrix] diff --git a/pennylane_qrack/qrack_device.cpp b/pennylane_qrack/qrack_device.cpp index 66f3a99..bbfe850 100644 --- a/pennylane_qrack/qrack_device.cpp +++ b/pennylane_qrack/qrack_device.cpp @@ -192,6 +192,20 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { qsim->U(target, Qrack::PI_R1 / 2, params[0U], params[1U]); } } + } else if (name == "QFT") { + const size_t maxLcv = wires.size() >> 1U; + const size_t end = wires.size() - 1U; + if (inverse) { + for (size_t i = 0U; i < maxLcv; ++i) { + qsim->Swap(wires[i], wires[end - i]); + } + qsim->IQFTR(wires); + } else { + qsim->QFTR(wires); + for (size_t i = 0U; i < maxLcv; ++i) { + qsim->Swap(wires[i], wires[end - i]); + } + } } else if (name != "Identity") { throw std::domain_error("Unrecognized gate name: " + name); } diff --git a/pennylane_qrack/qrack_device.py b/pennylane_qrack/qrack_device.py index 035718e..d7295f7 100644 --- a/pennylane_qrack/qrack_device.py +++ b/pennylane_qrack/qrack_device.py @@ -154,6 +154,7 @@ class QrackDevice(QubitDevice): "C(CPhase)", "MultiControlledX", "C(MultiControlledX)", + "QFT", } config = pathlib.Path( @@ -597,6 +598,14 @@ def _apply_gate(self, op): -par[1], -par[2], ) + elif opname == "QFT": + self._state.qft(device_wires.labels) + for i in range(len(device_wires.labels) >> 1): + self._state.swap(device_wires.labels[i], device_wires.labels[-i]) + elif opname == "QFT.inv": + for i in range(len(device_wires.labels) >> 1): + self._state.swap(device_wires.labels[i], device_wires.labels[-i]) + self._state.iqft(device_wires.labels) elif opname not in [ "Identity", "Identity.inv", From 9b67ca5de27830d56bf22e0b3134dcf85bea72a7 Mon Sep 17 00:00:00 2001 From: WrathfulSpatula Date: Fri, 31 May 2024 18:11:15 -0400 Subject: [PATCH 15/27] Decompose QFT --- pennylane_qrack/QrackDeviceConfig.toml | 2 +- pennylane_qrack/qrack_device.cpp | 14 -------------- pennylane_qrack/qrack_device.py | 9 --------- 3 files changed, 1 insertion(+), 24 deletions(-) diff --git a/pennylane_qrack/QrackDeviceConfig.toml b/pennylane_qrack/QrackDeviceConfig.toml index 24ec83d..ca3a67e 100644 --- a/pennylane_qrack/QrackDeviceConfig.toml +++ b/pennylane_qrack/QrackDeviceConfig.toml @@ -48,7 +48,6 @@ U2 = { properties = [ "controllable", "invertible" ] } U1 = { properties = [ "controllable", "invertible" ] } MultiControlledX = { properties = [ "controllable", "invertible" ] } Identity = { properties = [ "controllable", "invertible" ] } -QFT = { properties = [ "invertible" ] } # Operators that should be decomposed according to the algorithm used # by PennyLane's device API. @@ -79,6 +78,7 @@ QFT = { properties = [ "invertible" ] } # IsingYY = {} # IsingZZ = {} # IsingXY = {} +# QFT = {} # Gates which should be translated to QubitUnitary # [operators.gates.matrix] diff --git a/pennylane_qrack/qrack_device.cpp b/pennylane_qrack/qrack_device.cpp index bbfe850..66f3a99 100644 --- a/pennylane_qrack/qrack_device.cpp +++ b/pennylane_qrack/qrack_device.cpp @@ -192,20 +192,6 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { qsim->U(target, Qrack::PI_R1 / 2, params[0U], params[1U]); } } - } else if (name == "QFT") { - const size_t maxLcv = wires.size() >> 1U; - const size_t end = wires.size() - 1U; - if (inverse) { - for (size_t i = 0U; i < maxLcv; ++i) { - qsim->Swap(wires[i], wires[end - i]); - } - qsim->IQFTR(wires); - } else { - qsim->QFTR(wires); - for (size_t i = 0U; i < maxLcv; ++i) { - qsim->Swap(wires[i], wires[end - i]); - } - } } else if (name != "Identity") { throw std::domain_error("Unrecognized gate name: " + name); } diff --git a/pennylane_qrack/qrack_device.py b/pennylane_qrack/qrack_device.py index d7295f7..035718e 100644 --- a/pennylane_qrack/qrack_device.py +++ b/pennylane_qrack/qrack_device.py @@ -154,7 +154,6 @@ class QrackDevice(QubitDevice): "C(CPhase)", "MultiControlledX", "C(MultiControlledX)", - "QFT", } config = pathlib.Path( @@ -598,14 +597,6 @@ def _apply_gate(self, op): -par[1], -par[2], ) - elif opname == "QFT": - self._state.qft(device_wires.labels) - for i in range(len(device_wires.labels) >> 1): - self._state.swap(device_wires.labels[i], device_wires.labels[-i]) - elif opname == "QFT.inv": - for i in range(len(device_wires.labels) >> 1): - self._state.swap(device_wires.labels[i], device_wires.labels[-i]) - self._state.iqft(device_wires.labels) elif opname not in [ "Identity", "Identity.inv", From 79793ddf3ec46f26eeffbb34150c0dfdcb8506c7 Mon Sep 17 00:00:00 2001 From: WrathfulSpatula Date: Fri, 31 May 2024 18:39:05 -0400 Subject: [PATCH 16/27] reverseWires() -> getReverseWires() --- pennylane_qrack/qrack_device.cpp | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/pennylane_qrack/qrack_device.cpp b/pennylane_qrack/qrack_device.cpp index 66f3a99..a0f18a3 100644 --- a/pennylane_qrack/qrack_device.cpp +++ b/pennylane_qrack/qrack_device.cpp @@ -38,16 +38,6 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { static constexpr bool QRACK_RESULT_TRUE_CONST = true; static constexpr bool QRACK_RESULT_FALSE_CONST = false; - inline auto reverseWires() -> void - { - const bitLenInt count = qsim->GetQubitCount(); - const bitLenInt end = count - 1U; - const bitLenInt maxLcv = count >> 1U; - for (bitLenInt i = 0U; i < maxLcv; ++i) { - qsim->Swap(i, end - i); - } - } - inline auto getDeviceWires(const std::vector &wires) -> std::vector { std::vector res; @@ -57,6 +47,14 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { return res; } + inline auto getReverseWires(const std::vector &wires) -> std::vector + { + std::vector res; + res.reserve(wires.size()); + std::transform(wires.begin(), wires.end(), std::back_inserter(res), [](auto w) { return (bitLenInt)w; }); + return res; + } + inline auto wiresToMask(const std::vector &wires) -> bitCapInt { bitCapInt mask = Qrack::ZERO_BCI; @@ -745,8 +743,7 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { void PartialProbs(DataView &p, const std::vector &wires) override { RT_FAIL_IF((size_t)Qrack::pow2(wires.size()) != p.size(), "Invalid size for the pre-allocated probabilities vector"); - auto &&dev_wires = getDeviceWires(wires); - reverseWires(); + auto &&dev_wires = getReverseWires(wires); #if FPPOW == 6 qsim->ProbBitsAll(dev_wires, &(*(p.begin()))); #else @@ -754,7 +751,6 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { qsim->ProbBitsAll(dev_wires, _p.get()); std::copy(_p.get(), _p.get() + p.size(), p.begin()); #endif - reverseWires(); } void Sample(DataView &samples, size_t shots) override { @@ -782,14 +778,12 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { // that could be instead implied by the size of "samples." RT_FAIL_IF(samples.size() != shots, "Invalid size for the pre-allocated samples"); - auto &&dev_wires = getDeviceWires(wires); + auto &&dev_wires = getReverseWires(wires); std::vector qPowers(dev_wires.size()); for (size_t i = 0U; i < qPowers.size(); ++i) { qPowers[i] = Qrack::pow2((bitLenInt)dev_wires[i]); } - reverseWires(); auto q_samples = qsim->MultiShotMeasureMask(qPowers, shots); - reverseWires(); auto samplesIter = samples.begin(); for (size_t shot = 0U; shot < shots; ++shot) { @@ -841,14 +835,12 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { RT_FAIL_IF(eigvals.size() != numElements || counts.size() != numElements, "Invalid size for the pre-allocated counts"); - auto &&dev_wires = getDeviceWires(wires); + auto &&dev_wires = getReverseWires(wires); std::vector qPowers(dev_wires.size()); for (size_t i = 0U; i < qPowers.size(); ++i) { qPowers[i] = Qrack::pow2(dev_wires[i]); } - reverseWires(); auto q_samples = qsim->MultiShotMeasureMask(qPowers, shots); - reverseWires(); std::iota(eigvals.begin(), eigvals.end(), 0); std::fill(counts.begin(), counts.end(), 0); From cf6ac8a07df6fbc4057086b8d28136cb6ca37c3c Mon Sep 17 00:00:00 2001 From: WrathfulSpatula Date: Fri, 31 May 2024 18:47:47 -0400 Subject: [PATCH 17/27] Remove inefficient while loop --- pennylane_qrack/qrack_device.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pennylane_qrack/qrack_device.cpp b/pennylane_qrack/qrack_device.cpp index a0f18a3..0eadf57 100644 --- a/pennylane_qrack/qrack_device.cpp +++ b/pennylane_qrack/qrack_device.cpp @@ -656,17 +656,17 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { || (name == "CRZ") || (name == "CRot") || (name == "Toffoli")) { - while (dev_wires.size() > 1U) { - dev_controlled_wires.push_back(dev_wires[0]); - dev_controlled_values.push_back(true); - dev_wires.erase(dev_wires.begin()); - } + const size_t end = dev_wires.size() - 1U; + dev_controlled_wires.insert(dev_controlled_wires.end(), dev_wires.begin(), dev_wires.begin() + end); + dev_wires.erase(dev_wires.begin(), dev_wires.begin() + end); + const std::vector t(end, true); + dev_controlled_values.insert(dev_controlled_values.end(), t.begin(), t.end()); } else if (name == "CSWAP") { - while (dev_wires.size() > 2U) { - dev_controlled_wires.push_back(dev_wires[0]); - dev_controlled_values.push_back(true); - dev_wires.erase(dev_wires.begin()); - } + const size_t end = dev_wires.size() - 2U; + dev_controlled_wires.insert(dev_controlled_wires.end(), dev_wires.begin(), dev_wires.begin() + end); + dev_wires.erase(dev_wires.begin(), dev_wires.begin() + end); + const std::vector t(end, true); + dev_controlled_values.insert(dev_controlled_values.end(), t.begin(), t.end()); } // Update the state-vector From b7c4c519052e2a33c4a412782c35895edf48b415 Mon Sep 17 00:00:00 2001 From: WrathfulSpatula Date: Sat, 1 Jun 2024 08:16:45 -0400 Subject: [PATCH 18/27] Simplify phase gates --- pennylane_qrack/qrack_device.cpp | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/pennylane_qrack/qrack_device.cpp b/pennylane_qrack/qrack_device.cpp index 0eadf57..b95b888 100644 --- a/pennylane_qrack/qrack_device.cpp +++ b/pennylane_qrack/qrack_device.cpp @@ -144,9 +144,7 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { qsim->Swap(wires[0U], wires[1U]); qsim->CU(c, wires[1U], ZERO_R1, ZERO_R1, inverse ? -params[0U] : params[0U]); } else if ((name == "PhaseShift") || (name == "U1")) { - const Qrack::real1 cosine = (Qrack::real1)cos(inverse ? -params[0U] : params[0U]); - const Qrack::real1 sine = (Qrack::real1)sin(inverse ? -params[0U] : params[0U]); - const Qrack::complex bottomRight(cosine, sine); + const Qrack::complex bottomRight = exp(Qrack::I_CMPLX * (Qrack::real1)(inverse ? -params[0U] : params[0U])); for (const bitLenInt& target : wires) { qsim->Phase(Qrack::ONE_CMPLX, bottomRight, target); } @@ -243,12 +241,9 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { qsim->UCMtrx(control_wires, inverse ? iSqrtX : sqrtX, target, controlPerm); } } else if (name == "MultiRZ") { - const Qrack::real1 cosine = (Qrack::real1)cos((inverse ? -params[0U] : params[0U]) / 2); - const Qrack::real1 sine = (Qrack::real1)sin((inverse ? -params[0U] : params[0U]) / 2); - const Qrack::complex topLeft(cosine, -sine); - const Qrack::complex bottomRight(cosine, sine); + const Qrack::complex bottomRight = exp(Qrack::I_CMPLX * (Qrack::real1)((inverse ? -params[0U] : params[0U]) / 2)); for (const bitLenInt& target : wires) { - qsim->UCPhase(control_wires, topLeft, bottomRight, target, controlPerm); + qsim->UCPhase(control_wires, conj(bottomRight), bottomRight, target, controlPerm); } } else if (name == "Hadamard") { for (const bitLenInt& target : wires) { @@ -297,9 +292,7 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { } } } else if ((name == "PhaseShift") || (name == "U1") || (name == "ControlledPhaseShift") || (name == "CPhase")) { - const Qrack::real1 cosine = (Qrack::real1)cos(inverse ? -params[0U] : params[0U]); - const Qrack::real1 sine = (Qrack::real1)sin(inverse ? -params[0U] : params[0U]); - const Qrack::complex bottomRight(cosine, sine); + const Qrack::complex bottomRight = exp(Qrack::I_CMPLX * (Qrack::real1)(inverse ? -params[0U] : params[0U])); for (const bitLenInt& target : wires) { qsim->UCPhase(control_wires, Qrack::ONE_CMPLX, bottomRight, target, controlPerm); } @@ -330,12 +323,9 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { qsim->UCMtrx(control_wires, mtrx, target, controlPerm); } } else if ((name == "RZ") || (name == "CRZ")) { - const Qrack::real1 cosine = (Qrack::real1)cos((inverse ? -params[0U] : params[0U]) / 2); - const Qrack::real1 sine = (Qrack::real1)sin((inverse ? -params[0U] : params[0U]) / 2); - const Qrack::complex topLeft(cosine, -sine); - const Qrack::complex bottomRight(cosine, sine); + const Qrack::complex bottomRight = exp(Qrack::I_CMPLX * (Qrack::real1)((inverse ? -params[0U] : params[0U]) / 2)); for (const bitLenInt& target : wires) { - qsim->UCPhase(control_wires, topLeft, bottomRight, target, controlPerm); + qsim->UCPhase(control_wires, conj(bottomRight), bottomRight, target, controlPerm); } } else if ((name == "Rot") || (name == "CRot")) { const Qrack::real1 phi = inverse ? -params[0U] : params[0U]; From 3d9183f2e2316e8b949453a78775bbe7df64666f Mon Sep 17 00:00:00 2001 From: WrathfulSpatula Date: Sun, 2 Jun 2024 11:02:57 -0400 Subject: [PATCH 19/27] Fix wire map parsing --- pennylane_qrack/qrack_device.cpp | 63 +++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 22 deletions(-) diff --git a/pennylane_qrack/qrack_device.cpp b/pennylane_qrack/qrack_device.cpp index b95b888..07d2a60 100644 --- a/pennylane_qrack/qrack_device.cpp +++ b/pennylane_qrack/qrack_device.cpp @@ -32,6 +32,7 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { bool tapeRecording; size_t shots; Qrack::QInterfacePtr qsim; + std::map qubit_map; std::vector obs_cache; // static constants for RESULT values @@ -42,8 +43,13 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { { std::vector res; res.reserve(wires.size()); - const bitLenInt end = qsim->GetQubitCount() - 1U; - std::transform(wires.begin(), wires.end(), std::back_inserter(res), [end](auto w) { return end - (bitLenInt)w; }); + std::transform(wires.begin(), wires.end(), std::back_inserter(res), [this](auto w) { + const auto& it = qubit_map.find(w); + if (it == qubit_map.end()) { + throw std::invalid_argument("Qubit ID not in wire map: " + std::to_string(w)); + } + return it->second; + }); return res; } @@ -51,7 +57,14 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { { std::vector res; res.reserve(wires.size()); - std::transform(wires.begin(), wires.end(), std::back_inserter(res), [](auto w) { return (bitLenInt)w; }); + const bitLenInt end = qsim->GetQubitCount() - 1U; + std::transform(wires.begin(), wires.end(), std::back_inserter(res), [this, end](auto w) { + const auto& it = qubit_map.find(w); + if (it == qubit_map.end()) { + throw std::invalid_argument("Qubit ID not in wire map: " + std::to_string(w)); + } + return end - it->second; + }); return res; } @@ -389,15 +402,15 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { kwargs = trim(kwargs); std::map keyMap; - keyMap["wires"] = 1; - keyMap["shots"] = 2; - keyMap["is_hybrid_stabilizer"] = 3; - keyMap["is_tensor_network"] = 4; - keyMap["is_schmidt_decomposed"] = 5; - keyMap["is_schmidt_decomposition_parallel"] = 6; - keyMap["is_qbdd"] = 7; - keyMap["is_gpu"] = 8; - keyMap["is_host_pointer"] = 9; + keyMap["'wires'"] = 1; + keyMap["'shots'"] = 2; + keyMap["'is_hybrid_stabilizer'"] = 3; + keyMap["'is_tensor_network'"] = 4; + keyMap["'is_schmidt_decomposed'"] = 5; + keyMap["'is_schmidt_decomposition_parallel'"] = 6; + keyMap["'is_qbdd'"] = 7; + keyMap["'is_gpu'"] = 8; + keyMap["'is_host_pointer'"] = 9; bitLenInt wires = 0U; bool is_hybrid_stabilizer = true; @@ -410,13 +423,10 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { size_t pos; while ((pos = kwargs.find(":")) != std::string::npos) { - std::string key = kwargs.substr(0, pos); + std::string key = trim(kwargs.substr(0, pos)); kwargs.erase(0, pos + 1U); - // Leading and trailing quotes: - key.erase(0U, 1U); - key.erase(key.size() - 1U); - if (key == "wires") { + if (key == "'wires'") { // Handle if integer pos = kwargs.find(","); bool isInt = true; @@ -428,12 +438,16 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { } if (isInt) { wires = stoi(trim(kwargs.substr(0, pos))); + for (size_t i = 0U; i < wires; ++i) { + qubit_map[i] = wires - (i + 1U); + } kwargs.erase(0, pos + 1U); + continue; } // Handles if Wires object - pos = kwargs.find("]>,"); + pos = kwargs.find("]>"); std::string value = kwargs.substr(0, pos); kwargs.erase(0, pos + 3U); size_t p = value.find("["); @@ -441,13 +455,18 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { wires = 0U; size_t q; while ((q = value.find(",")) != std::string::npos) { - const bitLenInt label = stoi(value.substr(0, q)) + 1U; + qubit_map[(QubitIdType)stoi(trim(value.substr(0, q)))] = wires; + ++wires; value.erase(0, q + 1U); - if (label > wires) { - wires = label; - } } + qubit_map[stoi(trim(value))] = wires; ++wires; + + for (auto it = qubit_map.begin(); it != qubit_map.end(); ++it) { + it->second = wires - (it->second + 1U); + std::cout << (size_t)it->first << ", " << (size_t)it->second << std::endl; + } + continue; } From e563ee6230cf5ac81da87d3832e8bb3520b3c310 Mon Sep 17 00:00:00 2001 From: WrathfulSpatula Date: Sun, 2 Jun 2024 11:26:12 -0400 Subject: [PATCH 20/27] Remove getReverseWires() --- pennylane_qrack/qrack_device.cpp | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/pennylane_qrack/qrack_device.cpp b/pennylane_qrack/qrack_device.cpp index 07d2a60..7ff2359 100644 --- a/pennylane_qrack/qrack_device.cpp +++ b/pennylane_qrack/qrack_device.cpp @@ -53,21 +53,6 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { return res; } - inline auto getReverseWires(const std::vector &wires) -> std::vector - { - std::vector res; - res.reserve(wires.size()); - const bitLenInt end = qsim->GetQubitCount() - 1U; - std::transform(wires.begin(), wires.end(), std::back_inserter(res), [this, end](auto w) { - const auto& it = qubit_map.find(w); - if (it == qubit_map.end()) { - throw std::invalid_argument("Qubit ID not in wire map: " + std::to_string(w)); - } - return end - it->second; - }); - return res; - } - inline auto wiresToMask(const std::vector &wires) -> bitCapInt { bitCapInt mask = Qrack::ZERO_BCI; @@ -752,7 +737,7 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { void PartialProbs(DataView &p, const std::vector &wires) override { RT_FAIL_IF((size_t)Qrack::pow2(wires.size()) != p.size(), "Invalid size for the pre-allocated probabilities vector"); - auto &&dev_wires = getReverseWires(wires); + auto &&dev_wires = getDeviceWires(wires); #if FPPOW == 6 qsim->ProbBitsAll(dev_wires, &(*(p.begin()))); #else @@ -787,7 +772,7 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { // that could be instead implied by the size of "samples." RT_FAIL_IF(samples.size() != shots, "Invalid size for the pre-allocated samples"); - auto &&dev_wires = getReverseWires(wires); + auto &&dev_wires = getDeviceWires(wires); std::vector qPowers(dev_wires.size()); for (size_t i = 0U; i < qPowers.size(); ++i) { qPowers[i] = Qrack::pow2((bitLenInt)dev_wires[i]); @@ -844,7 +829,7 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { RT_FAIL_IF(eigvals.size() != numElements || counts.size() != numElements, "Invalid size for the pre-allocated counts"); - auto &&dev_wires = getReverseWires(wires); + auto &&dev_wires = getDeviceWires(wires); std::vector qPowers(dev_wires.size()); for (size_t i = 0U; i < qPowers.size(); ++i) { qPowers[i] = Qrack::pow2(dev_wires[i]); From 3900119ffb59bd565cf8a4c21d4791055394b81a Mon Sep 17 00:00:00 2001 From: WrathfulSpatula Date: Sun, 2 Jun 2024 12:09:05 -0400 Subject: [PATCH 21/27] Default to wires=1 --- pennylane_qrack/qrack_device.cpp | 6 +++++- pennylane_qrack/qrack_device.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pennylane_qrack/qrack_device.cpp b/pennylane_qrack/qrack_device.cpp index 7ff2359..056e81c 100644 --- a/pennylane_qrack/qrack_device.cpp +++ b/pennylane_qrack/qrack_device.cpp @@ -397,7 +397,8 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { keyMap["'is_gpu'"] = 8; keyMap["'is_host_pointer'"] = 9; - bitLenInt wires = 0U; + bitLenInt wires = 1U; + qubit_map[0U] = 0U; bool is_hybrid_stabilizer = true; bool is_tensor_network = false; bool is_schmidt_decomposed = true; @@ -412,6 +413,9 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { kwargs.erase(0, pos + 1U); if (key == "'wires'") { + wires = 0U; + qubit_map.clear(); + // Handle if integer pos = kwargs.find(","); bool isInt = true; diff --git a/pennylane_qrack/qrack_device.py b/pennylane_qrack/qrack_device.py index 035718e..c0d58c3 100644 --- a/pennylane_qrack/qrack_device.py +++ b/pennylane_qrack/qrack_device.py @@ -167,7 +167,7 @@ def get_c_interface(): os.path.dirname(sys.modules[__name__].__file__) + "/libqrack_device.so", ) - def __init__(self, wires=0, shots=None, **kwargs): + def __init__(self, wires=1, shots=None, **kwargs): super().__init__(wires=wires, shots=shots) if "isTensorNetwork" in kwargs: From 4cc3f5ebbb790e4cb28e83b9e7422e7493247158 Mon Sep 17 00:00:00 2001 From: WrathfulSpatula Date: Sun, 2 Jun 2024 18:24:59 -0400 Subject: [PATCH 22/27] Remove logging statement --- pennylane_qrack/QubitManager.hpp | 145 +++++++++++++++++++++++++++++++ pennylane_qrack/qrack_device.cpp | 1 - 2 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 pennylane_qrack/QubitManager.hpp diff --git a/pennylane_qrack/QubitManager.hpp b/pennylane_qrack/QubitManager.hpp new file mode 100644 index 0000000..7fb0610 --- /dev/null +++ b/pennylane_qrack/QubitManager.hpp @@ -0,0 +1,145 @@ +// Copyright 2022-2023 Xanadu Quantum Technologies Inc. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include + +#include "Exception.hpp" +#include "Types.h" + +namespace Catalyst::Runtime { + +/** + * Qubit Manager + * + * @brief That maintains mapping of qubit IDs between runtime and device + * ids (e.g., Lightning-Dynamic). When user allocates a qubit, the + * `QubitManager` adds the qubit as an active qubit that operations + * can act on. When user releases a qubit, the `QubitManager` removes + * that qubit from the list of active wires. + */ +template +class QubitManager { + private: + using LQMapT = std::map; + + SimQubitIdType next_idx{0}; + LQMapT qubits_map{}; + + template + [[nodiscard]] inline OIter _remove_simulator_qubit_id(SimQubitIdType s_idx) + { + const auto &&s_idx_iter = this->qubits_map.find(s_idx); + RT_FAIL_IF(s_idx_iter == this->qubits_map.end(), "Invalid simulator qubit index"); + + return this->qubits_map.erase(s_idx_iter); + } + + template + inline void _update_qubits_mapfrom(IIter s_idx_iter) + { + for (; s_idx_iter != this->qubits_map.end(); s_idx_iter++) { + s_idx_iter->second--; + } + } + + public: + QubitManager() = default; + ~QubitManager() = default; + + QubitManager(const QubitManager &) = delete; + QubitManager &operator=(const QubitManager &) = delete; + QubitManager(QubitManager &&) = delete; + QubitManager &operator=(QubitManager &&) = delete; + + [[nodiscard]] auto isValidQubitId(SimQubitIdType s_idx) -> bool + { + return this->qubits_map.contains(s_idx); + } + + [[nodiscard]] auto isValidQubitId(const std::vector &ss_idx) -> bool + { + return std::all_of(ss_idx.begin(), ss_idx.end(), + [this](SimQubitIdType s) { return isValidQubitId(s); }); + } + + [[nodiscard]] auto getAllQubitIds() -> std::vector + { + std::vector ids; + ids.reserve(this->qubits_map.size()); + for (const auto &it : this->qubits_map) { + ids.push_back(it.first); + } + + return ids; + } + + [[nodiscard]] auto getDeviceId(SimQubitIdType s_idx) -> DevQubitIdType + { + RT_FAIL_IF(!isValidQubitId(s_idx), "Invalid device qubit index"); + + return this->qubits_map[s_idx]; + } + + auto getDeviceIds(const std::vector &ss_idx) -> std::vector + { + std::vector dd_idx; + dd_idx.reserve(ss_idx.size()); + for (const auto &s : ss_idx) { + dd_idx.push_back(getDeviceId(s)); + } + return dd_idx; + } + + [[nodiscard]] auto getSimulatorId(DevQubitIdType d_idx) -> SimQubitIdType + { + auto s_idx = std::find_if(this->qubits_map.begin(), this->qubits_map.end(), + [&d_idx](auto &&p) { return p.second == d_idx; }); + + RT_FAIL_IF(s_idx == this->qubits_map.end(), "Invalid simulator qubit index"); + + return s_idx->first; + } + + [[nodiscard]] auto Allocate(DevQubitIdType d_next_idx) -> SimQubitIdType + { + this->qubits_map[this->next_idx++] = d_next_idx; + return this->next_idx - 1; + } + + auto AllocateRange(DevQubitIdType start_idx, size_t size) -> std::vector + { + std::vector ids; + ids.reserve(size); + for (DevQubitIdType i = start_idx; i < start_idx + size; i++) { + ids.push_back(this->next_idx); + this->qubits_map[this->next_idx++] = i; + } + return ids; + } + + void Release(SimQubitIdType s_idx) + { + _update_qubits_mapfrom(_remove_simulator_qubit_id(s_idx)); + } + + void ReleaseAll() + { + // Release all qubits by clearing the map. + this->qubits_map.clear(); + } +}; +} // namespace Catalyst::Runtime diff --git a/pennylane_qrack/qrack_device.cpp b/pennylane_qrack/qrack_device.cpp index 056e81c..595992e 100644 --- a/pennylane_qrack/qrack_device.cpp +++ b/pennylane_qrack/qrack_device.cpp @@ -453,7 +453,6 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { for (auto it = qubit_map.begin(); it != qubit_map.end(); ++it) { it->second = wires - (it->second + 1U); - std::cout << (size_t)it->first << ", " << (size_t)it->second << std::endl; } continue; From 752f910490623b86248620acfc73ff208d966d4b Mon Sep 17 00:00:00 2001 From: WrathfulSpatula Date: Mon, 3 Jun 2024 08:58:46 -0400 Subject: [PATCH 23/27] Don't reverse wires until output --- pennylane_qrack/qrack_device.cpp | 46 +++++++++++++++++++++++--------- pennylane_qrack/qrack_device.py | 11 ++++---- 2 files changed, 38 insertions(+), 19 deletions(-) diff --git a/pennylane_qrack/qrack_device.cpp b/pennylane_qrack/qrack_device.cpp index 595992e..dddd8d5 100644 --- a/pennylane_qrack/qrack_device.cpp +++ b/pennylane_qrack/qrack_device.cpp @@ -39,6 +39,15 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { static constexpr bool QRACK_RESULT_TRUE_CONST = true; static constexpr bool QRACK_RESULT_FALSE_CONST = false; + inline void reverseWires() + { + const bitLenInt end = qsim->GetQubitCount() - 1U; + const bitLenInt mid = qsim->GetQubitCount() >> 1U; + for (bitLenInt i = 0U; i < mid; ++i) { + qsim->Swap(i, end - i); + } + } + inline auto getDeviceWires(const std::vector &wires) -> std::vector { std::vector res; @@ -397,8 +406,7 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { keyMap["'is_gpu'"] = 8; keyMap["'is_host_pointer'"] = 9; - bitLenInt wires = 1U; - qubit_map[0U] = 0U; + bitLenInt wires = 0U; bool is_hybrid_stabilizer = true; bool is_tensor_network = false; bool is_schmidt_decomposed = true; @@ -413,9 +421,6 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { kwargs.erase(0, pos + 1U); if (key == "'wires'") { - wires = 0U; - qubit_map.clear(); - // Handle if integer pos = kwargs.find(","); bool isInt = true; @@ -428,7 +433,7 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { if (isInt) { wires = stoi(trim(kwargs.substr(0, pos))); for (size_t i = 0U; i < wires; ++i) { - qubit_map[i] = wires - (i + 1U); + qubit_map[i] = i; } kwargs.erase(0, pos + 1U); @@ -451,10 +456,6 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { qubit_map[stoi(trim(value))] = wires; ++wires; - for (auto it = qubit_map.begin(); it != qubit_map.end(); ++it) { - it->second = wires - (it->second + 1U); - } - continue; } @@ -533,12 +534,16 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { QrackDevice &operator=(QuantumDevice &&) = delete; auto AllocateQubit() -> QubitIdType override { - return qsim->Allocate(1U); + const QubitIdType label = qubit_map.rbegin()->first + 1U; + qubit_map[label] = qsim->Allocate(1U); + return label; } auto AllocateQubits(size_t num_qubits) -> std::vector override { std::vector ids(num_qubits); for (size_t i = 0U; i < num_qubits; ++i) { - ids[i] = qsim->Allocate(1U); + const QubitIdType label = qubit_map.rbegin()->first + 1U; + qubit_map[label] = qsim->Allocate(1U); + ids[i] = label; } return ids; } @@ -595,17 +600,20 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { return ret; } - void ReleaseQubit(QubitIdType id) override + void ReleaseQubit(QubitIdType label) override { // Measure to prevent denormalization + const bitLenInt id = qubit_map[label]; qsim->M(id); // Deallocate qsim->Dispose(id, 1U); + qubit_map.erase(label); } void ReleaseAllQubits() override { // State vector is left empty qsim->Dispose(0U, qsim->GetQubitCount()); + qubit_map.clear(); } [[nodiscard]] auto GetNumQubits() const -> size_t override { @@ -718,6 +726,7 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { void State(DataView, 1>& sv) override { RT_FAIL_IF(sv.size() != (size_t)qsim->GetMaxQPower(), "Invalid size for the pre-allocated state vector"); + reverseWires(); #if FPPOW == 6 qsim->GetQuantumState(&(*(sv.begin()))); #else @@ -725,10 +734,12 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { qsim->GetQuantumState(_sv.get()); std::copy(_sv.get(), _sv.get() + sv.size(), sv.begin()); #endif + reverseWires(); } void Probs(DataView& p) override { RT_FAIL_IF(p.size() != (size_t)qsim->GetMaxQPower(), "Invalid size for the pre-allocated probabilities vector"); + reverseWires(); #if FPPOW == 6 qsim->GetProbs(&(*(p.begin()))); #else @@ -736,6 +747,7 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { qsim->GetProbs(_p.get()); std::copy(_p.get(), _p.get() + p.size(), p.begin()); #endif + reverseWires(); } void PartialProbs(DataView &p, const std::vector &wires) override { @@ -755,6 +767,8 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { // that could be instead implied by the size of "samples." RT_FAIL_IF(samples.size() != shots, "Invalid size for the pre-allocated samples"); + reverseWires(); + std::vector qPowers(qsim->GetQubitCount()); for (bitLenInt i = 0U; i < qPowers.size(); ++i) { qPowers[i] = Qrack::pow2(i); @@ -768,6 +782,8 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { *(samplesIter++) = bi_to_double((sample >> wire) & 1U); } } + + reverseWires(); } void PartialSample(DataView &samples, const std::vector &wires, size_t shots) override { @@ -801,6 +817,8 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { RT_FAIL_IF(eigvals.size() != numElements || counts.size() != numElements, "Invalid size for the pre-allocated counts"); + reverseWires(); + std::vector qPowers(numQubits); for (bitLenInt i = 0U; i < qPowers.size(); ++i) { qPowers[i] = Qrack::pow2(i); @@ -819,6 +837,8 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice { } ++counts(static_cast(basisState.to_ulong())); } + + reverseWires(); } void PartialCounts(DataView &eigvals, DataView &counts, diff --git a/pennylane_qrack/qrack_device.py b/pennylane_qrack/qrack_device.py index c0d58c3..4812ee7 100644 --- a/pennylane_qrack/qrack_device.py +++ b/pennylane_qrack/qrack_device.py @@ -167,7 +167,7 @@ def get_c_interface(): os.path.dirname(sys.modules[__name__].__file__) + "/libqrack_device.so", ) - def __init__(self, wires=1, shots=None, **kwargs): + def __init__(self, wires=0, shots=None, **kwargs): super().__init__(wires=wires, shots=shots) if "isTensorNetwork" in kwargs: @@ -176,8 +176,7 @@ def __init__(self, wires=1, shots=None, **kwargs): self._state = QrackSimulator(self.num_wires, isTensorNetwork=False, **kwargs) def define_wire_map(self, wires): - consecutive_wires = Wires(range(self.num_wires - 1, -1, -1)) - + consecutive_wires = Wires(range(self.num_wires)) wire_map = zip(wires, consecutive_wires) return OrderedDict(wire_map) @@ -625,7 +624,7 @@ def analytic_probability(self, wires=None): if self._state is None: return None - all_probs = _reverse_state(self._abs(self.state) ** 2) + all_probs = self._abs(self.state) ** 2 prob = self.marginal_prob(all_probs, wires) if (not "QRACK_FPPOW" in os.environ) or (6 > int(os.environ.get("QRACK_FPPOW"))): @@ -671,7 +670,7 @@ def generate_samples(self): ) samples = np.array( - self._state.measure_shots(list(range(self.num_wires - 1, -1, -1)), self.shots) + self._state.measure_shots(list(range(self.num_wires)), self.shots) ) self._samples = QubitDevice.states_to_binary(samples, self.num_wires) @@ -680,7 +679,7 @@ def generate_samples(self): @property def state(self): # returns the state after all operations are applied - return self._state.out_ket() + return _reverse_state(self._state.out_ket()) def reset(self): self._state.reset_all() From bb1b69517beb77eceb22d945c8df02deeefe4f83 Mon Sep 17 00:00:00 2001 From: WrathfulSpatula Date: Mon, 3 Jun 2024 09:49:59 -0400 Subject: [PATCH 24/27] Revert Python wire map --- pennylane_qrack/QubitManager.hpp | 145 ------------------------------- pennylane_qrack/qrack_device.py | 9 +- 2 files changed, 5 insertions(+), 149 deletions(-) delete mode 100644 pennylane_qrack/QubitManager.hpp diff --git a/pennylane_qrack/QubitManager.hpp b/pennylane_qrack/QubitManager.hpp deleted file mode 100644 index 7fb0610..0000000 --- a/pennylane_qrack/QubitManager.hpp +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright 2022-2023 Xanadu Quantum Technologies Inc. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once - -#include -#include - -#include "Exception.hpp" -#include "Types.h" - -namespace Catalyst::Runtime { - -/** - * Qubit Manager - * - * @brief That maintains mapping of qubit IDs between runtime and device - * ids (e.g., Lightning-Dynamic). When user allocates a qubit, the - * `QubitManager` adds the qubit as an active qubit that operations - * can act on. When user releases a qubit, the `QubitManager` removes - * that qubit from the list of active wires. - */ -template -class QubitManager { - private: - using LQMapT = std::map; - - SimQubitIdType next_idx{0}; - LQMapT qubits_map{}; - - template - [[nodiscard]] inline OIter _remove_simulator_qubit_id(SimQubitIdType s_idx) - { - const auto &&s_idx_iter = this->qubits_map.find(s_idx); - RT_FAIL_IF(s_idx_iter == this->qubits_map.end(), "Invalid simulator qubit index"); - - return this->qubits_map.erase(s_idx_iter); - } - - template - inline void _update_qubits_mapfrom(IIter s_idx_iter) - { - for (; s_idx_iter != this->qubits_map.end(); s_idx_iter++) { - s_idx_iter->second--; - } - } - - public: - QubitManager() = default; - ~QubitManager() = default; - - QubitManager(const QubitManager &) = delete; - QubitManager &operator=(const QubitManager &) = delete; - QubitManager(QubitManager &&) = delete; - QubitManager &operator=(QubitManager &&) = delete; - - [[nodiscard]] auto isValidQubitId(SimQubitIdType s_idx) -> bool - { - return this->qubits_map.contains(s_idx); - } - - [[nodiscard]] auto isValidQubitId(const std::vector &ss_idx) -> bool - { - return std::all_of(ss_idx.begin(), ss_idx.end(), - [this](SimQubitIdType s) { return isValidQubitId(s); }); - } - - [[nodiscard]] auto getAllQubitIds() -> std::vector - { - std::vector ids; - ids.reserve(this->qubits_map.size()); - for (const auto &it : this->qubits_map) { - ids.push_back(it.first); - } - - return ids; - } - - [[nodiscard]] auto getDeviceId(SimQubitIdType s_idx) -> DevQubitIdType - { - RT_FAIL_IF(!isValidQubitId(s_idx), "Invalid device qubit index"); - - return this->qubits_map[s_idx]; - } - - auto getDeviceIds(const std::vector &ss_idx) -> std::vector - { - std::vector dd_idx; - dd_idx.reserve(ss_idx.size()); - for (const auto &s : ss_idx) { - dd_idx.push_back(getDeviceId(s)); - } - return dd_idx; - } - - [[nodiscard]] auto getSimulatorId(DevQubitIdType d_idx) -> SimQubitIdType - { - auto s_idx = std::find_if(this->qubits_map.begin(), this->qubits_map.end(), - [&d_idx](auto &&p) { return p.second == d_idx; }); - - RT_FAIL_IF(s_idx == this->qubits_map.end(), "Invalid simulator qubit index"); - - return s_idx->first; - } - - [[nodiscard]] auto Allocate(DevQubitIdType d_next_idx) -> SimQubitIdType - { - this->qubits_map[this->next_idx++] = d_next_idx; - return this->next_idx - 1; - } - - auto AllocateRange(DevQubitIdType start_idx, size_t size) -> std::vector - { - std::vector ids; - ids.reserve(size); - for (DevQubitIdType i = start_idx; i < start_idx + size; i++) { - ids.push_back(this->next_idx); - this->qubits_map[this->next_idx++] = i; - } - return ids; - } - - void Release(SimQubitIdType s_idx) - { - _update_qubits_mapfrom(_remove_simulator_qubit_id(s_idx)); - } - - void ReleaseAll() - { - // Release all qubits by clearing the map. - this->qubits_map.clear(); - } -}; -} // namespace Catalyst::Runtime diff --git a/pennylane_qrack/qrack_device.py b/pennylane_qrack/qrack_device.py index 4812ee7..035718e 100644 --- a/pennylane_qrack/qrack_device.py +++ b/pennylane_qrack/qrack_device.py @@ -176,7 +176,8 @@ def __init__(self, wires=0, shots=None, **kwargs): self._state = QrackSimulator(self.num_wires, isTensorNetwork=False, **kwargs) def define_wire_map(self, wires): - consecutive_wires = Wires(range(self.num_wires)) + consecutive_wires = Wires(range(self.num_wires - 1, -1, -1)) + wire_map = zip(wires, consecutive_wires) return OrderedDict(wire_map) @@ -624,7 +625,7 @@ def analytic_probability(self, wires=None): if self._state is None: return None - all_probs = self._abs(self.state) ** 2 + all_probs = _reverse_state(self._abs(self.state) ** 2) prob = self.marginal_prob(all_probs, wires) if (not "QRACK_FPPOW" in os.environ) or (6 > int(os.environ.get("QRACK_FPPOW"))): @@ -670,7 +671,7 @@ def generate_samples(self): ) samples = np.array( - self._state.measure_shots(list(range(self.num_wires)), self.shots) + self._state.measure_shots(list(range(self.num_wires - 1, -1, -1)), self.shots) ) self._samples = QubitDevice.states_to_binary(samples, self.num_wires) @@ -679,7 +680,7 @@ def generate_samples(self): @property def state(self): # returns the state after all operations are applied - return _reverse_state(self._state.out_ket()) + return self._state.out_ket() def reset(self): self._state.reset_all() From 57370ce0a9dc25c5f7a2bc074fce4308347ab7b1 Mon Sep 17 00:00:00 2001 From: WrathfulSpatula Date: Mon, 3 Jun 2024 10:31:21 -0400 Subject: [PATCH 25/27] Dynamic _reverse_state() --- pennylane_qrack/qrack_device.py | 36 ++++++++++++++++----------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/pennylane_qrack/qrack_device.py b/pennylane_qrack/qrack_device.py index 035718e..fbc86e2 100644 --- a/pennylane_qrack/qrack_device.py +++ b/pennylane_qrack/qrack_device.py @@ -43,19 +43,6 @@ tolerance = 1e-10 -def _reverse_state(state_vector): - """Reverse the qubit order for a vector of amplitudes. - Args: - state_vector (iterable[complex]): vector containing the amplitudes - Returns: - list[complex] - """ - state_vector = np.array(state_vector) - N = int(math.log2(len(state_vector))) - reversed_state = state_vector.reshape([2] * N).T.flatten() - return reversed_state - - class QrackDevice(QubitDevice): """Qrack device""" @@ -176,11 +163,16 @@ def __init__(self, wires=0, shots=None, **kwargs): self._state = QrackSimulator(self.num_wires, isTensorNetwork=False, **kwargs) def define_wire_map(self, wires): - consecutive_wires = Wires(range(self.num_wires - 1, -1, -1)) - + consecutive_wires = Wires(range(self.num_wires)) wire_map = zip(wires, consecutive_wires) return OrderedDict(wire_map) + def _reverse_state(self): + end = self.num_wires - 1 + mid = self.num_wires >> 1 + for i in range(mid): + self._state.swap(i, end - i) + def apply(self, operations, **kwargs): rotations = kwargs.get("rotations", []) @@ -242,11 +234,13 @@ def _apply_qubit_state_vector(self, op): if not np.isclose(np.linalg.norm(input_state, 2), 1.0, atol=tolerance): raise ValueError("Sum of amplitudes-squared does not equal one.") + self._reverse_state() if len(wires) != self.num_wires or sorted(wires, reverse=True) != wires: input_state = self._expand_state(input_state, wires) # call qrack' state initialization self._state.in_ket(input_state) + self._reverse_state() def _apply_basis_state(self, op): """Initialize a basis state""" @@ -625,7 +619,7 @@ def analytic_probability(self, wires=None): if self._state is None: return None - all_probs = _reverse_state(self._abs(self.state) ** 2) + all_probs = self._abs(self.state) ** 2 prob = self.marginal_prob(all_probs, wires) if (not "QRACK_FPPOW" in os.environ) or (6 > int(os.environ.get("QRACK_FPPOW"))): @@ -650,7 +644,10 @@ def expval(self, observable, **kwargs): if None not in b: q = self.map_wires(observable.wires) - return self._state.pauli_expectation(q, b) + self._reverse_state() + o = self._state.pauli_expectation(q, b) + self._reverse_state() + return o # exact expectation value if callable(observable.eigvals): @@ -680,7 +677,10 @@ def generate_samples(self): @property def state(self): # returns the state after all operations are applied - return self._state.out_ket() + self._reverse_state() + o = self._state.out_ket() + self._reverse_state() + return o def reset(self): self._state.reset_all() From 578ad15ca8597ae2f4a634b5bcc3874db6c0b8d6 Mon Sep 17 00:00:00 2001 From: WrathfulSpatula Date: Mon, 3 Jun 2024 10:36:25 -0400 Subject: [PATCH 26/27] Fix observables --- pennylane_qrack/qrack_device.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pennylane_qrack/qrack_device.py b/pennylane_qrack/qrack_device.py index fbc86e2..d8bf2bc 100644 --- a/pennylane_qrack/qrack_device.py +++ b/pennylane_qrack/qrack_device.py @@ -644,10 +644,7 @@ def expval(self, observable, **kwargs): if None not in b: q = self.map_wires(observable.wires) - self._reverse_state() - o = self._state.pauli_expectation(q, b) - self._reverse_state() - return o + return self._state.pauli_expectation(q, b) # exact expectation value if callable(observable.eigvals): From fcc43d896ed3ec140ce2c3d6b23615f5c51cf658 Mon Sep 17 00:00:00 2001 From: WrathfulSpatula Date: Mon, 3 Jun 2024 10:44:01 -0400 Subject: [PATCH 27/27] Don't override define_wire_map() --- pennylane_qrack/qrack_device.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pennylane_qrack/qrack_device.py b/pennylane_qrack/qrack_device.py index d8bf2bc..8394afa 100644 --- a/pennylane_qrack/qrack_device.py +++ b/pennylane_qrack/qrack_device.py @@ -162,11 +162,6 @@ def __init__(self, wires=0, shots=None, **kwargs): else: self._state = QrackSimulator(self.num_wires, isTensorNetwork=False, **kwargs) - def define_wire_map(self, wires): - consecutive_wires = Wires(range(self.num_wires)) - wire_map = zip(wires, consecutive_wires) - return OrderedDict(wire_map) - def _reverse_state(self): end = self.num_wires - 1 mid = self.num_wires >> 1