Skip to content

Commit

Permalink
✨ Provide access to visualization of simulated DD in Python (#306)
Browse files Browse the repository at this point in the history
## Description

This PR enables Python users of our tool to get access to the Graphviz
file for the simulated DD in order to inspect the final decision
diagram.
This required very little code changes and should be rather convenient
to use as, e.g., illustrated in the new docs section.

## Checklist:

<!---
This checklist serves as a reminder of a couple of things that ensure
your pull request will be merged swiftly.
-->

- [x] The pull request only contains commits that are related to it.
- [x] I have added appropriate tests and documentation.
- [x] I have made sure that all CI jobs on GitHub pass.
- [x] The pull request introduces no new warnings and follows the
project's style guidelines.

---------

Signed-off-by: burgholzer <burgholzer@me.com>
  • Loading branch information
burgholzer committed Oct 11, 2023
1 parent 44fb144 commit 3ec5ed0
Show file tree
Hide file tree
Showing 12 changed files with 290 additions and 30 deletions.
145 changes: 125 additions & 20 deletions docs/source/Usage.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
Expand All @@ -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."
Expand Down Expand Up @@ -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."
]
Expand All @@ -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",
Expand Down Expand Up @@ -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."
Expand All @@ -167,7 +272,7 @@
"id": "1dbb7dbe",
"metadata": {},
"source": [
"## PathQasmSimulator for Sampling"
"### PathQasmSimulator for Sampling"
]
},
{
Expand All @@ -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",
Expand Down
8 changes: 0 additions & 8 deletions docs/source/api/ddsim.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,6 @@ ddsim package
Submodules
----------

ddsim.error module
------------------

.. automodule:: mqt.ddsim.error
:members:
:undoc-members:
:show-inheritance:

ddsim.hybridqasmsimulator module
--------------------------------

Expand Down
4 changes: 4 additions & 0 deletions include/HybridSchrodingerFeynmanSimulator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ class HybridSchrodingerFeynmanSimulator: public CircuitSimulator<Config> {

[[nodiscard]] Mode getMode() const { return mode; }

protected:
/// See Simulator<Config>::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{};
Expand Down
26 changes: 26 additions & 0 deletions include/Simulator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,30 @@ class Simulator {

dd::vEdge static removeNodes(std::unique_ptr<dd::Package<Config>>& localDD, dd::vEdge edge, std::map<dd::vNode*, dd::vEdge>& 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::Package<Config>> dd = std::make_unique<dd::Package<Config>>();
dd::vEdge rootEdge{};

Expand All @@ -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 {
Expand Down
5 changes: 5 additions & 0 deletions include/UnitarySimulator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ class UnitarySimulator: public CircuitSimulator<Config> {
[[nodiscard]] std::size_t getFinalNodeCount() const { return e.size(); }
[[nodiscard]] std::size_t getMaxNodeCount() const override { return Simulator<Config>::dd->template getUniqueTable<dd::mNode>().getPeakNumActiveEntries(); }

protected:
/// See Simulator<Config>::exportDDtoGraphviz
void exportDDtoGraphviz(std::ostream& os, bool colored,
bool edgeLabels, bool classic, bool memory, bool formatAsPolar) override;

private:
qc::MatrixDD e{};

Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -63,6 +64,7 @@ docs = [
"sphinxext-opengraph",
"sphinx-autodoc-typehints",
"qiskit-terra[visualization]",
"graphviz",
]
dev = ["mqt.ddsim[tnflow, coverage, docs]"]

Expand Down
8 changes: 8 additions & 0 deletions src/HybridSchrodingerFeynmanSimulator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -281,4 +281,12 @@ void HybridSchrodingerFeynmanSimulator<Config>::simulateHybridAmplitudes(qc::Qub
finalAmplitudes = std::move(amplitudes[0]);
}

template<class Config>
void HybridSchrodingerFeynmanSimulator<Config>::exportDDtoGraphviz(std::ostream& os, const bool colored, const bool edgeLabels, const bool classic, const bool memory, const bool formatAsPolar) {
if (mode == Mode::Amplitude) {
Simulator<Config>::rootEdge = Simulator<Config>::dd->makeStateFromVector(finalAmplitudes);
}
return Simulator<Config>::exportDDtoGraphviz(os, colored, edgeLabels, classic, memory, formatAsPolar);
}

template class HybridSchrodingerFeynmanSimulator<dd::DDPackageConfig>;
22 changes: 22 additions & 0 deletions src/Simulator.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "Simulator.hpp"

#include "dd/Export.hpp"

#include <cassert>
#include <cmath>
#include <iostream>
Expand Down Expand Up @@ -333,5 +335,25 @@ std::pair<dd::ComplexValue, std::string> Simulator<Config>::getPathOfLeastResist
std::string{result.rbegin(), result.rend()}};
}

template<class Config>
void Simulator<Config>::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<class Config>
std::string Simulator<Config>::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<class Config>
void Simulator<Config>::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<dd::DDPackageConfig>;
template class Simulator<StochasticNoiseSimulatorDDPackageConfig>;
template class Simulator<DensityMatrixSimulatorDDPackageConfig>;
6 changes: 6 additions & 0 deletions src/UnitarySimulator.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "UnitarySimulator.hpp"

#include "dd/Export.hpp"
#include "dd/FunctionalityConstruction.hpp"

#include <chrono>
Expand All @@ -17,4 +18,9 @@ void UnitarySimulator<Config>::construct() {
constructionTime = std::chrono::duration<double>(end - start).count();
}

template<class Config>
void UnitarySimulator<Config>::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<dd::DDPackageConfig>;
4 changes: 3 additions & 1 deletion src/python/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,9 @@ py::class_<Sim> 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, UnitarySimulator<>>) {
sim.def("construct", &Sim::construct, "Construct the DD representing the unitary matrix of the circuit.");
Expand Down
3 changes: 2 additions & 1 deletion test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 3ec5ed0

Please sign in to comment.