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

🐛 Numerical instability in DD package for large-scale systems #575

Open
burgholzer opened this issue Mar 27, 2024 · 3 comments
Open

🐛 Numerical instability in DD package for large-scale systems #575

burgholzer opened this issue Mar 27, 2024 · 3 comments
Labels
bug Something isn't working c++ Anything related to C++ code DD Anything related to the DD package help wanted Extra attention is needed

Comments

@burgholzer
Copy link
Member

burgholzer commented Mar 27, 2024

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));
  }
}
@burgholzer burgholzer added bug Something isn't working help wanted Extra attention is needed DD Anything related to the DD package c++ Anything related to C++ code labels Mar 27, 2024
@burgholzer burgholzer added this to the DD Package Improvements milestone Mar 27, 2024
@burgholzer burgholzer changed the title 🐛 Matrix DD normalization is instable for large-scale systems 🐛 Numerical instability in DD package for large-scale systems Jun 10, 2024
@burgholzer
Copy link
Member Author

burgholzer commented Jun 10, 2024

It turns out that this issue is not limited to the matrix DD case and its not solely the matrix normalization that is at fault.
A similar phenomenon also happens when conducting simulations of an even simpler circuit, i.e.

OPENQASM 2.0;
include "qelib1.inc";

qreg q[81];

h q;

works as expected, but, starting from $81+k$ qubits with $k&gt;0$, the top edge weight starts to grow exponentially with a value of $\sqrt{2}^k$.

The main cause for this issue is the tolerance that is being employed in the package.
The tolerance defaults to $2^{-52} * 1024 = 2^{-42} = 2.274e^{-13}$.
Now,

  • $\frac{1}{\sqrt{2}}^{81} = 6.431e^{-13}$,
  • $\frac{1}{\sqrt{2}}^{82} = 4.547e^{-13} = 2^{-41}$,
  • $\frac{1}{\sqrt{2}}^{83} = 3.216e^{-13}$,
  • $\frac{1}{\sqrt{2}}^{84} = 2.274e^{-13} = 2^{-42}$.

So, at $84$ qubits, each individual amplitude after applying the Hadamard transform is as small as the tolerance itself. Such a number would be considered approximatelyZero, which probably leads to all kinds of errors. These errors are likely to be more pronounced in the matrix-only case, given that the normalization scheme explicitly pulls out the common factor of $\frac{1}{\sqrt{2}}$ from each Hadamard gate and makes that the new top edge-weight.

Two questions that pop up:

  • Why does this also affect the vector decision diagrams where the normalization scheme does not propagate the common factor through the decision diagram?
  • Why does the error already show at $82$ qubits and not only at $84$ qubits and beyond?

I'll try to address them in a follow-up comment.

@burgholzer
Copy link
Member Author

Ok. Just noticed, that this might, in fact, be only due to the matrix normalization.
The following circuit works smoothly

OPENQASM 2.0;
include "qelib1.inc";

qreg q[81];
qreg k[1];

h q;
h k;

The reason for the circuit from the previous comment triggering the issue is that the statement

h q;

Is translated to a CompoundOperation in the resulting QuantumComputation.
During simulation getDD(compOp,...) builds the full matrix DD for the compound operation before applying it to the state.
That means, if an error is introduced in the MxM multiplications that form the overall DD for the Hadamard transform, this error is propagated to the state as a result of the subsequent MxV multiplication.

That eliminates the first question and reduces the scope for the error hunt.

@burgholzer
Copy link
Member Author

As for why the error already shows at $82$ qubits:
$$\frac{1}{\sqrt{2}}^{81} - \frac{1}{\sqrt{2}}^{82} = 1.884e^{-13} &lt; 2.274e^{-13} = 2^{-42} = \varepsilon$$
Thus, the lookup of the top edge weight for the matrix DD after applying the $82^{nd}$ Hadamard gate onward will just return $\frac{1}{\sqrt{2}}^{81}$ and, hence, incur an error of $\frac{1}{\sqrt{2}}$.

This error could, in principle, be fixed by adopting a new normalization scheme that is similar to the vector normalization scheme and does not propagate the greatest common divisor upward.

Even then, one has to be very careful as, during DD addition, all edge weights along a path to a terminal are multiplied together before the addition is carried out and the resulting edge weight is put through normalization and a unique table lookup.
Extra care has to be taken that the multiplication of the edge weights as well as the addition itself is performed with higher numerical accuracy without interfering via the tolerance.
Anyone investigating this issue should carefully consider the CachedEdge normalization routines and make sure that these operations happen with the highest possible accuracy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working c++ Anything related to C++ code DD Anything related to the DD package help wanted Extra attention is needed
Projects
Status: In Progress
Status: Todo
Development

No branches or pull requests

1 participant