dense-evolution 8.2.2__tar.gz → 8.3.0__tar.gz

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 (169) hide show
  1. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/PKG-INFO +11 -3
  2. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/README.md +8 -0
  3. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/__init__.py +6 -2
  4. dense_evolution-8.3.0/dense_evolution/native_hf/libcint_bridge.py +167 -0
  5. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/physics/__init__.py +5 -1
  6. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/physics/qec.py +359 -0
  7. dense_evolution-8.3.0/dense_evolution/protocols/__init__.py +27 -0
  8. dense_evolution-8.3.0/dense_evolution/protocols/bb84.py +133 -0
  9. dense_evolution-8.3.0/dense_evolution/protocols/di_qkd_ghz.py +199 -0
  10. dense_evolution-8.3.0/dense_evolution/protocols/dicka_protocol2.py +124 -0
  11. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/qec.py +5 -2
  12. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution.egg-info/PKG-INFO +11 -3
  13. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution.egg-info/SOURCES.txt +4 -0
  14. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution.egg-info/requires.txt +2 -3
  15. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/pyproject.toml +6 -12
  16. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/dashboard/core/hamiltonians.py +21 -4
  17. dense_evolution-8.2.2/dense_evolution/native_hf/libcint_bridge.py +0 -225
  18. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/autodiff.py +0 -0
  19. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/backends/__init__.py +0 -0
  20. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/backends/chunk/__init__.py +0 -0
  21. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/backends/chunk/_engine_imports.py +0 -0
  22. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/backends/chunk/circuit_chunker.py +0 -0
  23. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/backends/chunk/core.py +0 -0
  24. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/backends/chunk/disk_overflow.py +0 -0
  25. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/backends/chunk/geometry.py +0 -0
  26. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/backends/chunk/guard.py +0 -0
  27. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/backends/chunk/kernels.py +0 -0
  28. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/backends/mps.py +0 -0
  29. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/backends/statevector.py +0 -0
  30. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/chunk.py +0 -0
  31. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/circuits/__init__.py +0 -0
  32. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/circuits/compiler.py +0 -0
  33. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/circuits/diagram.py +0 -0
  34. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/circuits/gates.py +0 -0
  35. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/circuits/parser.py +0 -0
  36. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/circuits/qft.py +0 -0
  37. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/circuits/random_circuit.py +0 -0
  38. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/circuits/registry.py +0 -0
  39. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/circuits/topology.py +0 -0
  40. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/circuits/trotter.py +0 -0
  41. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/circuits/uccsd.py +0 -0
  42. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/cli.py +0 -0
  43. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/compiler.py +0 -0
  44. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/config.py +0 -0
  45. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/drawing.py +0 -0
  46. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/entropy.py +0 -0
  47. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/fermions.py +0 -0
  48. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/gates.py +0 -0
  49. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/harrison_tb.py +0 -0
  50. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/healing.py +0 -0
  51. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/interop/__init__.py +0 -0
  52. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/interop/qiskit_pennylane.py +0 -0
  53. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/measurement.py +0 -0
  54. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/mitigation/__init__.py +0 -0
  55. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/mitigation/healing.py +0 -0
  56. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/mitigation/kl_divergence.py +0 -0
  57. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/mitigation/magic_entropy.py +0 -0
  58. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/mitigation/magic_entropy_shadows.py +0 -0
  59. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/mitigation/renyi.py +0 -0
  60. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/mitigation/stabilizer_renyi_entropy.py +0 -0
  61. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/mitigation/zne.py +0 -0
  62. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/mps.py +0 -0
  63. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/native_hf/__init__.py +0 -0
  64. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/native_hf/assembly.py +0 -0
  65. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/native_hf/basis.py +0 -0
  66. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/native_hf/boys.py +0 -0
  67. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/native_hf/bridge.py +0 -0
  68. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/native_hf/cartesian.py +0 -0
  69. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/native_hf/coulomb.py +0 -0
  70. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/native_hf/differentiable.py +0 -0
  71. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/native_hf/gaussians.py +0 -0
  72. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/native_hf/kinetic.py +0 -0
  73. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/native_hf/overlap.py +0 -0
  74. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/native_hf/scf.py +0 -0
  75. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/noise/__init__.py +0 -0
  76. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/noise/coherent_attack.py +0 -0
  77. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/noise/cosmic_ray.py +0 -0
  78. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/noise/density_matrix_channels.py +0 -0
  79. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/noise/differentiable.py +0 -0
  80. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/noise/kraus/__init__.py +0 -0
  81. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/noise/kraus/amplitude_damping.py +0 -0
  82. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/noise/kraus/bitflip.py +0 -0
  83. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/noise/kraus/combined.py +0 -0
  84. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/noise/kraus/depolarizing.py +0 -0
  85. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/noise/kraus/ideal.py +0 -0
  86. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/noise/kraus/phaseflip.py +0 -0
  87. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/noise/kraus_channels.py +0 -0
  88. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/noise/oscillating.py +0 -0
  89. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/noise/pink.py +0 -0
  90. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/observables.py +0 -0
  91. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/parser.py +0 -0
  92. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/physics/entropy.py +0 -0
  93. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/physics/fermions.py +0 -0
  94. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/physics/observables.py +0 -0
  95. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/physics/spectral.py +0 -0
  96. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/physics/states.py +0 -0
  97. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/qft.py +0 -0
  98. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/qmmm/__init__.py +0 -0
  99. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/qmmm/ase_bridge.py +0 -0
  100. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/qmmm/forces.py +0 -0
  101. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/qmmm/propagation.py +0 -0
  102. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/qmmm/region.py +0 -0
  103. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/random_circuit.py +0 -0
  104. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/registry.py +0 -0
  105. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/simulator.py +0 -0
  106. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/solvers/__init__.py +0 -0
  107. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/solvers/autodiff.py +0 -0
  108. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/solvers/harrison_tb.py +0 -0
  109. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/solvers/vhd_tb.py +0 -0
  110. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/states.py +0 -0
  111. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/topology.py +0 -0
  112. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/trotter.py +0 -0
  113. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/utils/__init__.py +0 -0
  114. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/utils/drawing.py +0 -0
  115. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/utils/mass_decomposition.py +0 -0
  116. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/utils/measurement.py +0 -0
  117. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution/vhd_tb.py +0 -0
  118. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution.egg-info/dependency_links.txt +0 -0
  119. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution.egg-info/entry_points.txt +0 -0
  120. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/dense_evolution.egg-info/top_level.txt +0 -0
  121. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/license.md +0 -0
  122. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/research/local_site/__init__.py +0 -0
  123. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/research/local_site/app/__init__.py +0 -0
  124. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/research/local_site/app/server.py +0 -0
  125. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/setup.cfg +0 -0
  126. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/dashboard/core/__init__.py +0 -0
  127. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/dashboard/core/_gate_tables.py +0 -0
  128. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/dashboard/core/band_structure.py +0 -0
  129. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/dashboard/core/circuit_builder_component.py +0 -0
  130. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/dashboard/core/circuit_diagram.py +0 -0
  131. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/dashboard/core/crypto_protocols.py +0 -0
  132. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/dashboard/core/engine.py +0 -0
  133. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/dashboard/core/graphical_builder.py +0 -0
  134. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/dashboard/core/mass_decomposition_tool.py +0 -0
  135. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/dashboard/core/mitigation.py +0 -0
  136. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/dashboard/core/native_hf_diagnostics.py +0 -0
  137. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/dashboard/core/noise_tools.py +0 -0
  138. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/dashboard/core/qasm_library.py +0 -0
  139. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/dashboard/core/qmmm.py +0 -0
  140. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/dashboard/core/rag_tool.py +0 -0
  141. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/dashboard/core/state_visuals.py +0 -0
  142. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/dashboard/core/system_limits.py +0 -0
  143. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/dashboard/core/vector_healing.py +0 -0
  144. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/dashboard/core/visuals.py +0 -0
  145. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/dashboard/core/vqe.py +0 -0
  146. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/dashboard/core/wormhole.py +0 -0
  147. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/ia_utils/__init__.py +0 -0
  148. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/ia_utils/adversarial_vector_attack.py +0 -0
  149. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/ia_utils/rag.py +0 -0
  150. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/ia_utils/vector_healing.py +0 -0
  151. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/mcp_server/__init__.py +0 -0
  152. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/mcp_server/client.py +0 -0
  153. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/mcp_server/config.py +0 -0
  154. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/mcp_server/models.py +0 -0
  155. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/mcp_server/molecules.py +0 -0
  156. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/mcp_server/server.py +0 -0
  157. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/mcp_server/tools/__init__.py +0 -0
  158. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/mcp_server/tools/chemistry_tools.py +0 -0
  159. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/mcp_server/tools/circuit_tools.py +0 -0
  160. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/mcp_server/tools/crypto_tools.py +0 -0
  161. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/mcp_server/tools/mitigation_tools.py +0 -0
  162. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/mcp_server/tools/noise_tools.py +0 -0
  163. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/mcp_server/tools/retrieval_tools.py +0 -0
  164. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/mcp_server/tools/system_tools.py +0 -0
  165. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/mcp_server/tools/wormhole_tools.py +0 -0
  166. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/mcp_server/utils/__init__.py +0 -0
  167. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/mcp_server/utils/cache.py +0 -0
  168. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/mcp_server/utils/images.py +0 -0
  169. {dense_evolution-8.2.2 → dense_evolution-8.3.0}/tools/mcp_server/utils/truncation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dense-evolution
3
- Version: 8.2.2
3
+ Version: 8.3.0
4
4
  Summary: High-performance quantum simulation toolkit -- Statevector/MPS engines with JIT compilation, noise modeling, VQE, QEC, quantum chemistry, and agent-native tooling
5
5
  Author-email: Salvatore Pennacchio <jtatopenn@libero.it>
6
6
  License: Business Source License 1.1
@@ -39,6 +39,8 @@ Requires-Dist: streamlit>=1.30.0; extra == "dashboard"
39
39
  Requires-Dist: qiskit>=1.0.0; extra == "dashboard"
40
40
  Requires-Dist: pylatexenc>=2.10; extra == "dashboard"
41
41
  Requires-Dist: plotly>=5.0.0; extra == "dashboard"
42
+ Requires-Dist: rdkit>=2023.9.1; extra == "dashboard"
43
+ Requires-Dist: basis_set_exchange>=0.9; extra == "dashboard"
42
44
  Provides-Extra: qiskit
43
45
  Requires-Dist: qiskit>=1.0.0; extra == "qiskit"
44
46
  Provides-Extra: pennylane
@@ -58,8 +60,6 @@ Provides-Extra: mcp
58
60
  Requires-Dist: mcp>=2.0.0; extra == "mcp"
59
61
  Requires-Dist: httpx>=0.27; extra == "mcp"
60
62
  Requires-Dist: pydantic>=2.0.0; extra == "mcp"
61
- Provides-Extra: libcint
62
- Requires-Dist: pyscf>=2.0.0; extra == "libcint"
63
63
  Provides-Extra: qmmm
64
64
  Requires-Dist: rdkit>=2023.9.1; extra == "qmmm"
65
65
  Provides-Extra: ase
@@ -108,6 +108,13 @@ Dynamic: license-file
108
108
  **A high-performance quantum simulation toolkit
109
109
  Statevector/MPS engines with compilation, noise, VQE, QEC, chemistry, and agent-native tooling.**
110
110
 
111
+ <p align="center">
112
+ <img src="docs/assets/favicon.svg" width="32" alt=""><br>
113
+ <b>Download the Dense Evolution Dashboard (Streamlit)</b><br>
114
+ <a href="https://github.com/tatopenn-cell/Dense-Evolution/raw/main/tools/installer/install-dashboard.bat">Windows (.bat)</a> &middot;
115
+ <a href="https://github.com/tatopenn-cell/Dense-Evolution/raw/main/tools/installer/install-dashboard.sh">macOS / Linux (.sh)</a>
116
+ </p>
117
+
111
118
  [![CI](https://github.com/tatopenn-cell/Dense-Evolution/actions/workflows/ci.yml/badge.svg)](https://github.com/tatopenn-cell/Dense-Evolution/actions/workflows/ci.yml)
112
119
  [![Docs](https://img.shields.io/badge/docs-tatopenn--cell.github.io-00e5ff?style=flat-square)](https://tatopenn-cell.github.io/Dense-Evolution/)
113
120
  [![codecov](https://codecov.io/gh/tatopenn-cell/Dense-Evolution/branch/main/graph/badge.svg)](https://codecov.io/gh/tatopenn-cell/Dense-Evolution)
@@ -295,6 +302,7 @@ sim.run_chunk(circuit.to_tuples(), chunk_size_gates=500)
295
302
  - **Anti-OOM `Chunk` engine.** Circuits too large for one array, split dynamically and sized off the real compute device's own free memory — with disk-backed overflow past even that ceiling. [Chunk guide](https://tatopenn-cell.github.io/Dense-Evolution/api/chunk/).
296
303
  - **Real noise, real mitigation.** Stochastic Kraus channels, real-device noise imported from Qiskit backends, and Zero-Noise Extrapolation to correct for it. [Noise](https://tatopenn-cell.github.io/Dense-Evolution/api/noise/) · [Mitigation](https://tatopenn-cell.github.io/Dense-Evolution/api/mitigation/) · [what noise/mitigation/healing each mean](https://tatopenn-cell.github.io/Dense-Evolution/concepts/).
297
304
  - **Differentiable VQE, from scratch.** `circuit_to_energy_fn` is the same JAX-differentiable engine real molecular VQE runs on — real Hartree-Fock Hamiltonians, UCCSD/hardware-efficient ansätze, Adam optimization. [Autodiff](https://tatopenn-cell.github.io/Dense-Evolution/api/autodiff/).
305
+ - **libcint integrals on Windows, macOS and Linux.** `pip install dense-evolution` ships libcint compiled for each system: the full two-electron tensor of ethanol in 6-31G* in 0.32 s. [native_hf](https://tatopenn-cell.github.io/Dense-Evolution/api/native_hf/).
298
306
  - **OpenQASM 2.0/3.0, both directions.** A real parser, plus Qiskit/PennyLane interop bridges. [QASM Parser](https://tatopenn-cell.github.io/Dense-Evolution/api/parser/) · [Interop](https://tatopenn-cell.github.io/Dense-Evolution/api/interop/).
299
307
  - **Code-agnostic QEC decoding**, Majorana/Jordan-Wigner fermion mapping, from-scratch Hartree-Fock for elements outside PennyLane's own basis set, a traversable-wormhole-inspired teleportation protocol, and real QM/MM region partitioning (ring-safe BFS, Diffuse2Seg-derived relevance propagation) — see the [full API reference](https://tatopenn-cell.github.io/Dense-Evolution/api/) for all of it.
300
308
 
@@ -8,6 +8,13 @@
8
8
  **A high-performance quantum simulation toolkit
9
9
  Statevector/MPS engines with compilation, noise, VQE, QEC, chemistry, and agent-native tooling.**
10
10
 
11
+ <p align="center">
12
+ <img src="docs/assets/favicon.svg" width="32" alt=""><br>
13
+ <b>Download the Dense Evolution Dashboard (Streamlit)</b><br>
14
+ <a href="https://github.com/tatopenn-cell/Dense-Evolution/raw/main/tools/installer/install-dashboard.bat">Windows (.bat)</a> &middot;
15
+ <a href="https://github.com/tatopenn-cell/Dense-Evolution/raw/main/tools/installer/install-dashboard.sh">macOS / Linux (.sh)</a>
16
+ </p>
17
+
11
18
  [![CI](https://github.com/tatopenn-cell/Dense-Evolution/actions/workflows/ci.yml/badge.svg)](https://github.com/tatopenn-cell/Dense-Evolution/actions/workflows/ci.yml)
12
19
  [![Docs](https://img.shields.io/badge/docs-tatopenn--cell.github.io-00e5ff?style=flat-square)](https://tatopenn-cell.github.io/Dense-Evolution/)
13
20
  [![codecov](https://codecov.io/gh/tatopenn-cell/Dense-Evolution/branch/main/graph/badge.svg)](https://codecov.io/gh/tatopenn-cell/Dense-Evolution)
@@ -195,6 +202,7 @@ sim.run_chunk(circuit.to_tuples(), chunk_size_gates=500)
195
202
  - **Anti-OOM `Chunk` engine.** Circuits too large for one array, split dynamically and sized off the real compute device's own free memory — with disk-backed overflow past even that ceiling. [Chunk guide](https://tatopenn-cell.github.io/Dense-Evolution/api/chunk/).
196
203
  - **Real noise, real mitigation.** Stochastic Kraus channels, real-device noise imported from Qiskit backends, and Zero-Noise Extrapolation to correct for it. [Noise](https://tatopenn-cell.github.io/Dense-Evolution/api/noise/) · [Mitigation](https://tatopenn-cell.github.io/Dense-Evolution/api/mitigation/) · [what noise/mitigation/healing each mean](https://tatopenn-cell.github.io/Dense-Evolution/concepts/).
197
204
  - **Differentiable VQE, from scratch.** `circuit_to_energy_fn` is the same JAX-differentiable engine real molecular VQE runs on — real Hartree-Fock Hamiltonians, UCCSD/hardware-efficient ansätze, Adam optimization. [Autodiff](https://tatopenn-cell.github.io/Dense-Evolution/api/autodiff/).
205
+ - **libcint integrals on Windows, macOS and Linux.** `pip install dense-evolution` ships libcint compiled for each system: the full two-electron tensor of ethanol in 6-31G* in 0.32 s. [native_hf](https://tatopenn-cell.github.io/Dense-Evolution/api/native_hf/).
198
206
  - **OpenQASM 2.0/3.0, both directions.** A real parser, plus Qiskit/PennyLane interop bridges. [QASM Parser](https://tatopenn-cell.github.io/Dense-Evolution/api/parser/) · [Interop](https://tatopenn-cell.github.io/Dense-Evolution/api/interop/).
199
207
  - **Code-agnostic QEC decoding**, Majorana/Jordan-Wigner fermion mapping, from-scratch Hartree-Fock for elements outside PennyLane's own basis set, a traversable-wormhole-inspired teleportation protocol, and real QM/MM region partitioning (ring-safe BFS, Diffuse2Seg-derived relevance propagation) — see the [full API reference](https://tatopenn-cell.github.io/Dense-Evolution/api/) for all of it.
200
208
 
@@ -58,9 +58,11 @@ from .circuits.trotter import (pauli_rotation_ops, trotter_evolve_ops, continuou
58
58
  from .circuits.uccsd import find_excitations, single_excitation_ops, double_excitation_ops
59
59
  from .physics.qec import (pauli_commutes, compute_syndrome, erasure_aware_decode, pymatching_decode,
60
60
  blind_minimum_weight_decode, decode_with_erasure_fallback,
61
- counts_in_intervals_dimension, nearest_coset_decode)
61
+ counts_in_intervals_dimension, nearest_coset_decode, erasure_ml_decode,
62
+ peeling_decode, union_find_decode, matching_erasure_decode,
63
+ estimate_edge_probabilities_from_detection_events)
62
64
 
63
- __version__ = "8.2.2"
65
+ __version__ = "8.3.0"
64
66
 
65
67
  __all__ = [
66
68
  "__version__",
@@ -105,6 +107,8 @@ __all__ = [
105
107
  "majorana_pauli_terms", "total_parity_operator", "hubbard_hamiltonian_pauli_terms", "square_lattice_edges",
106
108
  "pauli_commutes", "compute_syndrome", "erasure_aware_decode", "pymatching_decode", "blind_minimum_weight_decode",
107
109
  "decode_with_erasure_fallback", "counts_in_intervals_dimension", "nearest_coset_decode",
110
+ "erasure_ml_decode", "peeling_decode", "union_find_decode", "matching_erasure_decode",
111
+ "estimate_edge_probabilities_from_detection_events",
108
112
  # Utils -- drawing, measurement, random circuits
109
113
  "draw_circuit", "plot_circuit", "sample_counts", "statevector_fidelity", "random_circuit",
110
114
  ]
@@ -0,0 +1,167 @@
1
+ """libcint bridge for native_hf's one- and two-electron integrals.
2
+
3
+ native_hf's own build_repulsion_tensor (assembly.py) computes the ERI
4
+ tensor via JAX-jitted Obara-Saika recursions -- correct, differentiable,
5
+ but pays a JIT-compilation tax on mixed-angular-momentum bases (~532s on
6
+ Ne/6-31G* even after the primitive-count-padding fix), and its one-electron
7
+ assembly ran out of memory during XLA compilation for a 4-heavy-atom
8
+ molecule at 6-31G* on a Kaggle CPU kernel. libcint (Sun, J. Comput. Chem.
9
+ 36, 1664 (2015), BSD-2) computes the identical integrals with C kernels
10
+ compiled once, ahead of time.
11
+
12
+ libcint is linked statically, together with csrc/cint_driver.c, into one
13
+ shared library shipped inside the platform wheels of dense-evolution
14
+ (dense_evolution/native_hf/_libcint/, built by
15
+ .github/scripts/build-libcint.sh in .github/workflows/libcint.yml) and
16
+ called through ctypes, with the atm/bas/env arrays built here from
17
+ native_hf's own ContractedShell list. The driver loops over shell blocks
18
+ in C (8-fold ERI symmetry, int2e_optimizer) and writes every integral
19
+ already rescaled and in native_hf's AO order. A source install has no
20
+ bundled library: run build-libcint.sh and point DENSE_EVOLUTION_LIBCINT at
21
+ the resulting file.
22
+
23
+ Shells are passed to libcint in native_hf's own order, so AO blocks line
24
+ up shell by shell. Two convention differences remain inside each shell,
25
+ confirmed empirically on Ne/6-31G*:
26
+ - Cartesian component order: libcint orders px,py,pz and
27
+ xx,xy,xz,yy,yz,zz; native_hf's cartesian_powers orders px,pz,py and
28
+ xx,xz,xy,zz,yz,yy.
29
+ - Normalization: libcint's raw Cartesian d components are not unit
30
+ self-overlap (xx/yy/zz vs. xy/xz/yz differ by exactly a factor of 3).
31
+ native_hf's primitive-normalized coefficients differ from libcint's
32
+ radial convention by a per-shell constant too. Both are removed by a
33
+ per-AO rescale computed from libcint's own overlap diagonal at call
34
+ time, exact for whatever exponent/element/degree is in play.
35
+ Degrees above 2 raise NotImplementedError (native_hf itself caps at 2).
36
+ """
37
+ import ctypes
38
+ import os
39
+ import sys
40
+ from pathlib import Path
41
+
42
+ import numpy as np
43
+
44
+ from dense_evolution.native_hf.basis import build_molecule_shells
45
+ from dense_evolution.native_hf.cartesian import cartesian_powers
46
+
47
+ _LIBCINT_CARTESIAN_ORDER = {
48
+ 0: [(0, 0, 0)],
49
+ 1: [(1, 0, 0), (0, 1, 0), (0, 0, 1)],
50
+ 2: [(2, 0, 0), (1, 1, 0), (1, 0, 1), (0, 2, 0), (0, 1, 1), (0, 0, 2)],
51
+ }
52
+ _LIB_NAMES = {"win32": "libdecint.dll", "darwin": "libdecint.dylib"}
53
+ _PTR_ENV_START = 20
54
+ _POINT_NUC = 1
55
+ _KINDS = {"ovlp": 0, "kin": 1, "nuc": 2}
56
+ _lib = None
57
+
58
+
59
+ def load_libcint() -> ctypes.CDLL:
60
+ """The bundled libcint + driver shared library (or DENSE_EVOLUTION_LIBCINT's),
61
+ loaded once; ImportError if neither exists."""
62
+ global _lib
63
+ if _lib is not None:
64
+ return _lib
65
+ path = os.environ.get("DENSE_EVOLUTION_LIBCINT") or str(
66
+ Path(__file__).with_name("_libcint") / _LIB_NAMES.get(sys.platform, "libdecint.so")
67
+ )
68
+ if not os.path.isfile(path):
69
+ raise ImportError(
70
+ f"native_hf.libcint_bridge needs the bundled libcint library, expected at {path}. "
71
+ "It ships inside the dense-evolution wheels for Windows, macOS and Linux "
72
+ "(pip install dense-evolution); for a source install, run "
73
+ ".github/scripts/build-libcint.sh and set DENSE_EVOLUTION_LIBCINT to the library file."
74
+ )
75
+ lib = ctypes.CDLL(path)
76
+ tail = [ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p]
77
+ lib.de_int1e.argtypes = [ctypes.c_int, ctypes.c_void_p, ctypes.c_int] + [ctypes.c_void_p] * 3 + tail
78
+ lib.de_int1e.restype = None
79
+ lib.de_int2e.argtypes = [ctypes.c_void_p, ctypes.c_int] + [ctypes.c_void_p] * 3 + tail
80
+ lib.de_int2e.restype = None
81
+ _lib = lib
82
+ return lib
83
+
84
+
85
+ def _permutation_libcint_to_native_hf(degree: int) -> np.ndarray:
86
+ """perm such that native_hf_ordered[i] == libcint_ordered[perm[i]]."""
87
+ if degree not in _LIBCINT_CARTESIAN_ORDER:
88
+ raise NotImplementedError(
89
+ f"libcint_bridge only has a verified AO-order mapping for degree <= 2; "
90
+ f"degree={degree} would need the same empirical check first, not an "
91
+ f"assumed extension of the pattern."
92
+ )
93
+ native_order = [tuple(int(x) for x in p) for p in cartesian_powers(degree)]
94
+ libcint_order = _LIBCINT_CARTESIAN_ORDER[degree]
95
+ return np.array([libcint_order.index(p) for p in native_order])
96
+
97
+
98
+ class _Cint:
99
+ """atm/bas/env arrays for one molecule plus per-shell AO offsets, the
100
+ native_hf AO permutation, evaluated by the C driver."""
101
+
102
+ def __init__(self, atomic_numbers: list, geometry_bohr: np.ndarray, basis_name: str):
103
+ self.lib = load_libcint()
104
+ shells = build_molecule_shells(atomic_numbers, geometry_bohr, basis_name)
105
+ env = [0.0] * _PTR_ENV_START
106
+ atm, bas = [], []
107
+ for z, r in zip(atomic_numbers, np.asarray(geometry_bohr, dtype=float)):
108
+ atm.append([int(z), len(env), _POINT_NUC, len(env) + 3, 0, 0])
109
+ env.extend([r[0], r[1], r[2], 0.0])
110
+ for s in shells:
111
+ e = np.asarray(s.exponents, dtype=float)
112
+ c = np.asarray(s.coefficients, dtype=float)
113
+ bas.append([s.atom_index, s.degree, e.shape[0], 1, 0, len(env), len(env) + e.shape[0], 0])
114
+ env.extend(e)
115
+ env.extend(c)
116
+ self.atm = np.ascontiguousarray(atm, dtype=np.int32)
117
+ self.bas = np.ascontiguousarray(bas, dtype=np.int32)
118
+ self.env = np.ascontiguousarray(env, dtype=np.float64)
119
+ self.sizes = [len(cartesian_powers(s.degree)) for s in shells]
120
+ self.offsets = np.concatenate([[0], np.cumsum(self.sizes)]).astype(int)
121
+ self.n = int(self.offsets[-1])
122
+ self.perm = np.concatenate(
123
+ [o + _permutation_libcint_to_native_hf(s.degree) for o, s in zip(self.offsets, shells)]
124
+ )
125
+ self.ao_loc = np.ascontiguousarray(self.offsets, dtype=np.int32)
126
+ self.pos = np.ascontiguousarray(np.argsort(self.perm), dtype=np.int32)
127
+
128
+ def _tail(self):
129
+ return (self.atm.ctypes.data, self.atm.shape[0], self.bas.ctypes.data, self.bas.shape[0], self.env.ctypes.data)
130
+
131
+ def one(self, kind: str, r: np.ndarray, pos: np.ndarray) -> np.ndarray:
132
+ out = np.empty((self.n, self.n))
133
+ r = np.ascontiguousarray(r, dtype=np.float64)
134
+ self.lib.de_int1e(_KINDS[kind], out.ctypes.data, self.n, self.ao_loc.ctypes.data, r.ctypes.data, pos.ctypes.data, *self._tail())
135
+ return out
136
+
137
+ def rescale(self) -> np.ndarray:
138
+ S = self.one("ovlp", np.ones(self.n), np.arange(self.n, dtype=np.int32))
139
+ return 1.0 / np.sqrt(np.diag(S))
140
+
141
+ def two(self, r: np.ndarray) -> np.ndarray:
142
+ out = np.empty((self.n,) * 4)
143
+ self.lib.de_int2e(out.ctypes.data, self.n, self.ao_loc.ctypes.data, r.ctypes.data, self.pos.ctypes.data, *self._tail())
144
+ return out
145
+
146
+
147
+ def build_overlap_and_core_hamiltonian_libcint(
148
+ atomic_numbers: list, geometry_bohr: np.ndarray, basis_name: str
149
+ ) -> tuple[np.ndarray, np.ndarray]:
150
+ """Same contract as assembly.build_overlap_matrix + build_core_hamiltonian
151
+ combined, computed via libcint. Returns (S, H_core) in native_hf's own AO
152
+ ordering/normalization, ready for scf.run_scf alongside
153
+ build_repulsion_tensor_libcint's output."""
154
+ c = _Cint(atomic_numbers, geometry_bohr, basis_name)
155
+ r = c.rescale()
156
+ return c.one("ovlp", r, c.pos), c.one("kin", r, c.pos) + c.one("nuc", r, c.pos)
157
+
158
+
159
+ def build_repulsion_tensor_libcint(atomic_numbers: list, geometry_bohr: np.ndarray, basis_name: str) -> np.ndarray:
160
+ """Same contract as assembly.build_repulsion_tensor, computed via libcint
161
+ with 8-fold permutational symmetry; takes (atomic_numbers, geometry_bohr,
162
+ basis_name) rather than a shells list.
163
+
164
+ geometry_bohr: shape (n_atoms, 3), atomic units, same convention as
165
+ build_molecule_shells."""
166
+ c = _Cint(atomic_numbers, geometry_bohr, basis_name)
167
+ return c.two(c.rescale())
@@ -7,7 +7,9 @@ from .entropy import partial_trace, von_neumann_entropy, mutual_information, cen
7
7
  from .fermions import majorana_pauli_terms, total_parity_operator, hubbard_hamiltonian_pauli_terms, square_lattice_edges
8
8
  from .qec import (pauli_commutes, compute_syndrome, erasure_aware_decode, pymatching_decode,
9
9
  blind_minimum_weight_decode, decode_with_erasure_fallback,
10
- counts_in_intervals_dimension, nearest_coset_decode)
10
+ counts_in_intervals_dimension, nearest_coset_decode, erasure_ml_decode,
11
+ peeling_decode, union_find_decode, matching_erasure_decode,
12
+ estimate_edge_probabilities_from_detection_events)
11
13
  from .spectral import has_exact_degeneracy, matrix_function_eigh, spectral_evolve
12
14
 
13
15
  __all__ = [
@@ -19,5 +21,7 @@ __all__ = [
19
21
  "majorana_pauli_terms", "total_parity_operator", "hubbard_hamiltonian_pauli_terms", "square_lattice_edges",
20
22
  "pauli_commutes", "compute_syndrome", "erasure_aware_decode", "pymatching_decode", "blind_minimum_weight_decode",
21
23
  "decode_with_erasure_fallback", "counts_in_intervals_dimension", "nearest_coset_decode",
24
+ "erasure_ml_decode", "peeling_decode", "union_find_decode", "matching_erasure_decode",
25
+ "estimate_edge_probabilities_from_detection_events",
22
26
  "has_exact_degeneracy", "matrix_function_eigh", "spectral_evolve",
23
27
  ]
@@ -72,6 +72,7 @@ report a fractal dimension as absurd as 142 in 3 dimensions purely
72
72
  from fit noise, not from any real structure in the data.
73
73
  """
74
74
  import itertools
75
+ from collections import deque
75
76
  from typing import Optional, Sequence
76
77
 
77
78
  import numpy as np
@@ -752,3 +753,361 @@ def nearest_coset_decode(measured_bits: str, coset_a: Sequence[str], coset_b: Se
752
753
  d_a = _min_hamming_distance(measured_bits, coset_a)
753
754
  d_b = _min_hamming_distance(measured_bits, coset_b)
754
755
  return 0 if d_a <= d_b else 1
756
+
757
+
758
+ def estimate_edge_probabilities_from_detection_events(check_matrix, events) -> np.ndarray:
759
+ """Error probability of every qubit, from detection events (Spitz et al.).
760
+
761
+ Implements the exact inversion of S. T. Spitz, B. Tarasinski,
762
+ C. W. J. Beenakker and T. E. O'Brien, "Adaptive weight estimator for
763
+ quantum error correction in a time-dependent environment",
764
+ arXiv:1712.02360, Eqs. (13) and (16). For a code in which every qubit is
765
+ checked by at most two checks (repetition and surface codes), each qubit is
766
+ an edge between two checks, or between one check and the boundary. A qubit
767
+ shared by checks ``i`` and ``j`` has probability
768
+
769
+ ``p = 1/2 - sqrt(1/4 - (<v_i v_j> - <v_i><v_j>) / (1 - 2 <v_i xor v_j>))``
770
+
771
+ where ``v`` are the detection events and ``<.>`` the average over cycles. A
772
+ qubit on the boundary of check ``i`` has
773
+
774
+ ``p = 1/2 + (<v_i> - 1/2) / prod(1 - 2 p_ij)`` over the other qubits of ``i``.
775
+
776
+ The result can be passed as ``weights`` (``-log(p / (1 - p))``) to
777
+ `pymatching_decode`.
778
+
779
+ Parameters
780
+ ----------
781
+ check_matrix : array_like of shape (n_checks, n_qubits)
782
+ 0/1 matrix, entry ``[c, q] = 1`` when check ``c`` detects an error on
783
+ qubit ``q``. Every column has one or two ones; no two qubits may join
784
+ the same pair of checks.
785
+ events : array_like of shape (n_cycles, n_checks)
786
+ 0/1 detection events: ``1`` when a check changed value since the
787
+ previous cycle (the syndrome of one cycle's new errors).
788
+
789
+ Returns
790
+ -------
791
+ numpy.ndarray of shape (n_qubits,)
792
+ Estimated error probability of each qubit, clipped to [0, 0.5].
793
+
794
+ Raises
795
+ ------
796
+ ValueError
797
+ If the shapes do not match, ``events`` is not 0/1, a column of
798
+ ``check_matrix`` does not have one or two ones, or two qubits join the
799
+ same pair of checks.
800
+
801
+ Notes
802
+ -----
803
+ Valid for independent errors and one error type at a time. Needs about
804
+ ``1 / p`` cycles per qubit for a stable estimate (the paper's Eq. 18). A
805
+ pair of checks whose correlation is below the statistical noise gives a
806
+ probability near zero.
807
+
808
+ Examples
809
+ --------
810
+ >>> import numpy as np
811
+ >>> from dense_evolution.physics.qec import estimate_edge_probabilities_from_detection_events
812
+ >>> checks = np.array([[1, 0], [1, 1]])
813
+ >>> events = np.array([[1, 1]] * 20 + [[0, 0]] * 80)
814
+ >>> p = estimate_edge_probabilities_from_detection_events(checks, events)
815
+ >>> bool(p[0] < 0.5 and p[1] < 0.5)
816
+ True
817
+ """
818
+ h = np.asarray(check_matrix, dtype=int)
819
+ v = np.asarray(events, dtype=float)
820
+ if h.ndim != 2:
821
+ raise ValueError("check_matrix must be 2-D (n_checks, n_qubits)")
822
+ if v.ndim != 2 or v.shape[1] != h.shape[0]:
823
+ raise ValueError(
824
+ f"events must have shape (n_cycles, {h.shape[0]}), got {v.shape}"
825
+ )
826
+ if v.shape[0] == 0:
827
+ raise ValueError("events needs at least one cycle")
828
+ if not np.isin(v, (0.0, 1.0)).all():
829
+ raise ValueError("events must contain only 0 and 1")
830
+
831
+ n_q = h.shape[1]
832
+ ends = []
833
+ for q in range(n_q):
834
+ rows = np.flatnonzero(h[:, q])
835
+ if len(rows) not in (1, 2):
836
+ raise ValueError(
837
+ f"qubit {q} is checked by {len(rows)} checks; need one or two"
838
+ )
839
+ ends.append(tuple(int(r) for r in rows))
840
+ pairs = [e for e in ends if len(e) == 2]
841
+ if len(set(pairs)) != len(pairs):
842
+ raise ValueError("two qubits join the same pair of checks")
843
+
844
+ mean = v.mean(axis=0)
845
+ p = np.zeros(n_q)
846
+ for q, e in enumerate(ends):
847
+ if len(e) == 2:
848
+ i, j = e
849
+ cov = (v[:, i] * v[:, j]).mean() - mean[i] * mean[j]
850
+ xor = np.abs(v[:, i] - v[:, j]).mean()
851
+ denom = 1.0 - 2.0 * xor
852
+ inside = 0.25 - cov / denom if denom > 0 else 0.25
853
+ p[q] = 0.5 - np.sqrt(max(inside, 0.0))
854
+ for q, e in enumerate(ends):
855
+ if len(e) == 1:
856
+ i = e[0]
857
+ prod = 1.0
858
+ for r, other in enumerate(ends):
859
+ if len(other) == 2 and i in other:
860
+ prod *= 1.0 - 2.0 * p[r]
861
+ if prod <= 0:
862
+ p[q] = 0.5
863
+ else:
864
+ p[q] = 0.5 + (mean[i] - 0.5) / prod
865
+ return np.clip(p, 0.0, 0.5)
866
+
867
+
868
+ def erasure_ml_decode(
869
+ observed_syndrome: tuple,
870
+ heralded_qubits: Sequence[int],
871
+ n_qubits: int,
872
+ stabilizers: Sequence[str],
873
+ ) -> Optional[str]:
874
+ """Maximum-likelihood decoder for erasures at known locations, for any
875
+ stabilizer code.
876
+
877
+ With the erased qubits known, the error is supported on them, and the
878
+ syndrome becomes a linear system over GF(2) in the X and Z bits of those
879
+ qubits (Delfosse & Zemor, arXiv:1703.01517; Kuo & Ouyang,
880
+ arXiv:2411.13509). Solving it by Gaussian elimination costs O(n^3), where
881
+ `erasure_aware_decode` tries 4**m assignments for m erased qubits. Any
882
+ solution is a valid correction when every zero-syndrome operator on the
883
+ erased qubits is a stabilizer element, so degenerate errors (several
884
+ errors that differ by a stabilizer) are decoded too, not rejected.
885
+
886
+ Parameters
887
+ ----------
888
+ observed_syndrome : sequence of int
889
+ One bit per stabilizer, same convention as `compute_syndrome`.
890
+ heralded_qubits : sequence of int
891
+ Indices of the erased qubits.
892
+ n_qubits : int
893
+ Number of physical qubits.
894
+ stabilizers : sequence of str
895
+ Stabilizer generators as Pauli strings of length `n_qubits`.
896
+
897
+ Returns
898
+ -------
899
+ str or None
900
+ A Pauli string supported on the erased qubits that reproduces the
901
+ syndrome and is equivalent, up to a stabilizer, to every other
902
+ solution. `None` when no error on the erased qubits explains the
903
+ syndrome, or when the erased qubits contain a logical operator so the
904
+ correction is ambiguous. With no erased qubits it returns the identity
905
+ for a zero syndrome and `None` otherwise.
906
+
907
+ Raises
908
+ ------
909
+ ValueError
910
+ If the syndrome length does not match the stabilizers, a stabilizer
911
+ has the wrong length, or an erased index is out of range.
912
+
913
+ Examples
914
+ --------
915
+ >>> stabs = ['IIIXXXX', 'IXXIIXX', 'XIXIXIX', 'IIIZZZZ', 'IZZIIZZ', 'ZIZIZIZ']
916
+ >>> syndrome = compute_syndrome('XIIIIIX', stabs)
917
+ >>> erasure_ml_decode(syndrome, [0, 6], 7, stabs)
918
+ 'XIIIIIX'
919
+ """
920
+ stabs = list(stabilizers)
921
+ if len(observed_syndrome) != len(stabs):
922
+ raise ValueError(
923
+ f"observed_syndrome has {len(observed_syndrome)} entries but there are "
924
+ f"{len(stabs)} stabilizers"
925
+ )
926
+ for i, s in enumerate(stabs):
927
+ if len(s) != n_qubits:
928
+ raise ValueError(f"stabilizers[{i}] has length {len(s)}, expected n_qubits={n_qubits}")
929
+ erased = sorted({int(q) for q in heralded_qubits})
930
+ if any(q < 0 or q >= n_qubits for q in erased):
931
+ raise ValueError(f"heralded_qubits must be in range(0, {n_qubits})")
932
+
933
+ syn = np.array(observed_syndrome, dtype=np.uint8) % 2
934
+ k = len(erased)
935
+ if k == 0:
936
+ return 'I' * n_qubits if not syn.any() else None
937
+
938
+ sym = np.array([_pauli_to_symplectic(s) for s in stabs], dtype=np.uint8)
939
+ sx, sz = sym[:, :n_qubits], sym[:, n_qubits:]
940
+ a = np.concatenate([sz[:, erased], sx[:, erased]], axis=1)
941
+ rref, pivots = _gf2_rref(np.concatenate([a, syn[:, None]], axis=1))
942
+ if 2 * k in pivots:
943
+ return None
944
+
945
+ sol = np.zeros(2 * k, dtype=np.uint8)
946
+ for row, col in enumerate(pivots):
947
+ sol[col] = rref[row, 2 * k]
948
+ free = [c for c in range(2 * k) if c not in pivots]
949
+ stab_rref, stab_pivots = _gf2_rref(sym)
950
+
951
+ def embed(v):
952
+ full = np.zeros(2 * n_qubits, dtype=np.uint8)
953
+ for j, q in enumerate(erased):
954
+ full[q] = v[j]
955
+ full[n_qubits + q] = v[k + j]
956
+ return full
957
+
958
+ for f in free:
959
+ vec = np.zeros(2 * k, dtype=np.uint8)
960
+ vec[f] = 1
961
+ for row, col in enumerate(pivots):
962
+ if rref[row, f]:
963
+ vec[col] = 1
964
+ if not _in_gf2_span(embed(vec), stab_rref, stab_pivots):
965
+ return None
966
+
967
+ full = embed(sol)
968
+ letters = {(0, 0): 'I', (1, 0): 'X', (0, 1): 'Z', (1, 1): 'Y'}
969
+ return ''.join(letters[(int(full[q]), int(full[n_qubits + q]))] for q in range(n_qubits))
970
+
971
+
972
+ def _peel(h, erased, syn):
973
+ m = h.shape[0]
974
+ adj = {}
975
+ for q in erased:
976
+ rows = np.flatnonzero(h[:, q])
977
+ u, v = (int(rows[0]), int(rows[1])) if len(rows) == 2 else (int(rows[0]), m)
978
+ adj.setdefault(u, []).append((v, q))
979
+ adj.setdefault(v, []).append((u, q))
980
+ syn = [int(b) for b in syn] + [0]
981
+ seen, order, parent = set(), [], {}
982
+ starts = ([m] if m in adj else []) + [v for v in adj if v != m]
983
+ for s in starts:
984
+ if s in seen:
985
+ continue
986
+ seen.add(s)
987
+ queue = deque([s])
988
+ while queue:
989
+ u = queue.popleft()
990
+ order.append(u)
991
+ for v, q in adj[u]:
992
+ if v not in seen:
993
+ seen.add(v)
994
+ parent[v] = (u, q)
995
+ queue.append(v)
996
+ corr = set()
997
+ for u in reversed(order):
998
+ if u in parent:
999
+ p, q = parent[u]
1000
+ if syn[u]:
1001
+ corr.add(q)
1002
+ syn[p] ^= 1
1003
+ syn[u] = 0
1004
+ elif u != m and syn[u]:
1005
+ return None
1006
+ if any(syn[v] for v in range(m) if v not in seen):
1007
+ return None
1008
+ return corr
1009
+
1010
+
1011
+ def peeling_decode(stabilizers, observed_syndrome, heralded_qubits, n_qubits) -> Optional[str]:
1012
+ """Peeling decoder for a CSS code whose X-type and Z-type checks each join
1013
+ every qubit to at most two checks (repetition and surface codes)."""
1014
+ stabs = list(stabilizers)
1015
+ syn = np.array(observed_syndrome, dtype=np.uint8)
1016
+ erased = sorted({int(q) for q in heralded_qubits})
1017
+ zi = [i for i, s in enumerate(stabs) if 'Z' in s and 'X' not in s]
1018
+ xi = [i for i, s in enumerate(stabs) if 'X' in s and 'Z' not in s]
1019
+ hz = np.array([[c != 'I' for c in stabs[i]] for i in zi], dtype=np.uint8)
1020
+ hx = np.array([[c != 'I' for c in stabs[i]] for i in xi], dtype=np.uint8)
1021
+ x_part = _peel(hz, erased, syn[zi])
1022
+ z_part = _peel(hx, erased, syn[xi])
1023
+ if x_part is None or z_part is None:
1024
+ return None
1025
+ return ''.join('IXZY'[(q in x_part) + 2 * (q in z_part)] for q in range(n_qubits))
1026
+
1027
+
1028
+ def _uf_grow(h, erased, syn):
1029
+ m, n = h.shape
1030
+ ends = []
1031
+ for q in range(n):
1032
+ rows = np.flatnonzero(h[:, q])
1033
+ ends.append((int(rows[0]), int(rows[1])) if len(rows) == 2 else (int(rows[0]), m))
1034
+ parent = list(range(m + 1))
1035
+
1036
+ def find(x):
1037
+ while parent[x] != x:
1038
+ parent[x] = parent[parent[x]]
1039
+ x = parent[x]
1040
+ return x
1041
+
1042
+ support = [0] * n
1043
+ for q in erased:
1044
+ support[q] = 2
1045
+ parent[find(ends[q][0])] = find(ends[q][1])
1046
+ s = [int(b) for b in syn] + [0]
1047
+
1048
+ def odd_roots():
1049
+ par = {}
1050
+ for v in range(m + 1):
1051
+ r = find(v)
1052
+ par[r] = par.get(r, 0) ^ s[v]
1053
+ return {r for r, p in par.items() if p and r != find(m)}
1054
+
1055
+ odd = odd_roots()
1056
+ while odd:
1057
+ grown = False
1058
+ for q in range(n):
1059
+ if support[q] < 2:
1060
+ ru, rv = find(ends[q][0]), find(ends[q][1])
1061
+ inc = (ru in odd) + (rv in odd and rv != ru)
1062
+ if inc:
1063
+ support[q] = min(2, support[q] + inc)
1064
+ grown = True
1065
+ if not grown:
1066
+ return None
1067
+ for q in range(n):
1068
+ if support[q] == 2:
1069
+ parent[find(ends[q][0])] = find(ends[q][1])
1070
+ odd = odd_roots()
1071
+ return [q for q in range(n) if support[q] == 2]
1072
+
1073
+
1074
+ def _css_split(stabilizers, observed_syndrome):
1075
+ stabs = list(stabilizers)
1076
+ syn = np.array(observed_syndrome, dtype=np.uint8)
1077
+ zi = [i for i, s in enumerate(stabs) if 'Z' in s and 'X' not in s]
1078
+ xi = [i for i, s in enumerate(stabs) if 'X' in s and 'Z' not in s]
1079
+ hz = np.array([[c != 'I' for c in stabs[i]] for i in zi], dtype=np.uint8)
1080
+ hx = np.array([[c != 'I' for c in stabs[i]] for i in xi], dtype=np.uint8)
1081
+ return hz, syn[zi], hx, syn[xi]
1082
+
1083
+
1084
+ def union_find_decode(stabilizers, observed_syndrome, heralded_qubits, n_qubits) -> Optional[str]:
1085
+ """Union-Find decoder with erasures and Pauli errors (Delfosse and
1086
+ Nickerson, arXiv:1709.06218): clusters start from the erased qubits and
1087
+ grow by half-edges until every cluster has even syndrome parity or touches
1088
+ the boundary, then each grown cluster is peeled."""
1089
+ hz, sz, hx, sx = _css_split(stabilizers, observed_syndrome)
1090
+ erased = sorted({int(q) for q in heralded_qubits})
1091
+ parts = []
1092
+ for h, s in ((hz, sz), (hx, sx)):
1093
+ grown = _uf_grow(h, erased, s)
1094
+ if grown is None:
1095
+ return None
1096
+ part = _peel(h, grown, s)
1097
+ if part is None:
1098
+ return None
1099
+ parts.append(part)
1100
+ return ''.join('IXZY'[(q in parts[0]) + 2 * (q in parts[1])] for q in range(n_qubits))
1101
+
1102
+
1103
+ def matching_erasure_decode(stabilizers, observed_syndrome, heralded_qubits, n_qubits) -> str:
1104
+ """Minimum-weight perfect matching with weight 0 on the erased qubits
1105
+ (Stace, Barrett and Doherty, arXiv:0904.3556), via pymatching."""
1106
+ import pymatching
1107
+
1108
+ hz, sz, hx, sx = _css_split(stabilizers, observed_syndrome)
1109
+ w = np.ones(n_qubits)
1110
+ w[list(heralded_qubits)] = 0.0
1111
+ xp = pymatching.Matching.from_check_matrix(hz, weights=w).decode(sz)
1112
+ zp = pymatching.Matching.from_check_matrix(hx, weights=w).decode(sx)
1113
+ return ''.join('IXZY'[int(xp[q]) + 2 * int(zp[q])] for q in range(n_qubits))