Skip to content

Commit

Permalink
Merge pull request #6 from unitaryfund/wires_and_endianness
Browse files Browse the repository at this point in the history
Unit tests, wires, and endianness
  • Loading branch information
WrathfulSpatula authored Jun 5, 2024
2 parents f100c29 + 2c1f3d5 commit 176e3df
Show file tree
Hide file tree
Showing 6 changed files with 231 additions and 141 deletions.
18 changes: 9 additions & 9 deletions pennylane_qrack/QrackDeviceConfig.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,6 @@ CRY = { properties = [ "controllable", "invertible" ] }
CRZ = { properties = [ "controllable", "invertible" ] }
CRot = { properties = [ "controllable", "invertible" ] }
U3 = { properties = [ "controllable", "invertible" ] }
U2 = { properties = [ "controllable", "invertible" ] }
U1 = { properties = [ "controllable", "invertible" ] }
MultiControlledX = { properties = [ "controllable", "invertible" ] }
Identity = { properties = [ "controllable", "invertible" ] }

Expand Down Expand Up @@ -78,6 +76,8 @@ Identity = { properties = [ "controllable", "invertible" ] }
# IsingYY = {}
# IsingZZ = {}
# IsingXY = {}
# U2 = {}
# U1 = {}
# QFT = {}

# Gates which should be translated to QubitUnitary
Expand Down Expand Up @@ -132,19 +132,19 @@ wires = "wires"
# Number of shots per job
shots = "shots"
# Use "hybrid" stabilizer optimization? (Default is "true"; non-Clifford circuits will fall back to near-Clifford or universal simulation)
is_hybrid_stabilizer = "is_hybrid_stabilizer"
is_hybrid_stabilizer = "isStabilizerHybrid"
# Use "tensor network" optimization? (Default is "false"; prevents dynamic qubit de-allocation; might function sub-optimally with "hybrid" stabilizer enabled)
is_tensor_network = "is_tensor_network"
is_tensor_network = "isTensorNetwork"
# Use Schmidt decomposition optimizations? (Default is "true")
is_schmidt_decomposed = "is_schmidt_decomposed"
is_schmidt_decomposed = "isSchmidtDecompose"
# Distribute Schmidt-decomposed qubit subsystems to multiple GPUs or accelerators, if available? (Default is "true"; mismatched device capacities might hurt overall performance)
is_schmidt_decomposition_parallel = "is_schmidt_decomposition_parallel"
is_schmidt_decomposition_parallel = "isSchmidtDecomposeMulti"
# Use "quantum binary decision diagram" ("QBDD") methods? (Default is "false"; note that QBDD is CPU-only)
is_qbdd = "is_qbdd"
is_qbdd = "isBinaryDecisionTree"
# Use GPU acceleration? (Default is "true")
is_gpu = "is_gpu"
is_gpu = "isOpenCL"
# Allocate GPU buffer from general host heap? (Default is "false"; "true" might improve performance or reliability in certain cases, like if using an Intel HD as accelerator)
is_host_pointer = "is_host_pointer"
is_host_pointer = "isHostPointer"

# In the above example, a dictionary will be constructed at run time.
# The dictionary will contain the string key "option_key" and its value
Expand Down
103 changes: 43 additions & 60 deletions pennylane_qrack/qrack_device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,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") || (name == "U1")) {
} else if (name == "PhaseShift") {
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);
Expand All @@ -169,33 +169,28 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice {
qsim->RZ(inverse ? -params[0U] : params[0U], target);
}
} else if (name == "Rot") {
const Qrack::real1 phi = inverse ? -params[2U] : params[0U];
const Qrack::real1 theta = inverse ? -params[1U] : params[1U];
const Qrack::real1 omega = inverse ? -params[0U] : params[2U];
const Qrack::real1 cos0 = (Qrack::real1)cos(theta / 2);
const Qrack::real1 sin0 = (Qrack::real1)sin(theta / 2);
const Qrack::complex expP = exp(Qrack::I_CMPLX * (phi + omega) / (2 * ONE_R1));
const Qrack::complex expM = exp(Qrack::I_CMPLX * (phi - omega) / (2 * ONE_R1));
const Qrack::complex mtrx[4U]{
cos0 / expP, -sin0 * expM,
sin0 / expM, cos0 * expP
};
for (const bitLenInt& target : wires) {
if (inverse) {
qsim->RZ(-params[2U], target);
qsim->RY(-params[1U], target);
qsim->RZ(-params[0U], target);
} else {
qsim->RZ(params[0U], target);
qsim->RY(params[1U], target);
qsim->RZ(params[2U], target);
}
qsim->Mtrx(mtrx, target);
}
} else if (name == "U3") {
for (const bitLenInt& target : wires) {
if (inverse) {
qsim->U(target, -params[0U], -params[1U], -params[2U]);
qsim->U(target, -params[0U], -params[2U], -params[1U]);
} else {
qsim->U(target, params[0U], params[1U], params[2U]);
}
}
} else if (name == "U2") {
for (const bitLenInt& target : wires) {
if (inverse) {
qsim->U(target, -Qrack::PI_R1 / 2, -params[0U], -params[1U]);
} else {
qsim->U(target, Qrack::PI_R1 / 2, params[0U], params[1U]);
}
}
} else if (name != "Identity") {
throw std::domain_error("Unrecognized gate name: " + name);
}
Expand Down Expand Up @@ -299,7 +294,7 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice {
qsim->X(control_wires[i]);
}
}
} else if ((name == "PhaseShift") || (name == "U1") || (name == "ControlledPhaseShift") || (name == "CPhase")) {
} else if ((name == "PhaseShift") || (name == "ControlledPhaseShift") || (name == "CPhase")) {
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);
Expand Down Expand Up @@ -336,9 +331,9 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice {
qsim->UCPhase(control_wires, conj(bottomRight), bottomRight, target, controlPerm);
}
} else if ((name == "Rot") || (name == "CRot")) {
const Qrack::real1 phi = inverse ? -params[0U] : params[0U];
const Qrack::real1 phi = inverse ? -params[2U] : params[0U];
const Qrack::real1 theta = inverse ? -params[1U] : params[1U];
const Qrack::real1 omega = inverse ? -params[2U] : params[2U];
const Qrack::real1 omega = inverse ? -params[0U] : params[2U];
const Qrack::real1 cos0 = (Qrack::real1)cos(theta / 2);
const Qrack::real1 sin0 = (Qrack::real1)sin(theta / 2);
const Qrack::complex expP = exp(Qrack::I_CMPLX * (phi + omega) / (2 * ONE_R1));
Expand All @@ -351,34 +346,21 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice {
qsim->UCMtrx(control_wires, mtrx, target, controlPerm);
}
} else if (name == "U3") {
const Qrack::real1 theta = inverse ? -params[0U] : params[0U];
const Qrack::real1 phi = inverse ? -params[1U] : params[1U];
const Qrack::real1 lambda = inverse ? -params[2U] : params[2U];
const Qrack::real1 cos0 = (Qrack::real1)cos(theta / 2);
const Qrack::real1 sin0 = (Qrack::real1)sin(theta / 2);
const Qrack::real1 th = params[0U];
const Qrack::real1 ph = params[1U];
const Qrack::real1 lm = params[2U];
const Qrack::real1 cos0 = (Qrack::real1)cos(th / 2);
const Qrack::real1 sin0 = (Qrack::real1)sin(th / 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))
Qrack::complex(cos0, ZERO_R1), sin0 * Qrack::complex((Qrack::real1)(-cos(lm)),
(Qrack::real1)(-sin(lm))),
sin0 * Qrack::complex((Qrack::real1)cos(ph), (Qrack::real1)sin(ph)),
cos0 * Qrack::complex((Qrack::real1)cos(ph + lm), (Qrack::real1)sin(ph + lm))
};
Qrack::complex iMtrx[4U];
Qrack::inv2x2(mtrx, iMtrx);
for (const bitLenInt& target : wires) {
qsim->UCMtrx(control_wires, mtrx, target, controlPerm);
}
} else if (name == "U2") {
const Qrack::real1 theta = (inverse ? -Qrack::PI_R1 : Qrack::PI_R1) / 2;
const Qrack::real1 phi = inverse ? -params[0U] : params[0U];
const Qrack::real1 lambda = inverse ? -params[1U] : params[1U];
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);
qsim->UCMtrx(control_wires, inverse ? iMtrx : mtrx, target, controlPerm);
}
} else if (name != "Identity") {
throw std::domain_error("Unrecognized gate name: " + name);
Expand Down Expand Up @@ -423,6 +405,12 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice {
kwargs.erase(0, pos + 1U);

if (key == "'wires'") {
// Handle if empty
// We look for ',' or npos, to respect other Wires value kwargs
if (kwargs.find("<Wires = []>") != kwargs.find("<Wires = []>,")) {
continue;
}

// Handle if integer
pos = kwargs.find(",");
bool isInt = true;
Expand Down Expand Up @@ -537,7 +525,9 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice {

auto AllocateQubit() -> QubitIdType override {
if (allocated_qubits >= qubit_map.size()) {
throw std::runtime_error("Catalyst has requested more qubits than exist in device. (Set your wires count high enough, for the device.)");
throw std::runtime_error("Catalyst has requested more qubits than exist in device, with "
+ std::to_string(allocated_qubits) + " allocated qubits. "
+ "(Set your wires count high enough, for the device.)");
}
auto it = qubit_map.begin();
std::advance(it, allocated_qubits);
Expand Down Expand Up @@ -758,6 +748,7 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice {
{
RT_FAIL_IF((size_t)Qrack::pow2(wires.size()) != p.size(), "Invalid size for the pre-allocated probabilities vector");
auto &&dev_wires = getDeviceWires(wires);
std::reverse(dev_wires.begin(), dev_wires.end());
#if FPPOW == 6
qsim->ProbBitsAll(dev_wires, &(*(p.begin())));
#else
Expand All @@ -772,11 +763,9 @@ 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<bitCapInt> qPowers(qsim->GetQubitCount());
for (bitLenInt i = 0U; i < qPowers.size(); ++i) {
qPowers[i] = Qrack::pow2(i);
qPowers[i] = Qrack::pow2(qPowers.size() - (i + 1U));
}
auto q_samples = qsim->MultiShotMeasureMask(qPowers, shots);

Expand All @@ -787,8 +776,6 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice {
*(samplesIter++) = bi_to_double((sample >> wire) & 1U);
}
}

reverseWires();
}
void PartialSample(DataView<double, 2> &samples, const std::vector<QubitIdType> &wires, size_t shots) override
{
Expand All @@ -799,7 +786,7 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice {
auto &&dev_wires = getDeviceWires(wires);
std::vector<bitCapInt> qPowers(dev_wires.size());
for (size_t i = 0U; i < qPowers.size(); ++i) {
qPowers[i] = Qrack::pow2((bitLenInt)dev_wires[i]);
qPowers[i] = Qrack::pow2(dev_wires[qPowers.size() - (i + 1U)]);
}
auto q_samples = qsim->MultiShotMeasureMask(qPowers, shots);

Expand All @@ -822,11 +809,9 @@ 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<bitCapInt> qPowers(numQubits);
for (bitLenInt i = 0U; i < qPowers.size(); ++i) {
qPowers[i] = Qrack::pow2(i);
qPowers[i] = Qrack::pow2(qPowers.size() - (i + 1U));
}
auto q_samples = qsim->MultiShotMeasureMask(qPowers, shots);

Expand All @@ -842,8 +827,6 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice {
}
++counts(static_cast<size_t>(basisState.to_ulong()));
}

reverseWires();
}

void PartialCounts(DataView<double, 1> &eigvals, DataView<int64_t, 1> &counts,
Expand All @@ -860,7 +843,7 @@ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice {
auto &&dev_wires = getDeviceWires(wires);
std::vector<bitCapInt> qPowers(dev_wires.size());
for (size_t i = 0U; i < qPowers.size(); ++i) {
qPowers[i] = Qrack::pow2(dev_wires[i]);
qPowers[i] = Qrack::pow2(dev_wires[qPowers.size() - (i + 1U)]);
}
auto q_samples = qsim->MultiShotMeasureMask(qPowers, shots);

Expand Down
63 changes: 10 additions & 53 deletions pennylane_qrack/qrack_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,6 @@ class QrackDevice(QubitDevice):
"C(SX)",
"PhaseShift",
"C(PhaseShift)",
"U1",
"C(U1)",
"U2",
"C(U2)",
"U3",
"C(U3)",
"Rot",
Expand Down Expand Up @@ -342,16 +338,17 @@ def _apply_gate(self, op):
theta = par[1]
omega = par[2]
if ".inv" in opname:
phi = -phi
tmp = phi
phi = -omega
theta = -theta
omega = -omega
omega = -phi
c = math.cos(theta / 2)
s = math.sin(theta / 2)
mtrx = [
cmath.exp(-0.5j * (phi + omega)) * c,
cmath.exp(0.5j * (phi - omega)) * s,
cmath.exp(-0.5j * (phi - omega)) * s,
cmath.exp(0.5j * (phi + omega)) * c,
cmath.exp(0.5j * (phi + omega)) * np.cos(theta / 2),
]
self._state.mcmtrx(device_wires.labels[:-1], mtrx, device_wires.labels[-1])
elif opname in ["SWAP", "SWAP.inv"]:
Expand Down Expand Up @@ -472,21 +469,21 @@ def _apply_gate(self, op):
[(1 + 1j) / 2, (1 - 1j) / 2, (1 - 1j) / 2, (1 + 1j) / 2],
device_wires.labels[-1],
)
elif opname in ["PhaseShift", "U1"]:
elif opname == "PhaseShift":
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"]:
elif opname == "PhaseShift.inv":
ip_mtrx = [1, 0, 0, cmath.exp(1j * -par[0])]
for label in device_wires.labels:
self._state.mtrx(ip_mtrx, label)
elif opname in ["C(PhaseShift)", "C(U1)"]:
elif opname == "C(PhaseShift)":
self._state.mtrx(
device_wires.labels[:-1],
[1, 0, 0, cmath.exp(1j * par[0])],
device_wires.labels[-1],
)
elif opname in ["C(PhaseShift).inv", "C(U1).inv"]:
elif opname == "C(PhaseShift).inv":
self._state.mtrx(
device_wires.labels[:-1],
[1, 0, 0, cmath.exp(1j * -par[0])],
Expand Down Expand Up @@ -514,52 +511,12 @@ def _apply_gate(self, op):
[1, 0, 0, cmath.exp(1j * -par[0])],
device_wires.labels[-1],
)
elif opname == "U2":
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":
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],
[
1,
cmath.exp(1j * par[1]),
cmath.exp(1j * par[0]),
cmath.exp(1j * (par[0] + par[1])),
],
device_wires.labels[-1],
)
elif opname == "C(U2).inv":
self._state.mcmtrx(
device_wires.labels[:-1],
[
1,
cmath.exp(1j * -par[1]),
cmath.exp(1j * -par[0]),
cmath.exp(1j * (-par[0] - par[1])),
],
device_wires.labels[-1],
)
elif opname == "U3":
for label in device_wires.labels:
self._state.u(label, par[0], par[1], par[2])
elif opname == "U3.inv":
for label in device_wires.labels:
self._state.u(label, -par[0], -par[1], -par[2])
self._state.u(label, -par[0], -par[2], -par[1])
elif opname == "Rot":
for label in device_wires.labels:
self._state.r(Pauli.PauliZ, par[0], label)
Expand All @@ -583,8 +540,8 @@ def _apply_gate(self, op):
device_wires.labels[:-1],
device_wires.labels[-1],
-par[0],
-par[1],
-par[2],
-par[1],
)
elif opname not in [
"Identity",
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pennylane>=0.32
pennylane-catalyst>=0.6
pyqrack>=0.13.0
numpy~=1.16
scikit-build
Loading

0 comments on commit 176e3df

Please sign in to comment.