Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unit tests, wires, and endianness #6

Merged
merged 18 commits into from
Jun 5, 2024
Merged
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
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
Loading