Skip to content
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
46 changes: 46 additions & 0 deletions .agent/plans/dd-abi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Concrete DD operations

Status: complete.

## Outcome and scope

Node-specific DD operations are concrete overloads in `dd/Edge.hpp` and
`dd/CachedEdge.hpp`. C++ callers use `dd::getVector(edge)`,
`dd::getMatrix(edge, numQubits)`, and the corresponding free operations. Generic
`Edge<Node>` and `CachedEdge<Node>` handles retain their layouts and
node-independent operations. Python methods and array ownership stay the same.
The migration covers Package dispatch, DD and MLIR tests, DDSIM, bindings, and
`docs/cpp_api.md`.

## Decisions

Constrained exported template members have different symbol names across
supported GCC and Clang versions. Concrete overloads remove that constraint
mangling boundary without compatibility wrappers or new traits. Numerical
algorithms and error handling stay unchanged. File-local traversal helpers pass
the callback by reference; the public matrix traversal owns one callback copy.
Cached-edge class instantiations are unnecessary because all remaining members
are defined in the header. Out-of-line edge and hash instantiations remain.

## Validation

- Native release build and `ctest --preset release --parallel 4`: 3,950 tests
passed; the superconducting device's unsupported job-ID property caused one
expected skip. This includes the DD, MLIR, QIR, and DDSIM consumers. The build
used GCC 13 and the assertion-free LLVM/MLIR 23.1.2 SDK without IPO.
- `uv run --no-sync pytest test/python -n4`: all 1,920 tests passed with both
bundled devices enabled. `uvx nox -s stubs` produced no tracked stub changes.
- `uvx nox -s lint` and `uvx nox -s cpp-lint`: passed. C++ lint inspected every
changed source file, including the full contents outside changed lines.
- Doxygen 1.17 built the native reference with the repository's Doxyfile and
QDMI inventory configuration from `docs/_ext/cpp_api.py`. The C++ guide's GHZ
example compiled and produced the expected amplitudes.
- Unoptimized GCC 13 and Clang 23 local consumers compiled and ran. Compile-time
probes accepted the matching vector, matrix, and cached-edge overloads and
rejected mismatched edge kinds. Both public edge headers compiled alone.

## Limits

Installed cross-compiler and LTO qualification belongs to the release-build
validation that accompanies this change. The complete Sphinx documentation build
and external link check were not run for this migration.
13 changes: 7 additions & 6 deletions bindings/dd/register_matrix_dds.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
#include "nanobind/stl/string.h" // NOLINT(misc-include-cleaner)
#include "nanobind/stl/vector.h" // NOLINT(misc-include-cleaner)

#include <cmath>
#include <complex>
#include <cstddef>
#include <memory>
Expand Down Expand Up @@ -51,8 +50,8 @@ Matrix getMatrix(const dd::mEdge& m, const size_t numQubits,

const auto dim = 1ULL << numQubits;
auto dataPtr = std::make_unique<dd::CVec>(dim * dim);
m.traverseMatrix(
std::complex<dd::fp>{1., 0.}, 0ULL, 0ULL,
dd::traverseMatrix(
m, std::complex<dd::fp>{1., 0.}, 0ULL, 0ULL,
[&dataPtr, dim](const std::size_t i, const std::size_t j,
const std::complex<dd::fp>& c) {
(*dataPtr)[(i * dim) + j] = c;
Expand All @@ -76,7 +75,9 @@ void registerMatrixDDs(const nb::module_& m) {
mat.def("is_zero_terminal", &dd::mEdge::isZeroTerminal,
"Check if the DD is a zero terminal node.");

mat.def("is_identity", &dd::mEdge::isIdentity, "up_to_global_phase"_a = true,
mat.def("is_identity",
nb::overload_cast<const dd::mEdge&, bool>(&dd::isIdentity),
"up_to_global_phase"_a = true,
R"pb(Check if the DD represents the identity matrix.

Args:
Expand All @@ -89,8 +90,8 @@ void registerMatrixDDs(const nb::module_& m) {
"Get the size of the DD by traversing it once.");

mat.def("get_entry",
nb::overload_cast<size_t, size_t, size_t>(&dd::mEdge::getValueByIndex,
nb::const_),
nb::overload_cast<const dd::mEdge&, size_t, size_t, size_t>(
&dd::getValueByIndex),
"num_qubits"_a, "row"_a, "col"_a,
"Get the entry of the matrix by row and column index.");

Expand Down
6 changes: 3 additions & 3 deletions bindings/dd/register_vector_dds.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*/

#include "dd/DDDefinitions.hpp"
#include "dd/Edge.hpp"
#include "dd/Node.hpp"

#include "register_dd_export.hpp"
Expand All @@ -19,7 +20,6 @@
#include "nanobind/stl/string.h" // NOLINT(misc-include-cleaner)
#include "nanobind/stl/vector.h" // NOLINT(misc-include-cleaner)

#include <cmath>
#include <complex>
#include <cstddef>
#include <limits>
Expand All @@ -35,7 +35,7 @@ using Vector = nb::ndarray<nb::numpy, std::complex<dd::fp>, nb::ndim<1>>;

// NOLINTNEXTLINE(misc-use-internal-linkage)
Vector getVector(const dd::vEdge& v, const dd::fp threshold) {
auto dataPtr = std::make_unique<dd::CVec>(v.getVector(threshold));
auto dataPtr = std::make_unique<dd::CVec>(dd::getVector(v, threshold));
auto* const data = dataPtr->data();
const auto size = dataPtr->size();
const nb::capsule owner(dataPtr.get(), [](void* ptr) noexcept {
Expand Down Expand Up @@ -85,7 +85,7 @@ void registerVectorDDs(const nb::module_& m) {
}
return v.getValueByPath(numQubits, decisions);
}
return v.getValueByIndex(index);
return dd::getValueByIndex(v, index);
},
"key"_a, "Get the amplitude of a basis state by index.");

Expand Down
6 changes: 3 additions & 3 deletions bindings/mlir/register_mlir.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -639,7 +639,7 @@ sampleQCO(const mlir::QCOProgram& program, size_t shots, uint64_t seed) {
"dense statevector dimensions exceed addressable memory");
}
}
auto dataPtr = std::make_unique<dd::CVec>(state.getVector());
auto dataPtr = std::make_unique<dd::CVec>(dd::getVector(state));
auto* const data = dataPtr->data();
const auto size = dataPtr->size();
const nb::capsule owner(dataPtr.get(), [](void* ptr) noexcept {
Expand All @@ -662,8 +662,8 @@ sampleQCO(const mlir::QCOProgram& program, size_t shots, uint64_t seed) {
}
auto dataPtr = std::make_unique<dd::CVec>(dim * dim);
auto* const data = dataPtr->data();
matrix.traverseMatrix(
std::complex<dd::fp>{1., 0.}, 0ULL, 0ULL,
dd::traverseMatrix(
matrix, std::complex<dd::fp>{1., 0.}, 0ULL, 0ULL,
[data, dim](size_t i, size_t j, const std::complex<dd::fp>& value) {
data[i * dim + j] = value;
},
Expand Down
3 changes: 2 additions & 1 deletion docs/cpp_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Create a two-qubit GHZ state, which is a Bell state, and print its amplitudes.
Save this as `main.cpp`:

```cpp
#include "dd/Edge.hpp"
#include "dd/Package.hpp"
#include "dd/StateGeneration.hpp"

Expand All @@ -24,7 +25,7 @@ Save this as `main.cpp`:
int main() {
dd::Package package(2);
const auto state = dd::makeGHZState(2, package);
for (const auto amplitude : state.getVector()) {
for (const auto amplitude : dd::getVector(state)) {
std::cout << amplitude << '\n';
}
package.decRef(state);
Expand Down
69 changes: 33 additions & 36 deletions include/mqt-core/dd/CachedEdge.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,44 +90,41 @@ template <typename Node> struct CachedEdge {
[[nodiscard]] constexpr bool isTerminal() const {
return Node::isTerminal(p);
}
};

/// Get a normalized vector DD from a fresh node and a list of edges.
/// @param p the fresh node
/// @param e the list of edges that form the successor nodes
/// @param mm a reference to the memory manager (for returning unused nodes)
/// @param cn a reference to the complex number manager (for adding new
/// complex numbers)
/// @return the normalized vector DD
static auto normalize(Node* p, const std::array<CachedEdge, RADIX>& e,
MemoryManager& mm, ComplexNumbers& cn) -> CachedEdge
requires IsVector<Node>;

/// Get a normalized matrix DD from a fresh node and a list
/// of edges.
/// @param p the fresh node
/// @param e the list of edges that form the successor nodes
/// @param mm a reference to the memory manager (for returning unused nodes)
/// @param cn a reference to the complex number manager (for adding new
/// complex numbers)
/// @return the normalized matrix DD
static auto normalize(Node* p, const std::array<CachedEdge, NEDGE>& e,
MemoryManager& mm, ComplexNumbers& cn) -> CachedEdge
requires IsMatrix<Node>;

/// Check whether the matrix represented by the DD is the identity.
/// @return whether the matrix is the identity
[[nodiscard]] bool isIdentity(const bool upToGlobalPhase = true) const
requires IsMatrix<Node>
{
if (!isTerminal()) {
return false;
}
if (upToGlobalPhase) {
return !w.exactlyZero();
}
return w.exactlyOne();
/// Get a normalized vector DD from a fresh node and a list of edges.
/// @param p the fresh node
/// @param e the list of edges that form the successor nodes
/// @param mm a reference to the memory manager (for returning unused nodes)
/// @param cn a reference to the complex number manager (for adding new
/// complex numbers)
/// @return the normalized vector DD
auto normalize(vNode* p, const std::array<CachedEdge<vNode>, RADIX>& e,
MemoryManager& mm, ComplexNumbers& cn) -> CachedEdge<vNode>;

/// Get a normalized matrix DD from a fresh node and a list
/// of edges.
/// @param p the fresh node
/// @param e the list of edges that form the successor nodes
/// @param mm a reference to the memory manager (for returning unused nodes)
/// @param cn a reference to the complex number manager (for adding new
/// complex numbers)
/// @return the normalized matrix DD
auto normalize(mNode* p, const std::array<CachedEdge<mNode>, NEDGE>& e,
MemoryManager& mm, ComplexNumbers& cn) -> CachedEdge<mNode>;

/// Check whether the matrix represented by the DD is the identity.
/// @return whether the matrix is the identity
[[nodiscard]] inline bool isIdentity(const CachedEdge<mNode>& edge,
const bool upToGlobalPhase = true) {
if (edge.p != nullptr) {
return false;
}
};
if (upToGlobalPhase) {
return !edge.w.exactlyZero();
}
return edge.w.exactlyOne();
}

// Deduction guide for constructor: CachedEdge(Node*, const ComplexValue&)
template <class Node>
Expand Down
Loading
Loading