diff --git a/docs/source/Usage.ipynb b/docs/source/Usage.ipynb index f5edf246..14b531ff 100644 --- a/docs/source/Usage.ipynb +++ b/docs/source/Usage.ipynb @@ -2,31 +2,136 @@ "cells": [ { "cell_type": "markdown", - "id": "ef64ecb9", + "id": "a517773a2212f919", "metadata": {}, "source": [ "# Usage from Python\n", "\n", "MQT DDSIM is available for multiple Python versions (>=3.8) from [PyPI](https://pypi.org/project/mqt.ddsim/). \n", - "Using it as backend for [Qiskit](https://qiskit.org/) additionally requires at least [qiskit-terra](https://pypi.org/project/qiskit-terra/).\n", + "See the [Installation guide](Installation) for more information on how to install MQT DDSIM." + ] + }, + { + "cell_type": "markdown", + "id": "3deb856baff0407", + "metadata": {}, + "source": [ + "## Standalone Usage\n", "\n", - "In a [virtual environment](https://docs.python.org/3/tutorial/venv.html) you can use the following snippet:\n", - "```\n", - "$ python3 -m venv .venv\n", - "$ . .venv/bin/activate\n", - "(.venv) $ pip install -U pip setuptools wheel\n", - "(.venv) $ pip install mqt.ddsim jupyter\n", - "(.venv) $ jupyter notebook\n", + "The classical simulation methods available in MQT DDSIM can be used in a standalone fashion.\n", + "To this end, DDSIM currently offers four different kinds of simulators:" + ] + }, + { + "cell_type": "markdown", + "id": "e83bfda7b09df376", + "metadata": {}, + "source": [ + "### The CircuitSimulator\n", + "The standard, Schrödinger-style simulator. \n", + "Takes a circuit and sequentially simulates it using decision diagrams by successively applying the operations in the circuit.\n", + "Can be used to obtain the full statevector of the circuit or to sample from the circuit." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9e784f484d4d137e", + "metadata": {}, + "outputs": [], + "source": [ + "from qiskit import QuantumCircuit\n", + "from mqt.ddsim import CircuitSimulator\n", + "\n", + "# A simple circuit that creates a Bell state\n", + "circ = QuantumCircuit(2)\n", + "circ.h(0)\n", + "circ.cx(0, 1)\n", "\n", - "```\n", + "# Create the simulator\n", + "sim = CircuitSimulator(circ)\n", + "\n", + "# Simulate the circuit and sample 1024 shots from the resulting state\n", + "result = sim.simulate(shots=1024)\n", + "print(result)" + ] + }, + { + "cell_type": "markdown", + "id": "3613d6a1555e984b", + "metadata": {}, + "source": [ + "Obtaining the full statevector is also possible:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cb4ecf46083a8588", + "metadata": {}, + "outputs": [], + "source": [ + "sv = sim.get_vector()\n", + "print(sv)" + ] + }, + { + "cell_type": "markdown", + "id": "c8aa4daa649b4f0a", + "metadata": {}, + "source": [ + "If you want to inspect the final decision diagram, you can get a Graphviz representation of it.\n", + "For that, make sure that you have Graphviz installed and that the `graphviz` Python package is available.\n", + "A simple `pip install graphviz` should do the trick.\n", + "Then, you can call the `export_dd_to_graphviz_str` method on the simulator to obtain a Graphviz representation of the decision diagram. The following shows the default configuration options for the export." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3623b43251acdb1c", + "metadata": {}, + "outputs": [], + "source": [ + "import graphviz\n", + "\n", + "dot = sim.export_dd_to_graphviz_str(colored=True, edge_labels=False, classic=False, memory=False, format_as_polar=True)\n", + "\n", + "graphviz.Source(source=dot)" + ] + }, + { + "cell_type": "markdown", + "id": "4e26c61591c7a434", + "metadata": {}, + "source": [ + "### The UnitarySimulator\n", + "\n", + "To come soon.\n", + "\n", + "### The HybridCircuitSimulator\n", + "\n", + "To come soon.\n", + "\n", + "### The PathSimulator\n", + "\n", + "To come soon." + ] + }, + { + "cell_type": "markdown", + "id": "ef64ecb9", + "metadata": {}, + "source": [ + "## Usage as Qiskit Backends\n", "\n", - "The DDSIMProvider currently has five backends\n", + "The DDSIMProvider currently has seven backends\n", "\n", "- `QasmSimulator` simulates a circuit and generates the given number of shots\n", "- `StatevectorSimulator` simulates the circuit and returns the statevector\n", - "- `HybridQasmSimulator` simualtes a circuit in parallel using a hybrid Schrodinger-Feynman technique and generates the given number of shots\n", - "- `HybridStatevectorSimulator` simulates the circuit in parallel using a hybrid Schrodinger-Feynman technique and returns the statevector\n", - "- `PathQuasmSimulator` simulates a circuit by potential using a different order of multiplying operations and operation/state and generates the requested number of shots\n", + "- `HybridQasmSimulator` simulates a circuit in parallel using a hybrid Schrödinger-Feynman technique and generates the given number of shots\n", + "- `HybridStatevectorSimulator` simulates the circuit in parallel using a hybrid Schrödinger-Feynman technique and returns the statevector\n", + "- `PathQasmSimulator` simulates a circuit by potential using a different order of multiplying operations and operation/state and generates the requested number of shots\n", "- `PathStatevectorSimulator` simulates a circuit by potential using a different order of multiplying operations and operation/state and returns the statevector\n", "- `UnitarySimulator` constructs the unitary functionality of a circuit and returns the corresponding unitary matrix" ] @@ -36,7 +141,7 @@ "id": "b806c0fb", "metadata": {}, "source": [ - "## QasmSimulator for Sampling\n", + "### QasmSimulator for Sampling\n", "\n", "The QasmSimulator-Backend takes a QuantumCircuit object and simulates it using decision diagrams in the underlying C++ implementation.\n", "For circuits with no non-unitary operations (except for measurements at the end of the circuit) the simulation is only done once and the samples subsequently drawn from the decision diagram, resulting in fast runtime." @@ -79,7 +184,7 @@ "id": "4ede16a7", "metadata": {}, "source": [ - "## StatevectorSimulator for Observing the Statevector\n", + "### StatevectorSimulator for Observing the Statevector\n", "\n", "The StatevectorSimulator-Backend takes a QuantumCircuit as above but returns the state vector instead of a number of samples." ] @@ -105,7 +210,7 @@ "id": "f08043a9", "metadata": {}, "source": [ - "## HybridQasmSimulator for Sampling\n", + "### HybridQasmSimulator for Sampling\n", "\n", "The HybridQasmSimulator-Backend takes a QuantumCircuit object and uses a hybrid Schrodinger-Feynman technique to simulate the circuit in parallel using decision diagrams.\n", "It currently assumes that no non-unitary operations (besides measurements at the end of the circuit) are present in the circuit.\n", @@ -140,7 +245,7 @@ "id": "48f4ce30", "metadata": {}, "source": [ - "## HybridStatevectorSimulator for Observing the Statevector\n", + "### HybridStatevectorSimulator for Observing the Statevector\n", "\n", "The HybridStatevectorSimulator-Backend provides the same options as the HybridQasmSimulator-Backend, but returns the final statevector as a result.\n", "Note that `shots` has to be set to `0` when using the `amplitude` mode as the statevector array is modified in-place for sampling and, hence, the state vector is no longer available afterwards." @@ -167,7 +272,7 @@ "id": "1dbb7dbe", "metadata": {}, "source": [ - "## PathQasmSimulator for Sampling" + "### PathQasmSimulator for Sampling" ] }, { @@ -190,7 +295,7 @@ "id": "c72242ec", "metadata": {}, "source": [ - "## UnitarySimulator for Constructing Functional Representations\n", + "### UnitarySimulator for Constructing Functional Representations\n", "\n", "The UnitarySimulator-Backend takes a quantum circuit and constructs the corresponding unitary matrix using decision diagrams.\n", "\n", diff --git a/docs/source/api/ddsim.rst b/docs/source/api/ddsim.rst index 4ee63830..57960d92 100644 --- a/docs/source/api/ddsim.rst +++ b/docs/source/api/ddsim.rst @@ -4,14 +4,6 @@ ddsim package Submodules ---------- -ddsim.error module ------------------- - -.. automodule:: mqt.ddsim.error - :members: - :undoc-members: - :show-inheritance: - ddsim.hybridqasmsimulator module -------------------------------- diff --git a/include/HybridSchrodingerFeynmanSimulator.hpp b/include/HybridSchrodingerFeynmanSimulator.hpp index 71271081..16e209e7 100644 --- a/include/HybridSchrodingerFeynmanSimulator.hpp +++ b/include/HybridSchrodingerFeynmanSimulator.hpp @@ -65,6 +65,10 @@ class HybridSchrodingerFeynmanSimulator: public CircuitSimulator { [[nodiscard]] Mode getMode() const { return mode; } +protected: + /// See Simulator::exportDDtoGraphviz + void exportDDtoGraphviz(std::ostream& os, bool colored, bool edgeLabels, bool classic, bool memory, bool formatAsPolar) override; + private: std::size_t nthreads = 2; dd::CVec finalAmplitudes{}; diff --git a/include/Simulator.hpp b/include/Simulator.hpp index d6a3b775..ce2321ee 100644 --- a/include/Simulator.hpp +++ b/include/Simulator.hpp @@ -115,6 +115,30 @@ class Simulator { dd::vEdge static removeNodes(std::unique_ptr>& localDD, dd::vEdge edge, std::map& dagEdges); + /** + * @brief Get a GraphViz representation of the currently stored DD. + * @param colored Whether to output color-coded edge weights or black and white. + * @param edgeLabels Whether to output edge labels. + * @param classic Whether to use the classic visualization or a more modern representation. + * @param memory An alternative representation for nodes that includes detailed memory information. + * @param formatAsPolar Whether to format the complex numbers as polar or cartesian coordinates. + * @returns A Graphviz program representing the current DD + */ + std::string exportDDtoGraphvizString(bool colored = true, + bool edgeLabels = false, bool classic = false, bool memory = false, bool formatAsPolar = true); + + /** + * @brief Write a GraphViz representation of the currently stored DD to a file. + * @param filename The name of the file to write to. + * @param colored Whether to output color-coded edge weights or black and white. + * @param edgeLabels Whether to output edge labels. + * @param classic Whether to use the classic visualization or a more modern representation. + * @param memory An alternative representation for nodes that includes detailed memory information. + * @param formatAsPolar Whether to format the complex numbers as polar or cartesian coordinates. + */ + void exportDDtoGraphvizFile(const std::string& filename, bool colored = true, + bool edgeLabels = false, bool classic = false, bool memory = false, bool formatAsPolar = true); + std::unique_ptr> dd = std::make_unique>(); dd::vEdge rootEdge{}; @@ -124,6 +148,8 @@ class Simulator { std::uint64_t seed = 0; bool hasFixedSeed; dd::fp epsilon = 0.001; + + virtual void exportDDtoGraphviz(std::ostream& os, bool colored, bool edgeLabels, bool classic, bool memory, bool formatAsPolar); }; struct StochasticNoiseSimulatorDDPackageConfig: public dd::DDPackageConfig { diff --git a/include/UnitarySimulator.hpp b/include/UnitarySimulator.hpp index a8f78103..8741c930 100644 --- a/include/UnitarySimulator.hpp +++ b/include/UnitarySimulator.hpp @@ -48,6 +48,11 @@ class UnitarySimulator: public CircuitSimulator { [[nodiscard]] std::size_t getFinalNodeCount() const { return e.size(); } [[nodiscard]] std::size_t getMaxNodeCount() const override { return Simulator::dd->template getUniqueTable().getPeakNumActiveEntries(); } +protected: + /// See Simulator::exportDDtoGraphviz + void exportDDtoGraphviz(std::ostream& os, bool colored, + bool edgeLabels, bool classic, bool memory, bool formatAsPolar) override; + private: qc::MatrixDD e{}; diff --git a/pyproject.toml b/pyproject.toml index 870a6630..4c68c3a1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,7 @@ tnflow = [ test = ["pytest>=7.0"] coverage = ["mqt.ddsim[test]", "pytest-cov"] docs = [ + "mqt.ddsim[tnflow]", "furo>=2023.08.17", "sphinx", "setuptools-scm>=7", @@ -63,6 +64,7 @@ docs = [ "sphinxext-opengraph", "sphinx-autodoc-typehints", "qiskit-terra[visualization]", + "graphviz", ] dev = ["mqt.ddsim[tnflow, coverage, docs]"] diff --git a/src/HybridSchrodingerFeynmanSimulator.cpp b/src/HybridSchrodingerFeynmanSimulator.cpp index 3366294b..42050f0d 100644 --- a/src/HybridSchrodingerFeynmanSimulator.cpp +++ b/src/HybridSchrodingerFeynmanSimulator.cpp @@ -281,4 +281,12 @@ void HybridSchrodingerFeynmanSimulator::simulateHybridAmplitudes(qc::Qub finalAmplitudes = std::move(amplitudes[0]); } +template +void HybridSchrodingerFeynmanSimulator::exportDDtoGraphviz(std::ostream& os, const bool colored, const bool edgeLabels, const bool classic, const bool memory, const bool formatAsPolar) { + if (mode == Mode::Amplitude) { + Simulator::rootEdge = Simulator::dd->makeStateFromVector(finalAmplitudes); + } + return Simulator::exportDDtoGraphviz(os, colored, edgeLabels, classic, memory, formatAsPolar); +} + template class HybridSchrodingerFeynmanSimulator; diff --git a/src/Simulator.cpp b/src/Simulator.cpp index 09b1cd87..3380ea95 100644 --- a/src/Simulator.cpp +++ b/src/Simulator.cpp @@ -1,5 +1,7 @@ #include "Simulator.hpp" +#include "dd/Export.hpp" + #include #include #include @@ -333,5 +335,25 @@ std::pair Simulator::getPathOfLeastResist std::string{result.rbegin(), result.rend()}}; } +template +void Simulator::exportDDtoGraphviz(std::ostream& os, const bool colored, const bool edgeLabels, const bool classic, const bool memory, const bool formatAsPolar) { + assert(os.good()); + dd::toDot(rootEdge, os, colored, edgeLabels, classic, memory, formatAsPolar); +} + +template +std::string Simulator::exportDDtoGraphvizString(const bool colored, const bool edgeLabels, const bool classic, const bool memory, const bool formatAsPolar) { + std::ostringstream oss{}; + exportDDtoGraphviz(oss, colored, edgeLabels, classic, memory, formatAsPolar); + return oss.str(); +} + +template +void Simulator::exportDDtoGraphvizFile(const std::string& filename, const bool colored, const bool edgeLabels, const bool classic, const bool memory, const bool formatAsPolar) { + std::ofstream ofs(filename); + exportDDtoGraphviz(ofs, colored, edgeLabels, classic, memory, formatAsPolar); +} + template class Simulator; template class Simulator; +template class Simulator; diff --git a/src/UnitarySimulator.cpp b/src/UnitarySimulator.cpp index 1b06c26f..e0d65a5a 100644 --- a/src/UnitarySimulator.cpp +++ b/src/UnitarySimulator.cpp @@ -1,5 +1,6 @@ #include "UnitarySimulator.hpp" +#include "dd/Export.hpp" #include "dd/FunctionalityConstruction.hpp" #include @@ -17,4 +18,9 @@ void UnitarySimulator::construct() { constructionTime = std::chrono::duration(end - start).count(); } +template +void UnitarySimulator::exportDDtoGraphviz(std::ostream& os, const bool colored, const bool edgeLabels, const bool classic, const bool memory, const bool formatAsPolar) { + dd::toDot(e, os, colored, edgeLabels, classic, memory, formatAsPolar); +} + template class UnitarySimulator; diff --git a/src/python/bindings.cpp b/src/python/bindings.cpp index e1b5ac61..3be52c7d 100644 --- a/src/python/bindings.cpp +++ b/src/python/bindings.cpp @@ -155,7 +155,9 @@ py::class_ createSimulator(py::module_ m, const std::string& name) { .def("get_max_vector_node_count", &Sim::getMaxNodeCount, "Get the maximum number of (active) vector nodes, i.e., the maximum number of vector DD nodes in the unique table at any point during the simulation.") .def("get_max_matrix_node_count", &Sim::getMaxMatrixNodeCount, "Get the maximum number of (active) matrix nodes, i.e., the maximum number of matrix DD nodes in the unique table at any point during the simulation.") .def("get_tolerance", &Sim::getTolerance, "Get the tolerance for the DD package.") - .def("set_tolerance", &Sim::setTolerance, "tol"_a, "Set the tolerance for the DD package."); + .def("set_tolerance", &Sim::setTolerance, "tol"_a, "Set the tolerance for the DD package.") + .def("export_dd_to_graphviz_str", &Sim::exportDDtoGraphvizString, "colored"_a = true, "edge_labels"_a = false, "classic"_a = false, "memory"_a = false, "format_as_polar"_a = true, "Get a Graphviz representation of the currently stored DD.") + .def("export_dd_to_graphviz_file", &Sim::exportDDtoGraphvizFile, "filename"_a, "colored"_a = true, "edge_labels"_a = false, "classic"_a = false, "memory"_a = false, "format_as_polar"_a = true, "Write a Graphviz representation of the currently stored DD to a file."); if constexpr (std::is_same_v>) { sim.def("construct", &Sim::construct, "Construct the DD representing the unitary matrix of the circuit."); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 7cca8927..1ad67580 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -19,7 +19,8 @@ package_add_test( ${CMAKE_CURRENT_SOURCE_DIR}/test_stoch_noise_sim.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_det_noise_sim.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_unitary_sim.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/test_path_sim.cpp) + ${CMAKE_CURRENT_SOURCE_DIR}/test_path_sim.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_output_ddvis.cpp) add_custom_command( TARGET ${PROJECT_NAME}_test diff --git a/test/test_output_ddvis.cpp b/test/test_output_ddvis.cpp new file mode 100644 index 00000000..c10ac250 --- /dev/null +++ b/test/test_output_ddvis.cpp @@ -0,0 +1,87 @@ +#include "CircuitSimulator.hpp" +#include "HybridSchrodingerFeynmanSimulator.hpp" +#include "UnitarySimulator.hpp" + +#include +#include +#include + +using namespace qc::literals; + +class DDVis: public testing::Test { +protected: + void SetUp() override { + qc = std::make_unique(2U); + qc->h(0U); + qc->x(1U, 0_pc); + } + + std::unique_ptr qc; + std::size_t numShots = 1024U; + + static void fileExistsAndIsNonEmpty(const std::string& filename) { + std::ifstream file(filename); + ASSERT_TRUE(file.good()); + ASSERT_NE(file.peek(), std::ifstream::traits_type::eof()); + file.close(); + } +}; + +TEST_F(DDVis, CircuitSimulator) { + CircuitSimulator sim(std::move(qc)); + sim.simulate(numShots); + const auto dot = sim.exportDDtoGraphvizString(); + EXPECT_FALSE(dot.empty()); + std::cout << dot << "\n"; + + const std::string filename = "test_circuit_simulator.gv"; + sim.exportDDtoGraphvizFile(filename); + + fileExistsAndIsNonEmpty(filename); + std::filesystem::remove(filename); +} + +TEST_F(DDVis, UnitarySimulator) { + UnitarySimulator sim(std::move(qc)); + sim.construct(); + + const auto dot = sim.exportDDtoGraphvizString(true, false, false, false, true); + EXPECT_FALSE(dot.empty()); + std::cout << dot << "\n"; + + const std::string filename = "test_unitary_simulator.gv"; + sim.exportDDtoGraphvizFile(filename); + + fileExistsAndIsNonEmpty(filename); + std::filesystem::remove(filename); +} + +TEST_F(DDVis, HSFSimulatorDD) { + HybridSchrodingerFeynmanSimulator sim(std::move(qc), HybridSchrodingerFeynmanSimulator<>::Mode::DD); + sim.simulate(numShots); + + const auto dot = sim.exportDDtoGraphvizString(true, false, false, false, true); + EXPECT_FALSE(dot.empty()); + std::cout << dot << "\n"; + + const std::string filename = "test_hsf_simulator_dd.gv"; + sim.exportDDtoGraphvizFile(filename); + + fileExistsAndIsNonEmpty(filename); + std::filesystem::remove(filename); +} + +TEST_F(DDVis, HSFSimulatorAmplitude) { + HybridSchrodingerFeynmanSimulator sim(std::move(qc), HybridSchrodingerFeynmanSimulator<>::Mode::Amplitude); + sim.simulate(numShots); + + const auto dot = sim.exportDDtoGraphvizString(true, false, false, false, true); + EXPECT_FALSE(dot.empty()); + std::cout << dot << "\n"; + + const std::string filename = "test_hsf_simulator_amplitude.gv"; + sim.exportDDtoGraphvizFile(filename); + + fileExistsAndIsNonEmpty(filename); + std::filesystem::remove(filename); +}