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..84c33b0a1a 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,14 +100,16 @@ 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); + /// @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); /// Print the given complex number to the given output stream. /// /// @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); @@ -151,7 +145,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)