From 5cebbab6a97140c6ac8d4c08488066ab56c101f7 Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Sun, 25 Oct 2026 12:50:45 +0000 Subject: [PATCH 1/2] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Use=20LLVM=20streams?= =?UTF-8?q?=20for=20DD=20input=20and=20output?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace DD iostreams with LLVM output streams and borrowed byte input. Use LLVM file and process facilities for serialization and Graphviz, and update Python and worker consumers without intermediate stream copies. Preserve direct throwing I/O contracts and existing exception/RTTI policy. Keep the native binary representation and text grammar, including finite subnormal weights, and clean up output descriptors during unwinding. Co-authored-by: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Signed-off-by: Lukas Burgholzer Assisted-by: GPT-6 via Codex --- bindings/dd/register_dd_export.hpp | 44 ++- cmake/mqt-core-config.cmake.in | 1 + docs/cpp_api.md | 13 + include/mqt-core/dd/Complex.hpp | 7 +- include/mqt-core/dd/ComplexValue.hpp | 21 +- include/mqt-core/dd/ComputeTable.hpp | 6 +- include/mqt-core/dd/DDDefinitions.hpp | 10 +- include/mqt-core/dd/Export.hpp | 319 +++++++---------- include/mqt-core/dd/Package.hpp | 8 +- include/mqt-core/dd/RealNumber.hpp | 14 +- include/mqt-core/dd/RealNumberUniqueTable.hpp | 6 +- include/mqt-core/dd/UniqueTable.hpp | 28 +- .../dd/statistics/PackageStatistics.hpp | 5 +- include/mqt-core/dd/statistics/Statistics.hpp | 6 +- src/dd/CMakeLists.txt | 3 +- src/dd/Complex.cpp | 7 +- src/dd/ComplexValue.cpp | 77 +++-- src/dd/Edge.cpp | 43 +-- src/dd/Export.cpp | 323 ++++++++++++------ src/dd/Package.cpp | 9 +- src/dd/RealNumber.cpp | 12 +- src/dd/RealNumberUniqueTable.cpp | 32 +- src/dd/Serialization.cpp | 127 +++---- src/dd/statistics/PackageStatistics.cpp | 6 +- src/qdmi/devices/dd/Device.cpp | 8 +- src/qdmi/devices/dd/WorkerMain.cpp | 4 +- test/dd/test_complex.cpp | 62 +++- test/dd/test_edge_functionality.cpp | 8 + test/dd/test_package.cpp | 223 ++++++++---- test/dd/test_statistics.cpp | 10 + test/python/dd/test_export.py | 22 +- 31 files changed, 829 insertions(+), 635 deletions(-) diff --git a/bindings/dd/register_dd_export.hpp b/bindings/dd/register_dd_export.hpp index de25c8b4a6..b1b2ab2700 100644 --- a/bindings/dd/register_dd_export.hpp +++ b/bindings/dd/register_dd_export.hpp @@ -17,12 +17,12 @@ #include "nanobind/nanobind.h" #include "nanobind/stl/string.h" // NOLINT(misc-include-cleaner) -#include -#include -#include -#include +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.h" + #include -#include namespace mqt { @@ -33,9 +33,9 @@ void registerDDExport(nanobind::class_>& edgeClass) { edgeClass.def( "to_bytes", [](const dd::Edge& e, const bool binary = true) { - std::ostringstream os(std::ios::out | std::ios::binary); + std::string data; + llvm::raw_string_ostream os(data); dd::serialize(e, os, binary); - const auto data = os.view(); return nb::bytes(data.data(), data.size()); }, "binary"_a = true, R"pb(Serialize the DD to bytes. @@ -53,9 +53,8 @@ void registerDDExport(nanobind::class_>& edgeClass) { edgeClass.def_static( "from_bytes", [](dd::Package& p, const nb::bytes& data, const bool binary = true) { - std::istringstream is(std::string(data.c_str(), data.size()), - std::ios::in | std::ios::binary); - return p.deserialize(is, binary); + return p.deserialize(llvm::StringRef(data.c_str(), data.size()), + binary); }, "dd_package"_a, "data"_a, "binary"_a = true, // keep the DD package alive while the returned DD is alive. @@ -78,9 +77,10 @@ void registerDDExport(nanobind::class_>& edgeClass) { [](const dd::Edge& e, const bool colored = true, const bool edgeLabels = false, const bool classic = false, const bool memory = false, const bool formatAsPolar = true) { - std::ostringstream os; + std::string data; + llvm::raw_string_ostream os(data); dd::toDot(e, os, colored, edgeLabels, classic, memory, formatAsPolar); - return std::move(os).str(); + return data; }, "colored"_a = true, "edge_labels"_a = false, "classic"_a = false, "memory"_a = false, "format_as_polar"_a = true, @@ -102,8 +102,8 @@ void registerDDExport(nanobind::class_>& edgeClass) { const bool colored = true, const bool edgeLabels = false, const bool classic = false, const bool memory = false, const bool formatAsPolar = true) { - const auto dotFilename = - std::filesystem::u8path(filename).replace_extension(".dot"); + llvm::SmallString<128> dotFilename(filename); + llvm::sys::path::replace_extension(dotFilename, ".dot"); nb::object pygraphviz; try { pygraphviz = nb::module_::import_("pygraphviz"); @@ -122,16 +122,12 @@ void registerDDExport(nanobind::class_>& edgeClass) { !usePyGraphviz, formatAsPolar); if (usePyGraphviz) { auto svgFilename = dotFilename; - svgFilename.replace_extension(".svg"); - const auto utf8Path = [](const std::filesystem::path& path) { - const auto bytes = path.u8string(); - std::string value(bytes.size(), '\0'); - std::memcpy(value.data(), bytes.data(), bytes.size()); - return value; - }; - pygraphviz.attr("AGraph")("filename"_a = utf8Path(dotFilename)) - .attr("draw")(utf8Path(svgFilename), "format"_a = "svg", - "prog"_a = "dot"); + llvm::sys::path::replace_extension(svgFilename, ".svg"); + pygraphviz + .attr("AGraph")("filename"_a = nb::str(dotFilename.data(), + dotFilename.size())) + .attr("draw")(nb::str(svgFilename.data(), svgFilename.size()), + "format"_a = "svg", "prog"_a = "dot"); } }, "filename"_a, "colored"_a = true, "edge_labels"_a = false, diff --git a/cmake/mqt-core-config.cmake.in b/cmake/mqt-core-config.cmake.in index 716685401a..98c5c29b35 100644 --- a/cmake/mqt-core-config.cmake.in +++ b/cmake/mqt-core-config.cmake.in @@ -34,6 +34,7 @@ if(mqt-core_Development_FOUND find_package(LLVM @LLVM_VERSION_MAJOR@.@LLVM_VERSION_MINOR@ CONFIG QUIET) if(LLVM_FOUND) include("${CMAKE_CURRENT_LIST_DIR}/mqt-core-development-targets.cmake") + target_include_directories(MQT::CoreDD SYSTEM INTERFACE ${LLVM_INCLUDE_DIRS}) target_include_directories(MQT::CoreQDMICommon SYSTEM INTERFACE ${LLVM_INCLUDE_DIRS}) target_include_directories(MQT::CoreBench SYSTEM INTERFACE ${LLVM_INCLUDE_DIRS}) target_include_directories(MQT::CoreQDMIScDeviceConfig SYSTEM INTERFACE ${LLVM_INCLUDE_DIRS}) diff --git a/docs/cpp_api.md b/docs/cpp_api.md index eb23f49e18..b6c9ca88d6 100644 --- a/docs/cpp_api.md +++ b/docs/cpp_api.md @@ -136,3 +136,16 @@ part of the installed public-header reference above. See [device compilation from C++](mlir/target_compilation.md#c-source-tree-api), the {doc}`QIR runtime guide `, and the {doc}`MLIR dialect and pass references `. + +## DD input and output + +DD serializers, DOT exporters, statistics and number printers write to +`llvm::raw_ostream`. The caller owns the stream and checks any sink errors; +file-writing overloads report failures with exceptions. Use +`llvm::raw_string_ostream` to collect output in a `std::string`. + +`Package::deserialize` reads borrowed `llvm::StringRef` bytes, including +embedded NUL bytes. Use `Package::deserializeFile` for a filename. Binary +serialization retains its native representation and is not portable between +architectures. Serialized decimal weights are locale-independent; finite +subnormal values are preserved. diff --git a/include/mqt-core/dd/Complex.hpp b/include/mqt-core/dd/Complex.hpp index b05a51512a..f2e428c00f 100644 --- a/include/mqt-core/dd/Complex.hpp +++ b/include/mqt-core/dd/Complex.hpp @@ -16,12 +16,13 @@ #include "dd/DDDefinitions.hpp" #include "dd/RealNumber.hpp" +#include "llvm/Support/raw_ostream.h" + #include #include #include #include #include -#include #include namespace dd { @@ -105,7 +106,7 @@ struct Complex { /// /// @param os The output stream to write to. /// @see RealNumber::writeBinary - void writeBinary(std::ostream& os) const; + void writeBinary(llvm::raw_ostream& os) const; /// Convert the Complex number to an std::complex. /// @@ -123,7 +124,7 @@ struct Complex { /// @param os The output stream to write to. /// @param c The complex number to print. /// @returns The output stream. -std::ostream& operator<<(std::ostream& os, const Complex& c); +llvm::raw_ostream& operator<<(llvm::raw_ostream& os, const Complex& c); ComplexValue operator*(const Complex& c1, const ComplexValue& c2); ComplexValue operator*(const ComplexValue& c1, const Complex& c2); diff --git a/include/mqt-core/dd/ComplexValue.hpp b/include/mqt-core/dd/ComplexValue.hpp index 701bc2352a..c4aea316eb 100644 --- a/include/mqt-core/dd/ComplexValue.hpp +++ b/include/mqt-core/dd/ComplexValue.hpp @@ -15,12 +15,13 @@ #include "dd/DDDefinitions.hpp" +#include "llvm/Support/raw_ostream.h" + #include #include #include #include #include -#include #include #include @@ -80,18 +81,9 @@ struct ComplexValue { /// stream. /// /// @param os The output stream to write to. - void writeBinary(std::ostream& os) const; + void writeBinary(llvm::raw_ostream& os) const; - /// Read a binary representation of the complex number from the given input - /// stream. - /// - /// @param is The input stream to read from. - void readBinary(std::istream& is); - - /// Construct a complex number from a string. - /// - /// @param realStr The string representation of the real part. - /// @param imagStr The string representation of the imaginary part. + /// Parse the real and imaginary parts of a serialized complex value. void fromString(const std::string& realStr, std::string imagStr); /// Get the closest fraction to the given number. @@ -108,7 +100,8 @@ struct ComplexValue { /// @param os The output stream to write to. /// @param num The number to print. /// @param imaginary Whether the number is imaginary. - static void printFormatted(std::ostream& os, fp num, bool imaginary = false); + static void printFormatted(llvm::raw_ostream& os, fp num, + bool imaginary = false, int precision = -1); /// Print the given complex number to the given output stream. /// @@ -151,7 +144,7 @@ ComplexValue operator/(const ComplexValue& c1, const ComplexValue& c2); /// @param os The output stream to write to. /// @param c The complex value to print. /// @returns The output stream. -std::ostream& operator<<(std::ostream& os, const ComplexValue& c); +llvm::raw_ostream& operator<<(llvm::raw_ostream& os, const ComplexValue& c); } // namespace dd /// Hash function for complex values diff --git a/include/mqt-core/dd/ComputeTable.hpp b/include/mqt-core/dd/ComputeTable.hpp index 8e7bcf50d2..060bafdbf9 100644 --- a/include/mqt-core/dd/ComputeTable.hpp +++ b/include/mqt-core/dd/ComputeTable.hpp @@ -16,9 +16,10 @@ #include "dd/DDDefinitions.hpp" #include "dd/statistics/TableStatistics.hpp" +#include "llvm/Support/raw_ostream.h" + #include #include -#include #include #include @@ -148,7 +149,8 @@ class ComputeTable { /// /// @param os The output stream to print to /// @return The output stream - std::ostream& printStatistics(std::ostream& os = std::cout) const { + llvm::raw_ostream& + printStatistics(llvm::raw_ostream& os = llvm::outs()) const { return os << stats; } diff --git a/include/mqt-core/dd/DDDefinitions.hpp b/include/mqt-core/dd/DDDefinitions.hpp index a57bd6b945..4e281bb0f6 100644 --- a/include/mqt-core/dd/DDDefinitions.hpp +++ b/include/mqt-core/dd/DDDefinitions.hpp @@ -13,6 +13,8 @@ #pragma once +#include "llvm/Support/raw_ostream.h" + #include #include #include @@ -21,7 +23,6 @@ #include #include #include -#include #include #include #include @@ -60,10 +61,11 @@ struct Control { // NOLINTEND(misc-explicit-constructor) [[nodiscard]] std::string toString() const { - std::ostringstream oss{}; - oss << "Control(qubit=" << qubit << ", type_=\"" + std::string result; + llvm::raw_string_ostream(result) + << "Control(qubit=" << qubit << ", type_=\"" << (type == Type::Pos ? "Pos" : "Neg") << "\")"; - return oss.str(); + return result; } }; diff --git a/include/mqt-core/dd/Export.hpp b/include/mqt-core/dd/Export.hpp index ebe268ce7c..96e75f7665 100644 --- a/include/mqt-core/dd/Export.hpp +++ b/include/mqt-core/dd/Export.hpp @@ -19,17 +19,18 @@ #include "dd/Node.hpp" #include "dd/RealNumber.hpp" +#include "llvm/ADT/ScopeExit.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Format.h" +#include "llvm/Support/Process.h" +#include "llvm/Support/raw_ostream.h" + #include #include -#include -#include -#include -#include #include #include -#include -#include -#include +#include namespace dd { @@ -37,18 +38,18 @@ std::string colorFromPhase(const Complex& a); fp thicknessFromMagnitude(const Complex& a); -void printPhaseFormatted(std::ostream& os, fp r); +void printPhaseFormatted(llvm::raw_ostream& os, fp r); std::string conditionalFormat(const Complex& a, bool formatAsPolar = true); /// Render a DOT file as a sibling SVG using Graphviz. /// -/// Requires `dot` on PATH and throws if Graphviz cannot render the file. -void renderDotToSvg(const std::filesystem::path& dotFilename); +/// Requires `dot` on PATH and throws if rendering fails. +void renderDotToSvg(llvm::StringRef dotFilename); template -static std::ostream& header(const Edge& e, std::ostream& os, - bool edgeLabels, bool formatAsPolar = true) { +static llvm::raw_ostream& header(const Edge& e, llvm::raw_ostream& os, + bool edgeLabels, bool formatAsPolar = true) { os << "digraph \"DD\" {graph[];node[shape=plain];edge[arrowhead=none]\n"; os << "root [label=\"\",shape=point,style=invis]\n"; os << "t [label=<& e, std::ostream& os, return os; } template -static std::ostream& coloredHeader(const Edge& e, std::ostream& os, - bool edgeLabels, bool formatAsPolar = true) { +static llvm::raw_ostream& coloredHeader(const Edge& e, + llvm::raw_ostream& os, bool edgeLabels, + bool formatAsPolar = true) { os << "digraph \"DD\" {graph[];node[shape=plain];edge[arrowhead=none]\n"; os << "root [label=\"\",shape=point,style=invis]\n"; os << "t [label=<& e, std::ostream& os, return os; } template -static std::ostream& memoryHeader(const Edge& e, std::ostream& os, - bool edgeLabels) { +static llvm::raw_ostream& memoryHeader(const Edge& e, + llvm::raw_ostream& os, bool edgeLabels) { os << "digraph \"DD\" {graph[];node[shape=plain];edge[arrowhead=none]\n"; os << "root [label=\"\",shape=point,style=invis]\n"; os << "t [label=<& e, std::ostream& os, } else if (RealNumber::exactlyOne(e.w.r)) { os << "1"; } else { - os << std::hex << reinterpret_cast(e.w.r) << std::dec; + os << llvm::format_hex_no_prefix( + reinterpret_cast(e.w.r), 1); } os << " "; if (RealNumber::exactlyZero(e.w.i)) { @@ -128,7 +131,8 @@ static std::ostream& memoryHeader(const Edge& e, std::ostream& os, } else if (RealNumber::exactlyOne(e.w.i)) { os << "1"; } else { - os << std::hex << reinterpret_cast(e.w.i) << std::dec; + os << llvm::format_hex_no_prefix( + reinterpret_cast(e.w.i), 1); } } os << "]>"; @@ -137,22 +141,25 @@ static std::ostream& memoryHeader(const Edge& e, std::ostream& os, return os; } -std::ostream& modernNode(const mEdge& e, size_t nodeId, std::ostream& os, - bool formatAsPolar = true); -std::ostream& modernNode(const vEdge& e, size_t nodeId, std::ostream& os, - bool formatAsPolar = true); -std::ostream& classicNode(const mEdge& e, size_t nodeId, std::ostream& os, - bool formatAsPolar = true); -std::ostream& classicNode(const vEdge& e, size_t nodeId, std::ostream& os, - bool formatAsPolar = true); +llvm::raw_ostream& modernNode(const mEdge& e, size_t nodeId, + llvm::raw_ostream& os, bool formatAsPolar = true); +llvm::raw_ostream& modernNode(const vEdge& e, size_t nodeId, + llvm::raw_ostream& os, bool formatAsPolar = true); +llvm::raw_ostream& classicNode(const mEdge& e, size_t nodeId, + llvm::raw_ostream& os, + bool formatAsPolar = true); +llvm::raw_ostream& classicNode(const vEdge& e, size_t nodeId, + llvm::raw_ostream& os, + bool formatAsPolar = true); template -static std::ostream& memoryNode(const Edge& e, size_t nodeId, - std::ostream& os) { +static llvm::raw_ostream& memoryNode(const Edge& e, size_t nodeId, + llvm::raw_ostream& os) { constexpr std::size_t n = std::tuple_size_ve)>; os << nodeId << "[label=<"; os << R"()"; - os << R"("; + os << R"("; os << ""; for (std::size_t i = 0; i < n; ++i) { os << ""; } os << ""; - os << "
)" << std::hex - << reinterpret_cast(e.p) << std::dec << "
)" + << llvm::format_hex_no_prefix(reinterpret_cast(e.p), 1) + << "
& e, size_t nodeId, os << "
>,tooltip=\"" << std::hex - << reinterpret_cast(e.p) << "\"]\n" - << std::dec; + os << "
>,tooltip=\"" + << llvm::format_hex_no_prefix(reinterpret_cast(e.p), 1) + << "\"]\n"; return os; } -std::ostream& bwEdge(const mEdge& to, size_t fromId, size_t toId, - std::uint16_t idx, std::ostream& os, - bool edgeLabels = false, bool classic = false, - bool formatAsPolar = true); -std::ostream& bwEdge(const vEdge& to, size_t fromId, size_t toId, - std::uint16_t idx, std::ostream& os, - bool edgeLabels = false, bool classic = false, - bool formatAsPolar = true); -std::ostream& coloredEdge(const mEdge& to, size_t fromId, size_t toId, - std::uint16_t idx, std::ostream& os, +llvm::raw_ostream& bwEdge(const mEdge& to, size_t fromId, size_t toId, + std::uint16_t idx, llvm::raw_ostream& os, bool edgeLabels = false, bool classic = false, bool formatAsPolar = true); -std::ostream& coloredEdge(const vEdge& to, size_t fromId, size_t toId, - std::uint16_t idx, std::ostream& os, +llvm::raw_ostream& bwEdge(const vEdge& to, size_t fromId, size_t toId, + std::uint16_t idx, llvm::raw_ostream& os, bool edgeLabels = false, bool classic = false, bool formatAsPolar = true); +llvm::raw_ostream& coloredEdge(const mEdge& to, size_t fromId, size_t toId, + std::uint16_t idx, llvm::raw_ostream& os, + bool edgeLabels = false, bool classic = false, + bool formatAsPolar = true); +llvm::raw_ostream& coloredEdge(const vEdge& to, size_t fromId, size_t toId, + std::uint16_t idx, llvm::raw_ostream& os, + bool edgeLabels = false, bool classic = false, + bool formatAsPolar = true); template -static std::ostream& memoryEdge(const Edge& to, size_t fromId, - size_t toId, std::uint16_t idx, - std::ostream& os, bool edgeLabels = false) { +static llvm::raw_ostream& +memoryEdge(const Edge& to, size_t fromId, size_t toId, std::uint16_t idx, + llvm::raw_ostream& os, bool edgeLabels = false) { os << fromId << ":" << idx << ":s->"; if (to.isTerminal()) { os << "t"; @@ -215,7 +222,8 @@ static std::ostream& memoryEdge(const Edge& to, size_t fromId, } else if (RealNumber::exactlyOne(to.w.r)) { os << "1"; } else { - os << std::hex << reinterpret_cast(to.w.r) << std::dec; + os << llvm::format_hex_no_prefix( + reinterpret_cast(to.w.r), 1); } os << " "; if (RealNumber::exactlyZero(to.w.i)) { @@ -225,7 +233,8 @@ static std::ostream& memoryEdge(const Edge& to, size_t fromId, } else if (RealNumber::exactlyOne(to.w.i)) { os << "1"; } else { - os << std::hex << reinterpret_cast(to.w.i) << std::dec; + os << llvm::format_hex_no_prefix( + reinterpret_cast(to.w.i), 1); } } os << "]
>"; @@ -235,96 +244,38 @@ static std::ostream& memoryEdge(const Edge& to, size_t fromId, return os; } +/// Write DOT text; the caller owns the output stream and its error state. template -static void toDot(const Edge& e, std::ostream& os, bool colored = true, - bool edgeLabels = false, bool classic = false, - bool memory = false, bool formatAsPolar = true) { - std::ostringstream oss{}; - // header, root and terminal declaration +void toDot(const Edge& e, llvm::raw_ostream& os, bool colored = true, + bool edgeLabels = false, bool classic = false, bool memory = false, + bool formatAsPolar = true); - if (memory) { - memoryHeader(e, oss, edgeLabels); - } else if (colored) { - coloredHeader(e, oss, edgeLabels, formatAsPolar); - } else { - header(e, oss, edgeLabels, formatAsPolar); +template +static void exportToDot(Edge basic, llvm::StringRef outputFilename, + bool colored = true, bool edgeLabels = false, + bool classic = false, bool memory = false, + bool show = true, bool formatAsPolar = true) { + int descriptor = -1; + auto error = llvm::sys::fs::openFileForWrite(outputFilename, descriptor); + if (error) { + throw std::runtime_error("Cannot open DOT file: " + error.message()); } - - // Assign IDs on discovery, independent of allocation addresses. - std::unordered_map nodes{{e.p, 0}}; - std::queue*> q; - q.push(&e); - - // bfs until finished - while (!q.empty()) { - auto node = q.front(); - q.pop(); - - // base case - if (node->isTerminal()) { - continue; - } - - const auto nodeId = nodes.at(node->p); - - // node definition as HTML-like label (href="javascript:;" is used as - // workaround to make tooltips work) - if (memory) { - memoryNode(*node, nodeId, oss); - } else if (classic) { - classicNode(*node, nodeId, oss, formatAsPolar); - } else { - modernNode(*node, nodeId, oss, formatAsPolar); - } - - // iterate over edges in reverse to guarantee correct processing order - for (auto i = static_cast(node->p->e.size() - 1); i >= 0; - --i) { - auto& edge = node->p->e[static_cast(i)]; - if ((!memory && edge.w.approximatelyZero()) || edge.w.exactlyZero()) { - // potentially add zero stubs here - continue; - } - - size_t childId = 0; - if (!edge.isTerminal()) { - const auto [it, inserted] = nodes.try_emplace(edge.p, nodes.size()); - childId = it->second; - if (inserted) { - q.push(&edge); - } + { + llvm::raw_fd_ostream output(descriptor, false); + const llvm::scope_exit close([&] { + output.flush(); + error = output.error(); + output.clear_error(); + const auto closeError = + llvm::sys::Process::SafelyCloseFileDescriptor(descriptor); + if (!error) { + error = closeError; } - - if (memory) { - memoryEdge(edge, nodeId, childId, static_cast(i), oss, - edgeLabels); - } else if (colored) { - coloredEdge(edge, nodeId, childId, static_cast(i), oss, - edgeLabels, classic, formatAsPolar); - } else { - bwEdge(edge, nodeId, childId, static_cast(i), oss, - edgeLabels, classic, formatAsPolar); - } - } + }); + toDot(basic, output, colored, edgeLabels, classic, memory, formatAsPolar); } - oss << "}\n"; - - os << oss.str() << std::flush; -} - -template -[[maybe_unused]] static void -exportToDot(Edge basic, const std::filesystem::path& outputFilename, - bool colored = true, bool edgeLabels = false, bool classic = false, - bool memory = false, bool show = true, bool formatAsPolar = true) { - std::ofstream init(outputFilename); - if (!init) { - throw std::runtime_error("Cannot open DOT file"); - } - toDot(basic, init, colored, edgeLabels, classic, memory, formatAsPolar); - init.close(); - if (!init) { - throw std::runtime_error("Cannot write DOT file"); + if (error) { + throw std::runtime_error("Cannot write DOT file: " + error.message()); } if (show) { @@ -338,83 +289,41 @@ exportToDot(Edge basic, const std::filesystem::path& outputFilename, /// architectures/platforms /// -void serialize(const vEdge& basic, std::ostream& os, bool writeBinary = false); +void serialize(const vEdge& basic, llvm::raw_ostream& os, + bool writeBinary = false); -void serializeMatrix(const mEdge& basic, std::int64_t& idx, - std::unordered_map& nodeIndex, - std::unordered_set& visited, std::ostream& os, - bool writeBinary = false); -void serialize(const mEdge& basic, std::ostream& os, bool writeBinary = false); +void serialize(const mEdge& basic, llvm::raw_ostream& os, + bool writeBinary = false); template -static void serialize(const Edge& basic, - const std::string& outputFilename, +static void serialize(const Edge& basic, llvm::StringRef outputFilename, bool writeBinary = false) { - std::ofstream ofs = std::ofstream(outputFilename, std::ios::binary); - - if (!ofs.good()) { - throw std::invalid_argument("Cannot open file: " + outputFilename); + int descriptor = -1; + auto error = llvm::sys::fs::openFileForWrite(outputFilename, descriptor); + if (error) { + throw std::invalid_argument("Cannot open serialized file: " + + error.message()); } - - serialize(basic, ofs, writeBinary); - ofs.close(); - if (!ofs) { - throw std::runtime_error("Cannot write file: " + outputFilename); + { + llvm::raw_fd_ostream output(descriptor, false); + const llvm::scope_exit close([&] { + output.flush(); + error = output.error(); + output.clear_error(); + const auto closeError = + llvm::sys::Process::SafelyCloseFileDescriptor(descriptor); + if (!error) { + error = closeError; + } + }); + serialize(basic, output, writeBinary); + } + if (error) { + throw std::runtime_error("Cannot write serialized file: " + + error.message()); } } template -static void exportEdgeWeights(const Edge& edge, std::ostream& stream) { - struct Priocmp { - bool operator()(const Edge* left, const Edge* right) { - if (left->p == nullptr) { - return right->p != nullptr; - } - if (right->p == nullptr) { - return false; - } - return left->p->v < right->p->v; - } - }; - stream << std::showpos << RealNumber::val(edge.w.r) - << RealNumber::val(edge.w.i) << std::noshowpos << "i\n"; - - std::unordered_set nodes{}; - - std::priority_queue*, std::vector*>, - Priocmp> - q; - q.push(&edge); - - // bfs until finished - while (!q.empty()) { - auto edgePtr = q.top(); - q.pop(); - - // base case - if (edgePtr->isTerminal()) { - continue; - } - - // check if edgePtr has already been processed - if (auto ret = nodes.emplace(edgePtr->p); !ret.second) { - continue; - } - - // iterate over edges in reverse to guarantee correct processing order - for (auto i = static_cast(edgePtr->p->e.size() - 1); i >= 0; - --i) { - auto& child = edgePtr->p->e[static_cast(i)]; - if (child.w.approximatelyZero()) { - // potentially add zero stubs here - continue; - } - - // non-zero child to be included - q.push(&child); - stream << std::showpos << RealNumber::val(child.w.r) - << RealNumber::val(child.w.i) << std::noshowpos << "i\n"; - } - } -} +void exportEdgeWeights(const Edge& edge, llvm::raw_ostream& stream); } // namespace dd diff --git a/include/mqt-core/dd/Package.hpp b/include/mqt-core/dd/Package.hpp index db74fe87e9..0ab7bddd95 100644 --- a/include/mqt-core/dd/Package.hpp +++ b/include/mqt-core/dd/Package.hpp @@ -29,6 +29,8 @@ #include "dd/UnaryComputeTable.hpp" #include "dd/UniqueTable.hpp" +#include "llvm/ADT/StringRef.h" + #include #include #include @@ -1553,14 +1555,14 @@ class Package { /// architectures/platforms /// - /// Deserialize a vector (`vNode`) or matrix (`mNode`) DD from a stream. + /// Deserialize a vector (`vNode`) or matrix (`mNode`) DD from borrowed bytes. template , std::size_t N = std::tuple_size_v> - Edge deserialize(std::istream& is, bool readBinary = false); + Edge deserialize(llvm::StringRef data, bool readBinary = false); /// Deserialize a vector (`vNode`) or matrix (`mNode`) DD from a file. template > - Edge deserialize(const std::string& inputFilename, bool readBinary); + Edge deserializeFile(llvm::StringRef inputFilename, bool readBinary); private: template > diff --git a/include/mqt-core/dd/RealNumber.hpp b/include/mqt-core/dd/RealNumber.hpp index 9d27f54ccc..66dde57c21 100644 --- a/include/mqt-core/dd/RealNumber.hpp +++ b/include/mqt-core/dd/RealNumber.hpp @@ -17,9 +17,9 @@ #include "dd/LinkedListBase.hpp" #include "dd/mqt_core_dd_export.h" -#include +#include "llvm/Support/raw_ostream.h" + #include -#include namespace dd { /// A struct for representing real numbers as part of the DD package. @@ -109,19 +109,13 @@ struct RealNumber final : LLBase { /// /// @param e The number to write. /// @param os The stream to write to. - static void writeBinary(const RealNumber* e, std::ostream& os); + static void writeBinary(const RealNumber* e, llvm::raw_ostream& os); /// Write a binary representation of a floating point number to a /// /// @param num The number to write. /// @param os The stream to write to. - static void writeBinary(fp num, std::ostream& os); - - /// Read a binary representation of a number from a stream. - /// - /// @param num The number to read into. - /// @param is The stream to read from. - static void readBinary(fp& num, std::istream& is); + static void writeBinary(fp num, llvm::raw_ostream& os); /// Get an aligned pointer to the number. /// diff --git a/include/mqt-core/dd/RealNumberUniqueTable.hpp b/include/mqt-core/dd/RealNumberUniqueTable.hpp index 8b33c8f164..05bd9dc9cf 100644 --- a/include/mqt-core/dd/RealNumberUniqueTable.hpp +++ b/include/mqt-core/dd/RealNumberUniqueTable.hpp @@ -17,8 +17,9 @@ #include "dd/MemoryManager.hpp" #include "dd/statistics/UniqueTableStatistics.hpp" +#include "llvm/Support/raw_ostream.h" + #include -#include #include #include @@ -120,7 +121,8 @@ class RealNumberUniqueTable { /// /// @param os The output stream to print to. /// @returns The output stream. - std::ostream& printBucketDistribution(std::ostream& os = std::cout); + llvm::raw_ostream& + printBucketDistribution(llvm::raw_ostream& os = llvm::outs()); /// Count the marked entries in the table [[nodiscard]] std::size_t countMarkedEntries() const noexcept; diff --git a/include/mqt-core/dd/UniqueTable.hpp b/include/mqt-core/dd/UniqueTable.hpp index 40b2a33901..279b7010c3 100644 --- a/include/mqt-core/dd/UniqueTable.hpp +++ b/include/mqt-core/dd/UniqueTable.hpp @@ -18,10 +18,12 @@ #include "dd/Node.hpp" #include "dd/statistics/UniqueTableStatistics.hpp" +#include "llvm/Support/Format.h" +#include "llvm/Support/raw_ostream.h" + #include #include #include -#include #include #include #include @@ -178,22 +180,30 @@ class UniqueTable { "Node must be derived from NodeBase"); auto q = cfg.nVars - 1U; for (const auto& table : std::ranges::reverse_view(tables)) { - std::cout << "\tq" << q << ":" << "\n"; + llvm::outs() << "\tq" << q << ":" << "\n"; for (std::size_t key = 0; key < table.size(); ++key) { const auto* p = static_cast(table[key]); if (p != nullptr) { - std::cout << "\tkey=" << key << ": "; + llvm::outs() << "\tkey=" << key << ": "; } while (p != nullptr) { - std::cout << "\t\t" << std::hex - << reinterpret_cast(p); + llvm::outs() << "\t\t" + << llvm::format_hex_no_prefix( + reinterpret_cast(p), 0); for (const auto& e : p->e) { - std::cout << " p" << reinterpret_cast(e.p) << "(r" - << reinterpret_cast(e.w.r) << " i" - << reinterpret_cast(e.w.i) << ")"; + llvm::outs() << " p" + << llvm::format_hex_no_prefix( + reinterpret_cast(e.p), 0) + << "(r" + << llvm::format_hex_no_prefix( + reinterpret_cast(e.w.r), 0) + << " i" + << llvm::format_hex_no_prefix( + reinterpret_cast(e.w.i), 0) + << ")"; } - std::cout << std::dec << "\n"; + llvm::outs() << "\n"; p = p->next(); } } diff --git a/include/mqt-core/dd/statistics/PackageStatistics.hpp b/include/mqt-core/dd/statistics/PackageStatistics.hpp index ad5181ee57..e0198abf9b 100644 --- a/include/mqt-core/dd/statistics/PackageStatistics.hpp +++ b/include/mqt-core/dd/statistics/PackageStatistics.hpp @@ -15,7 +15,8 @@ #include "dd/Package.hpp" -#include +#include "llvm/Support/raw_ostream.h" + #include namespace dd { @@ -52,6 +53,6 @@ namespace dd { [[nodiscard]] std::string getStatisticsString(Package& package, bool includeIndividualTables = false); -void printStatistics(Package& package, std::ostream& os = std::cout); +void printStatistics(Package& package, llvm::raw_ostream& os = llvm::outs()); } // namespace dd diff --git a/include/mqt-core/dd/statistics/Statistics.hpp b/include/mqt-core/dd/statistics/Statistics.hpp index cd7bf93b24..ddadc0575c 100644 --- a/include/mqt-core/dd/statistics/Statistics.hpp +++ b/include/mqt-core/dd/statistics/Statistics.hpp @@ -13,7 +13,8 @@ #pragma once -#include +#include "llvm/Support/raw_ostream.h" + #include namespace dd { @@ -39,7 +40,8 @@ struct Statistics { /// @param os The output stream /// @param stats The statistics /// @return The output stream - friend std::ostream& operator<<(std::ostream& os, const Statistics& stats) { + friend llvm::raw_ostream& operator<<(llvm::raw_ostream& os, + const Statistics& stats) { return os << stats.toString(); } }; diff --git a/src/dd/CMakeLists.txt b/src/dd/CMakeLists.txt index ce8ac1c50f..e71d84bed9 100644 --- a/src/dd/CMakeLists.txt +++ b/src/dd/CMakeLists.txt @@ -43,7 +43,8 @@ if(NOT TARGET ${MQT_CORE_TARGET_NAME}-dd) FILE_SET HEADERS BASE_DIRS ${CMAKE_CURRENT_BINARY_DIR}/.. FILES ${CMAKE_CURRENT_BINARY_DIR}/mqt_core_dd_export.h) - target_link_libraries(${dd_target} PRIVATE LLVMSupport) + target_link_libraries(${dd_target} PUBLIC LLVMSupport) + target_include_directories(${dd_target} SYSTEM PUBLIC $) endforeach() set(MQT_CORE_TARGETS ${MQT_CORE_TARGETS} ${MQT_CORE_TARGET_NAME}-dd PARENT_SCOPE) diff --git a/src/dd/Complex.cpp b/src/dd/Complex.cpp index 03d397d2d0..9f40872ca0 100644 --- a/src/dd/Complex.cpp +++ b/src/dd/Complex.cpp @@ -14,8 +14,9 @@ #include "dd/DDDefinitions.hpp" #include "dd/RealNumber.hpp" +#include "llvm/Support/raw_ostream.h" + #include -#include #include namespace dd { @@ -44,7 +45,7 @@ std::string Complex::toString(bool formatted, int precision) const { formatted, precision); } -void Complex::writeBinary(std::ostream& os) const { +void Complex::writeBinary(llvm::raw_ostream& os) const { RealNumber::writeBinary(r, os); RealNumber::writeBinary(i, os); } @@ -57,7 +58,7 @@ Complex::operator ComplexValue() const noexcept { return ComplexValue{RealNumber::val(r), RealNumber::val(i)}; } -std::ostream& operator<<(std::ostream& os, const Complex& c) { +llvm::raw_ostream& operator<<(llvm::raw_ostream& os, const Complex& c) { return os << c.toString(); } diff --git a/src/dd/ComplexValue.cpp b/src/dd/ComplexValue.cpp index 22fc724451..85bf2dae4c 100644 --- a/src/dd/ComplexValue.cpp +++ b/src/dd/ComplexValue.cpp @@ -13,16 +13,18 @@ #include "dd/DDDefinitions.hpp" #include "dd/RealNumber.hpp" +#include "llvm/ADT/APFloat.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/raw_ostream.h" + +#include #include #include #include #include -#include #include -#include -#include -#include -#include #include #include #include @@ -46,28 +48,25 @@ bool ComplexValue::approximatelyZero() const noexcept { return RealNumber::approximatelyZero(r) && RealNumber::approximatelyZero(i); } -void ComplexValue::writeBinary(std::ostream& os) const { +void ComplexValue::writeBinary(llvm::raw_ostream& os) const { RealNumber::writeBinary(r, os); RealNumber::writeBinary(i, os); } -void ComplexValue::readBinary(std::istream& is) { - RealNumber::readBinary(r, is); - RealNumber::readBinary(i, is); -} - void ComplexValue::fromString(const std::string& realStr, std::string imagStr) { const auto parse = [](const std::string& text) { - try { - return std::stod(text); - } catch (const std::out_of_range&) { - // stod may report underflow for a representable subnormal value. - const auto value = std::strtod(text.c_str(), nullptr); - if (std::fpclassify(value) == FP_SUBNORMAL) { - return value; - } - throw; + llvm::APFloat value(0.); + auto status = + value.convertFromString(text, llvm::APFloat::rmNearestTiesToEven); + if (!status) { + throw std::invalid_argument(llvm::toString(status.takeError())); } + if ((*status & llvm::APFloat::opOverflow) != 0 || + ((*status & llvm::APFloat::opUnderflow) != 0 && value.isZero())) { + throw std::out_of_range( + "Serialized number is outside the floating-point range."); + } + return value.convertToDouble(); }; r = realStr.empty() ? 0. : parse(realStr); @@ -116,7 +115,8 @@ ComplexValue::getLowestFraction(const fp x, return lowerBound; } -void ComplexValue::printFormatted(std::ostream& os, fp num, bool imaginary) { +void ComplexValue::printFormatted(llvm::raw_ostream& os, fp num, bool imaginary, + int precision) { if (std::signbit(num)) { os << "-"; num = -num; @@ -190,58 +190,63 @@ void ComplexValue::printFormatted(std::ostream& os, fp num, bool imaginary) { } if (imaginary) { // default - os << num << "i"; + os << toString(num, 0., false, precision) << "i"; } else { - os << num; + os << toString(num, 0., false, precision); } } std::string ComplexValue::toString(const fp& real, const fp& imag, bool formatted, int precision) { - std::ostringstream ss{}; + std::string result; + llvm::raw_string_ostream ss(result); + const auto number = [precision](fp value) { + llvm::SmallString<32> text; + llvm::APFloat(value).toString( + text, + precision < 0 ? 6U : static_cast(std::max(1, precision))); + return text; + }; const auto zero = [formatted](fp value) { return formatted ? RealNumber::approximatelyZero(value) : value == 0.; }; - if (precision >= 0) { - ss << std::setprecision(precision); - } if (zero(real) && zero(imag)) { return "0"; } if (!zero(real)) { if (formatted) { - printFormatted(ss, real); + printFormatted(ss, real, false, precision); } else { - ss << real; + ss << number(real); } } if (!zero(imag)) { if (formatted) { if (RealNumber::approximatelyEquals(real, imag)) { ss << "(1+i)"; - return ss.str(); + return result; } if (RealNumber::approximatelyEquals(real, -imag)) { ss << "(1-i)"; - return ss.str(); + return result; } - printFormatted(ss, imag, true); + printFormatted(ss, imag, true, precision); } else { if (zero(real)) { - ss << imag; + ss << number(imag); } else { if (imag > 0.) { ss << "+"; } - ss << imag; + ss << number(imag); } ss << "i"; } } - return ss.str(); + return result; } ComplexValue& ComplexValue::operator+=(const ComplexValue& rhs) noexcept { @@ -315,7 +320,7 @@ ComplexValue operator/(const ComplexValue& c1, const ComplexValue& c2) { return {gr / d, gi / d}; } -std::ostream& operator<<(std::ostream& os, const ComplexValue& c) { +llvm::raw_ostream& operator<<(llvm::raw_ostream& os, const ComplexValue& c) { return os << ComplexValue::toString(c.r, c.i); } } // namespace dd diff --git a/src/dd/Edge.cpp b/src/dd/Edge.cpp index 076792eb61..a34f685ef4 100644 --- a/src/dd/Edge.cpp +++ b/src/dd/Edge.cpp @@ -18,7 +18,8 @@ #include "dd/RealNumber.hpp" #include "llvm/ADT/STLFunctionalExtras.h" -#include "llvm/ADT/ScopeExit.h" +#include "llvm/Support/Format.h" +#include "llvm/Support/raw_ostream.h" #include #include @@ -27,8 +28,6 @@ #include #include #include -#include -#include #include #include #include @@ -334,14 +333,14 @@ auto printVector(const vEdge& edge) -> void { edge.p->v >= std::numeric_limits::digits - 1U) { throw std::out_of_range("Vector dimension does not fit in size_t."); } - constexpr auto precision = 3; - const auto oldPrecision = std::cout.precision(); - const auto restorePrecision = - llvm::scope_exit([oldPrecision] { std::cout.precision(oldPrecision); }); - std::cout << std::setprecision(precision); + auto& output = llvm::outs(); if (edge.isTerminal()) { - std::cout << "0: " << static_cast>(edge.w) << "\n"; + output << "0: " + << llvm::format("(%.3g,%.3g)", RealNumber::val(edge.w.r), + RealNumber::val(edge.w.i)) + << "\n"; + output.flush(); return; } const size_t element = size_t{1} << (edge.p->v + 1U); @@ -349,11 +348,13 @@ auto printVector(const vEdge& edge) -> void { const auto amplitude = getValueByIndex(edge, i); const auto n = static_cast(edge.p->v) + 1U; for (auto j = n; j > 0; --j) { - std::cout << ((i >> (j - 1)) & 1ULL); + output << ((i >> (j - 1)) & 1ULL); } - std::cout << ": " << amplitude << "\n"; + output << ": " + << llvm::format("(%.3g,%.3g)", amplitude.real(), amplitude.imag()) + << "\n"; } - std::cout << std::flush; + output.flush(); } auto addToVector(const vEdge& edge, CVec& amplitudes) -> void { @@ -535,25 +536,25 @@ auto printMatrix(const mEdge& edge, const size_t numQubits) -> void { if (numQubits >= std::numeric_limits::digits) { throw std::out_of_range("Matrix dimension does not fit in size_t."); } - constexpr auto precision = 3; - const auto oldPrecision = std::cout.precision(); - const auto restorePrecision = - llvm::scope_exit([oldPrecision] { std::cout.precision(oldPrecision); }); - std::cout << std::setprecision(precision); + auto& output = llvm::outs(); if (numQubits == 0U) { - std::cout << static_cast>(edge.w) << "\n"; + output << llvm::format("(%.3g,%.3g)", RealNumber::val(edge.w.r), + RealNumber::val(edge.w.i)) + << "\n"; + output.flush(); return; } const size_t element = size_t{1} << numQubits; for (size_t i = 0; i < element; ++i) { for (size_t j = 0; j < element; ++j) { const auto amplitude = getValueByIndex(edge, numQubits, i, j); - std::cout << amplitude << " "; + output << llvm::format("(%.3g,%.3g)", amplitude.real(), amplitude.imag()) + << " "; } - std::cout << "\n"; + output << "\n"; } - std::cout << std::flush; + output.flush(); } void traverseMatrix(const mEdge& edge, const std::complex& amp, diff --git a/src/dd/Export.cpp b/src/dd/Export.cpp index 1338acdd99..4a1794fe2a 100644 --- a/src/dd/Export.cpp +++ b/src/dd/Export.cpp @@ -12,97 +12,59 @@ #include "dd/Complex.hpp" #include "dd/ComplexNumbers.hpp" +#include "dd/ComplexValue.hpp" #include "dd/DDDefinitions.hpp" #include "dd/Edge.hpp" #include "dd/Node.hpp" #include "dd/RealNumber.hpp" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Format.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Program.h" +#include "llvm/Support/raw_ostream.h" + #include #include -#include #include #include #include -#include -#include -#include #include -#include +#include +#include #include #include #include #include #include #include - -#ifdef _WIN32 -#include -#else -#ifdef __APPLE__ -#include -#endif -#include -#include -#include -#include -#endif +#include namespace dd { -void renderDotToSvg(const std::filesystem::path& dotFilename) { - const auto input = std::filesystem::absolute(dotFilename); +void renderDotToSvg(llvm::StringRef dotFilename) { + llvm::SmallString<128> input(dotFilename); + if (const auto error = llvm::sys::fs::make_absolute(input)) { + throw std::system_error(error, "Cannot resolve DOT filename"); + } auto output = input; - output.replace_extension(".svg"); - -#ifdef _WIN32 - const auto inputName = input.wstring(); - const auto outputName = output.wstring(); - const std::array args = {L"dot.exe", L"-Tsvg", - inputName.c_str(), L"-o", - outputName.c_str(), nullptr}; - const auto exitCode = _wspawnvp(_P_WAIT, L"dot.exe", args.data()); - if (exitCode == -1) { - throw std::system_error(errno, std::generic_category(), - "Cannot start Graphviz dot"); + llvm::sys::path::replace_extension(output, ".svg"); + auto program = llvm::sys::findProgramByName("dot"); + if (!program) { + throw std::system_error(program.getError(), "Cannot find Graphviz dot"); } -#else - std::string command = "dot"; - std::string format = "-Tsvg"; - std::string inputName = input.string(); - std::string outputFlag = "-o"; - std::string outputName = output.string(); - const std::array args = { - command.data(), format.data(), inputName.data(), - outputFlag.data(), outputName.data(), nullptr, + const std::array args{ + *program, "-Tsvg", input, "-o", output, }; -#ifdef __APPLE__ - const auto* const environment = *_NSGetEnviron(); -#else - const auto* const environment = environ; -#endif - pid_t pid{}; - const auto spawnError = - posix_spawnp(&pid, "dot", nullptr, nullptr, args.data(), environment); - if (spawnError != 0) { - throw std::system_error(spawnError, std::generic_category(), - "Cannot start Graphviz dot"); - } - - int status{}; - while (waitpid(pid, &status, 0) == -1) { - if (errno != EINTR) { - throw std::system_error(errno, std::generic_category(), - "Cannot wait for Graphviz dot"); - } + std::string message; + const auto exitCode = llvm::sys::ExecuteAndWait(*program, args, std::nullopt, + {}, 0, 0, &message); + if (exitCode < 0) { + throw std::runtime_error("Cannot run Graphviz dot: " + message); } - // sys/wait.h supplies these macros through a libc internal header. - // NOLINTNEXTLINE(misc-include-cleaner) - if (!WIFEXITED(status)) { - throw std::runtime_error("Graphviz dot terminated abnormally"); - } - // NOLINTNEXTLINE(misc-include-cleaner) - const auto exitCode = WEXITSTATUS(status); -#endif if (exitCode != 0) { throw std::runtime_error("Graphviz dot exited with code " + std::to_string(exitCode)); @@ -114,15 +76,12 @@ std::string colorFromPhase(const Complex& a) { if (phase < 0) { phase += 1.; } - std::ostringstream oss{}; - oss << std::fixed << std::setprecision(3) << phase << " " << 0.667 << " " - << 0.75; - return oss.str(); + return llvm::formatv("{0:F3} 0.667 0.750", phase).str(); } fp thicknessFromMagnitude(const Complex& a) { return 3.0 * std::max(ComplexNumbers::mag(a), 0.10); } -void printPhaseFormatted(std::ostream& os, fp r) { +void printPhaseFormatted(llvm::raw_ostream& os, fp r) { const auto tol = RealNumber::eps; r /= PI; @@ -185,7 +144,7 @@ void printPhaseFormatted(std::ostream& os, fp r) { } // default - os << " " << absr << ")"; + os << " " << ComplexValue::toString(absr, 0., false) << ")"; } std::string conditionalFormat(const Complex& a, const bool formatAsPolar) { if (!formatAsPolar) { @@ -199,7 +158,8 @@ std::string conditionalFormat(const Complex& a, const bool formatAsPolar) { return "0"; } - std::ostringstream ss{}; + std::string result; + llvm::raw_string_ostream ss(result); // magnitude is (almost) 1 if (RealNumber::approximatelyEquals(mag, 1.)) { if (RealNumber::approximatelyZero(phase)) { @@ -215,27 +175,27 @@ std::string conditionalFormat(const Complex& a, const bool formatAsPolar) { return "-1"; } printPhaseFormatted(ss, phase); - return ss.str(); + return result; } if (RealNumber::approximatelyEquals(std::abs(phase), PI)) { ss << "-"; ComplexValue::printFormatted(ss, mag); - return ss.str(); + return result; } if (RealNumber::approximatelyZero(phase)) { ComplexValue::printFormatted(ss, mag); - return ss.str(); + return result; } ComplexValue::printFormatted(ss, mag); ss << " "; printPhaseFormatted(ss, phase); - return ss.str(); + return result; } -std::ostream& modernNode(const mEdge& e, const size_t nodeId, std::ostream& os, - const bool formatAsPolar) { +llvm::raw_ostream& modernNode(const mEdge& e, const size_t nodeId, + llvm::raw_ostream& os, const bool formatAsPolar) { os << nodeId << "[label=<"; os << R"()"; os << R"(
)"; os << R"(
q)" @@ -297,8 +257,9 @@ std::ostream& modernNode(const vEdge& e, const size_t nodeId, std::ostream& os, << "\"]\n"; return os; } -std::ostream& classicNode(const mEdge& e, const size_t nodeId, std::ostream& os, - const bool formatAsPolar) { +llvm::raw_ostream& classicNode(const mEdge& e, const size_t nodeId, + llvm::raw_ostream& os, + const bool formatAsPolar) { os << nodeId << "[shape=circle, width=0.53, fixedsize=true, label=<"; os << R"()"; os << R"(
q)" @@ -346,8 +307,9 @@ std::ostream& classicNode(const mEdge& e, const size_t nodeId, std::ostream& os, << static_cast(e.p->v) << "\"]\n"; return os; } -std::ostream& classicNode(const vEdge& e, const size_t nodeId, std::ostream& os, - const bool formatAsPolar) { +llvm::raw_ostream& classicNode(const vEdge& e, const size_t nodeId, + llvm::raw_ostream& os, + const bool formatAsPolar) { os << nodeId << "[shape=circle, width=0.46, fixedsize=true, label=<"; os << R"()"; os << R"(
q)" @@ -377,11 +339,11 @@ std::ostream& classicNode(const vEdge& e, const size_t nodeId, std::ostream& os, } namespace { template -std::ostream& renderEdge(const Edge& to, const size_t fromId, - const size_t toId, const std::uint16_t idx, - std::ostream& os, const bool colored, - const bool edgeLabels, const bool classic, - const bool formatAsPolar) { +llvm::raw_ostream& renderEdge(const Edge& to, const size_t fromId, + const size_t toId, const std::uint16_t idx, + llvm::raw_ostream& os, const bool colored, + const bool edgeLabels, const bool classic, + const bool formatAsPolar) { os << fromId << ":" << idx << ":"; if constexpr (IsVector) { os << (idx == 0 ? "sw" : "se"); @@ -427,35 +389,36 @@ std::ostream& renderEdge(const Edge& to, const size_t fromId, } } // namespace -std::ostream& bwEdge(const mEdge& to, const size_t fromId, const size_t toId, - const std::uint16_t idx, std::ostream& os, - const bool edgeLabels, const bool classic, - const bool formatAsPolar) { +llvm::raw_ostream& bwEdge(const mEdge& to, const size_t fromId, + const size_t toId, const std::uint16_t idx, + llvm::raw_ostream& os, const bool edgeLabels, + const bool classic, const bool formatAsPolar) { return renderEdge(to, fromId, toId, idx, os, false, edgeLabels, classic, formatAsPolar); } -std::ostream& bwEdge(const vEdge& to, const size_t fromId, const size_t toId, - const std::uint16_t idx, std::ostream& os, - const bool edgeLabels, const bool classic, - const bool formatAsPolar) { +llvm::raw_ostream& bwEdge(const vEdge& to, const size_t fromId, + const size_t toId, const std::uint16_t idx, + llvm::raw_ostream& os, const bool edgeLabels, + const bool classic, const bool formatAsPolar) { return renderEdge(to, fromId, toId, idx, os, false, edgeLabels, classic, formatAsPolar); } -std::ostream& coloredEdge(const mEdge& to, const size_t fromId, - const size_t toId, const std::uint16_t idx, - std::ostream& os, const bool edgeLabels, - const bool classic, const bool formatAsPolar) { +llvm::raw_ostream& coloredEdge(const mEdge& to, const size_t fromId, + const size_t toId, const std::uint16_t idx, + llvm::raw_ostream& os, const bool edgeLabels, + const bool classic, const bool formatAsPolar) { return renderEdge(to, fromId, toId, idx, os, true, edgeLabels, classic, formatAsPolar); } -std::ostream& coloredEdge(const vEdge& to, const size_t fromId, - const size_t toId, const std::uint16_t idx, - std::ostream& os, const bool edgeLabels, - const bool classic, const bool formatAsPolar) { +llvm::raw_ostream& coloredEdge(const vEdge& to, const size_t fromId, + const size_t toId, const std::uint16_t idx, + llvm::raw_ostream& os, const bool edgeLabels, + const bool classic, const bool formatAsPolar) { return renderEdge(to, fromId, toId, idx, os, true, edgeLabels, classic, formatAsPolar); } -void serialize(const vEdge& basic, std::ostream& os, const bool writeBinary) { +void serialize(const vEdge& basic, llvm::raw_ostream& os, + const bool writeBinary) { if (writeBinary) { os.write(reinterpret_cast(&SERIALIZATION_VERSION), sizeof(decltype(SERIALIZATION_VERSION))); @@ -557,9 +520,10 @@ void serialize(const vEdge& basic, std::ostream& os, const bool writeBinary) { } while (!stack.empty()); } } +namespace { void serializeMatrix(const mEdge& basic, std::int64_t& idx, std::unordered_map& nodeIndex, - std::unordered_set& visited, std::ostream& os, + std::unordered_set& visited, llvm::raw_ostream& os, const bool writeBinary) { if (!basic.isTerminal()) { for (auto const& e : basic.p->e) { @@ -601,7 +565,10 @@ void serializeMatrix(const mEdge& basic, std::int64_t& idx, } } } -void serialize(const mEdge& basic, std::ostream& os, const bool writeBinary) { +} // namespace + +void serialize(const mEdge& basic, llvm::raw_ostream& os, + const bool writeBinary) { if (writeBinary) { os.write(reinterpret_cast(&SERIALIZATION_VERSION), sizeof(decltype(SERIALIZATION_VERSION))); @@ -616,4 +583,140 @@ void serialize(const mEdge& basic, std::ostream& os, const bool writeBinary) { std::unordered_set visited{}; serializeMatrix(basic, idx, nodeIndex, visited, os, writeBinary); } + +template +void toDot(const Edge& e, llvm::raw_ostream& os, bool colored, + bool edgeLabels, bool classic, bool memory, bool formatAsPolar) { + // header, root and terminal declaration + + if (memory) { + memoryHeader(e, os, edgeLabels); + } else if (colored) { + coloredHeader(e, os, edgeLabels, formatAsPolar); + } else { + header(e, os, edgeLabels, formatAsPolar); + } + + // Assign IDs on discovery, independent of allocation addresses. + std::unordered_map nodes{{e.p, 0}}; + std::queue*> q; + q.push(&e); + + // bfs until finished + while (!q.empty()) { + auto node = q.front(); + q.pop(); + + // base case + if (node->isTerminal()) { + continue; + } + + const auto nodeId = nodes.at(node->p); + + // node definition as HTML-like label (href="javascript:;" is used as + // workaround to make tooltips work) + if (memory) { + memoryNode(*node, nodeId, os); + } else if (classic) { + classicNode(*node, nodeId, os, formatAsPolar); + } else { + modernNode(*node, nodeId, os, formatAsPolar); + } + + // iterate over edges in reverse to guarantee correct processing order + for (auto i = static_cast(node->p->e.size() - 1); i >= 0; + --i) { + const auto& edge = node->p->e[static_cast(i)]; + if ((!memory && edge.w.approximatelyZero()) || edge.w.exactlyZero()) { + // potentially add zero stubs here + continue; + } + + size_t childId = 0; + if (!edge.isTerminal()) { + const auto [it, inserted] = nodes.try_emplace(edge.p, nodes.size()); + childId = it->second; + if (inserted) { + q.push(&edge); + } + } + + if (memory) { + memoryEdge(edge, nodeId, childId, static_cast(i), os, + edgeLabels); + } else if (colored) { + coloredEdge(edge, nodeId, childId, static_cast(i), os, + edgeLabels, classic, formatAsPolar); + } else { + bwEdge(edge, nodeId, childId, static_cast(i), os, + edgeLabels, classic, formatAsPolar); + } + } + } + os << "}\n"; +} + +template +void exportEdgeWeights(const Edge& edge, llvm::raw_ostream& stream) { + struct Priocmp { + bool operator()(const Edge* left, const Edge* right) { + if (left->p == nullptr) { + return right->p != nullptr; + } + if (right->p == nullptr) { + return false; + } + return left->p->v < right->p->v; + } + }; + stream << llvm::format("%+g%+gi\n", RealNumber::val(edge.w.r), + RealNumber::val(edge.w.i)); + + std::unordered_set nodes{}; + + std::priority_queue*, std::vector*>, + Priocmp> + q; + q.push(&edge); + + // bfs until finished + while (!q.empty()) { + auto edgePtr = q.top(); + q.pop(); + + // base case + if (edgePtr->isTerminal()) { + continue; + } + + // check if edgePtr has already been processed + if (const auto ret = nodes.emplace(edgePtr->p); !ret.second) { + continue; + } + + // iterate over edges in reverse to guarantee correct processing order + for (auto i = static_cast(edgePtr->p->e.size() - 1); i >= 0; + --i) { + const auto& child = edgePtr->p->e[static_cast(i)]; + if (child.w.approximatelyZero()) { + // potentially add zero stubs here + continue; + } + + // non-zero child to be included + q.push(&child); + stream << llvm::format("%+g%+gi\n", RealNumber::val(child.w.r), + RealNumber::val(child.w.i)); + } + } +} + +template void toDot(const vEdge&, llvm::raw_ostream&, bool, bool, bool, bool, + bool); +template void toDot(const mEdge&, llvm::raw_ostream&, bool, bool, bool, bool, + bool); +template void exportEdgeWeights(const vEdge&, llvm::raw_ostream&); +template void exportEdgeWeights(const mEdge&, llvm::raw_ostream&); + } // namespace dd diff --git a/src/dd/Package.cpp b/src/dd/Package.cpp index 26076b2237..46a6e1561d 100644 --- a/src/dd/Package.cpp +++ b/src/dd/Package.cpp @@ -25,6 +25,8 @@ #include "dd/UnaryComputeTable.hpp" #include "dd/UniqueTable.hpp" +#include "llvm/Support/raw_ostream.h" + #include #include #include @@ -35,7 +37,6 @@ #include #include #include -#include #include #include #include @@ -69,9 +70,9 @@ void checkMeasurementNormalization(const vEdge& state, const fp epsilon) { throw std::runtime_error("Numerical instability in measurement state"); } if (std::abs(probability - 1) > epsilon) { - std::cerr << "WARNING in MAll: numerical instability occurred during " - "simulation: |alpha|^2 + |beta|^2 = " - << probability << ", but should be 1!\n"; + llvm::errs() << "WARNING in MAll: numerical instability occurred during " + "simulation: |alpha|^2 + |beta|^2 = " + << probability << ", but should be 1!\n"; } } diff --git a/src/dd/RealNumber.cpp b/src/dd/RealNumber.cpp index 3a500f0885..938c3ceec1 100644 --- a/src/dd/RealNumber.cpp +++ b/src/dd/RealNumber.cpp @@ -13,11 +13,11 @@ #include "dd/DDDefinitions.hpp" #include "dd/LinkedListBase.hpp" +#include "llvm/Support/raw_ostream.h" + #include #include #include -#include -#include namespace dd { namespace { @@ -112,19 +112,15 @@ bool RealNumber::approximatelyZero(const RealNumber* e) noexcept { return e == &constants::zero || approximatelyZero(val(e)); } -void RealNumber::writeBinary(const RealNumber* e, std::ostream& os) { +void RealNumber::writeBinary(const RealNumber* e, llvm::raw_ostream& os) { const auto temp = val(e); writeBinary(temp, os); } -void RealNumber::writeBinary(const fp num, std::ostream& os) { +void RealNumber::writeBinary(const fp num, llvm::raw_ostream& os) { os.write(reinterpret_cast(&num), sizeof(fp)); } -void RealNumber::readBinary(dd::fp& num, std::istream& is) { - is.read(reinterpret_cast(&num), sizeof(fp)); -} - namespace constants { // NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables) RealNumber zero{{nullptr}, 0.}; diff --git a/src/dd/RealNumberUniqueTable.cpp b/src/dd/RealNumberUniqueTable.cpp index 8e8699e5a8..919346a43f 100644 --- a/src/dd/RealNumberUniqueTable.cpp +++ b/src/dd/RealNumberUniqueTable.cpp @@ -14,15 +14,18 @@ #include "dd/MemoryManager.hpp" #include "dd/RealNumber.hpp" +#include "llvm/ADT/APFloat.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/raw_ostream.h" + #include #include #include #include #include #include -#include #include -#include namespace dd { @@ -250,32 +253,33 @@ void RealNumberUniqueTable::clear() noexcept { } void RealNumberUniqueTable::print() const { - const auto precision = std::cout.precision(); - std::cout.precision(std::numeric_limits::max_digits10); + const auto printEntry = [](const RealNumber* entry, llvm::StringRef prefix) { + llvm::SmallString<32> value; + llvm::APFloat(entry->value) + .toString(value, std::numeric_limits::max_digits10); + llvm::outs() << prefix << value << " " << reinterpret_cast(entry) + << "\n"; + }; for (std::size_t key = 0; key < table.size(); ++key) { const auto* p = table[key]; if (p != nullptr) { - std::cout << key << ": \n"; + llvm::outs() << key << ": \n"; } - while (p != nullptr) { - std::cout << "\t\t" << p->value << " " - << reinterpret_cast(p) << "\n"; + printEntry(p, "\t\t"); p = p->next(); } - if (table[key] != nullptr) { - std::cout << "\n"; + llvm::outs() << "\n"; } } for (const auto& [value, entry] : exactRoots) { - std::cout << "root\t" << entry->value << " " - << reinterpret_cast(entry) << "\n"; + printEntry(entry, "root\t"); } - std::cout.precision(precision); } -std::ostream& RealNumberUniqueTable::printBucketDistribution(std::ostream& os) { +llvm::raw_ostream& +RealNumberUniqueTable::printBucketDistribution(llvm::raw_ostream& os) { for (const auto* bucket : table) { if (bucket == nullptr) { os << "0\n"; diff --git a/src/dd/Serialization.cpp b/src/dd/Serialization.cpp index c0af705004..8deffe022c 100644 --- a/src/dd/Serialization.cpp +++ b/src/dd/Serialization.cpp @@ -9,21 +9,24 @@ */ #include "dd/CachedEdge.hpp" +#include "dd/ComplexNumbers.hpp" #include "dd/ComplexValue.hpp" #include "dd/DDDefinitions.hpp" #include "dd/Node.hpp" #include "dd/Package.hpp" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/MemoryBuffer.h" + #include #include #include #include -#include -#include -#include +#include #include #include #include +#include #include #include #include @@ -79,56 +82,62 @@ Package::deserializeNode(const std::int64_t index, const Qubit v, return r; } -template -Edge Package::deserialize(std::istream& is, const bool readBinary) { +template +Edge Package::deserialize(llvm::StringRef data, const bool readBinary) { auto result = CachedEdge::one(); ComplexValue rootweight{}; - - std::unordered_map nodes{}; - std::int64_t nodeIndex{}; - Qubit v{}; - std::array edgeWeights{}; - std::array edgeIndices{}; - edgeIndices.fill(-2); - - if (readBinary) { - std::remove_const_t version{}; - is.read(reinterpret_cast(&version), - sizeof(decltype(SERIALIZATION_VERSION))); - if (!is) { - throw std::runtime_error("Truncated serialized DD version."); + std::unordered_map nodes; + const auto invalid = [] { + return std::runtime_error("Invalid or truncated serialized DD."); + }; + const auto read = [&data](T& value) { + if (data.size() < sizeof(T)) { + return false; } - if (version != SERIALIZATION_VERSION) { - throw std::runtime_error( - "Wrong Version of serialization file version. version of file: " + - std::to_string(version) + - "; current version: " + std::to_string(SERIALIZATION_VERSION)); + const auto bytes = data.take_front(sizeof(T)); + std::memcpy(&value, bytes.data(), bytes.size()); + data = data.drop_front(sizeof(T)); + return true; + }; + const auto readLine = [&data](std::string& line) { + if (data.empty()) { + return false; } - - rootweight.readBinary(is); - if (!is) { - throw std::runtime_error("Truncated serialized DD root weight."); + const auto parts = data.split('\n'); + line = parts.first.str(); + data = parts.second; + return true; + }; + if (readBinary) { + std::remove_const_t version{}; + if (!read(version) || version != SERIALIZATION_VERSION || + !read(rootweight.r) || !read(rootweight.i) || + !std::isfinite(rootweight.r) || !std::isfinite(rootweight.i)) { + throw invalid(); } - - while (is.read(reinterpret_cast(&nodeIndex), - sizeof(decltype(nodeIndex)))) { - is.read(reinterpret_cast(&v), sizeof(decltype(v))); - for (std::size_t i = 0U; i < N; i++) { - is.read(reinterpret_cast(&edgeIndices[i]), - sizeof(decltype(edgeIndices[i]))); - edgeWeights[i].readBinary(is); + while (!data.empty()) { + int64_t index{}; + Qubit wire{}; + std::array indices{}; + std::array weights{}; + if (!read(index) || !read(wire)) { + throw invalid(); } - if (!is) { - throw std::runtime_error("Truncated serialized DD node."); + for (size_t i = 0; i < N; ++i) { + if (!read(indices[i]) || !read(weights[i].r) || !read(weights[i].i)) { + throw invalid(); + } } - result = deserializeNode(nodeIndex, v, edgeIndices, edgeWeights, nodes); - } - if (!is.eof() || is.gcount() != 0) { - throw std::runtime_error("Truncated serialized DD node index."); + result = deserializeNode(index, wire, indices, weights, nodes); } } else { + std::int64_t nodeIndex{}; + Qubit v{}; + std::array edgeWeights{}; + std::array edgeIndices{}; + edgeIndices.fill(-2); std::string version; - if (!std::getline(is, version)) { + if (!readLine(version)) { throw std::runtime_error("Missing serialized DD version."); } size_t versionEnd = 0; @@ -159,7 +168,7 @@ Edge Package::deserialize(std::istream& is, const bool readBinary) { std::smatch m; std::string line; - if (!std::getline(is, line) || line.empty()) { + if (!readLine(line) || line.empty()) { throw std::runtime_error("Missing serialized DD root weight."); } if (!std::regex_match(line, m, COMPLEX_WEIGHT_REGEX)) { @@ -167,7 +176,7 @@ Edge Package::deserialize(std::istream& is, const bool readBinary) { } rootweight.fromString(m.str(1), m.str(2)); - while (std::getline(is, line)) { + while (readLine(line)) { if (line.empty()) { continue; } @@ -207,9 +216,6 @@ Edge Package::deserialize(std::istream& is, const bool readBinary) { result = deserializeNode(nodeIndex, v, edgeIndices, edgeWeights, nodes); } } - if (is.bad()) { - throw std::runtime_error("Cannot read serialized DD."); - } if (!std::isfinite(rootweight.r) || !std::isfinite(rootweight.i)) { throw std::runtime_error("Serialized DD weights must be finite."); } @@ -217,21 +223,20 @@ Edge Package::deserialize(std::istream& is, const bool readBinary) { } template -Edge Package::deserialize(const std::string& inputFilename, - const bool readBinary) { - auto ifs = std::ifstream(inputFilename, std::ios::binary); - - if (!ifs.good()) { - throw std::invalid_argument("Cannot open serialized file: " + - inputFilename); +Edge Package::deserializeFile(llvm::StringRef inputFilename, + const bool readBinary) { + auto input = llvm::MemoryBuffer::getFile(inputFilename); + if (!input) { + throw std::invalid_argument( + "Cannot open serialized file: " + inputFilename.str() + ": " + + input.getError().message()); } - - return deserialize(ifs, readBinary); + return deserialize((*input)->getBuffer(), readBinary); } -template vEdge Package::deserialize(std::istream&, bool); -template mEdge Package::deserialize(std::istream&, bool); -template vEdge Package::deserialize(const std::string&, bool); -template mEdge Package::deserialize(const std::string&, bool); +template vEdge Package::deserialize(llvm::StringRef, bool); +template mEdge Package::deserialize(llvm::StringRef, bool); +template vEdge Package::deserializeFile(llvm::StringRef, bool); +template mEdge Package::deserializeFile(llvm::StringRef, bool); } // namespace dd diff --git a/src/dd/statistics/PackageStatistics.cpp b/src/dd/statistics/PackageStatistics.cpp index b693167969..19085adba0 100644 --- a/src/dd/statistics/PackageStatistics.cpp +++ b/src/dd/statistics/PackageStatistics.cpp @@ -25,7 +25,6 @@ #include "llvm/Support/JSON.h" #include "llvm/Support/raw_ostream.h" -#include #include namespace dd { @@ -182,7 +181,8 @@ std::string getStatisticsString(Package& package, return result; } -void printStatistics(Package& package, std::ostream& os) { - os << getStatisticsString(package); +void printStatistics(Package& package, llvm::raw_ostream& os) { + llvm::json::OStream json(os, 2); + writeStatistics(json, package, false); } } // namespace dd diff --git a/src/qdmi/devices/dd/Device.cpp b/src/qdmi/devices/dd/Device.cpp index 87fb5e6bac..355064ef4a 100644 --- a/src/qdmi/devices/dd/Device.cpp +++ b/src/qdmi/devices/dd/Device.cpp @@ -53,7 +53,6 @@ #include #include #include -#include #include #include #include @@ -856,15 +855,12 @@ auto MQT_DDSIM_QDMI_Device_Job_impl_d::submit() -> QDMI_STATUS { if (response.state) { result->dd_ = std::make_unique( response.qubits, RESULT_PACKAGE_CONFIG); - std::istringstream bytes(std::move(*response.state), - std::ios::binary); result->stateVecDD_ = - result->dd_->deserialize(bytes, true); + result->dd_->deserialize(*response.state, true); const auto root = result->stateVecDD_; const auto qubits = root.isTerminal() ? 0U : static_cast(root.p->v) + 1; - if (qubits != response.qubits || - bytes.peek() != std::char_traits::eof()) { + if (qubits != response.qubits) { result.reset(); reusable = false; } else { diff --git a/src/qdmi/devices/dd/WorkerMain.cpp b/src/qdmi/devices/dd/WorkerMain.cpp index 3488e375cf..fa2f23a7e7 100644 --- a/src/qdmi/devices/dd/WorkerMain.cpp +++ b/src/qdmi/devices/dd/WorkerMain.cpp @@ -41,7 +41,6 @@ #include #include #include -#include #include #include #include @@ -321,9 +320,8 @@ qdmi::dd::WorkerResponse execute(const qdmi::dd::WorkerRequest& request, const auto root = execution.stateVecDD_; response.qubits = root.isTerminal() ? 0 : static_cast(root.p->v) + 1; - std::ostringstream bytes(std::ios::binary); + llvm::raw_string_ostream bytes(response.state.emplace()); dd::serialize(root, bytes, true); - response.state = std::move(bytes).str(); } } return response; diff --git a/test/dd/test_complex.cpp b/test/dd/test_complex.cpp index 43a29c0dda..beee0bb9af 100644 --- a/test/dd/test_complex.cpp +++ b/test/dd/test_complex.cpp @@ -19,15 +19,19 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "llvm/ADT/ScopeExit.h" +#include "llvm/Support/raw_ostream.h" + #include +#include #include #include #include #include #include #include -#include #include +#include #include #include #include @@ -73,28 +77,28 @@ TEST_F(CNTest, ComplexNumberCreation) { EXPECT_EQ(RealNumber::val(cn.lookup(-1., -1.).r), -1.); EXPECT_EQ(RealNumber::val(cn.lookup(-1., -1.).i), -1.); auto c = cn.lookup(0., -1.); - std::cout << c << "\n"; + llvm::outs() << c << "\n"; EXPECT_EQ(RealNumber::val(cn.lookup(c).r), 0.); EXPECT_EQ(RealNumber::val(cn.lookup(c).i), -1.); c = cn.lookup(0., 1.); EXPECT_EQ(RealNumber::val(cn.lookup(c).r), 0.); EXPECT_EQ(RealNumber::val(cn.lookup(c).i), 1.); c = cn.lookup(0., -0.5); - std::cout << c << "\n"; + llvm::outs() << c << "\n"; EXPECT_EQ(RealNumber::val(cn.lookup(c).r), 0.); EXPECT_EQ(RealNumber::val(cn.lookup(c).i), -0.5); c = cn.lookup(-1., -1.); EXPECT_EQ(RealNumber::val(cn.lookup(c).r), -1.); EXPECT_EQ(RealNumber::val(cn.lookup(c).i), -1.); - std::cout << c << "\n"; + llvm::outs() << c << "\n"; auto const e = cn.lookup(1., -1.); - std::cout << e << "\n"; - std::cout << ComplexValue{1., 1.} << "\n"; - std::cout << ComplexValue{1., -1.} << "\n"; - std::cout << ComplexValue{1., -0.5} << "\n"; + llvm::outs() << e << "\n"; + llvm::outs() << ComplexValue{1., 1.} << "\n"; + llvm::outs() << ComplexValue{1., -1.} << "\n"; + llvm::outs() << ComplexValue{1., -0.5} << "\n"; ut.print(); - std::cout << ut.getStats(); + llvm::outs() << ut.getStats(); } TEST_F(CNTest, NearZeroLookup) { @@ -288,10 +292,11 @@ TEST(DDComplexTest, NumberPrintingFormatted) { for (const auto& [value, real, imaginary] : cases) { SCOPED_TRACE(value); - std::stringstream ss; + std::string ssStorage; + llvm::raw_string_ostream ss(ssStorage); ComplexValue::printFormatted(ss, value, false); EXPECT_STREQ(ss.str().c_str(), real); - ss.str(""); + ssStorage.clear(); ComplexValue::printFormatted(ss, value, true); EXPECT_STREQ(ss.str().c_str(), imaginary); } @@ -499,3 +504,38 @@ TEST(DDComplexTest, ParsesImaginaryUnits) { EXPECT_EQ(value.r, 0.5); EXPECT_EQ(value.i, 0.); } + +TEST(DDComplexTest, ParsesFiniteSerializedNumbers) { + for (const auto value : { + std::numeric_limits::denorm_min(), + std::numeric_limits::min(), + std::numeric_limits::max(), + -1e-310, + }) { + SCOPED_TRACE(value); + ComplexValue parsed; + parsed.fromString( + ComplexValue::toString(value, 0., false, + std::numeric_limits::max_digits10), + ""); + EXPECT_EQ(parsed.r, value); + EXPECT_EQ(parsed.i, 0.); + } + EXPECT_EQ(ComplexValue::toString(0.123456789, 0., false, 0), "0.1"); + EXPECT_EQ(ComplexValue::toString(0.123456789, 0., true, 3), "0.123"); +} + +TEST(DDComplexTest, SerializedNumbersIgnoreNumericLocale) { + const auto originalLocale = std::string(std::setlocale(LC_NUMERIC, nullptr)); + const llvm::scope_exit restoreLocale( + [&] { std::setlocale(LC_NUMERIC, originalLocale.c_str()); }); + if (std::setlocale(LC_NUMERIC, "de_DE.UTF-8") == nullptr && + std::setlocale(LC_NUMERIC, "de_DE.utf8") == nullptr) { + GTEST_SKIP() << "A comma-decimal numeric locale is unavailable"; + } + EXPECT_EQ(ComplexValue::toString(0.125, -0.375, false), "0.125-0.375i"); + ComplexValue parsed; + parsed.fromString("0.125", "-0.375i"); + EXPECT_EQ(parsed.r, 0.125); + EXPECT_EQ(parsed.i, -0.375); +} diff --git a/test/dd/test_edge_functionality.cpp b/test/dd/test_edge_functionality.cpp index 64ddaaa475..a3a4273933 100644 --- a/test/dd/test_edge_functionality.cpp +++ b/test/dd/test_edge_functionality.cpp @@ -17,6 +17,8 @@ #include "gtest/gtest.h" +#include "llvm/Support/raw_ostream.h" + #include #include #include @@ -271,11 +273,13 @@ TEST(VectorFunctionality, GetSparseVectorTolerance) { TEST(VectorFunctionality, PrintVectorTerminal) { const auto oldPrecision = std::cout.precision(12); + llvm::outs().flush(); testing::internal::CaptureStdout(); dd::printVector(vEdge::zero()); const auto zeroStr = testing::internal::GetCapturedStdout(); EXPECT_EQ(zeroStr, "0: (0,0)\n"); EXPECT_EQ(std::cout.precision(), 12); + llvm::outs().flush(); testing::internal::CaptureStdout(); dd::printVector(vEdge::one()); const auto oneStr = testing::internal::GetCapturedStdout(); @@ -293,6 +297,7 @@ TEST(VectorFunctionality, PrintVector) { std::sqrt(0.4), }; const auto stateDD = makeStateFromVector(state, *dd); + llvm::outs().flush(); testing::internal::CaptureStdout(); dd::printVector(stateDD); const auto stateStr = testing::internal::GetCapturedStdout(); @@ -479,11 +484,13 @@ TEST(MatrixFunctionality, GetSparseMatrixTolerance) { TEST(MatrixFunctionality, PrintMatrixTerminal) { const auto oldPrecision = std::cout.precision(12); + llvm::outs().flush(); testing::internal::CaptureStdout(); dd::printMatrix(mEdge::zero(), 0); const auto zeroStr = testing::internal::GetCapturedStdout(); EXPECT_EQ(zeroStr, "(0,0)\n"); EXPECT_EQ(std::cout.precision(), 12); + llvm::outs().flush(); testing::internal::CaptureStdout(); dd::printMatrix(mEdge::one(), 0); const auto oneStr = testing::internal::GetCapturedStdout(); @@ -503,6 +510,7 @@ TEST(MatrixFunctionality, PrintMatrix) { // clang-format on const auto matDD = dd->makeDDFromMatrix(mat); + llvm::outs().flush(); testing::internal::CaptureStdout(); dd::printMatrix(matDD, dd->qubits()); const auto matStr = testing::internal::GetCapturedStdout(); diff --git a/test/dd/test_package.cpp b/test/dd/test_package.cpp index 6aa2f92cf8..2183cd2204 100644 --- a/test/dd/test_package.cpp +++ b/test/dd/test_package.cpp @@ -31,6 +31,8 @@ #include "llvm/Support/Error.h" #include "llvm/Support/JSON.h" +#include "llvm/Support/Process.h" +#include "llvm/Support/raw_ostream.h" #include #include @@ -39,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -95,7 +98,8 @@ template void checkDotIds() { for (const bool classic : {false, true}) { for (const bool memory : {false, true}) { SCOPED_TRACE(::testing::Message() << colored << classic << memory); - std::ostringstream first; + std::string firstStorage; + llvm::raw_string_ostream first(firstStorage); toDot(far, first, colored, true, classic, memory); const auto dot = first.str(); EXPECT_NE(dot.find("root->0["), std::string::npos); @@ -106,7 +110,8 @@ template void checkDotIds() { EXPECT_NE(dot.find(memory ? "0:0:s->1[" : "0:0:sw->1["), std::string::npos); if (!memory) { - std::ostringstream second; + std::string secondStorage; + llvm::raw_string_ostream second(secondStorage); toDot(near, second, colored, true, classic, memory); EXPECT_EQ(dot, second.str()); } @@ -133,12 +138,12 @@ TEST(DDPackageTest, SvgExportUsesLiteralPathsAndReportsDotFailures) { std::filesystem::perm_options::add); const auto dotDirectory = temp.path() / "folder.with.dot"; std::filesystem::create_directory(dotDirectory); - const auto dotFile = dotDirectory / "diagram with spaces;literal.dot"; + const auto dotFile = dotDirectory / "測定 with spaces;literal.dot"; auto svgFile = dotFile; svgFile.replace_extension(".svg"); Package package(1); - exportToDot(makeZeroState(1, package), dotFile); + exportToDot(makeZeroState(1, package), dotFile.string()); std::ifstream svg(svgFile); std::string rendered; std::getline(svg, rendered); @@ -147,14 +152,14 @@ TEST(DDPackageTest, SvgExportUsesLiteralPathsAndReportsDotFailures) { temp.write("dot", "#!/bin/sh\nexit 7\n"); try { - renderDotToSvg(dotFile); + renderDotToSvg(dotFile.string()); FAIL() << "Graphviz failure should throw"; } catch (const std::runtime_error& error) { EXPECT_STREQ(error.what(), "Graphviz dot exited with code 7"); } std::filesystem::remove(executable); - EXPECT_THROW(renderDotToSvg(dotFile), std::system_error); + EXPECT_THROW(renderDotToSvg(dotFile.string()), std::system_error); } #endif @@ -334,7 +339,7 @@ TEST(DDPackageTest, BellState) { ASSERT_DOUBLE_EQ(dd->fidelity(zeroState, bellState), 0.5); checkDotFile(bellState, "bell_state.dot"); - exportEdgeWeights(bellState, std::cout); + exportEdgeWeights(bellState, llvm::outs()); printStatistics(*dd); } @@ -369,7 +374,7 @@ TEST(DDPackageTest, QFTState) { ASSERT_EQ(dd::getValueByIndex(qftState, qubit).imag(), 0); } - exportEdgeWeights(qftState, std::cout); + exportEdgeWeights(qftState, llvm::outs()); printStatistics(*dd); } @@ -601,9 +606,10 @@ TEST(DDPackageTest, VectorSerializationTest) { auto dd = std::make_unique(2); for (const bool binary : {false, true}) { - std::stringstream serialized{}; + std::string serializedStorage; + llvm::raw_string_ostream serialized(serializedStorage); serialize(vEdge::one(), serialized, binary); - EXPECT_EQ(dd->deserialize(serialized, binary), vEdge::one()); + EXPECT_EQ(dd->deserialize(serializedStorage, binary), vEdge::one()); } auto const hGate = getDD(TestGate(1, Fixture::H), *dd); @@ -613,12 +619,14 @@ TEST(DDPackageTest, VectorSerializationTest) { auto const bellState = dd->multiply(dd->multiply(cxGate, hGate), zeroState); serialize(bellState, "bell_state.dd", false); - auto deserializedBellState = dd->deserialize("bell_state.dd", false); + auto deserializedBellState = + dd->deserializeFile("bell_state.dd", false); EXPECT_EQ(bellState, deserializedBellState); std::filesystem::remove("bell_state.dd"); serialize(bellState, "bell_state_binary.dd", true); - deserializedBellState = dd->deserialize("bell_state_binary.dd", true); + deserializedBellState = + dd->deserializeFile("bell_state_binary.dd", true); EXPECT_EQ(bellState, deserializedBellState); std::filesystem::remove("bell_state_binary.dd"); } @@ -654,9 +662,10 @@ TEST(DDPackageTest, MatrixSerializationTest) { auto dd = std::make_unique(2); for (const bool binary : {false, true}) { - std::stringstream serialized{}; + std::string serializedStorage; + llvm::raw_string_ostream serialized(serializedStorage); serialize(mEdge::one(), serialized, binary); - EXPECT_EQ(dd->deserialize(serialized, binary), mEdge::one()); + EXPECT_EQ(dd->deserialize(serializedStorage, binary), mEdge::one()); } auto const hGate = getDD(TestGate(1, Fixture::H), *dd); @@ -665,13 +674,14 @@ TEST(DDPackageTest, MatrixSerializationTest) { auto const bellMatrix = dd->multiply(cxGate, hGate); serialize(bellMatrix, "bell_matrix.dd", false); - auto deserializedBellMatrix = dd->deserialize("bell_matrix.dd", false); + auto deserializedBellMatrix = + dd->deserializeFile("bell_matrix.dd", false); EXPECT_EQ(bellMatrix, deserializedBellMatrix); std::filesystem::remove("bell_matrix.dd"); serialize(bellMatrix, "bell_matrix_binary.dd", true); deserializedBellMatrix = - dd->deserialize("bell_matrix_binary.dd", true); + dd->deserializeFile("bell_matrix_binary.dd", true); EXPECT_EQ(bellMatrix, deserializedBellMatrix); std::filesystem::remove("bell_matrix_binary.dd"); } @@ -687,50 +697,52 @@ TEST(DDPackageTest, SerializationErrors) { // test non-existing file EXPECT_THROW(serialize(bellState, "./path/that/does/not/exist/filename.dd"), std::invalid_argument); - EXPECT_THROW( - dd->deserialize("./path/that/does/not/exist/filename.dd", true), - std::invalid_argument); + EXPECT_THROW(dd->deserializeFile( + "./path/that/does/not/exist/filename.dd", true), + std::invalid_argument); // test wrong version number - std::stringstream ss{}; + std::string ssStorage; + llvm::raw_string_ostream ss(ssStorage); ss << 2 << "\n"; - EXPECT_THROW(dd->deserialize(ss, false), std::runtime_error); + EXPECT_THROW(dd->deserialize(ssStorage, false), std::runtime_error); ss << 2 << "\n"; - EXPECT_THROW(dd->deserialize(ss, false), std::runtime_error); + EXPECT_THROW(dd->deserialize(ssStorage, false), std::runtime_error); - ss.str(""); + ssStorage.clear(); std::remove_const_t version = 2; ss.write(reinterpret_cast(&version), sizeof(decltype(SERIALIZATION_VERSION))); - EXPECT_THROW(dd->deserialize(ss, true), std::runtime_error); + EXPECT_THROW(dd->deserialize(ssStorage, true), std::runtime_error); ss.write(reinterpret_cast(&version), sizeof(decltype(SERIALIZATION_VERSION))); - EXPECT_THROW(dd->deserialize(ss, true), std::runtime_error); + EXPECT_THROW(dd->deserialize(ssStorage, true), std::runtime_error); // test wrong format - ss.str(""); + ssStorage.clear(); ss << "1\n"; ss << "not_complex\n"; - EXPECT_THROW(dd->deserialize(ss), std::runtime_error); + EXPECT_THROW(dd->deserialize(ssStorage), std::runtime_error); ss << "1\n"; ss << "not_complex\n"; - EXPECT_THROW(dd->deserialize(ss), std::runtime_error); + EXPECT_THROW(dd->deserialize(ssStorage), std::runtime_error); - ss.str(""); + ssStorage.clear(); ss << "1\n"; ss << "1.0\n"; ss << "no_node_here\n"; - EXPECT_THROW(dd->deserialize(ss), std::runtime_error); + EXPECT_THROW(dd->deserialize(ssStorage), std::runtime_error); ss << "1\n"; ss << "1.0\n"; ss << "no_node_here\n"; - EXPECT_THROW(dd->deserialize(ss), std::runtime_error); + EXPECT_THROW(dd->deserialize(ssStorage), std::runtime_error); } TEST(DDPackageTest, DeserializationRejectsTruncatedInput) { Package package(2); const auto state = makeZeroState(2, package); - std::ostringstream binary; + std::string binaryStorage; + llvm::raw_string_ostream binary(binaryStorage); serialize(state, binary, true); const auto bytes = binary.str(); constexpr auto rootOffset = sizeof(SERIALIZATION_VERSION); @@ -744,11 +756,11 @@ TEST(DDPackageTest, DeserializationRejectsTruncatedInput) { bytes.size() - 1, }) { SCOPED_TRACE(length); - std::istringstream input(bytes.substr(0, length)); + const std::string input(bytes.substr(0, length)); EXPECT_THROW(package.deserialize(input, true), std::runtime_error); } for (const auto* text : {"", "\n", "1\n"}) { - std::istringstream input(text); + const std::string input(text); EXPECT_ANY_THROW(package.deserialize(input)); } } @@ -770,9 +782,9 @@ TEST(DDPackageTest, DeserializationPreservesImaginaryUnits) { for (const auto* unit : {"i", "I", "+i", "+I", "-i", "-I"}) { SCOPED_TRACE(unit); const std::complex phase{0., *unit == '-' ? -1. : 1.}; - std::istringstream root(std::string("1\n") + unit + "\n"); + const std::string root(std::string("1\n") + unit + "\n"); EXPECT_EQ(dd::getValueByIndex(package.deserialize(root), 0), phase); - std::istringstream edge(std::string("1\n1\n0 0 (-1 ") + unit + ") ()\n"); + const std::string edge(std::string("1\n1\n0 0 (-1 ") + unit + ") ()\n"); EXPECT_EQ(dd::getValueByIndex(package.deserialize(edge), 0), phase); } } @@ -800,18 +812,19 @@ TEST(DDPackageTest, DeserializationRejectsMalformedTextAndRecovers) { "1\n1\n999999999999999999999999999 0 (-1 1) ()\n", }) { SCOPED_TRACE(text); - std::istringstream input(text); + const std::string input(text); EXPECT_ANY_THROW(package.deserialize(input)); } - std::istringstream valid("1\n1\n2147483648 0 (-1 1) ()\n9223372036854775807 " - "1 (2147483648 1) ()\n"); + const std::string valid("1\n1\n2147483648 0 (-1 1) ()\n9223372036854775807 " + "1 (2147483648 1) ()\n"); EXPECT_EQ(package.deserialize(valid), makeZeroState(2, package)); } TEST(DDPackageTest, DeserializationRejectsInvalidBinaryFieldsAndRecovers) { Package package(2); const auto state = makeZeroState(2, package); - std::ostringstream binary; + std::string binaryStorage; + llvm::raw_string_ostream binary(binaryStorage); serialize(state, binary, true); const auto bytes = binary.str(); constexpr auto rootOffset = sizeof(SERIALIZATION_VERSION); @@ -825,7 +838,7 @@ TEST(DDPackageTest, DeserializationRejectsInvalidBinaryFieldsAndRecovers) { SCOPED_TRACE(::testing::Message() << offset << ": " << value); auto invalid = bytes; std::memcpy(&invalid.at(offset), &value, sizeof(value)); - std::istringstream input(invalid); + const std::string input(invalid); EXPECT_THROW(package.deserialize(input, true), std::runtime_error); }; reject(nodeOffset, int64_t{-1}); @@ -850,9 +863,10 @@ TEST(DDPackageTest, DeserializationRejectsInvalidBinaryFieldsAndRecovers) { } } for (const bool isBinary : {false, true}) { - std::stringstream stream; + std::string streamStorage; + llvm::raw_string_ostream stream(streamStorage); serialize(state, stream, isBinary); - EXPECT_EQ(package.deserialize(stream, isBinary), state); + EXPECT_EQ(package.deserialize(streamStorage, isBinary), state); } } @@ -865,9 +879,10 @@ TEST(DDPackageTest, DeserializationRejectsSkippedVectorLevels) { vNode node{}; node.v = 2; node.e = {child, vEdge::zero()}; - std::stringstream stream; + std::string streamStorage; + llvm::raw_string_ostream stream(streamStorage); serialize(vEdge{.p = &node, .w = Complex::one()}, stream, binary); - EXPECT_THROW(package.deserialize(stream, binary), + EXPECT_THROW(package.deserialize(streamStorage, binary), std::runtime_error); } } @@ -879,9 +894,10 @@ TEST(DDPackageTest, DeserializationPreservesSkippedMatrixLevels) { const auto low = package.makeGateDD(X_MAT, 0); for (const auto& matrix : {high, package.multiply(high, low)}) { for (const bool binary : {false, true}) { - std::stringstream stream; + std::string streamStorage; + llvm::raw_string_ostream stream(streamStorage); serialize(matrix, stream, binary); - EXPECT_EQ(package.deserialize(stream, binary), matrix); + EXPECT_EQ(package.deserialize(streamStorage, binary), matrix); } } } @@ -1455,7 +1471,8 @@ TEST(DDPackageTest, SpecialCaseTerminal) { std::array{false, false, false, false}, std::array{true, true, false, true}, }) { - std::ostringstream os; + std::string osStorage; + llvm::raw_string_ostream os(osStorage); toDot(one, os, options[0], options[1], options[2], options[3]); EXPECT_FALSE(os.str().empty()); } @@ -1675,7 +1692,9 @@ TEST(DDPackageTest, NormalizationDominantPhaseIsIndependentOfScale) { ComplexValue{0., 0x1p-24}, ComplexValue{0.5, 0.5}, }) { - SCOPED_TRACE(::testing::Message() << second << " * " << factor); + SCOPED_TRACE(::testing::Message() + << ComplexValue::toString(second.r, second.i) << " * " + << ComplexValue::toString(factor.r, factor.i)); const auto scaled = make(second, factor); EXPECT_EQ(scaled.p, reference.p); const auto expected = static_cast(reference.w) * factor; @@ -2056,72 +2075,73 @@ TEST(DDPackageTest, FullMeasurementPreservesBitOrder) { } TEST(DDPackageTest, ExportPolarPhaseFormatted) { - std::ostringstream phaseString; + std::string phaseStringStorage; + llvm::raw_string_ostream phaseString(phaseStringStorage); // zero case printPhaseFormatted(phaseString, 0); EXPECT_STREQ(phaseString.str().c_str(), "ℯ(iπ 0)"); - phaseString.str(""); + phaseStringStorage.clear(); // one cases printPhaseFormatted(phaseString, 0.5 * PI); EXPECT_STREQ(phaseString.str().c_str(), "ℯ(iπ/2)"); - phaseString.str(""); + phaseStringStorage.clear(); printPhaseFormatted(phaseString, -0.5 * PI); EXPECT_STREQ(phaseString.str().c_str(), "ℯ(-iπ/2)"); - phaseString.str(""); + phaseStringStorage.clear(); printPhaseFormatted(phaseString, PI); EXPECT_STREQ(phaseString.str().c_str(), "ℯ(iπ)"); - phaseString.str(""); + phaseStringStorage.clear(); printPhaseFormatted(phaseString, -PI); EXPECT_STREQ(phaseString.str().c_str(), "ℯ(-iπ)"); - phaseString.str(""); + phaseStringStorage.clear(); // 1/sqrt(2) cases printPhaseFormatted(phaseString, SQRT2_2 * PI); EXPECT_STREQ(phaseString.str().c_str(), "ℯ(iπ/√2)"); - phaseString.str(""); + phaseStringStorage.clear(); printPhaseFormatted(phaseString, 2 * SQRT2_2 * PI); EXPECT_STREQ(phaseString.str().c_str(), "ℯ(iπ 2/√2)"); - phaseString.str(""); + phaseStringStorage.clear(); printPhaseFormatted(phaseString, 0.5 * SQRT2_2 * PI); EXPECT_STREQ(phaseString.str().c_str(), "ℯ(iπ/(2√2))"); - phaseString.str(""); + phaseStringStorage.clear(); printPhaseFormatted(phaseString, 0.75 * SQRT2_2 * PI); EXPECT_STREQ(phaseString.str().c_str(), "ℯ(iπ 3/(4√2))"); - phaseString.str(""); + phaseStringStorage.clear(); // pi cases mhhh pie printPhaseFormatted(phaseString, PI); EXPECT_STREQ(phaseString.str().c_str(), "ℯ(iπ)"); - phaseString.str(""); + phaseStringStorage.clear(); printPhaseFormatted(phaseString, 2 * PI); EXPECT_STREQ(phaseString.str().c_str(), "ℯ(iπ 2)"); - phaseString.str(""); + phaseStringStorage.clear(); printPhaseFormatted(phaseString, 0.5 * PI); EXPECT_STREQ(phaseString.str().c_str(), "ℯ(iπ/2)"); - phaseString.str(""); + phaseStringStorage.clear(); printPhaseFormatted(phaseString, 0.75 * PI); EXPECT_STREQ(phaseString.str().c_str(), "ℯ(iπ 3/4)"); - phaseString.str(""); + phaseStringStorage.clear(); printPhaseFormatted(phaseString, 0.25 * PI); EXPECT_STREQ(phaseString.str().c_str(), "ℯ(iπ/4)"); - phaseString.str(""); + phaseStringStorage.clear(); // general case printPhaseFormatted(phaseString, 0.12345 * PI); EXPECT_STREQ(phaseString.str().c_str(), "ℯ(iπ 0.12345)"); - phaseString.str(""); + phaseStringStorage.clear(); } TEST(DDPackageTest, BasicNumericStabilityTest) { @@ -2142,7 +2162,7 @@ TEST(DDPackageTest, BasicNumericStabilityTest) { result.p->e[1].w.toString(false, limits::max_digits10); std::cout << topWeight << " | " << leftWeight << " | " << rightWeight << "\n"; EXPECT_EQ(topWeight, "1"); - std::ostringstream oss{}; + std::ostringstream oss; oss << std::setprecision(limits::max_digits10) << SQRT2_2; EXPECT_EQ(leftWeight, oss.str()); oss.str(""); @@ -2866,7 +2886,9 @@ TEST(DDPackageTest, AddCacheKeyRetainsCommonScale) { TEST(DDPackageTest, WideCoherentAdditionRetainsNormalizationAndPhase) { for (const size_t width : {8U, 120U, 150U}) { for (const ComplexValue phase : {ComplexValue{1., 0.}, {0., 1.}}) { - SCOPED_TRACE(::testing::Message() << width << " / " << phase); + SCOPED_TRACE(::testing::Message() + << width << " / " + << ComplexValue::toString(phase.r, phase.i)); Package package(width); const auto plus = makeBasisState(width, std::vector(width, BasisStates::plus), package); @@ -3192,9 +3214,10 @@ TEST(DDPackageTest, WideHadamardMatricesPreserveRootScale) { Package other(width); check(other.transfer(matrix), width, expected); for (const bool binary : {false, true}) { - std::stringstream stream; + std::string streamStorage; + llvm::raw_string_ostream stream(streamStorage); serialize(matrix, stream, binary); - check(other.deserialize(stream, binary), width, expected); + check(other.deserialize(streamStorage, binary), width, expected); } const auto h = package.makeGateDD(H_MAT, 0); for (size_t repetition = 0; repetition < 2; ++repetition) { @@ -3247,9 +3270,10 @@ TEST(DDPackageTest, MatrixRootSerializationPreservesSubnormals) { SCOPED_TRACE(value); const auto root = mEdge::terminal(package.cn.lookupRoot({value, -value})); for (const bool binary : {false, true}) { - std::stringstream stream; + std::string streamStorage; + llvm::raw_string_ostream stream(streamStorage); serialize(root, stream, binary); - const auto restored = package.deserialize(stream, binary); + const auto restored = package.deserialize(streamStorage, binary); EXPECT_EQ(RealNumber::val(restored.w.r), value); EXPECT_EQ(RealNumber::val(restored.w.i), -value); } @@ -3311,3 +3335,60 @@ TEST(DDStatistics, ResetMemoryUsageIsFinite) { EXPECT_EQ(value.getAsObject()->getNumber("usage_ratio"), 0.); EXPECT_EQ(value.getAsObject()->getInteger("num_used_peak"), 1); } + +namespace dd { + +TEST(DDPackageTest, BinarySerializationPreservesNulsAndRejectsTrailingBytes) { + std::string bytes; + llvm::raw_string_ostream output(bytes); + serialize(vEdge::one(), output, true); + ASSERT_EQ(bytes.size(), sizeof(SERIALIZATION_VERSION) + (2 * sizeof(fp))); + EXPECT_NE(bytes.find('\0'), std::string::npos); + EXPECT_EQ(bytes.back(), '\0'); + std::string expected(bytes.size(), '\0'); + const auto version = SERIALIZATION_VERSION; + const fp real = 1.; + std::memcpy(expected.data(), &version, sizeof(version)); + std::memcpy(std::addressof(expected.at(sizeof(version))), &real, + sizeof(real)); + EXPECT_EQ(bytes, expected); + Package package(1); + EXPECT_EQ(package.deserialize(bytes, true), vEdge::one()); + bytes.push_back('\0'); + EXPECT_THROW(package.deserialize(bytes, true), std::runtime_error); +} + +TEST(DDPackageTest, OutputFilesCanReuseClosedStandardDescriptors) { + const mqt::test::TemporaryDirectory temp; + const auto filename = (temp.path() / "output.dd").string(); + EXPECT_EXIT( + { + if (llvm::sys::Process::SafelyCloseFileDescriptor(0)) { + std::_Exit(1); + } + serialize(vEdge::one(), filename, true); + exportToDot(vEdge::one(), filename, true, false, false, false, false); + std::_Exit(0); + }, + testing::ExitedWithCode(0), ""); +} + +TEST(DDPackageTest, TextDeserializationPreservesSpacingAndVersionGrammar) { + Package package(1); + for (const auto* version : {"1", "+1", " 1", "\t1"}) { + for (const auto* weight : {"1+2i", "1 + 2i", "1 +2i", "1+ 2i"}) { + SCOPED_TRACE(::testing::Message() << version << ": " << weight); + const auto state = package.deserialize(std::string(version) + + "\n" + weight + "\n"); + EXPECT_EQ(getValueByIndex(state, 0), (std::complex{1., 2.})); + } + } + for (const auto* weight : + {"1+2 3i", "1 + 2i", "1+ 2i", "1+2i ", "1+2\ti"}) { + SCOPED_TRACE(weight); + EXPECT_THROW(package.deserialize(std::string("1\n") + weight + "\n"), + std::runtime_error); + } +} + +} // namespace dd diff --git a/test/dd/test_statistics.cpp b/test/dd/test_statistics.cpp index 8063b825c1..530b60e8ee 100644 --- a/test/dd/test_statistics.cpp +++ b/test/dd/test_statistics.cpp @@ -21,6 +21,7 @@ #include "llvm/ADT/ScopeExit.h" #include "llvm/Support/Error.h" #include "llvm/Support/JSON.h" +#include "llvm/Support/raw_ostream.h" #include #include @@ -45,12 +46,21 @@ TEST(DDStatistics, SerializesIndependentlyOfNumericLocale) { dd::Package package(2); package.incRef(dd::makeZeroState(2, package)); const auto serialize = [&] { + std::string streamedMemory; + llvm::raw_string_ostream(streamedMemory) << memory; + std::string streamedPackage; + llvm::raw_string_ostream out(streamedPackage); + dd::printStatistics(package, out); + EXPECT_EQ(streamedMemory, memory.toString()); + EXPECT_EQ(streamedPackage, dd::getStatisticsString(package, false)); return std::array{ memory.toString(), table.dd::TableStatistics::toString(), table.toString(), dd::getStatisticsString(package, true), dd::getStatisticsString(package, false), + streamedMemory, + streamedPackage, }; }; diff --git a/test/python/dd/test_export.py b/test/python/dd/test_export.py index 14eebd706e..6c18c2290d 100644 --- a/test/python/dd/test_export.py +++ b/test/python/dd/test_export.py @@ -17,7 +17,7 @@ import numpy as np import pytest -from mqt.core.dd import DDPackage +from mqt.core.dd import DDPackage, MatrixDD if TYPE_CHECKING: from pathlib import Path @@ -78,7 +78,7 @@ def test_svg_native_fallback(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> dd = package.zero_state(1) directory = tmp_path / "folder.with.dot" directory.mkdir() - filename = directory / "diagram with spaces;literal" + filename = directory / "測定 with spaces;literal" dd.to_svg(str(filename)) @@ -95,5 +95,21 @@ def test_svg_native_fallback(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> dd.to_svg(str(filename)) dot.unlink() - with pytest.raises(RuntimeError, match="Cannot start Graphviz dot"): + with pytest.raises(RuntimeError, match="Cannot find Graphviz dot"): dd.to_svg(str(filename)) + + +@pytest.mark.parametrize("matrix", [False, True]) +@pytest.mark.parametrize("binary", [False, True]) +def test_serialized_bytes_and_errors(*, matrix: bool, binary: bool) -> None: + """Preserve serialized values and translate malformed-input exceptions.""" + package = DDPackage(2) + values = np.array([[1, 2j], [-3, -4j]]) if matrix else np.array([1, 2j, -3, -4j]) + dd = package.from_matrix(values) if matrix else package.from_vector(values) + payload = dd.to_bytes(binary=binary) + restored = type(dd).from_bytes(package, payload, binary=binary) + actual = restored.get_matrix(1) if isinstance(restored, MatrixDD) else restored.get_vector() + np.testing.assert_allclose(actual, values, rtol=1e-14, atol=0) + malformed = payload + b"\0" if binary else b"1\ninvalid\n" + with pytest.raises(RuntimeError): + type(dd).from_bytes(package, malformed, binary=binary) From f1e45d3c625aae0a279585a3d93bc9ed628f7bc0 Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Sun, 25 Oct 2026 13:41:11 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=93=9D=20Document=20DD=20decimal=20fo?= =?UTF-8?q?rmatting=20precision?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Describe the new precision argument on printFormatted so Doxygen's parameter check passes. Use the same documented defaults on toString. Reproduce the RTD warning locally and validate the complete executable Sphinx/Doxygen documentation, generated links and rendered API entry. Signed-off-by: Lukas Burgholzer Assisted-by: GPT-6 via Codex --- include/mqt-core/dd/ComplexValue.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/mqt-core/dd/ComplexValue.hpp b/include/mqt-core/dd/ComplexValue.hpp index c4aea316eb..84c33b0a1a 100644 --- a/include/mqt-core/dd/ComplexValue.hpp +++ b/include/mqt-core/dd/ComplexValue.hpp @@ -100,6 +100,7 @@ struct ComplexValue { /// @param os The output stream to write to. /// @param num The number to print. /// @param imaginary Whether the number is imaginary. + /// @param precision Significant decimal digits; negative uses six, zero one. static void printFormatted(llvm::raw_ostream& os, fp num, bool imaginary = false, int precision = -1); @@ -108,7 +109,7 @@ struct ComplexValue { /// @param real The real part of the complex number. /// @param imag The imaginary part of the complex number. /// @param formatted Whether to pretty print the number. - /// @param precision The precision to use for printing numbers.. + /// @param precision Significant decimal digits; negative uses six, zero one. /// @returns The string representation of the complex number. static std::string toString(const fp& real, const fp& imag, bool formatted = true, int precision = -1);