|
| 1 | +/* |
| 2 | + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM |
| 3 | + * Copyright (c) 2025 Munich Quantum Software Company GmbH |
| 4 | + * All rights reserved. |
| 5 | + * |
| 6 | + * SPDX-License-Identifier: MIT |
| 7 | + * |
| 8 | + * Licensed under the MIT License |
| 9 | + */ |
| 10 | + |
| 11 | +#include "cliffordsynthesis/CliffordSynthesizer.hpp" |
| 12 | +#include "cliffordsynthesis/Configuration.hpp" |
| 13 | +#include "cliffordsynthesis/Results.hpp" |
| 14 | +#include "cliffordsynthesis/Tableau.hpp" |
| 15 | +#include "cliffordsynthesis/TargetMetric.hpp" |
| 16 | +#include "ir/QuantumComputation.hpp" |
| 17 | +#include "qasm3/Importer.hpp" |
| 18 | + |
| 19 | +#include <cstddef> |
| 20 | +#include <nlohmann/json.hpp> |
| 21 | +#include <plog/Severity.h> |
| 22 | +#include <pybind11/cast.h> |
| 23 | +#include <pybind11/detail/common.h> |
| 24 | +#include <pybind11/pybind11.h> |
| 25 | +#include <pybind11/pytypes.h> |
| 26 | +// NOLINTNEXTLINE(misc-include-cleaner) |
| 27 | +#include <pybind11/stl.h> |
| 28 | +#include <string> |
| 29 | + |
| 30 | +namespace py = pybind11; |
| 31 | +using namespace pybind11::literals; |
| 32 | + |
| 33 | +PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { |
| 34 | + // Target metric for the Clifford synthesizer |
| 35 | + py::enum_<cs::TargetMetric>(m, "TargetMetric") |
| 36 | + .value("gates", cs::TargetMetric::Gates, "Optimize gate count.") |
| 37 | + .value("two_qubit_gates", cs::TargetMetric::TwoQubitGates, |
| 38 | + "Optimize two-qubit gate count.") |
| 39 | + .value("depth", cs::TargetMetric::Depth, "Optimize circuit depth.") |
| 40 | + .export_values() |
| 41 | + .def(py::init([](const std::string& name) { |
| 42 | + return cs::targetMetricFromString(name); |
| 43 | + })); |
| 44 | + py::implicitly_convertible<py::str, cs::TargetMetric>(); |
| 45 | + |
| 46 | + py::enum_<plog::Severity>(m, "Verbosity") |
| 47 | + .value("none", plog::Severity::none, "No output.") |
| 48 | + .value("fatal", plog::Severity::fatal, "Only show fatal errors.") |
| 49 | + .value("error", plog::Severity::error, "Show errors.") |
| 50 | + .value("warning", plog::Severity::warning, "Show warnings.") |
| 51 | + .value("info", plog::Severity::info, "Show general information.") |
| 52 | + .value("debug", plog::Severity::debug, |
| 53 | + "Show additional debug information.") |
| 54 | + .value("verbose", plog::Severity::verbose, "Show all information.") |
| 55 | + .export_values() |
| 56 | + .def(py::init([](const std::string& name) { |
| 57 | + return plog::severityFromString(name.c_str()); |
| 58 | + })); |
| 59 | + py::implicitly_convertible<py::str, plog::Severity>(); |
| 60 | + |
| 61 | + // Configuration for the synthesis |
| 62 | + py::class_<cs::Configuration>( |
| 63 | + m, "SynthesisConfiguration", |
| 64 | + "Configuration options for the MQT QMAP Clifford synthesis tool.") |
| 65 | + .def(py::init<>()) |
| 66 | + .def_readwrite("initial_timestep_limit", |
| 67 | + &cs::Configuration::initialTimestepLimit, |
| 68 | + "Initial timestep limit for the Clifford synthesis. " |
| 69 | + "Defaults to `0`, which implies that the initial timestep " |
| 70 | + "limit is determined automatically.") |
| 71 | + .def_readwrite( |
| 72 | + "minimal_timesteps", &cs::Configuration::minimalTimesteps, |
| 73 | + "Minimal timestep considered for the Clifford synthesis. " |
| 74 | + "This option limits the lower bound of the interval in " |
| 75 | + "which the binary search method looks for solutions. " |
| 76 | + "Set this if you know a lower bound for the circuit depth. " |
| 77 | + "Defaults to `0`, which implies that no lower bound for depth " |
| 78 | + "is known.") |
| 79 | + .def_readwrite( |
| 80 | + "use_maxsat", &cs::Configuration::useMaxSAT, |
| 81 | + "Use MaxSAT to solve the synthesis problem or to really on the " |
| 82 | + "binary search scheme for finding the optimum. Defaults to `false`.") |
| 83 | + .def_readwrite("linear_search", &cs::Configuration::linearSearch, |
| 84 | + "Use liner search instead of binary search " |
| 85 | + "scheme for finding the optimum. Defaults to `false`.") |
| 86 | + .def_readwrite( |
| 87 | + "target_metric", &cs::Configuration::target, |
| 88 | + "Target metric for the Clifford synthesis. Defaults to `gates`.") |
| 89 | + .def_readwrite("use_symmetry_breaking", |
| 90 | + &cs::Configuration::useSymmetryBreaking, |
| 91 | + "Use symmetry breaking clauses to speed up the synthesis " |
| 92 | + "process. Defaults to `true`.") |
| 93 | + .def_readwrite("dump_intermediate_results", |
| 94 | + &cs::Configuration::dumpIntermediateResults, |
| 95 | + "Dump intermediate results of the synthesis process. " |
| 96 | + "Defaults to `false`.") |
| 97 | + .def_readwrite("intermediate_results_path", |
| 98 | + &cs::Configuration::intermediateResultsPath, |
| 99 | + "Path to the directory where intermediate results should " |
| 100 | + "be dumped. Defaults to `./`. The path needs to include a " |
| 101 | + "path separator at the end.") |
| 102 | + .def_readwrite( |
| 103 | + "verbosity", &cs::Configuration::verbosity, |
| 104 | + "Verbosity level for the synthesis process. Defaults to 'warning'.") |
| 105 | + .def_readwrite("solver_parameters", &cs::Configuration::solverParameters, |
| 106 | + "Parameters to be passed to Z3 as dict[str, bool | int | " |
| 107 | + "float | str]") |
| 108 | + .def_readwrite( |
| 109 | + "minimize_gates_after_depth_optimization", |
| 110 | + &cs::Configuration::minimizeGatesAfterDepthOptimization, |
| 111 | + "Depth optimization might produce a circuit with more gates than " |
| 112 | + "necessary. This option enables an additional run of the synthesizer " |
| 113 | + "to minimize the overall number of gates. Defaults to `false`.") |
| 114 | + .def_readwrite( |
| 115 | + "try_higher_gate_limit_for_two_qubit_gate_optimization", |
| 116 | + &cs::Configuration::tryHigherGateLimitForTwoQubitGateOptimization, |
| 117 | + "When optimizing two-qubit gates, the synthesizer might fail " |
| 118 | + "to find an optimal solution for a certain timestep limit, but there " |
| 119 | + "might be a better solution for some higher timestep limit. This " |
| 120 | + "option enables an additional run of the synthesizer with a higher " |
| 121 | + "gate limit. Defaults to `false`.") |
| 122 | + .def_readwrite("gate_limit_factor", &cs::Configuration::gateLimitFactor, |
| 123 | + "Factor by which the gate limit is increased when " |
| 124 | + "trying to find a better solution for the two-qubit " |
| 125 | + "gate optimization. Defaults to `1.1`.") |
| 126 | + .def_readwrite( |
| 127 | + "minimize_gates_after_two_qubit_gate_optimization", |
| 128 | + &cs::Configuration::minimizeGatesAfterTwoQubitGateOptimization, |
| 129 | + "Two-qubit gate optimization might produce a circuit " |
| 130 | + "with more gates than necessary. This option enables " |
| 131 | + "an additional run of the synthesizer to minimize the " |
| 132 | + "overall number of gates. Defaults to `false`.") |
| 133 | + .def_readwrite("heuristic", &cs::Configuration::heuristic, |
| 134 | + "Use heuristic to synthesize the circuit. " |
| 135 | + "This method synthesizes shallow intermediate circuits " |
| 136 | + "and combines them. Defaults to `false`.") |
| 137 | + .def_readwrite("split_size", &cs::Configuration::splitSize, |
| 138 | + "Size of subcircuits used in heuristic. " |
| 139 | + "Defaults to `5`.") |
| 140 | + .def_readwrite( |
| 141 | + "n_threads_heuristic", &cs::Configuration::nThreadsHeuristic, |
| 142 | + "Maximum number of threads used for the heuristic optimizer. " |
| 143 | + "Defaults to the number of available threads on the system.") |
| 144 | + .def("json", &cs::Configuration::json, |
| 145 | + "Returns a JSON-style dictionary of all the information present in " |
| 146 | + "the :class:`.Configuration`") |
| 147 | + .def( |
| 148 | + "__repr__", |
| 149 | + [](const cs::Configuration& config) { return config.json().dump(2); }, |
| 150 | + "Prints a JSON-formatted representation of all the information " |
| 151 | + "present in the :class:`.Configuration`"); |
| 152 | + |
| 153 | + // Results of the synthesis |
| 154 | + py::class_<cs::Results>(m, "SynthesisResults", |
| 155 | + "Results of the MQT QMAP Clifford synthesis tool.") |
| 156 | + .def(py::init<>()) |
| 157 | + .def_property_readonly("gates", &cs::Results::getGates, |
| 158 | + "Returns the number of gates in the circuit.") |
| 159 | + .def_property_readonly("single_qubit_gates", |
| 160 | + &cs::Results::getSingleQubitGates, |
| 161 | + "Returns the number of single-qubit gates in the " |
| 162 | + "synthesized circuit.") |
| 163 | + .def_property_readonly("two_qubit_gates", &cs::Results::getTwoQubitGates, |
| 164 | + "Returns the number of two-qubit gates in the " |
| 165 | + "synthesized circuit.") |
| 166 | + .def_property_readonly("depth", &cs::Results::getDepth, |
| 167 | + "Returns the depth of the synthesized circuit.") |
| 168 | + .def_property_readonly("runtime", &cs::Results::getRuntime, |
| 169 | + "Returns the runtime of the synthesis in seconds.") |
| 170 | + .def_property_readonly("solver_calls", &cs::Results::getSolverCalls, |
| 171 | + "Returns the number of calls to the SAT solver.") |
| 172 | + .def_property_readonly( |
| 173 | + "circuit", &cs::Results::getResultCircuit, |
| 174 | + "Returns the synthesized circuit as a qasm string.") |
| 175 | + .def_property_readonly("tableau", &cs::Results::getResultTableau, |
| 176 | + "Returns a string representation of the " |
| 177 | + "synthesized circuit's tableau.") |
| 178 | + .def("sat", &cs::Results::sat, |
| 179 | + "Returns `true` if the synthesis was successful.") |
| 180 | + .def("unsat", &cs::Results::unsat, |
| 181 | + "Returns `true` if the synthesis was unsuccessful."); |
| 182 | + |
| 183 | + auto tableau = py::class_<cs::Tableau>( |
| 184 | + m, "Tableau", "A class for representing stabilizer tableaus."); |
| 185 | + tableau.def(py::init<std::size_t, bool>(), "n"_a, |
| 186 | + "include_destabilizers"_a = false, |
| 187 | + "Creates a tableau for an n-qubit Clifford."); |
| 188 | + tableau.def( |
| 189 | + py::init<const std::string&>(), "tableau"_a, |
| 190 | + "Constructs a tableau from a string description. This can either be a " |
| 191 | + "semicolon separated binary matrix or a list of Pauli strings."); |
| 192 | + tableau.def( |
| 193 | + py::init<const std::string&, const std::string&>(), "stabilizers"_a, |
| 194 | + "destabilizers"_a, |
| 195 | + "Constructs a tableau from two lists of Pauli strings, the Stabilizers" |
| 196 | + "and Destabilizers."); |
| 197 | + |
| 198 | + auto synthesizer = py::class_<cs::CliffordSynthesizer>( |
| 199 | + m, "CliffordSynthesizer", "A class for synthesizing Clifford circuits."); |
| 200 | + |
| 201 | + synthesizer.def(py::init<cs::Tableau, cs::Tableau>(), "initial_tableau"_a, |
| 202 | + "target_tableau"_a, |
| 203 | + "Constructs a synthesizer for two tableaus representing the " |
| 204 | + "initial and target state."); |
| 205 | + synthesizer.def(py::init<cs::Tableau>(), "target_tableau"_a, |
| 206 | + "Constructs a synthesizer for a tableau representing the " |
| 207 | + "target state."); |
| 208 | + synthesizer.def(py::init<qc::QuantumComputation&, bool>(), "qc"_a, |
| 209 | + "use_destabilizers"_a |
| 210 | + "Constructs a synthesizer for a quantum computation " |
| 211 | + "representing the target state."); |
| 212 | + synthesizer.def( |
| 213 | + py::init<cs::Tableau, qc::QuantumComputation&>(), "initial_tableau"_a, |
| 214 | + "qc"_a, |
| 215 | + "Constructs a synthesizer for a quantum computation representing the " |
| 216 | + "target state that starts in an initial state represented by a tableau."); |
| 217 | + synthesizer.def("synthesize", &cs::CliffordSynthesizer::synthesize, |
| 218 | + "config"_a = cs::Configuration(), |
| 219 | + "Runs the synthesis with the given configuration."); |
| 220 | + synthesizer.def_property_readonly("results", |
| 221 | + &cs::CliffordSynthesizer::getResults, |
| 222 | + "Returns the results of the synthesis."); |
| 223 | + synthesizer.def_property_readonly( |
| 224 | + "result_circuit", [](cs::CliffordSynthesizer& self) { |
| 225 | + return qasm3::Importer::imports(self.getResults().getResultCircuit()); |
| 226 | + }); |
| 227 | +} |
0 commit comments