Description
Environment information
Dates back to the very origin of the (QM)DD package.
Description
The code down below demonstrates an instability in the matrix DD normalization.
The script just creates a bunch of Hadamards and then reverses them.
Up until (including) 81 qubits, this produces results as expected. The result is equal to the identity and the top edge weight is one.
For n=81+k
, the top edge weight will be sqrt(2)^k
... Meaning that, for example, at n=128
the top edge weight is equal to roughly a million 😅
The main reason for this is that the first bunch of H's generates a DD that has a top edge weight of (1/sqrt(2))^n
until n
hits 81
where the default numerical tolerance kicks in and the value remains constant at 6.4311e-13
, which is just above the default tolerance. Now each qubit above 81
in the second series of H's induces an error of sqrt(2)
.
In extreme cases, this may even overflow the range of double. Particularly, for n>=2129
, sqrt(2)^(n-81)
overflows the largest possible double precision floating point value (1.7976931348623158e+308
).
Possible consequences of the above include
NaN
values after overflows- DDs collapsing to the all-zero DD (because the top-weight underflows the tolerance) and subsequent segfaults
- Performance problems due to polluting the 1.0 unique table bucket.
Expected behavior
Matrix normalization should be stable and not induce these kinds of errors.
This will most surely require the development of a new normalization scheme for matrix DDs (probably inspired by the one currently used for vectors), which, from past experience, might not be that easy to get right.
How to Reproduce
Run the following test
TEST(DDPackageTest, MatrixNormalizationRegression) {
const qc::Qubit nqubits = 100U;
for (auto q = 1U; q <= nqubits; ++q) {
auto dd = std::make_unique<dd::Package<>>(q);
auto f = dd->makeIdent();
for (qc::Qubit i = 0; i < q; ++i) {
const auto h = dd->makeGateDD(dd::H_MAT, i);
f = dd->multiply(f, h);
}
for (qc::Qubit i = 0; i < q; ++i) {
const auto h = dd->makeGateDD(dd::H_MAT, q - i - 1);
f = dd->multiply(f, h);
}
std::cout << "Qubits: " << q << ", ";
std::cout << "Top edge weight: " << f.w.toString(false) << ", ";
std::cout << "Is identity: " << f.isIdentity(false) << "\n";
const auto w = static_cast<dd::ComplexValue>(f.w);
EXPECT_TRUE(w.approximatelyEquals({1., 0.}));
EXPECT_TRUE(f.isIdentity(false));
}
}