mqt-core 3.3.2__cp313-cp313t-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (537) hide show
  1. mqt/core/__init__.py +89 -0
  2. mqt/core/__main__.py +55 -0
  3. mqt/core/_commands.py +52 -0
  4. mqt/core/_compat/__init__.py +11 -0
  5. mqt/core/_compat/typing.py +29 -0
  6. mqt/core/_version.py +34 -0
  7. mqt/core/_version.pyi +12 -0
  8. mqt/core/bin/mqt-core-algorithms.dll +0 -0
  9. mqt/core/bin/mqt-core-circuit-optimizer.dll +0 -0
  10. mqt/core/bin/mqt-core-dd.dll +0 -0
  11. mqt/core/bin/mqt-core-ds.dll +0 -0
  12. mqt/core/bin/mqt-core-fomac.dll +0 -0
  13. mqt/core/bin/mqt-core-ir.dll +0 -0
  14. mqt/core/bin/mqt-core-na-fomac.dll +0 -0
  15. mqt/core/bin/mqt-core-na.dll +0 -0
  16. mqt/core/bin/mqt-core-qasm.dll +0 -0
  17. mqt/core/bin/mqt-core-qdmi-driver.dll +0 -0
  18. mqt/core/bin/mqt-core-qdmi-na-device.dll +0 -0
  19. mqt/core/bin/mqt-core-zx.dll +0 -0
  20. mqt/core/dd.cp313t-win_amd64.pyd +0 -0
  21. mqt/core/dd.pyi +1016 -0
  22. mqt/core/dd_evaluation.py +368 -0
  23. mqt/core/fomac.cp313t-win_amd64.pyd +0 -0
  24. mqt/core/fomac.pyi +125 -0
  25. mqt/core/include/mqt-core/algorithms/BernsteinVazirani.hpp +39 -0
  26. mqt/core/include/mqt-core/algorithms/GHZState.hpp +18 -0
  27. mqt/core/include/mqt-core/algorithms/Grover.hpp +33 -0
  28. mqt/core/include/mqt-core/algorithms/QFT.hpp +21 -0
  29. mqt/core/include/mqt-core/algorithms/QPE.hpp +30 -0
  30. mqt/core/include/mqt-core/algorithms/RandomCliffordCircuit.hpp +22 -0
  31. mqt/core/include/mqt-core/algorithms/StatePreparation.hpp +43 -0
  32. mqt/core/include/mqt-core/algorithms/WState.hpp +18 -0
  33. mqt/core/include/mqt-core/algorithms/mqt_core_algorithms_export.h +43 -0
  34. mqt/core/include/mqt-core/boost/config/abi/borland_prefix.hpp +27 -0
  35. mqt/core/include/mqt-core/boost/config/abi/borland_suffix.hpp +12 -0
  36. mqt/core/include/mqt-core/boost/config/abi/msvc_prefix.hpp +22 -0
  37. mqt/core/include/mqt-core/boost/config/abi/msvc_suffix.hpp +8 -0
  38. mqt/core/include/mqt-core/boost/config/abi_prefix.hpp +25 -0
  39. mqt/core/include/mqt-core/boost/config/abi_suffix.hpp +25 -0
  40. mqt/core/include/mqt-core/boost/config/assert_cxx03.hpp +211 -0
  41. mqt/core/include/mqt-core/boost/config/assert_cxx11.hpp +212 -0
  42. mqt/core/include/mqt-core/boost/config/assert_cxx14.hpp +47 -0
  43. mqt/core/include/mqt-core/boost/config/assert_cxx17.hpp +65 -0
  44. mqt/core/include/mqt-core/boost/config/assert_cxx20.hpp +59 -0
  45. mqt/core/include/mqt-core/boost/config/assert_cxx23.hpp +41 -0
  46. mqt/core/include/mqt-core/boost/config/assert_cxx98.hpp +23 -0
  47. mqt/core/include/mqt-core/boost/config/auto_link.hpp +525 -0
  48. mqt/core/include/mqt-core/boost/config/compiler/borland.hpp +342 -0
  49. mqt/core/include/mqt-core/boost/config/compiler/clang.hpp +370 -0
  50. mqt/core/include/mqt-core/boost/config/compiler/clang_version.hpp +89 -0
  51. mqt/core/include/mqt-core/boost/config/compiler/codegear.hpp +389 -0
  52. mqt/core/include/mqt-core/boost/config/compiler/comeau.hpp +59 -0
  53. mqt/core/include/mqt-core/boost/config/compiler/common_edg.hpp +185 -0
  54. mqt/core/include/mqt-core/boost/config/compiler/compaq_cxx.hpp +19 -0
  55. mqt/core/include/mqt-core/boost/config/compiler/cray.hpp +446 -0
  56. mqt/core/include/mqt-core/boost/config/compiler/diab.hpp +26 -0
  57. mqt/core/include/mqt-core/boost/config/compiler/digitalmars.hpp +146 -0
  58. mqt/core/include/mqt-core/boost/config/compiler/gcc.hpp +386 -0
  59. mqt/core/include/mqt-core/boost/config/compiler/gcc_xml.hpp +115 -0
  60. mqt/core/include/mqt-core/boost/config/compiler/greenhills.hpp +28 -0
  61. mqt/core/include/mqt-core/boost/config/compiler/hp_acc.hpp +153 -0
  62. mqt/core/include/mqt-core/boost/config/compiler/intel.hpp +577 -0
  63. mqt/core/include/mqt-core/boost/config/compiler/kai.hpp +33 -0
  64. mqt/core/include/mqt-core/boost/config/compiler/metrowerks.hpp +201 -0
  65. mqt/core/include/mqt-core/boost/config/compiler/mpw.hpp +143 -0
  66. mqt/core/include/mqt-core/boost/config/compiler/nvcc.hpp +64 -0
  67. mqt/core/include/mqt-core/boost/config/compiler/pathscale.hpp +141 -0
  68. mqt/core/include/mqt-core/boost/config/compiler/pgi.hpp +23 -0
  69. mqt/core/include/mqt-core/boost/config/compiler/sgi_mipspro.hpp +29 -0
  70. mqt/core/include/mqt-core/boost/config/compiler/sunpro_cc.hpp +225 -0
  71. mqt/core/include/mqt-core/boost/config/compiler/vacpp.hpp +189 -0
  72. mqt/core/include/mqt-core/boost/config/compiler/visualc.hpp +398 -0
  73. mqt/core/include/mqt-core/boost/config/compiler/xlcpp.hpp +303 -0
  74. mqt/core/include/mqt-core/boost/config/compiler/xlcpp_zos.hpp +174 -0
  75. mqt/core/include/mqt-core/boost/config/detail/cxx_composite.hpp +218 -0
  76. mqt/core/include/mqt-core/boost/config/detail/posix_features.hpp +95 -0
  77. mqt/core/include/mqt-core/boost/config/detail/select_compiler_config.hpp +157 -0
  78. mqt/core/include/mqt-core/boost/config/detail/select_platform_config.hpp +147 -0
  79. mqt/core/include/mqt-core/boost/config/detail/select_stdlib_config.hpp +121 -0
  80. mqt/core/include/mqt-core/boost/config/detail/suffix.hpp +1334 -0
  81. mqt/core/include/mqt-core/boost/config/header_deprecated.hpp +26 -0
  82. mqt/core/include/mqt-core/boost/config/helper_macros.hpp +37 -0
  83. mqt/core/include/mqt-core/boost/config/no_tr1/cmath.hpp +28 -0
  84. mqt/core/include/mqt-core/boost/config/no_tr1/complex.hpp +28 -0
  85. mqt/core/include/mqt-core/boost/config/no_tr1/functional.hpp +28 -0
  86. mqt/core/include/mqt-core/boost/config/no_tr1/memory.hpp +28 -0
  87. mqt/core/include/mqt-core/boost/config/no_tr1/utility.hpp +28 -0
  88. mqt/core/include/mqt-core/boost/config/platform/aix.hpp +33 -0
  89. mqt/core/include/mqt-core/boost/config/platform/amigaos.hpp +15 -0
  90. mqt/core/include/mqt-core/boost/config/platform/beos.hpp +26 -0
  91. mqt/core/include/mqt-core/boost/config/platform/bsd.hpp +83 -0
  92. mqt/core/include/mqt-core/boost/config/platform/cloudabi.hpp +18 -0
  93. mqt/core/include/mqt-core/boost/config/platform/cray.hpp +18 -0
  94. mqt/core/include/mqt-core/boost/config/platform/cygwin.hpp +71 -0
  95. mqt/core/include/mqt-core/boost/config/platform/haiku.hpp +31 -0
  96. mqt/core/include/mqt-core/boost/config/platform/hpux.hpp +87 -0
  97. mqt/core/include/mqt-core/boost/config/platform/irix.hpp +31 -0
  98. mqt/core/include/mqt-core/boost/config/platform/linux.hpp +106 -0
  99. mqt/core/include/mqt-core/boost/config/platform/macos.hpp +87 -0
  100. mqt/core/include/mqt-core/boost/config/platform/qnxnto.hpp +31 -0
  101. mqt/core/include/mqt-core/boost/config/platform/solaris.hpp +31 -0
  102. mqt/core/include/mqt-core/boost/config/platform/symbian.hpp +97 -0
  103. mqt/core/include/mqt-core/boost/config/platform/vms.hpp +25 -0
  104. mqt/core/include/mqt-core/boost/config/platform/vxworks.hpp +422 -0
  105. mqt/core/include/mqt-core/boost/config/platform/wasm.hpp +23 -0
  106. mqt/core/include/mqt-core/boost/config/platform/win32.hpp +90 -0
  107. mqt/core/include/mqt-core/boost/config/platform/zos.hpp +32 -0
  108. mqt/core/include/mqt-core/boost/config/pragma_message.hpp +31 -0
  109. mqt/core/include/mqt-core/boost/config/requires_threads.hpp +92 -0
  110. mqt/core/include/mqt-core/boost/config/stdlib/dinkumware.hpp +324 -0
  111. mqt/core/include/mqt-core/boost/config/stdlib/libcomo.hpp +93 -0
  112. mqt/core/include/mqt-core/boost/config/stdlib/libcpp.hpp +180 -0
  113. mqt/core/include/mqt-core/boost/config/stdlib/libstdcpp3.hpp +482 -0
  114. mqt/core/include/mqt-core/boost/config/stdlib/modena.hpp +79 -0
  115. mqt/core/include/mqt-core/boost/config/stdlib/msl.hpp +98 -0
  116. mqt/core/include/mqt-core/boost/config/stdlib/roguewave.hpp +208 -0
  117. mqt/core/include/mqt-core/boost/config/stdlib/sgi.hpp +168 -0
  118. mqt/core/include/mqt-core/boost/config/stdlib/stlport.hpp +258 -0
  119. mqt/core/include/mqt-core/boost/config/stdlib/vacpp.hpp +74 -0
  120. mqt/core/include/mqt-core/boost/config/stdlib/xlcpp_zos.hpp +61 -0
  121. mqt/core/include/mqt-core/boost/config/user.hpp +133 -0
  122. mqt/core/include/mqt-core/boost/config/warning_disable.hpp +47 -0
  123. mqt/core/include/mqt-core/boost/config/workaround.hpp +305 -0
  124. mqt/core/include/mqt-core/boost/config.hpp +67 -0
  125. mqt/core/include/mqt-core/boost/cstdint.hpp +556 -0
  126. mqt/core/include/mqt-core/boost/cxx11_char_types.hpp +70 -0
  127. mqt/core/include/mqt-core/boost/detail/workaround.hpp +10 -0
  128. mqt/core/include/mqt-core/boost/limits.hpp +146 -0
  129. mqt/core/include/mqt-core/boost/multiprecision/complex128.hpp +24 -0
  130. mqt/core/include/mqt-core/boost/multiprecision/complex_adaptor.hpp +1046 -0
  131. mqt/core/include/mqt-core/boost/multiprecision/concepts/mp_number_archetypes.hpp +257 -0
  132. mqt/core/include/mqt-core/boost/multiprecision/cpp_bin_float/io.hpp +698 -0
  133. mqt/core/include/mqt-core/boost/multiprecision/cpp_bin_float/transcendental.hpp +157 -0
  134. mqt/core/include/mqt-core/boost/multiprecision/cpp_bin_float.hpp +2297 -0
  135. mqt/core/include/mqt-core/boost/multiprecision/cpp_complex.hpp +12 -0
  136. mqt/core/include/mqt-core/boost/multiprecision/cpp_dec_float.hpp +3690 -0
  137. mqt/core/include/mqt-core/boost/multiprecision/cpp_int/add.hpp +368 -0
  138. mqt/core/include/mqt-core/boost/multiprecision/cpp_int/add_unsigned.hpp +387 -0
  139. mqt/core/include/mqt-core/boost/multiprecision/cpp_int/bitwise.hpp +889 -0
  140. mqt/core/include/mqt-core/boost/multiprecision/cpp_int/checked.hpp +178 -0
  141. mqt/core/include/mqt-core/boost/multiprecision/cpp_int/comparison.hpp +374 -0
  142. mqt/core/include/mqt-core/boost/multiprecision/cpp_int/cpp_int_config.hpp +161 -0
  143. mqt/core/include/mqt-core/boost/multiprecision/cpp_int/divide.hpp +703 -0
  144. mqt/core/include/mqt-core/boost/multiprecision/cpp_int/import_export.hpp +248 -0
  145. mqt/core/include/mqt-core/boost/multiprecision/cpp_int/intel_intrinsics.hpp +138 -0
  146. mqt/core/include/mqt-core/boost/multiprecision/cpp_int/limits.hpp +282 -0
  147. mqt/core/include/mqt-core/boost/multiprecision/cpp_int/literals.hpp +295 -0
  148. mqt/core/include/mqt-core/boost/multiprecision/cpp_int/misc.hpp +1457 -0
  149. mqt/core/include/mqt-core/boost/multiprecision/cpp_int/multiply.hpp +848 -0
  150. mqt/core/include/mqt-core/boost/multiprecision/cpp_int/serialize.hpp +211 -0
  151. mqt/core/include/mqt-core/boost/multiprecision/cpp_int/value_pack.hpp +42 -0
  152. mqt/core/include/mqt-core/boost/multiprecision/cpp_int.hpp +2360 -0
  153. mqt/core/include/mqt-core/boost/multiprecision/debug_adaptor.hpp +760 -0
  154. mqt/core/include/mqt-core/boost/multiprecision/detail/assert.hpp +29 -0
  155. mqt/core/include/mqt-core/boost/multiprecision/detail/atomic.hpp +62 -0
  156. mqt/core/include/mqt-core/boost/multiprecision/detail/bitscan.hpp +317 -0
  157. mqt/core/include/mqt-core/boost/multiprecision/detail/check_cpp11_config.hpp +64 -0
  158. mqt/core/include/mqt-core/boost/multiprecision/detail/constexpr.hpp +88 -0
  159. mqt/core/include/mqt-core/boost/multiprecision/detail/default_ops.hpp +4052 -0
  160. mqt/core/include/mqt-core/boost/multiprecision/detail/digits.hpp +49 -0
  161. mqt/core/include/mqt-core/boost/multiprecision/detail/dynamic_array.hpp +44 -0
  162. mqt/core/include/mqt-core/boost/multiprecision/detail/empty_value.hpp +87 -0
  163. mqt/core/include/mqt-core/boost/multiprecision/detail/endian.hpp +35 -0
  164. mqt/core/include/mqt-core/boost/multiprecision/detail/et_ops.hpp +1831 -0
  165. mqt/core/include/mqt-core/boost/multiprecision/detail/float128_functions.hpp +95 -0
  166. mqt/core/include/mqt-core/boost/multiprecision/detail/float_string_cvt.hpp +333 -0
  167. mqt/core/include/mqt-core/boost/multiprecision/detail/fpclassify.hpp +101 -0
  168. mqt/core/include/mqt-core/boost/multiprecision/detail/functions/constants.hpp +288 -0
  169. mqt/core/include/mqt-core/boost/multiprecision/detail/functions/pow.hpp +905 -0
  170. mqt/core/include/mqt-core/boost/multiprecision/detail/functions/trig.hpp +1058 -0
  171. mqt/core/include/mqt-core/boost/multiprecision/detail/functions/trunc.hpp +82 -0
  172. mqt/core/include/mqt-core/boost/multiprecision/detail/generic_interconvert.hpp +687 -0
  173. mqt/core/include/mqt-core/boost/multiprecision/detail/hash.hpp +56 -0
  174. mqt/core/include/mqt-core/boost/multiprecision/detail/integer_ops.hpp +474 -0
  175. mqt/core/include/mqt-core/boost/multiprecision/detail/itos.hpp +39 -0
  176. mqt/core/include/mqt-core/boost/multiprecision/detail/min_max.hpp +106 -0
  177. mqt/core/include/mqt-core/boost/multiprecision/detail/no_et_ops.hpp +661 -0
  178. mqt/core/include/mqt-core/boost/multiprecision/detail/no_exceptions_support.hpp +55 -0
  179. mqt/core/include/mqt-core/boost/multiprecision/detail/number_base.hpp +1656 -0
  180. mqt/core/include/mqt-core/boost/multiprecision/detail/number_compare.hpp +848 -0
  181. mqt/core/include/mqt-core/boost/multiprecision/detail/precision.hpp +313 -0
  182. mqt/core/include/mqt-core/boost/multiprecision/detail/rebind.hpp +19 -0
  183. mqt/core/include/mqt-core/boost/multiprecision/detail/standalone_config.hpp +148 -0
  184. mqt/core/include/mqt-core/boost/multiprecision/detail/static_array.hpp +42 -0
  185. mqt/core/include/mqt-core/boost/multiprecision/detail/string_helpers.hpp +48 -0
  186. mqt/core/include/mqt-core/boost/multiprecision/detail/tables.hpp +80 -0
  187. mqt/core/include/mqt-core/boost/multiprecision/detail/ublas_interop.hpp +75 -0
  188. mqt/core/include/mqt-core/boost/multiprecision/detail/uniform_int_distribution.hpp +212 -0
  189. mqt/core/include/mqt-core/boost/multiprecision/detail/utype_helper.hpp +374 -0
  190. mqt/core/include/mqt-core/boost/multiprecision/eigen.hpp +248 -0
  191. mqt/core/include/mqt-core/boost/multiprecision/float128.hpp +920 -0
  192. mqt/core/include/mqt-core/boost/multiprecision/fwd.hpp +268 -0
  193. mqt/core/include/mqt-core/boost/multiprecision/gmp.hpp +4060 -0
  194. mqt/core/include/mqt-core/boost/multiprecision/integer.hpp +363 -0
  195. mqt/core/include/mqt-core/boost/multiprecision/logged_adaptor.hpp +834 -0
  196. mqt/core/include/mqt-core/boost/multiprecision/miller_rabin.hpp +221 -0
  197. mqt/core/include/mqt-core/boost/multiprecision/mpc.hpp +1721 -0
  198. mqt/core/include/mqt-core/boost/multiprecision/mpfi.hpp +2559 -0
  199. mqt/core/include/mqt-core/boost/multiprecision/mpfr.hpp +3644 -0
  200. mqt/core/include/mqt-core/boost/multiprecision/number.hpp +2500 -0
  201. mqt/core/include/mqt-core/boost/multiprecision/random.hpp +23 -0
  202. mqt/core/include/mqt-core/boost/multiprecision/rational_adaptor.hpp +1289 -0
  203. mqt/core/include/mqt-core/boost/multiprecision/tommath.hpp +1034 -0
  204. mqt/core/include/mqt-core/boost/multiprecision/traits/explicit_conversion.hpp +67 -0
  205. mqt/core/include/mqt-core/boost/multiprecision/traits/extract_exponent_type.hpp +28 -0
  206. mqt/core/include/mqt-core/boost/multiprecision/traits/is_backend.hpp +91 -0
  207. mqt/core/include/mqt-core/boost/multiprecision/traits/is_byte_container.hpp +51 -0
  208. mqt/core/include/mqt-core/boost/multiprecision/traits/is_complex.hpp +22 -0
  209. mqt/core/include/mqt-core/boost/multiprecision/traits/is_convertible_arithmetic.hpp +51 -0
  210. mqt/core/include/mqt-core/boost/multiprecision/traits/is_restricted_conversion.hpp +47 -0
  211. mqt/core/include/mqt-core/boost/multiprecision/traits/is_variable_precision.hpp +25 -0
  212. mqt/core/include/mqt-core/boost/multiprecision/traits/max_digits10.hpp +79 -0
  213. mqt/core/include/mqt-core/boost/multiprecision/traits/std_integer_traits.hpp +90 -0
  214. mqt/core/include/mqt-core/boost/multiprecision/traits/transcendental_reduction_type.hpp +21 -0
  215. mqt/core/include/mqt-core/boost/version.hpp +32 -0
  216. mqt/core/include/mqt-core/circuit_optimizer/CircuitOptimizer.hpp +119 -0
  217. mqt/core/include/mqt-core/circuit_optimizer/mqt_core_circuit_optimizer_export.h +43 -0
  218. mqt/core/include/mqt-core/datastructures/DirectedAcyclicGraph.hpp +117 -0
  219. mqt/core/include/mqt-core/datastructures/DirectedGraph.hpp +158 -0
  220. mqt/core/include/mqt-core/datastructures/DisjointSet.hpp +50 -0
  221. mqt/core/include/mqt-core/datastructures/Layer.hpp +172 -0
  222. mqt/core/include/mqt-core/datastructures/SymmetricMatrix.hpp +57 -0
  223. mqt/core/include/mqt-core/datastructures/UndirectedGraph.hpp +227 -0
  224. mqt/core/include/mqt-core/datastructures/mqt_core_ds_export.h +43 -0
  225. mqt/core/include/mqt-core/dd/Approximation.hpp +45 -0
  226. mqt/core/include/mqt-core/dd/CachedEdge.hpp +174 -0
  227. mqt/core/include/mqt-core/dd/Complex.hpp +165 -0
  228. mqt/core/include/mqt-core/dd/ComplexNumbers.hpp +150 -0
  229. mqt/core/include/mqt-core/dd/ComplexValue.hpp +184 -0
  230. mqt/core/include/mqt-core/dd/ComputeTable.hpp +183 -0
  231. mqt/core/include/mqt-core/dd/DDDefinitions.hpp +139 -0
  232. mqt/core/include/mqt-core/dd/DDpackageConfig.hpp +104 -0
  233. mqt/core/include/mqt-core/dd/DensityNoiseTable.hpp +114 -0
  234. mqt/core/include/mqt-core/dd/Edge.hpp +416 -0
  235. mqt/core/include/mqt-core/dd/Export.hpp +438 -0
  236. mqt/core/include/mqt-core/dd/FunctionalityConstruction.hpp +75 -0
  237. mqt/core/include/mqt-core/dd/GateMatrixDefinitions.hpp +43 -0
  238. mqt/core/include/mqt-core/dd/LinkedListBase.hpp +45 -0
  239. mqt/core/include/mqt-core/dd/MemoryManager.hpp +193 -0
  240. mqt/core/include/mqt-core/dd/Node.hpp +223 -0
  241. mqt/core/include/mqt-core/dd/NoiseFunctionality.hpp +144 -0
  242. mqt/core/include/mqt-core/dd/Operations.hpp +306 -0
  243. mqt/core/include/mqt-core/dd/Package.hpp +2036 -0
  244. mqt/core/include/mqt-core/dd/Package_fwd.hpp +22 -0
  245. mqt/core/include/mqt-core/dd/RealNumber.hpp +255 -0
  246. mqt/core/include/mqt-core/dd/RealNumberUniqueTable.hpp +217 -0
  247. mqt/core/include/mqt-core/dd/Simulation.hpp +98 -0
  248. mqt/core/include/mqt-core/dd/StateGeneration.hpp +143 -0
  249. mqt/core/include/mqt-core/dd/StochasticNoiseOperationTable.hpp +88 -0
  250. mqt/core/include/mqt-core/dd/UnaryComputeTable.hpp +121 -0
  251. mqt/core/include/mqt-core/dd/UniqueTable.hpp +243 -0
  252. mqt/core/include/mqt-core/dd/mqt_core_dd_export.h +43 -0
  253. mqt/core/include/mqt-core/dd/statistics/MemoryManagerStatistics.hpp +84 -0
  254. mqt/core/include/mqt-core/dd/statistics/PackageStatistics.hpp +55 -0
  255. mqt/core/include/mqt-core/dd/statistics/Statistics.hpp +48 -0
  256. mqt/core/include/mqt-core/dd/statistics/TableStatistics.hpp +79 -0
  257. mqt/core/include/mqt-core/dd/statistics/UniqueTableStatistics.hpp +31 -0
  258. mqt/core/include/mqt-core/fomac/FoMaC.hpp +568 -0
  259. mqt/core/include/mqt-core/ir/Definitions.hpp +108 -0
  260. mqt/core/include/mqt-core/ir/Permutation.hpp +213 -0
  261. mqt/core/include/mqt-core/ir/QuantumComputation.hpp +596 -0
  262. mqt/core/include/mqt-core/ir/Register.hpp +125 -0
  263. mqt/core/include/mqt-core/ir/mqt_core_ir_export.h +43 -0
  264. mqt/core/include/mqt-core/ir/operations/AodOperation.hpp +92 -0
  265. mqt/core/include/mqt-core/ir/operations/CompoundOperation.hpp +212 -0
  266. mqt/core/include/mqt-core/ir/operations/Control.hpp +142 -0
  267. mqt/core/include/mqt-core/ir/operations/Expression.hpp +847 -0
  268. mqt/core/include/mqt-core/ir/operations/IfElseOperation.hpp +169 -0
  269. mqt/core/include/mqt-core/ir/operations/NonUnitaryOperation.hpp +118 -0
  270. mqt/core/include/mqt-core/ir/operations/OpType.hpp +120 -0
  271. mqt/core/include/mqt-core/ir/operations/OpType.inc +76 -0
  272. mqt/core/include/mqt-core/ir/operations/Operation.hpp +247 -0
  273. mqt/core/include/mqt-core/ir/operations/StandardOperation.hpp +140 -0
  274. mqt/core/include/mqt-core/ir/operations/SymbolicOperation.hpp +144 -0
  275. mqt/core/include/mqt-core/mqt_na_qdmi/device.h +602 -0
  276. mqt/core/include/mqt-core/mqt_na_qdmi/types.h +78 -0
  277. mqt/core/include/mqt-core/na/NAComputation.hpp +185 -0
  278. mqt/core/include/mqt-core/na/device/Device.hpp +410 -0
  279. mqt/core/include/mqt-core/na/device/DeviceMemberInitializers.hpp +724 -0
  280. mqt/core/include/mqt-core/na/device/Generator.hpp +447 -0
  281. mqt/core/include/mqt-core/na/entities/Atom.hpp +62 -0
  282. mqt/core/include/mqt-core/na/entities/Location.hpp +154 -0
  283. mqt/core/include/mqt-core/na/entities/Zone.hpp +95 -0
  284. mqt/core/include/mqt-core/na/fomac/Device.hpp +169 -0
  285. mqt/core/include/mqt-core/na/mqt_core_na_export.h +43 -0
  286. mqt/core/include/mqt-core/na/operations/GlobalCZOp.hpp +38 -0
  287. mqt/core/include/mqt-core/na/operations/GlobalOp.hpp +58 -0
  288. mqt/core/include/mqt-core/na/operations/GlobalRYOp.hpp +42 -0
  289. mqt/core/include/mqt-core/na/operations/LoadOp.hpp +89 -0
  290. mqt/core/include/mqt-core/na/operations/LocalOp.hpp +56 -0
  291. mqt/core/include/mqt-core/na/operations/LocalRZOp.hpp +42 -0
  292. mqt/core/include/mqt-core/na/operations/LocalUOp.hpp +49 -0
  293. mqt/core/include/mqt-core/na/operations/MoveOp.hpp +66 -0
  294. mqt/core/include/mqt-core/na/operations/Op.hpp +62 -0
  295. mqt/core/include/mqt-core/na/operations/ShuttlingOp.hpp +51 -0
  296. mqt/core/include/mqt-core/na/operations/StoreOp.hpp +87 -0
  297. mqt/core/include/mqt-core/qasm3/Exception.hpp +85 -0
  298. mqt/core/include/mqt-core/qasm3/Gate.hpp +65 -0
  299. mqt/core/include/mqt-core/qasm3/Importer.hpp +192 -0
  300. mqt/core/include/mqt-core/qasm3/InstVisitor.hpp +145 -0
  301. mqt/core/include/mqt-core/qasm3/NestedEnvironment.hpp +41 -0
  302. mqt/core/include/mqt-core/qasm3/Parser.hpp +170 -0
  303. mqt/core/include/mqt-core/qasm3/Scanner.hpp +73 -0
  304. mqt/core/include/mqt-core/qasm3/Statement.hpp +486 -0
  305. mqt/core/include/mqt-core/qasm3/Statement_fwd.hpp +39 -0
  306. mqt/core/include/mqt-core/qasm3/StdGates.hpp +232 -0
  307. mqt/core/include/mqt-core/qasm3/Token.hpp +198 -0
  308. mqt/core/include/mqt-core/qasm3/Types.hpp +238 -0
  309. mqt/core/include/mqt-core/qasm3/Types_fwd.hpp +22 -0
  310. mqt/core/include/mqt-core/qasm3/mqt_core_qasm_export.h +43 -0
  311. mqt/core/include/mqt-core/qasm3/passes/CompilerPass.hpp +22 -0
  312. mqt/core/include/mqt-core/qasm3/passes/ConstEvalPass.hpp +102 -0
  313. mqt/core/include/mqt-core/qasm3/passes/TypeCheckPass.hpp +124 -0
  314. mqt/core/include/mqt-core/qdmi/Driver.hpp +431 -0
  315. mqt/core/include/mqt-core/zx/FunctionalityConstruction.hpp +125 -0
  316. mqt/core/include/mqt-core/zx/Rational.hpp +318 -0
  317. mqt/core/include/mqt-core/zx/Rules.hpp +132 -0
  318. mqt/core/include/mqt-core/zx/Simplify.hpp +182 -0
  319. mqt/core/include/mqt-core/zx/Utils.hpp +212 -0
  320. mqt/core/include/mqt-core/zx/ZXDefinitions.hpp +93 -0
  321. mqt/core/include/mqt-core/zx/ZXDiagram.hpp +480 -0
  322. mqt/core/include/mqt-core/zx/mqt_core_zx_export.h +43 -0
  323. mqt/core/include/nlohmann/adl_serializer.hpp +55 -0
  324. mqt/core/include/nlohmann/byte_container_with_subtype.hpp +103 -0
  325. mqt/core/include/nlohmann/detail/abi_macros.hpp +111 -0
  326. mqt/core/include/nlohmann/detail/conversions/from_json.hpp +577 -0
  327. mqt/core/include/nlohmann/detail/conversions/to_chars.hpp +1118 -0
  328. mqt/core/include/nlohmann/detail/conversions/to_json.hpp +479 -0
  329. mqt/core/include/nlohmann/detail/exceptions.hpp +291 -0
  330. mqt/core/include/nlohmann/detail/hash.hpp +129 -0
  331. mqt/core/include/nlohmann/detail/input/binary_reader.hpp +3068 -0
  332. mqt/core/include/nlohmann/detail/input/input_adapters.hpp +549 -0
  333. mqt/core/include/nlohmann/detail/input/json_sax.hpp +986 -0
  334. mqt/core/include/nlohmann/detail/input/lexer.hpp +1643 -0
  335. mqt/core/include/nlohmann/detail/input/parser.hpp +519 -0
  336. mqt/core/include/nlohmann/detail/input/position_t.hpp +37 -0
  337. mqt/core/include/nlohmann/detail/iterators/internal_iterator.hpp +35 -0
  338. mqt/core/include/nlohmann/detail/iterators/iter_impl.hpp +760 -0
  339. mqt/core/include/nlohmann/detail/iterators/iteration_proxy.hpp +235 -0
  340. mqt/core/include/nlohmann/detail/iterators/iterator_traits.hpp +61 -0
  341. mqt/core/include/nlohmann/detail/iterators/json_reverse_iterator.hpp +130 -0
  342. mqt/core/include/nlohmann/detail/iterators/primitive_iterator.hpp +132 -0
  343. mqt/core/include/nlohmann/detail/json_custom_base_class.hpp +39 -0
  344. mqt/core/include/nlohmann/detail/json_pointer.hpp +988 -0
  345. mqt/core/include/nlohmann/detail/json_ref.hpp +78 -0
  346. mqt/core/include/nlohmann/detail/macro_scope.hpp +595 -0
  347. mqt/core/include/nlohmann/detail/macro_unscope.hpp +46 -0
  348. mqt/core/include/nlohmann/detail/meta/call_std/begin.hpp +17 -0
  349. mqt/core/include/nlohmann/detail/meta/call_std/end.hpp +17 -0
  350. mqt/core/include/nlohmann/detail/meta/cpp_future.hpp +171 -0
  351. mqt/core/include/nlohmann/detail/meta/detected.hpp +70 -0
  352. mqt/core/include/nlohmann/detail/meta/identity_tag.hpp +21 -0
  353. mqt/core/include/nlohmann/detail/meta/is_sax.hpp +159 -0
  354. mqt/core/include/nlohmann/detail/meta/std_fs.hpp +29 -0
  355. mqt/core/include/nlohmann/detail/meta/type_traits.hpp +795 -0
  356. mqt/core/include/nlohmann/detail/meta/void_t.hpp +24 -0
  357. mqt/core/include/nlohmann/detail/output/binary_writer.hpp +1850 -0
  358. mqt/core/include/nlohmann/detail/output/output_adapters.hpp +147 -0
  359. mqt/core/include/nlohmann/detail/output/serializer.hpp +988 -0
  360. mqt/core/include/nlohmann/detail/string_concat.hpp +146 -0
  361. mqt/core/include/nlohmann/detail/string_escape.hpp +72 -0
  362. mqt/core/include/nlohmann/detail/string_utils.hpp +37 -0
  363. mqt/core/include/nlohmann/detail/value_t.hpp +118 -0
  364. mqt/core/include/nlohmann/json.hpp +5306 -0
  365. mqt/core/include/nlohmann/json_fwd.hpp +75 -0
  366. mqt/core/include/nlohmann/ordered_map.hpp +359 -0
  367. mqt/core/include/nlohmann/thirdparty/hedley/hedley.hpp +2045 -0
  368. mqt/core/include/nlohmann/thirdparty/hedley/hedley_undef.hpp +158 -0
  369. mqt/core/include/qdmi/qdmi/client.h +990 -0
  370. mqt/core/include/qdmi/qdmi/constants.h +1139 -0
  371. mqt/core/include/qdmi/qdmi/device.h +602 -0
  372. mqt/core/include/qdmi/qdmi/types.h +78 -0
  373. mqt/core/include/spdlog/async.h +99 -0
  374. mqt/core/include/spdlog/async_logger-inl.h +84 -0
  375. mqt/core/include/spdlog/async_logger.h +74 -0
  376. mqt/core/include/spdlog/cfg/argv.h +40 -0
  377. mqt/core/include/spdlog/cfg/env.h +36 -0
  378. mqt/core/include/spdlog/cfg/helpers-inl.h +107 -0
  379. mqt/core/include/spdlog/cfg/helpers.h +29 -0
  380. mqt/core/include/spdlog/common-inl.h +68 -0
  381. mqt/core/include/spdlog/common.h +406 -0
  382. mqt/core/include/spdlog/details/backtracer-inl.h +63 -0
  383. mqt/core/include/spdlog/details/backtracer.h +45 -0
  384. mqt/core/include/spdlog/details/circular_q.h +115 -0
  385. mqt/core/include/spdlog/details/console_globals.h +28 -0
  386. mqt/core/include/spdlog/details/file_helper-inl.h +153 -0
  387. mqt/core/include/spdlog/details/file_helper.h +61 -0
  388. mqt/core/include/spdlog/details/fmt_helper.h +141 -0
  389. mqt/core/include/spdlog/details/log_msg-inl.h +44 -0
  390. mqt/core/include/spdlog/details/log_msg.h +40 -0
  391. mqt/core/include/spdlog/details/log_msg_buffer-inl.h +54 -0
  392. mqt/core/include/spdlog/details/log_msg_buffer.h +32 -0
  393. mqt/core/include/spdlog/details/mpmc_blocking_q.h +177 -0
  394. mqt/core/include/spdlog/details/null_mutex.h +35 -0
  395. mqt/core/include/spdlog/details/os-inl.h +606 -0
  396. mqt/core/include/spdlog/details/os.h +127 -0
  397. mqt/core/include/spdlog/details/periodic_worker-inl.h +26 -0
  398. mqt/core/include/spdlog/details/periodic_worker.h +58 -0
  399. mqt/core/include/spdlog/details/registry-inl.h +270 -0
  400. mqt/core/include/spdlog/details/registry.h +131 -0
  401. mqt/core/include/spdlog/details/synchronous_factory.h +22 -0
  402. mqt/core/include/spdlog/details/tcp_client-windows.h +135 -0
  403. mqt/core/include/spdlog/details/tcp_client.h +127 -0
  404. mqt/core/include/spdlog/details/thread_pool-inl.h +126 -0
  405. mqt/core/include/spdlog/details/thread_pool.h +117 -0
  406. mqt/core/include/spdlog/details/udp_client-windows.h +98 -0
  407. mqt/core/include/spdlog/details/udp_client.h +81 -0
  408. mqt/core/include/spdlog/details/windows_include.h +11 -0
  409. mqt/core/include/spdlog/fmt/bin_to_hex.h +224 -0
  410. mqt/core/include/spdlog/fmt/bundled/args.h +220 -0
  411. mqt/core/include/spdlog/fmt/bundled/base.h +2989 -0
  412. mqt/core/include/spdlog/fmt/bundled/chrono.h +2330 -0
  413. mqt/core/include/spdlog/fmt/bundled/color.h +637 -0
  414. mqt/core/include/spdlog/fmt/bundled/compile.h +539 -0
  415. mqt/core/include/spdlog/fmt/bundled/core.h +5 -0
  416. mqt/core/include/spdlog/fmt/bundled/fmt.license.rst +27 -0
  417. mqt/core/include/spdlog/fmt/bundled/format-inl.h +1948 -0
  418. mqt/core/include/spdlog/fmt/bundled/format.h +4244 -0
  419. mqt/core/include/spdlog/fmt/bundled/os.h +427 -0
  420. mqt/core/include/spdlog/fmt/bundled/ostream.h +167 -0
  421. mqt/core/include/spdlog/fmt/bundled/printf.h +633 -0
  422. mqt/core/include/spdlog/fmt/bundled/ranges.h +850 -0
  423. mqt/core/include/spdlog/fmt/bundled/std.h +728 -0
  424. mqt/core/include/spdlog/fmt/bundled/xchar.h +369 -0
  425. mqt/core/include/spdlog/fmt/chrono.h +23 -0
  426. mqt/core/include/spdlog/fmt/compile.h +23 -0
  427. mqt/core/include/spdlog/fmt/fmt.h +30 -0
  428. mqt/core/include/spdlog/fmt/ostr.h +23 -0
  429. mqt/core/include/spdlog/fmt/ranges.h +23 -0
  430. mqt/core/include/spdlog/fmt/std.h +24 -0
  431. mqt/core/include/spdlog/fmt/xchar.h +23 -0
  432. mqt/core/include/spdlog/formatter.h +17 -0
  433. mqt/core/include/spdlog/fwd.h +18 -0
  434. mqt/core/include/spdlog/logger-inl.h +198 -0
  435. mqt/core/include/spdlog/logger.h +379 -0
  436. mqt/core/include/spdlog/mdc.h +52 -0
  437. mqt/core/include/spdlog/pattern_formatter-inl.h +1340 -0
  438. mqt/core/include/spdlog/pattern_formatter.h +118 -0
  439. mqt/core/include/spdlog/sinks/android_sink.h +137 -0
  440. mqt/core/include/spdlog/sinks/ansicolor_sink-inl.h +142 -0
  441. mqt/core/include/spdlog/sinks/ansicolor_sink.h +116 -0
  442. mqt/core/include/spdlog/sinks/base_sink-inl.h +59 -0
  443. mqt/core/include/spdlog/sinks/base_sink.h +51 -0
  444. mqt/core/include/spdlog/sinks/basic_file_sink-inl.h +48 -0
  445. mqt/core/include/spdlog/sinks/basic_file_sink.h +66 -0
  446. mqt/core/include/spdlog/sinks/callback_sink.h +56 -0
  447. mqt/core/include/spdlog/sinks/daily_file_sink.h +254 -0
  448. mqt/core/include/spdlog/sinks/dist_sink.h +81 -0
  449. mqt/core/include/spdlog/sinks/dup_filter_sink.h +91 -0
  450. mqt/core/include/spdlog/sinks/hourly_file_sink.h +193 -0
  451. mqt/core/include/spdlog/sinks/kafka_sink.h +119 -0
  452. mqt/core/include/spdlog/sinks/mongo_sink.h +108 -0
  453. mqt/core/include/spdlog/sinks/msvc_sink.h +68 -0
  454. mqt/core/include/spdlog/sinks/null_sink.h +41 -0
  455. mqt/core/include/spdlog/sinks/ostream_sink.h +43 -0
  456. mqt/core/include/spdlog/sinks/qt_sinks.h +304 -0
  457. mqt/core/include/spdlog/sinks/ringbuffer_sink.h +67 -0
  458. mqt/core/include/spdlog/sinks/rotating_file_sink-inl.h +179 -0
  459. mqt/core/include/spdlog/sinks/rotating_file_sink.h +93 -0
  460. mqt/core/include/spdlog/sinks/sink-inl.h +22 -0
  461. mqt/core/include/spdlog/sinks/sink.h +34 -0
  462. mqt/core/include/spdlog/sinks/stdout_color_sinks-inl.h +38 -0
  463. mqt/core/include/spdlog/sinks/stdout_color_sinks.h +49 -0
  464. mqt/core/include/spdlog/sinks/stdout_sinks-inl.h +127 -0
  465. mqt/core/include/spdlog/sinks/stdout_sinks.h +84 -0
  466. mqt/core/include/spdlog/sinks/syslog_sink.h +104 -0
  467. mqt/core/include/spdlog/sinks/systemd_sink.h +121 -0
  468. mqt/core/include/spdlog/sinks/tcp_sink.h +75 -0
  469. mqt/core/include/spdlog/sinks/udp_sink.h +69 -0
  470. mqt/core/include/spdlog/sinks/win_eventlog_sink.h +260 -0
  471. mqt/core/include/spdlog/sinks/wincolor_sink-inl.h +172 -0
  472. mqt/core/include/spdlog/sinks/wincolor_sink.h +82 -0
  473. mqt/core/include/spdlog/spdlog-inl.h +96 -0
  474. mqt/core/include/spdlog/spdlog.h +357 -0
  475. mqt/core/include/spdlog/stopwatch.h +66 -0
  476. mqt/core/include/spdlog/tweakme.h +148 -0
  477. mqt/core/include/spdlog/version.h +11 -0
  478. mqt/core/ir/__init__.pyi +2078 -0
  479. mqt/core/ir/operations.pyi +1011 -0
  480. mqt/core/ir/registers.pyi +91 -0
  481. mqt/core/ir/symbolic.pyi +177 -0
  482. mqt/core/ir.cp313t-win_amd64.pyd +0 -0
  483. mqt/core/lib/mqt-core-algorithms.lib +0 -0
  484. mqt/core/lib/mqt-core-circuit-optimizer.lib +0 -0
  485. mqt/core/lib/mqt-core-dd.lib +0 -0
  486. mqt/core/lib/mqt-core-ds.lib +0 -0
  487. mqt/core/lib/mqt-core-fomac.lib +0 -0
  488. mqt/core/lib/mqt-core-ir.lib +0 -0
  489. mqt/core/lib/mqt-core-na-fomac.lib +0 -0
  490. mqt/core/lib/mqt-core-na.lib +0 -0
  491. mqt/core/lib/mqt-core-qasm.lib +0 -0
  492. mqt/core/lib/mqt-core-qdmi-driver.lib +0 -0
  493. mqt/core/lib/mqt-core-qdmi-na-device-gen.lib +0 -0
  494. mqt/core/lib/mqt-core-qdmi-na-device.lib +0 -0
  495. mqt/core/lib/mqt-core-zx.lib +0 -0
  496. mqt/core/lib/pkgconfig/spdlog.pc +13 -0
  497. mqt/core/lib/spdlog.lib +0 -0
  498. mqt/core/na/__init__.py +12 -0
  499. mqt/core/na/fomac.cp313t-win_amd64.pyd +0 -0
  500. mqt/core/na/fomac.pyi +117 -0
  501. mqt/core/nlohmann_json.natvis +278 -0
  502. mqt/core/plugins/__init__.py +9 -0
  503. mqt/core/plugins/qiskit/__init__.py +19 -0
  504. mqt/core/plugins/qiskit/mqt_to_qiskit.py +420 -0
  505. mqt/core/plugins/qiskit/qiskit_to_mqt.py +562 -0
  506. mqt/core/py.typed +2 -0
  507. mqt/core/share/cmake/mqt-core/AddMQTPythonBinding.cmake +55 -0
  508. mqt/core/share/cmake/mqt-core/Cache.cmake +33 -0
  509. mqt/core/share/cmake/mqt-core/FindGMP.cmake +103 -0
  510. mqt/core/share/cmake/mqt-core/PackageAddTest.cmake +46 -0
  511. mqt/core/share/cmake/mqt-core/PreventInSourceBuilds.cmake +25 -0
  512. mqt/core/share/cmake/mqt-core/StandardProjectSettings.cmake +87 -0
  513. mqt/core/share/cmake/mqt-core/mqt-core-config-version.cmake +85 -0
  514. mqt/core/share/cmake/mqt-core/mqt-core-config.cmake +52 -0
  515. mqt/core/share/cmake/mqt-core/mqt-core-targets-release.cmake +141 -0
  516. mqt/core/share/cmake/mqt-core/mqt-core-targets.cmake +445 -0
  517. mqt/core/share/cmake/nlohmann_json/nlohmann_jsonConfig.cmake +15 -0
  518. mqt/core/share/cmake/nlohmann_json/nlohmann_jsonConfigVersion.cmake +20 -0
  519. mqt/core/share/cmake/nlohmann_json/nlohmann_jsonTargets.cmake +110 -0
  520. mqt/core/share/cmake/qdmi/Cache.cmake +44 -0
  521. mqt/core/share/cmake/qdmi/PrefixHandling.cmake +78 -0
  522. mqt/core/share/cmake/qdmi/prefix_defs.txt +26 -0
  523. mqt/core/share/cmake/qdmi/qdmi-config-version.cmake +85 -0
  524. mqt/core/share/cmake/qdmi/qdmi-config.cmake +42 -0
  525. mqt/core/share/cmake/qdmi/qdmi-targets.cmake +129 -0
  526. mqt/core/share/cmake/spdlog/spdlogConfig.cmake +44 -0
  527. mqt/core/share/cmake/spdlog/spdlogConfigTargets-release.cmake +19 -0
  528. mqt/core/share/cmake/spdlog/spdlogConfigTargets.cmake +121 -0
  529. mqt/core/share/cmake/spdlog/spdlogConfigVersion.cmake +65 -0
  530. mqt/core/share/pkgconfig/nlohmann_json.pc +7 -0
  531. mqt_core-3.3.2.dist-info/DELVEWHEEL +2 -0
  532. mqt_core-3.3.2.dist-info/METADATA +210 -0
  533. mqt_core-3.3.2.dist-info/RECORD +537 -0
  534. mqt_core-3.3.2.dist-info/WHEEL +5 -0
  535. mqt_core-3.3.2.dist-info/entry_points.txt +4 -0
  536. mqt_core-3.3.2.dist-info/licenses/LICENSE.md +22 -0
  537. mqt_core.libs/msvcp140.dll +0 -0
@@ -0,0 +1,2036 @@
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
+ #pragma once
12
+
13
+ #include "dd/CachedEdge.hpp"
14
+ #include "dd/Complex.hpp"
15
+ #include "dd/ComplexNumbers.hpp"
16
+ #include "dd/ComplexValue.hpp"
17
+ #include "dd/ComputeTable.hpp"
18
+ #include "dd/DDDefinitions.hpp"
19
+ #include "dd/DDpackageConfig.hpp"
20
+ #include "dd/DensityNoiseTable.hpp"
21
+ #include "dd/Edge.hpp"
22
+ #include "dd/MemoryManager.hpp"
23
+ #include "dd/Node.hpp"
24
+ #include "dd/Package_fwd.hpp" // IWYU pragma: export
25
+ #include "dd/RealNumber.hpp"
26
+ #include "dd/RealNumberUniqueTable.hpp"
27
+ #include "dd/StochasticNoiseOperationTable.hpp"
28
+ #include "dd/UnaryComputeTable.hpp"
29
+ #include "dd/UniqueTable.hpp"
30
+ #include "ir/Definitions.hpp"
31
+ #include "ir/Permutation.hpp"
32
+ #include "ir/operations/Control.hpp"
33
+
34
+ #include <algorithm>
35
+ #include <array>
36
+ #include <cassert>
37
+ #include <cmath>
38
+ #include <cstddef>
39
+ #include <cstdint>
40
+ #include <fstream>
41
+ #include <iostream>
42
+ #include <limits>
43
+ #include <random>
44
+ #include <regex>
45
+ #include <stack>
46
+ #include <stdexcept>
47
+ #include <string>
48
+ #include <type_traits>
49
+ #include <unordered_map>
50
+ #include <unordered_set>
51
+ #include <utility>
52
+ #include <vector>
53
+
54
+ namespace dd {
55
+
56
+ /**
57
+ * @brief The DD package class
58
+ *
59
+ * @details This is the main class of the decision diagram module in MQT Core.
60
+ * It contains the core functionality for working with quantum decision
61
+ * diagrams. Specifically, it provides the means to
62
+ * - represent quantum states as decision diagrams,
63
+ * - represent quantum operations as decision diagrams,
64
+ * - multiply decision diagrams (MxV, MxM, etc.),
65
+ * - perform collapsing measurements on decision diagrams,
66
+ * - sample from decision diagrams.
67
+ *
68
+ * To this end, it maintains several internal data structures, such as unique
69
+ * tables, compute tables, and memory managers, which are used to manage the
70
+ * nodes of the decision diagrams.
71
+ */
72
+ class Package {
73
+
74
+ ///
75
+ /// Construction, destruction, information, and reset
76
+ ///
77
+ public:
78
+ static constexpr std::size_t MAX_POSSIBLE_QUBITS =
79
+ static_cast<std::size_t>(std::numeric_limits<Qubit>::max()) + 1U;
80
+ static constexpr std::size_t DEFAULT_QUBITS = 32U;
81
+ /**
82
+ * @brief Construct a new DD Package instance
83
+ *
84
+ * @param nq The maximum number of qubits to allocate memory for. This can
85
+ * always be extended later using @ref resize.
86
+ * @param config The configuration of the package
87
+ */
88
+ explicit Package(std::size_t nq = DEFAULT_QUBITS,
89
+ const DDPackageConfig& config = DDPackageConfig{});
90
+ ~Package() = default;
91
+ Package(const Package& package) = delete;
92
+
93
+ Package& operator=(const Package& package) = delete;
94
+
95
+ /**
96
+ * @brief Resize the package to a new number of qubits
97
+ *
98
+ * @details This method will resize all the unique tables appropriately so
99
+ * that they can handle the new number of qubits.
100
+ *
101
+ * @param nq The new number of qubits
102
+ */
103
+ void resize(std::size_t nq);
104
+
105
+ /// Reset package state
106
+ void reset();
107
+
108
+ /// Get the number of qubits
109
+ [[nodiscard]] auto qubits() const { return nqubits; }
110
+
111
+ private:
112
+ std::size_t nqubits;
113
+ DDPackageConfig config_;
114
+
115
+ public:
116
+ /// The memory manager for vector nodes
117
+ MemoryManager vMemoryManager{
118
+ MemoryManager::create<vNode>(config_.utVecInitialAllocationSize)};
119
+ /// The memory manager for matrix nodes
120
+ MemoryManager mMemoryManager{
121
+ MemoryManager::create<mNode>(config_.utMatInitialAllocationSize)};
122
+ /// The memory manager for density matrix nodes
123
+ MemoryManager dMemoryManager{
124
+ MemoryManager::create<dNode>(config_.utDmInitialAllocationSize)};
125
+ /**
126
+ * @brief The memory manager for complex numbers
127
+ * @note The real and imaginary part of complex numbers are treated
128
+ * separately. Hence, it suffices for the manager to only manage real numbers.
129
+ */
130
+ MemoryManager cMemoryManager{MemoryManager::create<RealNumber>()};
131
+
132
+ /**
133
+ * @brief Get the memory manager for a given type
134
+ * @tparam T The type to get the manager for
135
+ * @return A reference to the manager
136
+ */
137
+ template <class T> [[nodiscard]] auto& getMemoryManager() {
138
+ if constexpr (std::is_same_v<T, vNode>) {
139
+ return vMemoryManager;
140
+ } else if constexpr (std::is_same_v<T, mNode>) {
141
+ return mMemoryManager;
142
+ } else if constexpr (std::is_same_v<T, dNode>) {
143
+ return dMemoryManager;
144
+ } else if constexpr (std::is_same_v<T, RealNumber>) {
145
+ return cMemoryManager;
146
+ }
147
+ }
148
+
149
+ /**
150
+ * @brief Reset all memory managers
151
+ * @arg resizeToTotal If set to true, each manager allocates one chunk of
152
+ * memory as large as all chunks combined before the reset.
153
+ * @see MemoryManager::reset
154
+ */
155
+ void resetMemoryManagers(bool resizeToTotal = false);
156
+
157
+ /// The unique table used for vector nodes
158
+ UniqueTable vUniqueTable{vMemoryManager, {0U, config_.utVecNumBucket}};
159
+ /// The unique table used for matrix nodes
160
+ UniqueTable mUniqueTable{mMemoryManager, {0U, config_.utMatNumBucket}};
161
+ /// The unique table used for density matrix nodes
162
+ UniqueTable dUniqueTable{dMemoryManager, {0U, config_.utDmNumBucket}};
163
+ /**
164
+ * @brief The unique table used for complex numbers
165
+ * @note The table actually only stores real numbers in the interval [0, 1],
166
+ * but is used to manages all complex numbers throughout the package.
167
+ * @see RealNumberUniqueTable
168
+ */
169
+ RealNumberUniqueTable cUniqueTable{cMemoryManager};
170
+ ComplexNumbers cn{cUniqueTable};
171
+
172
+ /**
173
+ * @brief Get the unique table for a given type
174
+ * @tparam T The type to get the unique table for
175
+ * @return A reference to the unique table
176
+ */
177
+ template <class T> [[nodiscard]] auto& getUniqueTable() {
178
+ if constexpr (std::is_same_v<T, vNode>) {
179
+ return vUniqueTable;
180
+ } else if constexpr (std::is_same_v<T, mNode>) {
181
+ return mUniqueTable;
182
+ } else if constexpr (std::is_same_v<T, dNode>) {
183
+ return dUniqueTable;
184
+ } else if constexpr (std::is_same_v<T, RealNumber>) {
185
+ return cUniqueTable;
186
+ }
187
+ }
188
+
189
+ /**
190
+ * @brief Clear all unique tables
191
+ * @see UniqueTable::clear
192
+ * @see RealNumberUniqueTable::clear
193
+ */
194
+ void clearUniqueTables();
195
+
196
+ /**
197
+ * @brief Add the DD to a tracking hashset and update its reference count.
198
+ * @tparam Node The node type of the edge.
199
+ * @param e The edge to increase the reference count of.
200
+ */
201
+ template <class Node> void incRef(const Edge<Node>& e) noexcept {
202
+ if (Edge<Node>::trackingRequired(e)) {
203
+ roots.addToRoots(e);
204
+ }
205
+ }
206
+
207
+ /**
208
+ * @brief Decrease the DD's reference count and remove it from the tracking
209
+ * hashset if the count hits zero.
210
+ * @tparam Node The node type of the edge.
211
+ * @param e The edge to decrease the reference count of.
212
+ * @throws std::invalid_argument If the edge is not part of the tracking
213
+ * hashset.
214
+ */
215
+ template <class Node> void decRef(const Edge<Node>& e) {
216
+ if (Edge<Node>::trackingRequired(e)) {
217
+ roots.removeFromRoots(e);
218
+ }
219
+ }
220
+
221
+ template <class Node> [[nodiscard]] auto& getRootSet() noexcept {
222
+ return roots.getRoots<Node>();
223
+ }
224
+
225
+ private:
226
+ struct RootSetManager {
227
+ public:
228
+ template <class Node>
229
+ using RootSet = std::unordered_map<Edge<Node>, std::size_t>;
230
+
231
+ /// @brief Add to respective root set.
232
+ template <class Node> void addToRoots(const Edge<Node>& e) noexcept {
233
+ getRoots<Node>()[e]++;
234
+ }
235
+
236
+ /// @brief Remove from respective root set.
237
+ template <class Node> void removeFromRoots(const Edge<Node>& e) {
238
+ auto& set = getRoots<Node>();
239
+ auto it = set.find(e);
240
+ if (it == set.end()) {
241
+ throw std::invalid_argument("Edge is not part of the root set.");
242
+ }
243
+ if (--it->second == 0U) {
244
+ set.erase(it);
245
+ }
246
+ }
247
+
248
+ /// @brief Execute mark() -> op() -> unmark().
249
+ template <class Result, typename Fn> Result execute(Fn& op) noexcept {
250
+ mark();
251
+ Result res = op();
252
+ unmark();
253
+ return res;
254
+ }
255
+
256
+ /// @brief Clear all root sets.
257
+ void reset() {
258
+ vRoots.clear();
259
+ mRoots.clear();
260
+ dRoots.clear();
261
+ }
262
+
263
+ private:
264
+ /// @brief Mark edges contained in @p roots.
265
+ template <class Node> static void mark(const RootSet<Node>& roots) {
266
+ for (auto& [edge, _] : roots) {
267
+ edge.mark();
268
+ }
269
+ }
270
+
271
+ /// @brief Unmark edges contained in @p roots.
272
+ template <class Node> static void unmark(const RootSet<Node>& roots) {
273
+ for (auto& [edge, _] : roots) {
274
+ edge.unmark();
275
+ }
276
+ }
277
+
278
+ /// @brief Mark edges contained in all root sets.
279
+ void mark() noexcept {
280
+ RootSetManager::mark(vRoots);
281
+ RootSetManager::mark(mRoots);
282
+ RootSetManager::mark(dRoots);
283
+ }
284
+
285
+ /// @brief Unmark edges contained in all root sets.
286
+ void unmark() noexcept {
287
+ RootSetManager::unmark(vRoots);
288
+ RootSetManager::unmark(mRoots);
289
+ RootSetManager::unmark(dRoots);
290
+ }
291
+
292
+ /// @brief Return vector roots.
293
+ template <class Node,
294
+ std::enable_if_t<std::is_same_v<Node, vNode>, bool> = true>
295
+ auto& getRoots() noexcept {
296
+ return vRoots;
297
+ }
298
+
299
+ /// @brief Return matrix roots.
300
+ template <class Node,
301
+ std::enable_if_t<std::is_same_v<Node, mNode>, bool> = true>
302
+ auto& getRoots() noexcept {
303
+ return mRoots;
304
+ }
305
+
306
+ /// @brief Return density roots.
307
+ template <class Node,
308
+ std::enable_if_t<std::is_same_v<Node, dNode>, bool> = true>
309
+ auto& getRoots() noexcept {
310
+ return dRoots;
311
+ }
312
+
313
+ RootSet<vNode> vRoots;
314
+ RootSet<mNode> mRoots;
315
+ RootSet<dNode> dRoots;
316
+
317
+ template <class Node> friend auto& Package::getRootSet() noexcept;
318
+ };
319
+
320
+ RootSetManager roots;
321
+
322
+ public:
323
+ /**
324
+ * @brief Trigger garbage collection on all unique tables.
325
+ * @details Mark-and-sweep algorithm: First, mark all nodes and complex
326
+ * numbers tracked in @p roots. Second, remove any unmarked nodes and numbers
327
+ * from the respective unique tables. Lastly, unmark all nodes and complex
328
+ * numbers again.
329
+ * @note By default, garbage collection is only triggered if the unique tables
330
+ * report that a collection might be necessary.
331
+ *
332
+ * @param force Force garbage collect, regardless of whether any
333
+ * table reports that it may need collecting.
334
+ * @returns Whether at least one vector, matrix, density-matrix node, or
335
+ * any complex number was reclaimed.
336
+ */
337
+ bool garbageCollect(bool force = false);
338
+
339
+ struct ActiveCounts {
340
+ std::size_t vector = 0U;
341
+ std::size_t matrix = 0U;
342
+ std::size_t density = 0U;
343
+ std::size_t reals = 0U;
344
+ };
345
+ /**
346
+ * @brief Compute the active number of nodes and numbers
347
+ * @note This traverses every currently tracked DD twice.
348
+ */
349
+ [[nodiscard]] ActiveCounts computeActiveCounts();
350
+
351
+ ///
352
+ /// Vector nodes, edges and quantum states
353
+ ///
354
+
355
+ /**
356
+ * @brief Construct the all-zero density operator
357
+ \f$|0...0\rangle\langle0...0|\f$
358
+ * @param n The number of qubits
359
+ * @return A decision diagram for the all-zero density operator
360
+ */
361
+ dEdge makeZeroDensityOperator(std::size_t n);
362
+
363
+ ///
364
+ /// Matrix nodes, edges and quantum gates
365
+ ///
366
+
367
+ /**
368
+ * @brief Construct the DD for a single-qubit gate
369
+ * @param mat The matrix representation of the gate
370
+ * @param target The target qubit
371
+ * @return A decision diagram for the gate
372
+ */
373
+ mEdge makeGateDD(const GateMatrix& mat, qc::Qubit target);
374
+
375
+ /**
376
+ * @brief Construct the DD for a single-qubit controlled gate
377
+ * @param mat The matrix representation of the gate
378
+ * @param control The control qubit
379
+ * @param target The target qubit
380
+ * @return A decision diagram for the gate
381
+ */
382
+ mEdge makeGateDD(const GateMatrix& mat, const qc::Control& control,
383
+ qc::Qubit target);
384
+
385
+ /**
386
+ * @brief Construct the DD for a multi-controlled single-qubit gate
387
+ * @param mat The matrix representation of the gate
388
+ * @param controls The control qubits
389
+ * @param target The target qubit
390
+ * @return A decision diagram for the gate
391
+ */
392
+ mEdge makeGateDD(const GateMatrix& mat, const qc::Controls& controls,
393
+ qc::Qubit target);
394
+
395
+ /**
396
+ * @brief Creates the DD for a two-qubit gate
397
+ * @param mat Matrix representation of the gate
398
+ * @param target0 First target qubit
399
+ * @param target1 Second target qubit
400
+ * @return DD representing the gate
401
+ * @throws std::runtime_error if the number of qubits is larger than the
402
+ * package configuration
403
+ */
404
+ mEdge makeTwoQubitGateDD(const TwoQubitGateMatrix& mat, qc::Qubit target0,
405
+ qc::Qubit target1);
406
+
407
+ /**
408
+ * @brief Creates the DD for a two-qubit gate
409
+ * @param mat Matrix representation of the gate
410
+ * @param control Control qubit of the two-qubit gate
411
+ * @param target0 First target qubit
412
+ * @param target1 Second target qubit
413
+ * @return DD representing the gate
414
+ * @throws std::runtime_error if the number of qubits is larger than the
415
+ * package configuration
416
+ */
417
+ mEdge makeTwoQubitGateDD(const TwoQubitGateMatrix& mat,
418
+ const qc::Control& control, qc::Qubit target0,
419
+ qc::Qubit target1);
420
+
421
+ /**
422
+ * @brief Creates the DD for a two-qubit gate
423
+ * @param mat Matrix representation of the gate
424
+ * @param controls Control qubits of the two-qubit gate
425
+ * @param target0 First target qubit
426
+ * @param target1 Second target qubit
427
+ * @return DD representing the gate
428
+ * @throws std::runtime_error if the number of qubits is larger than the
429
+ * package configuration
430
+ */
431
+ mEdge makeTwoQubitGateDD(const TwoQubitGateMatrix& mat,
432
+ const qc::Controls& controls, qc::Qubit target0,
433
+ qc::Qubit target1);
434
+
435
+ /**
436
+ * @brief Converts a given matrix to a decision diagram
437
+ * @param matrix A complex matrix to convert to a DD.
438
+ * @return A decision diagram representing the matrix.
439
+ * @throws std::invalid_argument If the given matrix is not square or its
440
+ * length is not a power of two.
441
+ */
442
+ mEdge makeDDFromMatrix(const CMat& matrix);
443
+
444
+ private:
445
+ /**
446
+ * @brief Constructs a decision diagram (DD) from a complex matrix using a
447
+ * recursive algorithm.
448
+ *
449
+ * @param matrix The complex matrix from which to create the DD.
450
+ * @param level The current level of recursion. Starts at the highest level of
451
+ * the matrix (log base 2 of the matrix size - 1).
452
+ * @param rowStart The starting row of the quadrant being processed.
453
+ * @param rowEnd The ending row of the quadrant being processed.
454
+ * @param colStart The starting column of the quadrant being processed.
455
+ * @param colEnd The ending column of the quadrant being processed.
456
+ * @return An mCachedEdge representing the root node of the created DD.
457
+ *
458
+ * @details This function recursively breaks down the matrix into quadrants
459
+ * until each quadrant has only one element. At each level of recursion, four
460
+ * new edges are created, one for each quadrant of the matrix. The four
461
+ * resulting decision diagram edges are used to create a new decision diagram
462
+ * node at the current level, and this node is returned as the result of the
463
+ * current recursive call. At the base case of recursion, the matrix has only
464
+ * one element, which is converted into a terminal node of the decision
465
+ * diagram.
466
+ *
467
+ * @note This function assumes that the matrix size is a power of two.
468
+ */
469
+ mCachedEdge makeDDFromMatrix(const CMat& matrix, Qubit level,
470
+ std::size_t rowStart, std::size_t rowEnd,
471
+ std::size_t colStart, std::size_t colEnd);
472
+
473
+ public:
474
+ /**
475
+ * @brief Create a normalized DD node and return an edge pointing to it.
476
+ *
477
+ * @details The node is not recreated if it already exists. This function
478
+ * retrieves a node from the memory manager, sets its variable, and normalizes
479
+ * the edges. If the node resembles the identity, it is skipped. The function
480
+ * then looks up the node in the unique table and returns an edge pointing to
481
+ * it.
482
+ *
483
+ * @tparam Node The type of the node.
484
+ * @tparam EdgeType The type of the edge.
485
+ * @param var The variable associated with the node.
486
+ * @param edges The edges of the node.
487
+ * @param generateDensityMatrix Flag to indicate if a density matrix node
488
+ * should be generated.
489
+ * @return An edge pointing to the normalized DD node.
490
+ */
491
+ template <class Node, template <class> class EdgeType>
492
+ EdgeType<Node>
493
+ makeDDNode(const Qubit var,
494
+ const std::array<EdgeType<Node>,
495
+ std::tuple_size_v<decltype(Node::e)>>& edges,
496
+ [[maybe_unused]] const bool generateDensityMatrix = false) {
497
+ auto& memoryManager = getMemoryManager<Node>();
498
+ auto p = memoryManager.template get<Node>();
499
+
500
+ p->v = var;
501
+ if constexpr (std::is_same_v<Node, mNode> || std::is_same_v<Node, dNode>) {
502
+ p->flags = 0;
503
+ if constexpr (std::is_same_v<Node, dNode>) {
504
+ p->setDensityMatrixNodeFlag(generateDensityMatrix);
505
+ }
506
+ }
507
+
508
+ auto e = EdgeType<Node>::normalize(p, edges, memoryManager, cn);
509
+ if constexpr (std::is_same_v<Node, mNode> || std::is_same_v<Node, dNode>) {
510
+ if (!e.isTerminal()) {
511
+ const auto& es = e.p->e;
512
+ // Check if node resembles the identity. If so, skip it.
513
+ if ((es[0].p == es[3].p) &&
514
+ (es[0].w.exactlyOne() && es[1].w.exactlyZero() &&
515
+ es[2].w.exactlyZero() && es[3].w.exactlyOne())) {
516
+ auto* ptr = es[0].p;
517
+ memoryManager.returnEntry(*e.p);
518
+ return EdgeType<Node>{ptr, e.w};
519
+ }
520
+ }
521
+ }
522
+
523
+ // look it up in the unique tables
524
+ auto& uniqueTable = getUniqueTable<Node>();
525
+ auto* l = uniqueTable.lookup(e.p);
526
+
527
+ return EdgeType<Node>{l, e.w};
528
+ }
529
+
530
+ /**
531
+ * @brief Delete an edge from the decision diagram.
532
+ *
533
+ * @tparam Node The type of the node.
534
+ * @param e The edge to delete.
535
+ * @param v The variable associated with the edge.
536
+ * @param edgeIdx The index of the edge to delete.
537
+ * @return The modified edge after deletion.
538
+ */
539
+ template <class Node>
540
+ Edge<Node> deleteEdge(const Edge<Node>& e, const Qubit v,
541
+ const std::size_t edgeIdx) {
542
+ std::unordered_map<Node*, Edge<Node>> nodes{};
543
+ return deleteEdge(e, v, edgeIdx, nodes);
544
+ }
545
+
546
+ /**
547
+ * @brief Helper function to delete an edge from the decision diagram.
548
+ *
549
+ * @tparam Node The type of the node.
550
+ * @param e The edge to delete.
551
+ * @param v The variable associated with the edge.
552
+ * @param edgeIdx The index of the edge to delete.
553
+ * @param nodes A map to keep track of processed nodes.
554
+ * @return The modified edge after deletion.
555
+ */
556
+ template <class Node>
557
+ Edge<Node> deleteEdge(const Edge<Node>& e, const Qubit v,
558
+ const std::size_t edgeIdx,
559
+ std::unordered_map<Node*, Edge<Node>>& nodes) {
560
+ if (e.isTerminal()) {
561
+ return e;
562
+ }
563
+
564
+ const auto& nodeIt = nodes.find(e.p);
565
+ Edge<Node> r{};
566
+ if (nodeIt != nodes.end()) {
567
+ r = nodeIt->second;
568
+ } else {
569
+ constexpr std::size_t n = std::tuple_size_v<decltype(e.p->e)>;
570
+ std::array<Edge<Node>, n> edges{};
571
+ if (e.p->v == v) {
572
+ for (std::size_t i = 0; i < n; i++) {
573
+ edges[i] = i == edgeIdx
574
+ ? Edge<Node>::zero()
575
+ : e.p->e[i]; // optimization -> node cannot occur below
576
+ // again, since dd is assumed to be free
577
+ }
578
+ } else {
579
+ for (std::size_t i = 0; i < n; i++) {
580
+ edges[i] = deleteEdge(e.p->e[i], v, edgeIdx, nodes);
581
+ }
582
+ }
583
+
584
+ r = makeDDNode(e.p->v, edges);
585
+ nodes[e.p] = r;
586
+ }
587
+ r.w = cn.lookup(r.w * e.w);
588
+ return r;
589
+ }
590
+
591
+ ///
592
+ /// Compute table definitions
593
+ ///
594
+
595
+ /**
596
+ * @brief Clear all compute tables.
597
+ *
598
+ * @details This method clears all entries in the compute tables used for
599
+ * various operations. It resets the state of the compute tables, making them
600
+ * ready for new computations.
601
+ */
602
+ void clearComputeTables();
603
+
604
+ ///
605
+ /// Measurements from state decision diagrams
606
+ ///
607
+
608
+ /**
609
+ * @brief Measure all qubits in the given decision diagram.
610
+ *
611
+ * @details This function measures all qubits in the decision diagram
612
+ * represented by `rootEdge`. It checks for numerical instabilities and
613
+ * collapses the state if requested.
614
+ *
615
+ * @param rootEdge The decision diagram to measure.
616
+ * @param collapse If true, the state is collapsed after measurement.
617
+ * @param mt A random number generator.
618
+ * @param epsilon The tolerance for numerical instabilities.
619
+ * @return A string representing the measurement result.
620
+ * @throws std::runtime_error If numerical instabilities are detected or if
621
+ * probabilities do not sum to 1.
622
+ */
623
+ std::string measureAll(vEdge& rootEdge, bool collapse, std::mt19937_64& mt,
624
+ fp epsilon = 0.001);
625
+
626
+ private:
627
+ /**
628
+ * @brief Assigns probabilities to nodes in a decision diagram.
629
+ *
630
+ * @details This function recursively assigns probabilities to nodes in a
631
+ * decision diagram. It calculates the probability of reaching each node and
632
+ * stores the result in a map.
633
+ *
634
+ * @param edge The edge to start the probability assignment from.
635
+ * @param probs A map to store the probabilities of each node.
636
+ * @return The probability of the given edge.
637
+ */
638
+ static fp assignProbabilities(const vEdge& edge,
639
+ std::unordered_map<const vNode*, fp>& probs);
640
+
641
+ public:
642
+ /**
643
+ * @brief Determine the measurement probabilities for a given qubit index.
644
+ *
645
+ * @param rootEdge The root edge of the decision diagram.
646
+ * @param index The qubit index to determine the measurement probabilities
647
+ * for.
648
+ * @return A pair of floating-point values representing the probabilities of
649
+ * measuring 0 and 1, respectively.
650
+ *
651
+ * @details This function calculates the probabilities of measuring 0 and 1
652
+ * for a given qubit index in the decision diagram. It uses a breadth-first
653
+ * search to traverse the decision diagram and accumulate the measurement
654
+ * probabilities. The function maintains a map of measurement probabilities
655
+ * for each node and a set of visited nodes to avoid redundant calculations.
656
+ * It also uses a queue to process nodes level by level.
657
+ */
658
+ static std::pair<fp, fp>
659
+ determineMeasurementProbabilities(const vEdge& rootEdge, Qubit index);
660
+
661
+ /**
662
+ * @brief Measures the qubit with the given index in the given state vector
663
+ * decision diagram. Collapses the state according to the measurement result.
664
+ * @param rootEdge the root edge of the state vector decision diagram
665
+ * @param index the index of the qubit to be measured
666
+ * @param mt the random number generator
667
+ * @param epsilon the numerical precision used for checking the normalization
668
+ * of the state vector decision diagram
669
+ * @return the measurement result ('0' or '1')
670
+ * @throws std::runtime_error if a numerical instability is detected during
671
+ * the measurement.
672
+ */
673
+ char measureOneCollapsing(vEdge& rootEdge, Qubit index, std::mt19937_64& mt,
674
+ fp epsilon = 0.001);
675
+
676
+ char measureOneCollapsing(dEdge& e, Qubit index, std::mt19937_64& mt);
677
+
678
+ /**
679
+ * @brief Performs a specific measurement on the given state vector decision
680
+ * diagram. Collapses the state according to the measurement result.
681
+ * @param rootEdge the root edge of the state vector decision diagram
682
+ * @param index the index of the qubit to be measured
683
+ * @param probability the probability of the measurement result (required for
684
+ * normalization)
685
+ * @param measureZero whether or not to measure '0' (otherwise '1' is
686
+ * measured)
687
+ */
688
+ void performCollapsingMeasurement(vEdge& rootEdge, Qubit index,
689
+ fp probability, bool measureZero);
690
+
691
+ ///
692
+ /// Addition
693
+ ///
694
+ ComputeTable<vCachedEdge, vCachedEdge, vCachedEdge> vectorAdd{
695
+ config_.ctVecAddNumBucket};
696
+ ComputeTable<mCachedEdge, mCachedEdge, mCachedEdge> matrixAdd{
697
+ config_.ctMatAddNumBucket};
698
+ ComputeTable<dCachedEdge, dCachedEdge, dCachedEdge> densityAdd{
699
+ config_.ctDmAddNumBucket};
700
+
701
+ /**
702
+ * @brief Get the compute table for addition operations.
703
+ *
704
+ * @tparam Node The type of the node.
705
+ * @return A reference to the appropriate compute table for the given node
706
+ * type.
707
+ */
708
+ template <class Node> [[nodiscard]] auto& getAddComputeTable() {
709
+ if constexpr (std::is_same_v<Node, vNode>) {
710
+ return vectorAdd;
711
+ } else if constexpr (std::is_same_v<Node, mNode>) {
712
+ return matrixAdd;
713
+ } else if constexpr (std::is_same_v<Node, dNode>) {
714
+ return densityAdd;
715
+ }
716
+ }
717
+
718
+ ComputeTable<vCachedEdge, vCachedEdge, vCachedEdge> vectorAddMagnitudes{
719
+ config_.ctVecAddMagNumBucket};
720
+ ComputeTable<mCachedEdge, mCachedEdge, mCachedEdge> matrixAddMagnitudes{
721
+ config_.ctMatAddMagNumBucket};
722
+
723
+ /**
724
+ * @brief Get the compute table for addition operations with magnitudes.
725
+ *
726
+ * @tparam Node The type of the node.
727
+ * @return A reference to the appropriate compute table for the given node
728
+ * type.
729
+ */
730
+ template <class Node> [[nodiscard]] auto& getAddMagnitudesComputeTable() {
731
+ if constexpr (std::is_same_v<Node, vNode>) {
732
+ return vectorAddMagnitudes;
733
+ } else if constexpr (std::is_same_v<Node, mNode>) {
734
+ return matrixAddMagnitudes;
735
+ }
736
+ }
737
+
738
+ /**
739
+ * @brief Add two decision diagrams.
740
+ *
741
+ * @tparam Node The type of the node.
742
+ * @param x The first DD.
743
+ * @param y The second DD.
744
+ * @return The resulting DD after addition.
745
+ *
746
+ * @details This function performs the addition of two decision diagrams
747
+ * (DDs). It uses a compute table to cache intermediate results and avoid
748
+ * redundant computations. The addition is conducted recursively, where the
749
+ * function traverses the nodes of the DDs, adds corresponding edges, and
750
+ * normalizes the resulting edges. If the nodes are terminal, their weights
751
+ * are directly added. The function ensures that the resulting DD is properly
752
+ * normalized and stored in the unique table to maintain the canonical form.
753
+ */
754
+ template <class Node>
755
+ Edge<Node> add(const Edge<Node>& x, const Edge<Node>& y) {
756
+ Qubit var{};
757
+ if (!x.isTerminal()) {
758
+ var = x.p->v;
759
+ }
760
+ if (!y.isTerminal() && (y.p->v) > var) {
761
+ var = y.p->v;
762
+ }
763
+
764
+ const auto result = add2(CachedEdge{x.p, x.w}, {y.p, y.w}, var);
765
+ return cn.lookup(result);
766
+ }
767
+
768
+ /**
769
+ * @brief Internal function to add two decision diagrams.
770
+ *
771
+ * This function is used internally to add two decision diagrams (DDs) of type
772
+ * Node. It is not intended to be called directly.
773
+ *
774
+ * @tparam Node The type of the node.
775
+ * @param x The first DD.
776
+ * @param y The second DD.
777
+ * @param var The variable associated with the current level of recursion.
778
+ * @return The resulting DD after addition.
779
+ */
780
+ template <class Node>
781
+ CachedEdge<Node> add2(const CachedEdge<Node>& x, const CachedEdge<Node>& y,
782
+ const Qubit var) {
783
+ if (x.w.exactlyZero()) {
784
+ if (y.w.exactlyZero()) {
785
+ return CachedEdge<Node>::zero();
786
+ }
787
+ return y;
788
+ }
789
+ if (y.w.exactlyZero()) {
790
+ return x;
791
+ }
792
+ if (x.p == y.p) {
793
+ const auto rWeight = x.w + y.w;
794
+ return {x.p, rWeight};
795
+ }
796
+
797
+ auto& computeTable = getAddComputeTable<Node>();
798
+ if (const auto* r = computeTable.lookup(x, y); r != nullptr) {
799
+ return *r;
800
+ }
801
+
802
+ constexpr std::size_t n = std::tuple_size_v<decltype(x.p->e)>;
803
+ std::array<CachedEdge<Node>, n> edge{};
804
+ for (std::size_t i = 0U; i < n; i++) {
805
+ CachedEdge<Node> e1{};
806
+ if constexpr (std::is_same_v<Node, mNode> ||
807
+ std::is_same_v<Node, dNode>) {
808
+ if (x.isIdentity() || x.p->v < var) {
809
+ // [ 0 | 1 ] [ x | 0 ]
810
+ // --------- = ---------
811
+ // [ 2 | 3 ] [ 0 | x ]
812
+ if (i == 0 || i == 3) {
813
+ e1 = x;
814
+ }
815
+ } else {
816
+ auto& xSuccessor = x.p->e[i];
817
+ e1 = {xSuccessor.p, 0};
818
+ if (!xSuccessor.w.exactlyZero()) {
819
+ e1.w = x.w * xSuccessor.w;
820
+ }
821
+ }
822
+ } else {
823
+ auto& xSuccessor = x.p->e[i];
824
+ e1 = {xSuccessor.p, 0};
825
+ if (!xSuccessor.w.exactlyZero()) {
826
+ e1.w = x.w * xSuccessor.w;
827
+ }
828
+ }
829
+ CachedEdge<Node> e2{};
830
+ if constexpr (std::is_same_v<Node, mNode> ||
831
+ std::is_same_v<Node, dNode>) {
832
+ if (y.isIdentity() || y.p->v < var) {
833
+ // [ 0 | 1 ] [ y | 0 ]
834
+ // --------- = ---------
835
+ // [ 2 | 3 ] [ 0 | y ]
836
+ if (i == 0 || i == 3) {
837
+ e2 = y;
838
+ }
839
+ } else {
840
+ auto& ySuccessor = y.p->e[i];
841
+ e2 = {ySuccessor.p, 0};
842
+ if (!ySuccessor.w.exactlyZero()) {
843
+ e2.w = y.w * ySuccessor.w;
844
+ }
845
+ }
846
+ } else {
847
+ auto& ySuccessor = y.p->e[i];
848
+ e2 = {ySuccessor.p, 0};
849
+ if (!ySuccessor.w.exactlyZero()) {
850
+ e2.w = y.w * ySuccessor.w;
851
+ }
852
+ }
853
+
854
+ if constexpr (std::is_same_v<Node, dNode>) {
855
+ dNode::applyDmChangesToNode(e1.p);
856
+ dNode::applyDmChangesToNode(e2.p);
857
+ edge[i] = add2(e1, e2, var - 1);
858
+ dNode::revertDmChangesToNode(e2.p);
859
+ dNode::revertDmChangesToNode(e1.p);
860
+ } else {
861
+ edge[i] = add2(e1, e2, var - 1);
862
+ }
863
+ }
864
+ auto r = makeDDNode(var, edge);
865
+ computeTable.insert(x, y, r);
866
+ return r;
867
+ }
868
+
869
+ /**
870
+ * @brief Compute the element-wise magnitude sum of two vectors or matrices.
871
+ *
872
+ * For two vectors (or matrices) \p x and \p y, this function returns a result
873
+ * \p r such that for each index \p i:
874
+ * \f$ r[i] = \sqrt{|x[i]|^2 + |y[i]|^2} \f$
875
+ *
876
+ * @param x DD representation of the first operand.
877
+ * @param y DD representation of the second operand.
878
+ * @param var Number of qubits in the DD.
879
+ * @return DD representing the result.
880
+ */
881
+ template <class Node>
882
+ CachedEdge<Node> addMagnitudes(const CachedEdge<Node>& x,
883
+ const CachedEdge<Node>& y, const Qubit var) {
884
+ if (x.w.exactlyZero()) {
885
+ if (y.w.exactlyZero()) {
886
+ return CachedEdge<Node>::zero();
887
+ }
888
+ const auto rWeight = y.w.mag();
889
+ return {y.p, rWeight};
890
+ }
891
+ if (y.w.exactlyZero()) {
892
+ const auto rWeight = x.w.mag();
893
+ return {x.p, rWeight};
894
+ }
895
+ if (x.p == y.p) {
896
+ const auto rWeight = std::sqrt(x.w.mag2() + y.w.mag2());
897
+ return {x.p, rWeight};
898
+ }
899
+
900
+ auto& computeTable = getAddMagnitudesComputeTable<Node>();
901
+ if (const auto* r = computeTable.lookup(x, y); r != nullptr) {
902
+ return *r;
903
+ }
904
+
905
+ constexpr std::size_t n = std::tuple_size_v<decltype(x.p->e)>;
906
+ std::array<CachedEdge<Node>, n> edge{};
907
+ for (std::size_t i = 0U; i < n; i++) {
908
+ CachedEdge<Node> e1{};
909
+ if constexpr (std::is_same_v<Node, mNode> ||
910
+ std::is_same_v<Node, dNode>) {
911
+ if (x.isIdentity() || x.p->v < var) {
912
+ if (i == 0 || i == 3) {
913
+ e1 = x;
914
+ }
915
+ } else {
916
+ auto& xSuccessor = x.p->e[i];
917
+ e1 = {xSuccessor.p, 0};
918
+ if (!xSuccessor.w.exactlyZero()) {
919
+ e1.w = x.w * xSuccessor.w;
920
+ }
921
+ }
922
+ } else {
923
+ auto& xSuccessor = x.p->e[i];
924
+ e1 = {xSuccessor.p, 0};
925
+ if (!xSuccessor.w.exactlyZero()) {
926
+ e1.w = x.w * xSuccessor.w;
927
+ }
928
+ }
929
+ CachedEdge<Node> e2{};
930
+ if constexpr (std::is_same_v<Node, mNode> ||
931
+ std::is_same_v<Node, dNode>) {
932
+ if (y.isIdentity() || y.p->v < var) {
933
+ if (i == 0 || i == 3) {
934
+ e2 = y;
935
+ }
936
+ } else {
937
+ auto& ySuccessor = y.p->e[i];
938
+ e2 = {ySuccessor.p, 0};
939
+ if (!ySuccessor.w.exactlyZero()) {
940
+ e2.w = y.w * ySuccessor.w;
941
+ }
942
+ }
943
+ } else {
944
+ auto& ySuccessor = y.p->e[i];
945
+ e2 = {ySuccessor.p, 0};
946
+ if (!ySuccessor.w.exactlyZero()) {
947
+ e2.w = y.w * ySuccessor.w;
948
+ }
949
+ }
950
+ edge[i] = addMagnitudes(e1, e2, var - 1);
951
+ }
952
+ auto r = makeDDNode(var, edge);
953
+ computeTable.insert(x, y, r);
954
+ return r;
955
+ }
956
+
957
+ ///
958
+ /// Vector conjugation
959
+ ///
960
+ UnaryComputeTable<vNode*, vCachedEdge> conjugateVector{
961
+ config_.ctVecConjNumBucket};
962
+
963
+ /**
964
+ * @brief Conjugates a given decision diagram edge.
965
+ *
966
+ * @param a The decision diagram edge to conjugate.
967
+ * @return The conjugated decision diagram edge.
968
+ */
969
+ vEdge conjugate(const vEdge& a);
970
+ /**
971
+ * @brief Recursively conjugates a given decision diagram edge.
972
+ *
973
+ * @param a The decision diagram edge to conjugate.
974
+ * @return The conjugated decision diagram edge.
975
+ */
976
+ vCachedEdge conjugateRec(const vEdge& a);
977
+
978
+ ///
979
+ /// Matrix (conjugate) transpose
980
+ ///
981
+ UnaryComputeTable<mNode*, mCachedEdge> conjugateMatrixTranspose{
982
+ config_.ctMatConjTransNumBucket};
983
+
984
+ /**
985
+ * @brief Computes the conjugate transpose of a given matrix edge.
986
+ *
987
+ * @param a The matrix edge to conjugate transpose.
988
+ * @return The conjugated transposed matrix edge.
989
+ */
990
+ mEdge conjugateTranspose(const mEdge& a);
991
+ /**
992
+ * @brief Recursively computes the conjugate transpose of a given matrix edge.
993
+ *
994
+ * @param a The matrix edge to conjugate transpose.
995
+ * @return The conjugated transposed matrix edge.
996
+ */
997
+ mCachedEdge conjugateTransposeRec(const mEdge& a);
998
+
999
+ ///
1000
+ /// Multiplication
1001
+ ///
1002
+ ComputeTable<mNode*, vNode*, vCachedEdge> matrixVectorMultiplication{
1003
+ config_.ctMatVecMultNumBucket};
1004
+ ComputeTable<mNode*, mNode*, mCachedEdge> matrixMatrixMultiplication{
1005
+ config_.ctMatMatMultNumBucket};
1006
+ ComputeTable<dNode*, dNode*, dCachedEdge> densityDensityMultiplication{
1007
+ config_.ctDmDmMultNumBucket};
1008
+
1009
+ /**
1010
+ * @brief Get the compute table for multiplication operations.
1011
+ *
1012
+ * @tparam RightOperandNode The type of the right operand node.
1013
+ * @return A reference to the appropriate compute table for the given node
1014
+ * type.
1015
+ */
1016
+ template <class RightOperandNode>
1017
+ [[nodiscard]] auto& getMultiplicationComputeTable() {
1018
+ if constexpr (std::is_same_v<RightOperandNode, vNode>) {
1019
+ return matrixVectorMultiplication;
1020
+ } else if constexpr (std::is_same_v<RightOperandNode, mNode>) {
1021
+ return matrixMatrixMultiplication;
1022
+ } else if constexpr (std::is_same_v<RightOperandNode, dNode>) {
1023
+ return densityDensityMultiplication;
1024
+ }
1025
+ }
1026
+
1027
+ /**
1028
+ * @brief Applies a matrix operation to a vector.
1029
+ *
1030
+ * @details The reference count of the input vector is decreased,
1031
+ * while the reference count of the result is increased. After the operation,
1032
+ * garbage collection is triggered.
1033
+ *
1034
+ * @param operation Matrix operation to apply
1035
+ * @param e Vector to apply the operation to
1036
+ * @return The appropriately reference-counted result.
1037
+ */
1038
+ VectorDD applyOperation(const MatrixDD& operation, const VectorDD& e);
1039
+
1040
+ /**
1041
+ * @brief Applies a matrix operation to a matrix.
1042
+ *
1043
+ * @details The reference count of the input matrix is decreased,
1044
+ * while the reference count of the result is increased. After the operation,
1045
+ * garbage collection is triggered.
1046
+ *
1047
+ * @param operation Matrix operation to apply
1048
+ * @param e Matrix to apply the operation to
1049
+ * @param applyFromLeft Flag to indicate if the operation should be applied
1050
+ * from the left (default) or right.
1051
+ * @return The appropriately reference-counted result.
1052
+ */
1053
+ MatrixDD applyOperation(const MatrixDD& operation, const MatrixDD& e,
1054
+ bool applyFromLeft = true);
1055
+
1056
+ dEdge applyOperationToDensity(dEdge& e, const mEdge& operation);
1057
+
1058
+ /**
1059
+ * @brief Multiplies two decision diagrams.
1060
+ *
1061
+ * @tparam LeftOperandNode The type of the left operand node.
1062
+ * @tparam RightOperandNode The type of the right operand node.
1063
+ * @param x The left operand decision diagram.
1064
+ * @param y The right operand decision diagram.
1065
+ * @param generateDensityMatrix Flag to indicate if a density matrix node
1066
+ * should be generated.
1067
+ * @return The resulting decision diagram after multiplication.
1068
+ *
1069
+ * @details This function performs the multiplication of two decision diagrams
1070
+ * (DDs). It uses a compute table to cache intermediate results and avoid
1071
+ * redundant computations. The multiplication is conducted recursively, where
1072
+ * the function traverses the nodes of the DDs, multiplies corresponding
1073
+ * edges, and normalizes the resulting edges. If the nodes are terminal, their
1074
+ * weights are directly multiplied. The function ensures that the resulting DD
1075
+ * is properly normalized and stored in the unique table to maintain the
1076
+ * canonical form.
1077
+ */
1078
+ template <class LeftOperandNode, class RightOperandNode>
1079
+ Edge<RightOperandNode>
1080
+ multiply(const Edge<LeftOperandNode>& x, const Edge<RightOperandNode>& y,
1081
+ [[maybe_unused]] const bool generateDensityMatrix = false) {
1082
+ using LEdge = Edge<LeftOperandNode>;
1083
+ using REdge = Edge<RightOperandNode>;
1084
+ static_assert(std::disjunction_v<std::is_same<LEdge, mEdge>,
1085
+ std::is_same<LEdge, dEdge>>,
1086
+ "Left operand must be a matrix or density matrix");
1087
+ static_assert(std::disjunction_v<std::is_same<REdge, vEdge>,
1088
+ std::is_same<REdge, mEdge>,
1089
+ std::is_same<REdge, dEdge>>,
1090
+ "Right operand must be a vector, matrix or density matrix");
1091
+ Qubit var{};
1092
+ if constexpr (std::is_same_v<LEdge, dEdge>) {
1093
+ auto xCopy = x;
1094
+ auto yCopy = y;
1095
+ dEdge::applyDmChangesToEdges(xCopy, yCopy);
1096
+
1097
+ if (!xCopy.isTerminal()) {
1098
+ var = xCopy.p->v;
1099
+ }
1100
+ if (!y.isTerminal() && yCopy.p->v > var) {
1101
+ var = yCopy.p->v;
1102
+ }
1103
+
1104
+ const auto e = multiply2(xCopy, yCopy, var, generateDensityMatrix);
1105
+ dEdge::revertDmChangesToEdges(xCopy, yCopy);
1106
+ return cn.lookup(e);
1107
+ } else {
1108
+ if (!x.isTerminal()) {
1109
+ var = x.p->v;
1110
+ }
1111
+ if (!y.isTerminal() && y.p->v > var) {
1112
+ var = y.p->v;
1113
+ }
1114
+ const auto e = multiply2(x, y, var);
1115
+ return cn.lookup(e);
1116
+ }
1117
+ }
1118
+
1119
+ private:
1120
+ /**
1121
+ * @brief Internal function to multiply two decision diagrams.
1122
+ *
1123
+ * This function is used internally to multiply two decision diagrams (DDs) of
1124
+ * type Node. It is not intended to be called directly.
1125
+ *
1126
+ * @tparam LeftOperandNode The type of the left operand node.
1127
+ * @tparam RightOperandNode The type of the right operand node.
1128
+ * @param x The left operand decision diagram.
1129
+ * @param y The right operand decision diagram.
1130
+ * @param var The variable associated with the current level of recursion.
1131
+ * @param generateDensityMatrix Flag to indicate if a density matrix node
1132
+ * should be generated.
1133
+ * @return The resulting DD after multiplication.
1134
+ */
1135
+ template <class LeftOperandNode, class RightOperandNode>
1136
+ CachedEdge<RightOperandNode>
1137
+ multiply2(const Edge<LeftOperandNode>& x, const Edge<RightOperandNode>& y,
1138
+ const Qubit var,
1139
+ [[maybe_unused]] const bool generateDensityMatrix = false) {
1140
+ using LEdge = Edge<LeftOperandNode>;
1141
+ using REdge = Edge<RightOperandNode>;
1142
+ using ResultEdge = CachedEdge<RightOperandNode>;
1143
+
1144
+ if (x.w.exactlyZero() || y.w.exactlyZero()) {
1145
+ return ResultEdge::zero();
1146
+ }
1147
+
1148
+ const auto xWeight = static_cast<ComplexValue>(x.w);
1149
+ const auto yWeight = static_cast<ComplexValue>(y.w);
1150
+ const auto rWeight = xWeight * yWeight;
1151
+ if (x.isIdentity()) {
1152
+ if constexpr (!std::is_same_v<RightOperandNode, dNode>) {
1153
+ return {y.p, rWeight};
1154
+ } else {
1155
+ if (y.isIdentity() ||
1156
+ (dNode::isDensityMatrixTempFlagSet(y.p->flags) &&
1157
+ generateDensityMatrix) ||
1158
+ (!dNode::isDensityMatrixTempFlagSet(y.p->flags) &&
1159
+ !generateDensityMatrix)) {
1160
+ return {y.p, rWeight};
1161
+ }
1162
+ }
1163
+ }
1164
+
1165
+ if constexpr (std::is_same_v<RightOperandNode, mNode> ||
1166
+ std::is_same_v<RightOperandNode, dNode>) {
1167
+ if (y.isIdentity()) {
1168
+ if constexpr (!std::is_same_v<LeftOperandNode, dNode>) {
1169
+ return {x.p, rWeight};
1170
+ } else {
1171
+ if (x.isIdentity() ||
1172
+ (dNode::isDensityMatrixTempFlagSet(x.p->flags) &&
1173
+ generateDensityMatrix) ||
1174
+ (!dNode::isDensityMatrixTempFlagSet(x.p->flags) &&
1175
+ !generateDensityMatrix)) {
1176
+ return {x.p, rWeight};
1177
+ }
1178
+ }
1179
+ }
1180
+ }
1181
+
1182
+ auto& computeTable = getMultiplicationComputeTable<RightOperandNode>();
1183
+ if (const auto* r = computeTable.lookup(x.p, y.p, generateDensityMatrix);
1184
+ r != nullptr) {
1185
+ return {r->p, r->w * rWeight};
1186
+ }
1187
+
1188
+ constexpr std::size_t n = std::tuple_size_v<decltype(y.p->e)>;
1189
+
1190
+ constexpr std::size_t rows = RADIX;
1191
+ constexpr std::size_t cols = n == NEDGE ? RADIX : 1U;
1192
+
1193
+ std::array<ResultEdge, n> edge{};
1194
+ for (auto i = 0U; i < rows; i++) {
1195
+ for (auto j = 0U; j < cols; j++) {
1196
+ auto idx = (cols * i) + j;
1197
+ edge[idx] = ResultEdge::zero();
1198
+ for (auto k = 0U; k < rows; k++) {
1199
+ const auto xIdx = (rows * i) + k;
1200
+ LEdge e1{};
1201
+ if (x.p != nullptr && x.p->v == var) {
1202
+ e1 = x.p->e[xIdx];
1203
+ } else {
1204
+ if (xIdx == 0 || xIdx == 3) {
1205
+ e1 = LEdge{x.p, Complex::one()};
1206
+ } else {
1207
+ e1 = LEdge::zero();
1208
+ }
1209
+ }
1210
+
1211
+ const auto yIdx = j + (cols * k);
1212
+ REdge e2{};
1213
+ if (y.p != nullptr && y.p->v == var) {
1214
+ e2 = y.p->e[yIdx];
1215
+ } else {
1216
+ if (yIdx == 0 || yIdx == 3) {
1217
+ e2 = REdge{y.p, Complex::one()};
1218
+ } else {
1219
+ e2 = REdge::zero();
1220
+ }
1221
+ }
1222
+
1223
+ const auto v = static_cast<Qubit>(var - 1);
1224
+ if constexpr (std::is_same_v<LeftOperandNode, dNode>) {
1225
+ dCachedEdge m;
1226
+ dEdge::applyDmChangesToEdges(e1, e2);
1227
+ if (!generateDensityMatrix || idx == 1) {
1228
+ // When generateDensityMatrix is false or I have the first edge I
1229
+ // don't optimize anything and set generateDensityMatrix to false
1230
+ // for all child edges
1231
+ m = multiply2(e1, e2, v, false);
1232
+ } else if (idx == 2) {
1233
+ // When I have the second edge and generateDensityMatrix == false,
1234
+ // then edge[2] == edge[1]
1235
+ if (k == 0) {
1236
+ if (edge[1].w.approximatelyZero()) {
1237
+ edge[2] = ResultEdge::zero();
1238
+ } else {
1239
+ edge[2] = edge[1];
1240
+ }
1241
+ }
1242
+ continue;
1243
+ } else {
1244
+ m = multiply2(e1, e2, v, generateDensityMatrix);
1245
+ }
1246
+
1247
+ if (k == 0 || edge[idx].w.exactlyZero()) {
1248
+ edge[idx] = m;
1249
+ } else if (!m.w.exactlyZero()) {
1250
+ dNode::applyDmChangesToNode(edge[idx].p);
1251
+ dNode::applyDmChangesToNode(m.p);
1252
+ edge[idx] = add2(edge[idx], m, v);
1253
+ dNode::revertDmChangesToNode(m.p);
1254
+ dNode::revertDmChangesToNode(edge[idx].p);
1255
+ }
1256
+ // Undo modifications on density matrices
1257
+ dEdge::revertDmChangesToEdges(e1, e2);
1258
+ } else {
1259
+ auto m = multiply2(e1, e2, v);
1260
+
1261
+ if (k == 0 || edge[idx].w.exactlyZero()) {
1262
+ edge[idx] = m;
1263
+ } else if (!m.w.exactlyZero()) {
1264
+ edge[idx] = add2(edge[idx], m, v);
1265
+ }
1266
+ }
1267
+ }
1268
+ }
1269
+ }
1270
+
1271
+ auto e = makeDDNode(var, edge, generateDensityMatrix);
1272
+ computeTable.insert(x.p, y.p, e);
1273
+
1274
+ e.w = e.w * rWeight;
1275
+ return e;
1276
+ }
1277
+
1278
+ ///
1279
+ /// Inner product, fidelity, expectation value
1280
+ ///
1281
+ public:
1282
+ ComputeTable<vNode*, vNode*, vCachedEdge> vectorInnerProduct{
1283
+ config_.ctVecInnerProdNumBucket};
1284
+
1285
+ /**
1286
+ * @brief Calculates the inner product of two vector decision diagrams.
1287
+ *
1288
+ * @param x A vector DD representing a quantum state.
1289
+ * @param y A vector DD representing a quantum state.
1290
+ * @return A complex number representing the scalar product of the DDs.
1291
+ */
1292
+ ComplexValue innerProduct(const vEdge& x, const vEdge& y);
1293
+
1294
+ /**
1295
+ * @brief Calculates the fidelity between two vector decision diagrams.
1296
+ *
1297
+ * @param x A vector DD representing a quantum state.
1298
+ * @param y A vector DD representing a quantum state.
1299
+ * @return The fidelity between the two quantum states.
1300
+ */
1301
+ fp fidelity(const vEdge& x, const vEdge& y);
1302
+
1303
+ /**
1304
+ * @brief Calculates the fidelity between a vector decision diagram and a
1305
+ * sparse probability vector.
1306
+ *
1307
+ * @details This function computes the fidelity between a quantum state
1308
+ * represented by a vector decision diagram and a sparse probability vector.
1309
+ * The optional permutation of qubits can be provided to match the qubit
1310
+ * ordering.
1311
+ *
1312
+ * @param e The root edge of the decision diagram.
1313
+ * @param probs A map of probabilities for each measurement outcome.
1314
+ * @param permutation An optional permutation of qubits.
1315
+ * @return The fidelity of the measurement outcomes.
1316
+ */
1317
+ static fp
1318
+ fidelityOfMeasurementOutcomes(const vEdge& e, const SparsePVec& probs,
1319
+ const qc::Permutation& permutation = {});
1320
+
1321
+ private:
1322
+ /**
1323
+ * @brief Recursively calculates the inner product of two vector decision
1324
+ * diagrams.
1325
+ *
1326
+ * @param x A vector DD representing a quantum state.
1327
+ * @param y A vector DD representing a quantum state.
1328
+ * @param var The number of levels contained in each vector DD.
1329
+ * @return A complex number representing the scalar product of the DDs.
1330
+ * @note This function is called recursively such that the number of levels
1331
+ * decreases each time to traverse the DDs.
1332
+ */
1333
+ ComplexValue innerProduct(const vEdge& x, const vEdge& y, Qubit var);
1334
+
1335
+ /**
1336
+ * @brief Recursively calculates the fidelity of measurement outcomes.
1337
+ *
1338
+ * @details This function computes the fidelity between a quantum state
1339
+ * represented by a vector decision diagram and a sparse probability vector.
1340
+ * It traverses the decision diagram recursively, calculating the contribution
1341
+ * of each path to the overall fidelity. An optional permutation of qubits can
1342
+ * be provided to match the qubit ordering.
1343
+ *
1344
+ * @param e The root edge of the decision diagram.
1345
+ * @param probs A map of probabilities for each measurement outcome.
1346
+ * @param i The current index in the decision diagram traversal.
1347
+ * @param permutation An optional permutation of qubits.
1348
+ * @param nQubits The number of qubits in the decision diagram.
1349
+ * @return The fidelity of the measurement outcomes.
1350
+ */
1351
+ static fp fidelityOfMeasurementOutcomesRecursive(
1352
+ const vEdge& e, const SparsePVec& probs, std::size_t i,
1353
+ const qc::Permutation& permutation, std::size_t nQubits);
1354
+
1355
+ public:
1356
+ /**
1357
+ * @brief Calculates the expectation value of an operator with respect to a
1358
+ * quantum state.
1359
+ *
1360
+ * @param x A matrix decision diagram (DD) representing the operator.
1361
+ * @param y A vector decision diagram (DD) representing the quantum state.
1362
+ * @return A floating-point value representing the expectation value of the
1363
+ * operator with respect to the quantum state.
1364
+ * @throws std::runtime_error if the edges are not on the same level or if the
1365
+ * expectation value is non-real.
1366
+ *
1367
+ * @details This function calls the multiply() function to apply the operator
1368
+ * to the quantum state, then calls innerProduct() to calculate the overlap
1369
+ * between the original state and the applied state (i.e., <Psi| Psi'> = <Psi|
1370
+ * (Op|Psi>)). It also calls the garbageCollect() function to free up any
1371
+ * unused memory.
1372
+ */
1373
+ fp expectationValue(const mEdge& x, const vEdge& y);
1374
+
1375
+ ///
1376
+ /// Kronecker/tensor product
1377
+ ///
1378
+
1379
+ ComputeTable<vNode*, vNode*, vCachedEdge> vectorKronecker{
1380
+ config_.ctVecKronNumBucket};
1381
+ ComputeTable<mNode*, mNode*, mCachedEdge> matrixKronecker{
1382
+ config_.ctMatKronNumBucket};
1383
+
1384
+ /**
1385
+ * @brief Get the compute table for Kronecker product operations.
1386
+ *
1387
+ * @tparam Node The type of the node.
1388
+ * @return A reference to the appropriate compute table for the given node
1389
+ * type.
1390
+ */
1391
+ template <class Node> [[nodiscard]] auto& getKroneckerComputeTable() {
1392
+ if constexpr (std::is_same_v<Node, vNode>) {
1393
+ return vectorKronecker;
1394
+ } else {
1395
+ return matrixKronecker;
1396
+ }
1397
+ }
1398
+
1399
+ /**
1400
+ * @brief Computes the Kronecker product of two decision diagrams.
1401
+ *
1402
+ * @tparam Node The type of the node.
1403
+ * @param x The first decision diagram.
1404
+ * @param y The second decision diagram.
1405
+ * @param yNumQubits The number of qubits in the second decision diagram.
1406
+ * @param incIdx Whether to increment the index of the nodes in the second
1407
+ * decision diagram.
1408
+ * @return The resulting decision diagram after computing the Kronecker
1409
+ * product.
1410
+ * @throws std::invalid_argument if the node type is `dNode` (density
1411
+ * matrices).
1412
+ */
1413
+ template <class Node>
1414
+ Edge<Node> kronecker(const Edge<Node>& x, const Edge<Node>& y,
1415
+ const std::size_t yNumQubits, const bool incIdx = true) {
1416
+ if constexpr (std::is_same_v<Node, dNode>) {
1417
+ throw std::invalid_argument(
1418
+ "Kronecker is currently not supported for density matrices");
1419
+ }
1420
+
1421
+ const auto e = kronecker2(x, y, yNumQubits, incIdx);
1422
+ return cn.lookup(e);
1423
+ }
1424
+
1425
+ private:
1426
+ /**
1427
+ * @brief Internal function to compute the Kronecker product of two decision
1428
+ * diagrams.
1429
+ *
1430
+ * This function is used internally to compute the Kronecker product of two
1431
+ * decision diagrams (DDs) of type Node. It is not intended to be called
1432
+ * directly.
1433
+ *
1434
+ * @tparam Node The type of the node.
1435
+ * @param x The first decision diagram.
1436
+ * @param y The second decision diagram.
1437
+ * @param yNumQubits The number of qubits in the second decision diagram.
1438
+ * @param incIdx Whether to increment the qubit index.
1439
+ * @return The resulting decision diagram after the Kronecker product.
1440
+ */
1441
+ template <class Node>
1442
+ CachedEdge<Node> kronecker2(const Edge<Node>& x, const Edge<Node>& y,
1443
+ const std::size_t yNumQubits,
1444
+ const bool incIdx = true) {
1445
+ if (x.w.exactlyZero() || y.w.exactlyZero()) {
1446
+ return CachedEdge<Node>::zero();
1447
+ }
1448
+ const auto xWeight = static_cast<ComplexValue>(x.w);
1449
+ if (xWeight.approximatelyZero()) {
1450
+ return CachedEdge<Node>::zero();
1451
+ }
1452
+ const auto yWeight = static_cast<ComplexValue>(y.w);
1453
+ if (yWeight.approximatelyZero()) {
1454
+ return CachedEdge<Node>::zero();
1455
+ }
1456
+ const auto rWeight = xWeight * yWeight;
1457
+ if (rWeight.approximatelyZero()) {
1458
+ return CachedEdge<Node>::zero();
1459
+ }
1460
+
1461
+ if (x.isTerminal() && y.isTerminal()) {
1462
+ return {x.p, rWeight};
1463
+ }
1464
+
1465
+ if constexpr (std::is_same_v<Node, mNode> || std::is_same_v<Node, dNode>) {
1466
+ if (x.isIdentity()) {
1467
+ return {y.p, rWeight};
1468
+ }
1469
+ } else {
1470
+ if (x.isTerminal()) {
1471
+ return {y.p, rWeight};
1472
+ }
1473
+ if (y.isTerminal()) {
1474
+ return {x.p, rWeight};
1475
+ }
1476
+ }
1477
+
1478
+ // check if we already computed the product before and return the result
1479
+ auto& computeTable = getKroneckerComputeTable<Node>();
1480
+ if (const auto* r = computeTable.lookup(x.p, y.p); r != nullptr) {
1481
+ return {r->p, rWeight};
1482
+ }
1483
+
1484
+ constexpr std::size_t n = std::tuple_size_v<decltype(x.p->e)>;
1485
+ std::array<CachedEdge<Node>, n> edge{};
1486
+ for (auto i = 0U; i < n; ++i) {
1487
+ edge[i] = kronecker2(x.p->e[i], y, yNumQubits, incIdx);
1488
+ }
1489
+
1490
+ // Increase the qubit index
1491
+ Qubit idx = x.p->v;
1492
+ if (incIdx) {
1493
+ // use the given number of qubits if y is an identity
1494
+ if constexpr (std::is_same_v<Node, mNode> ||
1495
+ std::is_same_v<Node, dNode>) {
1496
+ if (y.isIdentity()) {
1497
+ idx += static_cast<Qubit>(yNumQubits);
1498
+ } else {
1499
+ idx += static_cast<Qubit>(y.p->v + 1U);
1500
+ }
1501
+ } else {
1502
+ idx += static_cast<Qubit>(y.p->v + 1U);
1503
+ }
1504
+ }
1505
+ auto e = makeDDNode(idx, edge, true);
1506
+ computeTable.insert(x.p, y.p, {e.p, e.w});
1507
+ return {e.p, rWeight};
1508
+ }
1509
+
1510
+ ///
1511
+ /// (Partial) trace
1512
+ ///
1513
+ public:
1514
+ UnaryComputeTable<dNode*, dCachedEdge> densityTrace{
1515
+ config_.ctDmTraceNumBucket};
1516
+ UnaryComputeTable<mNode*, mCachedEdge> matrixTrace{
1517
+ config_.ctMatTraceNumBucket};
1518
+
1519
+ /**
1520
+ * @brief Get the compute table for trace operations.
1521
+ *
1522
+ * @tparam Node The type of the node.
1523
+ * @return A reference to the appropriate compute table for the given node
1524
+ * type.
1525
+ */
1526
+ template <class Node> [[nodiscard]] auto& getTraceComputeTable() {
1527
+ if constexpr (std::is_same_v<Node, mNode>) {
1528
+ return matrixTrace;
1529
+ } else {
1530
+ return densityTrace;
1531
+ }
1532
+ }
1533
+
1534
+ /**
1535
+ * @brief Computes the partial trace of a matrix decision diagram.
1536
+ *
1537
+ * @param a The matrix decision diagram.
1538
+ * @param eliminate A vector of booleans indicating which qubits to trace out.
1539
+ * @return The resulting matrix decision diagram after the partial trace.
1540
+ */
1541
+ mEdge partialTrace(const mEdge& a, const std::vector<bool>& eliminate);
1542
+
1543
+ /**
1544
+ * @brief Computes the trace of a decision diagram.
1545
+ *
1546
+ * @tparam Node The type of the node.
1547
+ * @param a The decision diagram.
1548
+ * @param numQubits The number of qubits in the decision diagram.
1549
+ * @return The trace of the decision diagram as a complex value.
1550
+ */
1551
+ template <class Node>
1552
+ ComplexValue trace(const Edge<Node>& a, const std::size_t numQubits) {
1553
+ if (a.isIdentity()) {
1554
+ return static_cast<ComplexValue>(a.w);
1555
+ }
1556
+ const auto eliminate = std::vector<bool>(numQubits, true);
1557
+ return trace(a, eliminate, numQubits).w;
1558
+ }
1559
+
1560
+ /**
1561
+ * @brief Checks if a given matrix is close to the identity matrix.
1562
+ * @details This function checks if a given matrix is close to the identity
1563
+ * matrix, while ignoring any potential garbage qubits and ignoring the
1564
+ * diagonal weights if `checkCloseToOne` is set to false.
1565
+ * @param m An mEdge that represents the DD of the matrix.
1566
+ * @param tol The accepted tolerance for the edge weights of the DD.
1567
+ * @param garbage A vector of boolean values that defines which qubits are
1568
+ * considered garbage qubits. If it's empty, then no qubit is considered to be
1569
+ * a garbage qubit.
1570
+ * @param checkCloseToOne If false, the function only checks if the matrix is
1571
+ * close to a diagonal matrix.
1572
+ */
1573
+ [[nodiscard]] bool isCloseToIdentity(const mEdge& m, fp tol = 1e-10,
1574
+ const std::vector<bool>& garbage = {},
1575
+ bool checkCloseToOne = true) const;
1576
+
1577
+ private:
1578
+ /**
1579
+ * @brief Computes the normalized (partial) trace using a compute table to
1580
+ * store results for eliminated nodes.
1581
+ * @details At each level, perform a lookup and store results in the compute
1582
+ * table only if all lower-level qubits are eliminated as well.
1583
+ *
1584
+ * This optimization allows the full trace
1585
+ * computation to scale linearly with respect to the number of nodes.
1586
+ * However, the partial trace computation still scales with the number of
1587
+ * paths to the lowest level in the DD that should be traced out.
1588
+ *
1589
+ * For matrices, normalization is continuously applied, dividing by two at
1590
+ * each level marked for elimination, thereby ensuring that the result is
1591
+ * mapped to the interval [0,1] (as opposed to the interval [0,2^N]).
1592
+ *
1593
+ * For density matrices, such normalization is not applied as the trace of
1594
+ * density matrices is always 1 by definition.
1595
+ */
1596
+ template <class Node>
1597
+ CachedEdge<Node> trace(const Edge<Node>& a,
1598
+ const std::vector<bool>& eliminate, std::size_t level,
1599
+ std::size_t alreadyEliminated = 0) {
1600
+ const auto aWeight = static_cast<ComplexValue>(a.w);
1601
+ if (aWeight.approximatelyZero()) {
1602
+ return CachedEdge<Node>::zero();
1603
+ }
1604
+
1605
+ // If `a` is the identity matrix or there is nothing left to eliminate,
1606
+ // then simply return `a`
1607
+ if (a.isIdentity() ||
1608
+ std::none_of(eliminate.begin(),
1609
+ eliminate.begin() +
1610
+ static_cast<std::vector<bool>::difference_type>(level),
1611
+ [](bool v) { return v; })) {
1612
+ return CachedEdge<Node>{a.p, aWeight};
1613
+ }
1614
+
1615
+ const auto v = a.p->v;
1616
+ if (eliminate[v]) {
1617
+ // Lookup nodes marked for elimination in the compute table if all
1618
+ // lower-level qubits are eliminated as well: if the trace has already
1619
+ // been computed, return the result
1620
+ const auto eliminateAll = std::all_of(
1621
+ eliminate.begin(),
1622
+ eliminate.begin() +
1623
+ static_cast<std::vector<bool>::difference_type>(level),
1624
+ [](bool e) { return e; });
1625
+ if (eliminateAll) {
1626
+ if (const auto* r = getTraceComputeTable<Node>().lookup(a.p);
1627
+ r != nullptr) {
1628
+ return {r->p, r->w * aWeight};
1629
+ }
1630
+ }
1631
+
1632
+ const auto elims = alreadyEliminated + 1;
1633
+ auto r = add2(trace(a.p->e[0], eliminate, level - 1, elims),
1634
+ trace(a.p->e[3], eliminate, level - 1, elims), v - 1);
1635
+
1636
+ // The resulting weight is continuously normalized to the range [0,1] for
1637
+ // matrix nodes
1638
+ if constexpr (std::is_same_v<Node, mNode>) {
1639
+ r.w = r.w / 2.0;
1640
+ }
1641
+
1642
+ // Insert result into compute table if all lower-level qubits are
1643
+ // eliminated as well
1644
+ if (eliminateAll) {
1645
+ getTraceComputeTable<Node>().insert(a.p, r);
1646
+ }
1647
+ r.w = r.w * aWeight;
1648
+ return r;
1649
+ }
1650
+
1651
+ std::array<CachedEdge<Node>, NEDGE> edge{};
1652
+ std::transform(a.p->e.cbegin(), a.p->e.cend(), edge.begin(),
1653
+ [this, &eliminate, &alreadyEliminated,
1654
+ &level](const Edge<Node>& e) -> CachedEdge<Node> {
1655
+ return trace(e, eliminate, level - 1, alreadyEliminated);
1656
+ });
1657
+ const auto adjustedV =
1658
+ static_cast<Qubit>(static_cast<std::size_t>(a.p->v) -
1659
+ (static_cast<std::size_t>(std::count(
1660
+ eliminate.begin(), eliminate.end(), true)) -
1661
+ alreadyEliminated));
1662
+ auto r = makeDDNode(adjustedV, edge);
1663
+ r.w = r.w * aWeight;
1664
+ return r;
1665
+ }
1666
+
1667
+ /**
1668
+ * @brief Recursively checks if a given matrix is close to the identity
1669
+ * matrix.
1670
+ *
1671
+ * @param m The matrix edge to check.
1672
+ * @param visited A set of visited nodes to avoid redundant checks.
1673
+ * @param tol The tolerance for comparing edge weights.
1674
+ * @param garbage A vector of boolean values indicating which qubits are
1675
+ * considered garbage.
1676
+ * @param checkCloseToOne A flag to indicate whether to check if diagonal
1677
+ * elements are close to one.
1678
+ * @return True if the matrix is close to the identity matrix, false
1679
+ * otherwise.
1680
+ */
1681
+ static bool isCloseToIdentityRecursive(
1682
+ const mEdge& m, std::unordered_set<decltype(m.p)>& visited, fp tol,
1683
+ const std::vector<bool>& garbage, bool checkCloseToOne);
1684
+
1685
+ public:
1686
+ ///
1687
+ /// Identity matrices
1688
+ ///
1689
+
1690
+ /// Create identity DD represented by the one-terminal.
1691
+ static mEdge makeIdent();
1692
+
1693
+ mEdge createInitialMatrix(const std::vector<bool>& ancillary);
1694
+
1695
+ ///
1696
+ /// Noise Operations
1697
+ ///
1698
+ StochasticNoiseOperationTable<mEdge> stochasticNoiseOperationCache{
1699
+ nqubits, config_.stochasticCacheOps};
1700
+ DensityNoiseTable<dEdge, dEdge> densityNoise{config_.ctDmNoiseNumBucket};
1701
+
1702
+ ///
1703
+ /// Ancillary and garbage reduction
1704
+ ///
1705
+
1706
+ /**
1707
+ * @brief Reduces the decision diagram by handling ancillary qubits.
1708
+ *
1709
+ * @param e The matrix decision diagram edge to be reduced.
1710
+ * @param ancillary A boolean vector indicating which qubits are ancillary
1711
+ * (true) or not (false).
1712
+ * @param regular Flag indicating whether to perform regular (true) or inverse
1713
+ * (false) reduction.
1714
+ * @return The reduced matrix decision diagram edge.
1715
+ *
1716
+ * @details This function modifies the decision diagram to account for
1717
+ * ancillary qubits by:
1718
+ * 1. Early returning if there are no ancillary qubits or if the edge is zero
1719
+ * 2. Special handling for identity matrices by creating appropriate zero
1720
+ * nodes
1721
+ * 3. Finding the lowest ancillary qubit as a starting point
1722
+ * 4. Recursively reducing nodes starting from the lowest ancillary qubit
1723
+ * 5. Adding zero nodes for any remaining higher ancillary qubits
1724
+ *
1725
+ * The function maintains proper reference counting by incrementing the
1726
+ * reference count of the result and decrementing the reference count of the
1727
+ * input edge.
1728
+ */
1729
+ mEdge reduceAncillae(mEdge e, const std::vector<bool>& ancillary,
1730
+ bool regular = true);
1731
+
1732
+ /**
1733
+ * @brief Reduces the given decision diagram by summing entries for garbage
1734
+ * qubits.
1735
+ *
1736
+ * For each garbage qubit q, this function sums all the entries for q = 0 and
1737
+ * q = 1, setting the entry for q = 0 to the sum and the entry for q = 1 to
1738
+ * zero. To ensure that the probabilities of the resulting state are the sum
1739
+ * of the probabilities of the initial state, the function computes
1740
+ * `sqrt(|a|^2 + |b|^2)` for two entries `a` and `b`.
1741
+ *
1742
+ * @param e DD representation of the matrix/vector.
1743
+ * @param garbage Vector that describes which qubits are garbage and which
1744
+ * ones are not. If garbage[i] = true, then qubit q_i is considered garbage.
1745
+ * @param normalizeWeights By default set to `false`. If set to `true`, the
1746
+ * function changes all weights in the DD to their magnitude, also for
1747
+ * non-garbage qubits. This is used for checking
1748
+ * partial equivalence of circuits. For partial equivalence, only the
1749
+ * measurement probabilities are considered, so we
1750
+ * need to consider only the magnitudes of each entry.
1751
+ * @return DD representing the reduced matrix/vector.
1752
+ */
1753
+ vEdge reduceGarbage(vEdge& e, const std::vector<bool>& garbage,
1754
+ bool normalizeWeights = false);
1755
+
1756
+ /**
1757
+ * @brief Reduces garbage qubits in a matrix decision diagram.
1758
+ *
1759
+ * @param e The matrix decision diagram edge to be reduced.
1760
+ * @param garbage A boolean vector indicating which qubits are garbage (true)
1761
+ * or not (false).
1762
+ * @param regular Flag indicating whether to apply regular (true) or inverse
1763
+ * (false) reduction. In regular mode, garbage entries are summed in the first
1764
+ * two components, in inverse mode, they are summed in the first and third
1765
+ * components.
1766
+ * @param normalizeWeights Flag indicating whether to normalize weights to
1767
+ * their magnitudes. When true, all weights in the DD are changed to their
1768
+ * magnitude, also for non-garbage qubits. This is used for checking partial
1769
+ * equivalence where only measurement probabilities matter.
1770
+ * @return The reduced matrix decision diagram edge.
1771
+ *
1772
+ * @details For each garbage qubit q, this function sums all the entries for
1773
+ * q=0 and q=1, setting the entry for q=0 to the sum and the entry for q=1 to
1774
+ * zero. To maintain proper probabilities, the function computes sqrt(|a|^2 +
1775
+ * |b|^2) for two entries a and b. The function handles special cases like
1776
+ * zero terminals and identity matrices separately and maintains proper
1777
+ * reference counting throughout the reduction process.
1778
+ */
1779
+ mEdge reduceGarbage(const mEdge& e, const std::vector<bool>& garbage,
1780
+ bool regular = true, bool normalizeWeights = false);
1781
+
1782
+ private:
1783
+ mCachedEdge reduceAncillaeRecursion(mNode* p,
1784
+ const std::vector<bool>& ancillary,
1785
+ Qubit lowerbound, bool regular = true);
1786
+
1787
+ vCachedEdge reduceGarbageRecursion(vNode* p, const std::vector<bool>& garbage,
1788
+ Qubit lowerbound,
1789
+ bool normalizeWeights = false);
1790
+ mCachedEdge reduceGarbageRecursion(mNode* p, const std::vector<bool>& garbage,
1791
+ Qubit lowerbound, bool regular = true,
1792
+ bool normalizeWeights = false);
1793
+
1794
+ ///
1795
+ /// Vector and matrix extraction from DDs
1796
+ ///
1797
+ public:
1798
+ /// transfers a decision diagram from another package to this package
1799
+ template <class Node> Edge<Node> transfer(Edge<Node>& original) {
1800
+ if (original.isTerminal()) {
1801
+ return {original.p, cn.lookup(original.w)};
1802
+ }
1803
+
1804
+ // POST ORDER TRAVERSAL USING ONE STACK
1805
+ // https://www.geeksforgeeks.org/iterative-postorder-traversal-using-stack/
1806
+ Edge<Node> root{};
1807
+ std::stack<Edge<Node>*> stack;
1808
+
1809
+ std::unordered_map<decltype(original.p), decltype(original.p)> mappedNode{};
1810
+
1811
+ Edge<Node>* currentEdge = &original;
1812
+ constexpr std::size_t n = std::tuple_size_v<decltype(original.p->e)>;
1813
+ // NOLINTNEXTLINE(cppcoreguidelines-avoid-do-while)
1814
+ do {
1815
+ while (currentEdge != nullptr && !currentEdge->isTerminal()) {
1816
+ for (std::size_t i = n - 1; i > 0; --i) {
1817
+ auto& edge = currentEdge->p->e[i];
1818
+ if (edge.isTerminal()) {
1819
+ continue;
1820
+ }
1821
+ if (edge.w.approximatelyZero()) {
1822
+ continue;
1823
+ }
1824
+ if (mappedNode.contains(edge.p)) {
1825
+ continue;
1826
+ }
1827
+
1828
+ // non-zero edge to be included
1829
+ stack.push(&edge);
1830
+ }
1831
+ stack.push(currentEdge);
1832
+ currentEdge = &currentEdge->p->e[0];
1833
+ }
1834
+ currentEdge = stack.top();
1835
+ stack.pop();
1836
+
1837
+ bool hasChild = false;
1838
+ for (std::size_t i = 1; i < n && !hasChild; ++i) {
1839
+ auto& edge = currentEdge->p->e[i];
1840
+ if (edge.w.approximatelyZero()) {
1841
+ continue;
1842
+ }
1843
+ if (mappedNode.contains(edge.p)) {
1844
+ continue;
1845
+ }
1846
+ hasChild = edge.p == stack.top()->p;
1847
+ }
1848
+
1849
+ if (hasChild) {
1850
+ Edge<Node>* temp = stack.top();
1851
+ stack.pop();
1852
+ stack.push(currentEdge);
1853
+ currentEdge = temp;
1854
+ } else {
1855
+ if (mappedNode.contains(currentEdge->p)) {
1856
+ currentEdge = nullptr;
1857
+ continue;
1858
+ }
1859
+ std::array<Edge<Node>, n> edges{};
1860
+ for (std::size_t i = 0; i < n; i++) {
1861
+ if (currentEdge->p->e[i].isTerminal()) {
1862
+ edges[i].p = currentEdge->p->e[i].p;
1863
+ } else {
1864
+ edges[i].p = mappedNode[currentEdge->p->e[i].p];
1865
+ }
1866
+ edges[i].w = cn.lookup(currentEdge->p->e[i].w);
1867
+ }
1868
+ root = makeDDNode(currentEdge->p->v, edges);
1869
+ mappedNode[currentEdge->p] = root.p;
1870
+ currentEdge = nullptr;
1871
+ }
1872
+ } while (!stack.empty());
1873
+ root.w = cn.lookup(original.w * root.w);
1874
+ return root;
1875
+ }
1876
+
1877
+ ///
1878
+ /// Deserialization
1879
+ /// Note: do not rely on the binary format being portable across different
1880
+ /// architectures/platforms
1881
+ ///
1882
+
1883
+ template <class Node, class Edge = Edge<Node>,
1884
+ std::size_t N = std::tuple_size_v<decltype(Node::e)>>
1885
+ Edge deserialize(std::istream& is, const bool readBinary = false) {
1886
+ auto result = CachedEdge<Node>{};
1887
+ ComplexValue rootweight{};
1888
+
1889
+ std::unordered_map<std::int64_t, Node*> nodes{};
1890
+ std::int64_t nodeIndex{};
1891
+ Qubit v{};
1892
+ std::array<ComplexValue, N> edgeWeights{};
1893
+ std::array<std::int64_t, N> edgeIndices{};
1894
+ edgeIndices.fill(-2);
1895
+
1896
+ if (readBinary) {
1897
+ std::remove_const_t<decltype(SERIALIZATION_VERSION)> version{};
1898
+ is.read(reinterpret_cast<char*>(&version),
1899
+ sizeof(decltype(SERIALIZATION_VERSION)));
1900
+ if (version != SERIALIZATION_VERSION) {
1901
+ throw std::runtime_error(
1902
+ "Wrong Version of serialization file version. version of file: " +
1903
+ std::to_string(version) +
1904
+ "; current version: " + std::to_string(SERIALIZATION_VERSION));
1905
+ }
1906
+
1907
+ if (!is.eof()) {
1908
+ rootweight.readBinary(is);
1909
+ }
1910
+
1911
+ while (is.read(reinterpret_cast<char*>(&nodeIndex),
1912
+ sizeof(decltype(nodeIndex)))) {
1913
+ is.read(reinterpret_cast<char*>(&v), sizeof(decltype(v)));
1914
+ for (std::size_t i = 0U; i < N; i++) {
1915
+ is.read(reinterpret_cast<char*>(&edgeIndices[i]),
1916
+ sizeof(decltype(edgeIndices[i])));
1917
+ edgeWeights[i].readBinary(is);
1918
+ }
1919
+ result = deserializeNode(nodeIndex, v, edgeIndices, edgeWeights, nodes);
1920
+ }
1921
+ } else {
1922
+ std::string version;
1923
+ std::getline(is, version);
1924
+ if (std::stoi(version) != SERIALIZATION_VERSION) {
1925
+ throw std::runtime_error(
1926
+ "Wrong Version of serialization file version. version of file: " +
1927
+ version +
1928
+ "; current version: " + std::to_string(SERIALIZATION_VERSION));
1929
+ }
1930
+
1931
+ const std::string complexRealRegex =
1932
+ R"(([+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?(?![ \d\.]*(?:[eE][+-])?\d*[iI]))?)";
1933
+ const std::string complexImagRegex =
1934
+ R"(( ?[+-]? ?(?:(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?)?[iI])?)";
1935
+ const std::string edgeRegex =
1936
+ " \\(((-?\\d+) (" + complexRealRegex + complexImagRegex + "))?\\)";
1937
+ const std::regex complexWeightRegex(complexRealRegex + complexImagRegex);
1938
+
1939
+ std::string lineConstruct = "(\\d+) (\\d+)";
1940
+ for (std::size_t i = 0U; i < N; ++i) {
1941
+ lineConstruct += "(?:" + edgeRegex + ")";
1942
+ }
1943
+ lineConstruct += " *(?:#.*)?";
1944
+ const std::regex lineRegex(lineConstruct);
1945
+ std::smatch m;
1946
+
1947
+ std::string line;
1948
+ if (std::getline(is, line)) {
1949
+ if (!std::regex_match(line, m, complexWeightRegex)) {
1950
+ throw std::runtime_error("Regex did not match second line: " + line);
1951
+ }
1952
+ rootweight.fromString(m.str(1), m.str(2));
1953
+ }
1954
+
1955
+ while (std::getline(is, line)) {
1956
+ if (line.empty() || line.size() == 1) {
1957
+ continue;
1958
+ }
1959
+
1960
+ if (!std::regex_match(line, m, lineRegex)) {
1961
+ throw std::runtime_error("Regex did not match line: " + line);
1962
+ }
1963
+
1964
+ // match 1: node_idx
1965
+ // match 2: qubit_idx
1966
+
1967
+ // repeats for every edge
1968
+ // match 3: edge content
1969
+ // match 4: edge_target_idx
1970
+ // match 5: real + imag (without i)
1971
+ // match 6: real
1972
+ // match 7: imag (without i)
1973
+ nodeIndex = std::stoi(m.str(1));
1974
+ v = static_cast<Qubit>(std::stoi(m.str(2)));
1975
+
1976
+ for (auto edgeIdx = 3U, i = 0U; i < N; i++, edgeIdx += 5) {
1977
+ if (m.str(edgeIdx).empty()) {
1978
+ continue;
1979
+ }
1980
+
1981
+ edgeIndices[i] = std::stoi(m.str(edgeIdx + 1));
1982
+ edgeWeights[i].fromString(m.str(edgeIdx + 3), m.str(edgeIdx + 4));
1983
+ }
1984
+
1985
+ result = deserializeNode(nodeIndex, v, edgeIndices, edgeWeights, nodes);
1986
+ }
1987
+ }
1988
+ return {result.p, cn.lookup(result.w * rootweight)};
1989
+ }
1990
+
1991
+ template <class Node, class Edge = Edge<Node>>
1992
+ Edge deserialize(const std::string& inputFilename, const bool readBinary) {
1993
+ auto ifs = std::ifstream(inputFilename, std::ios::binary);
1994
+
1995
+ if (!ifs.good()) {
1996
+ throw std::invalid_argument("Cannot open serialized file: " +
1997
+ inputFilename);
1998
+ }
1999
+
2000
+ return deserialize<Node>(ifs, readBinary);
2001
+ }
2002
+
2003
+ private:
2004
+ template <class Node, std::size_t N = std::tuple_size_v<decltype(Node::e)>>
2005
+ CachedEdge<Node>
2006
+ deserializeNode(const std::int64_t index, const Qubit v,
2007
+ std::array<std::int64_t, N>& edgeIdx,
2008
+ const std::array<ComplexValue, N>& edgeWeight,
2009
+ std::unordered_map<std::int64_t, Node*>& nodes) {
2010
+ if (index == -1) {
2011
+ return CachedEdge<Node>::zero();
2012
+ }
2013
+
2014
+ std::array<CachedEdge<Node>, N> edges{};
2015
+ for (auto i = 0U; i < N; ++i) {
2016
+ if (edgeIdx[i] == -2) {
2017
+ edges[i] = CachedEdge<Node>::zero();
2018
+ } else {
2019
+ if (edgeIdx[i] == -1) {
2020
+ edges[i] = CachedEdge<Node>::one();
2021
+ } else {
2022
+ edges[i].p = nodes[edgeIdx[i]];
2023
+ }
2024
+ edges[i].w = edgeWeight[i];
2025
+ }
2026
+ }
2027
+ // reset
2028
+ edgeIdx.fill(-2);
2029
+
2030
+ auto r = makeDDNode(v, edges);
2031
+ nodes[index] = r.p;
2032
+ return r;
2033
+ }
2034
+ };
2035
+
2036
+ } // namespace dd