qLDPC 0.4.0__tar.gz → 0.4.2.dev20261004__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 (159) hide show
  1. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/PKG-INFO +26 -13
  2. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/README.md +18 -10
  3. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/pyproject.toml +17 -4
  4. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/pyproject.toml.orig +19 -3
  5. qldpc-0.4.2.dev20261004/src/qldpc/cache.py +139 -0
  6. qldpc-0.4.2.dev20261004/src/qldpc/cache_test.py +121 -0
  7. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/circuits/benchmarking_test.py +3 -5
  8. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/circuits/transversal.py +4 -1
  9. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/circuits/transversal_test.py +11 -0
  10. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/codes/code_capacity.py +81 -43
  11. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/codes/code_capacity_test.py +293 -16
  12. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/codes/common.py +185 -147
  13. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/codes/common_test.py +245 -39
  14. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/__init__.py +133 -49
  15. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/adapters/error_decoders.py +53 -3
  16. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/adapters/error_decoders_test.py +20 -1
  17. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/adapters/observable_decoders.py +2 -0
  18. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/capabilities.py +7 -27
  19. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/capabilities_test.py +6 -27
  20. qldpc-0.4.2.dev20261004/src/qldpc/decoders/common.py +190 -0
  21. qldpc-0.4.2.dev20261004/src/qldpc/decoders/common_test.py +196 -0
  22. qldpc-0.4.2.dev20261004/src/qldpc/decoders/construction/__init__.py +3 -0
  23. qldpc-0.4.2.dev20261004/src/qldpc/decoders/construction/factories.py +58 -0
  24. qldpc-0.4.2.dev20261004/src/qldpc/decoders/construction/factories_test.py +85 -0
  25. qldpc-0.4.2.dev20261004/src/qldpc/decoders/construction/legacy.py +492 -0
  26. qldpc-0.4.2.dev20261004/src/qldpc/decoders/construction/legacy_test.py +454 -0
  27. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/construction/resolution.py +67 -35
  28. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/construction/resolution_test.py +367 -284
  29. qldpc-0.4.2.dev20261004/src/qldpc/decoders/construction/specs.py +386 -0
  30. qldpc-0.4.2.dev20261004/src/qldpc/decoders/construction/specs_test.py +573 -0
  31. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/custom/__init__.py +14 -4
  32. qldpc-0.4.2.dev20261004/src/qldpc/decoders/custom/_lookup_enumeration.py +231 -0
  33. qldpc-0.4.2.dev20261004/src/qldpc/decoders/custom/_lookup_enumeration_test.py +159 -0
  34. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/custom/composition_test.py +11 -10
  35. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/custom/guf.py +39 -13
  36. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/custom/guf_test.py +36 -39
  37. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/custom/ilp.py +72 -34
  38. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/custom/ilp_test.py +45 -32
  39. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/custom/lookup.py +529 -432
  40. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/custom/lookup_test.py +407 -333
  41. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/custom_test.py +8 -0
  42. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/dems.py +208 -199
  43. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/dems_test.py +195 -189
  44. qldpc-0.4.2.dev20261004/src/qldpc/decoders/external/__init__.py +15 -0
  45. qldpc-0.4.2.dev20261004/src/qldpc/decoders/external/frontier.py +329 -0
  46. qldpc-0.4.2.dev20261004/src/qldpc/decoders/external/frontier_test.py +289 -0
  47. qldpc-0.4.2.dev20261004/src/qldpc/decoders/external/ldpc.py +480 -0
  48. qldpc-0.4.2.dev20261004/src/qldpc/decoders/external/ldpc_test.py +221 -0
  49. qldpc-0.4.2.dev20261004/src/qldpc/decoders/external/pymatching.py +417 -0
  50. qldpc-0.4.2.dev20261004/src/qldpc/decoders/external/pymatching_test.py +220 -0
  51. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/external/relay_bp.py +339 -43
  52. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/external/relay_bp_test.py +80 -28
  53. qldpc-0.4.2.dev20261004/src/qldpc/decoders/external/tesseract.py +452 -0
  54. qldpc-0.4.2.dev20261004/src/qldpc/decoders/external/tesseract_test.py +418 -0
  55. qldpc-0.4.2.dev20261004/src/qldpc/decoders/legacy_api_test.py +233 -0
  56. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/protocols.py +123 -42
  57. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/protocols_test.py +5 -3
  58. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/sinter/core.py +43 -85
  59. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/sinter/core_test.py +86 -82
  60. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/sinter/subgraph.py +4 -6
  61. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/sinter/subgraph_test.py +48 -50
  62. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/sinter/window.py +10 -14
  63. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/sinter/window_test.py +110 -114
  64. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders_test.py +1 -2
  65. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/external/codes.py +64 -16
  66. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/external/codes_test.py +33 -6
  67. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/external/groups.py +2 -2
  68. qldpc-0.4.0/src/qldpc/cache.py +0 -90
  69. qldpc-0.4.0/src/qldpc/cache_test.py +0 -60
  70. qldpc-0.4.0/src/qldpc/decoders/common.py +0 -98
  71. qldpc-0.4.0/src/qldpc/decoders/common_test.py +0 -88
  72. qldpc-0.4.0/src/qldpc/decoders/construction/__init__.py +0 -92
  73. qldpc-0.4.0/src/qldpc/decoders/construction/legacy.py +0 -291
  74. qldpc-0.4.0/src/qldpc/decoders/construction/legacy_test.py +0 -164
  75. qldpc-0.4.0/src/qldpc/decoders/construction/specs.py +0 -453
  76. qldpc-0.4.0/src/qldpc/decoders/construction/specs_test.py +0 -258
  77. qldpc-0.4.0/src/qldpc/decoders/external/__init__.py +0 -31
  78. qldpc-0.4.0/src/qldpc/decoders/external/ldpc.py +0 -219
  79. qldpc-0.4.0/src/qldpc/decoders/external/ldpc_test.py +0 -100
  80. qldpc-0.4.0/src/qldpc/decoders/external/pymatching.py +0 -267
  81. qldpc-0.4.0/src/qldpc/decoders/external/pymatching_test.py +0 -111
  82. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/COPYRIGHT +0 -0
  83. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/LICENSE +0 -0
  84. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/__init__.py +0 -0
  85. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/_util.py +0 -0
  86. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/_util_test.py +0 -0
  87. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/abstract/__init__.py +0 -0
  88. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/abstract/_monomials.py +0 -0
  89. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/abstract/_monomials_test.py +0 -0
  90. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/abstract/groups.py +0 -0
  91. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/abstract/groups_test.py +0 -0
  92. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/abstract/linalg.py +0 -0
  93. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/abstract/linalg_test.py +0 -0
  94. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/abstract/ring_array.py +0 -0
  95. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/abstract/ring_array_test.py +0 -0
  96. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/abstract/rings.py +0 -0
  97. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/abstract/rings_test.py +0 -0
  98. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/abstract/wedderburn_artin.py +0 -0
  99. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/abstract/wedderburn_artin_test.py +0 -0
  100. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/circuits/__init__.py +0 -0
  101. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/circuits/benchmarking.py +0 -0
  102. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/circuits/bookkeeping.py +0 -0
  103. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/circuits/bookkeeping_test.py +0 -0
  104. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/circuits/common.py +0 -0
  105. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/circuits/common_test.py +0 -0
  106. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/circuits/conftest.py +0 -0
  107. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/circuits/encoding.py +0 -0
  108. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/circuits/encoding_test.py +0 -0
  109. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/circuits/memory/__init__.py +0 -0
  110. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/circuits/memory/alpha_syndrome.py +0 -0
  111. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/circuits/memory/alpha_syndrome_test.py +0 -0
  112. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/circuits/memory/memory.py +0 -0
  113. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/circuits/memory/memory_test.py +0 -0
  114. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/circuits/memory/syndrome_measurement.py +0 -0
  115. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/circuits/memory/syndrome_measurement_test.py +0 -0
  116. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/circuits/noise_model.py +0 -0
  117. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/circuits/noise_model_test.py +0 -0
  118. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/codes/__init__.py +0 -0
  119. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/codes/classical.py +0 -0
  120. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/codes/classical_test.py +0 -0
  121. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/codes/distance.py +0 -0
  122. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/codes/distance_test.py +0 -0
  123. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/codes/monte_carlo.py +0 -0
  124. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/codes/monte_carlo_test.py +0 -0
  125. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/codes/quantum.py +0 -0
  126. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/codes/quantum_test.py +0 -0
  127. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/conftest.py +0 -0
  128. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/adapters/__init__.py +0 -0
  129. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/adapters/observable_decoders_test.py +35 -35
  130. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/conftest.py +0 -0
  131. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/custom/composition.py +4 -4
  132. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/sinter/__init__.py +0 -0
  133. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/decoders/sinter_test.py +0 -0
  134. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/experimental/__init__.py +0 -0
  135. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/experimental/surgery/__init__.py +0 -0
  136. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/experimental/surgery/_compiler.py +0 -0
  137. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/experimental/surgery/_compiler_test.py +0 -0
  138. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/experimental/surgery/bridge.py +0 -0
  139. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/experimental/surgery/bridge_test.py +0 -0
  140. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/experimental/surgery/cheeger.py +0 -0
  141. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/experimental/surgery/cheeger_test.py +0 -0
  142. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/experimental/surgery/circuit.py +0 -0
  143. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/experimental/surgery/circuit_test.py +0 -0
  144. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/experimental/surgery/conftest.py +0 -0
  145. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/experimental/surgery/construction.py +0 -0
  146. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/experimental/surgery/construction_test.py +0 -0
  147. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/experimental/surgery/gadget.py +0 -0
  148. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/experimental/surgery/gadget_test.py +0 -0
  149. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/external/__init__.py +0 -0
  150. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/external/gap.py +0 -0
  151. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/external/gap_test.py +0 -0
  152. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/external/groups_test.py +0 -0
  153. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/external/sqetch.py +0 -0
  154. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/external/sqetch_test.py +0 -0
  155. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/math.py +0 -0
  156. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/math_test.py +0 -0
  157. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/objects.py +0 -0
  158. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/objects_test.py +0 -0
  159. {qldpc-0.4.0 → qldpc-0.4.2.dev20261004}/src/qldpc/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qldpc
3
- Version: 0.4.0
3
+ Version: 0.4.2.dev20261004
4
4
  Summary: Tools for constructing and analyzing quantum low density parity check (qLDPC) codes.
5
5
  Keywords: LDPC,low density parity check codes,quantum computing,quantum error correction
6
6
  Author: Michael A. Perlin
@@ -32,6 +32,8 @@ Requires-Dist: sinter>=1.16
32
32
  Requires-Dist: stim>=1.16
33
33
  Requires-Dist: sympy>=1.12
34
34
  Requires-Dist: tqdm>=4
35
+ Requires-Dist: qldpc[relay-bp] ; extra == 'decoders'
36
+ Requires-Dist: qldpc[tesseract] ; (python_full_version < '3.15' and platform_machine == 'arm64' and sys_platform == 'darwin' and extra == 'decoders') or (python_full_version < '3.15' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'decoders')
35
37
  Requires-Dist: checks-superstaq>=0.5.67 ; extra == 'dev'
36
38
  Requires-Dist: jupyter>=1.1.1 ; extra == 'dev'
37
39
  Requires-Dist: pypandoc-binary>=1.13 ; extra == 'dev'
@@ -40,22 +42,25 @@ Requires-Dist: pytest>=8 ; extra == 'dev'
40
42
  Requires-Dist: pytest-randomly>=3.10 ; extra == 'dev'
41
43
  Requires-Dist: pytest-socket>=0.7 ; extra == 'dev'
42
44
  Requires-Dist: python-lsp-server[all]>=1.14 ; extra == 'dev'
43
- Requires-Dist: qldpc[docs,gap,relay-bp] ; extra == 'dev'
45
+ Requires-Dist: qldpc[decoders,docs,gap] ; extra == 'dev'
44
46
  Requires-Dist: ipython>=8 ; extra == 'docs'
45
47
  Requires-Dist: nbsphinx>=0.9 ; extra == 'docs'
46
48
  Requires-Dist: sphinx>=7 ; extra == 'docs'
47
49
  Requires-Dist: sphinx-autoapi>=3 ; extra == 'docs'
48
50
  Requires-Dist: sphinx-rtd-theme>=2 ; extra == 'docs'
49
- Requires-Dist: passagemath-gap==10.8.12 ; python_full_version < '3.15' and sys_platform != 'win32' and extra == 'gap'
51
+ Requires-Dist: passagemath-gap==10.8.12 ; python_full_version == '3.13.*' and sys_platform != 'win32' and extra == 'gap'
50
52
  Requires-Dist: relay-bp[stim]==0.2.2 ; extra == 'relay-bp'
53
+ Requires-Dist: tesseract-decoder==0.1.1.dev20260910235247 ; python_full_version >= '3.12' and extra == 'tesseract'
51
54
  Requires-Python: >=3.11
52
55
  Project-URL: Documentation, https://qldpc.readthedocs.io/
53
56
  Project-URL: Issues, https://github.com/qLDPCOrg/qLDPC/issues
54
57
  Project-URL: Repository, https://github.com/qLDPCOrg/qLDPC
58
+ Provides-Extra: decoders
55
59
  Provides-Extra: dev
56
60
  Provides-Extra: docs
57
61
  Provides-Extra: gap
58
62
  Provides-Extra: relay-bp
63
+ Provides-Extra: tesseract
59
64
  Description-Content-Type: text/markdown
60
65
 
61
66
  # qLDPC
@@ -98,14 +103,25 @@ python -m pip install qldpc
98
103
 
99
104
  ### Optional extras
100
105
 
101
- #### Relay-BP decoder
106
+ #### Decoders
102
107
 
103
- Install the optional Relay-BP decoder with:
108
+ qLDPC installs `ldpc` and `pymatching` by default, which include a host of decoders.
109
+ Install the optional decoder integrations published as qLDPC extras for your platform with:
110
+
111
+ ```bash
112
+ python -m pip install 'qldpc[decoders]'
113
+ ```
114
+
115
+ You can also use individual extras to install a specific optional decoder:
104
116
 
105
117
  ```bash
106
118
  python -m pip install 'qldpc[relay-bp]'
119
+ python -m pip install 'qldpc[tesseract]'
107
120
  ```
108
121
 
122
+ The [Tesseract](https://github.com/quantumlib/tesseract-decoder) decoder requires CPython 3.12–3.14 on macOS arm64 or Linux x86-64.
123
+ [Frontier](https://github.com/aleverrier/frontier) is not published on PyPI and is not included in an extra; see the [decoder guide](https://qldpc.readthedocs.io/decoders.html) for availability and build-time installation guidance.
124
+
109
125
  #### GAP integration
110
126
 
111
127
  Some algebra tools, code lookups, and distance estimates require [GAP](https://www.gap-system.org).
@@ -115,10 +131,9 @@ For the recommended in-process integration, install qLDPC with its maintained li
115
131
  python -m pip install 'qldpc[gap]'
116
132
  ```
117
133
 
118
- qLDPC uses `passagemath-gap` when it is installed, so supported GAP operations do not launch a separate process.
119
- If the extra is unavailable, qLDPC still supports a GAP executable on `PATH` (`conda install -c conda-forge gap` is one option) and retains its documented manual copy/paste fallback.
120
- If a package such as GUAVA or QDistRnd is absent from the in-process binding, qLDPC asks for permission to install it through GAP's PackageManager.
121
- If that attempt fails and a separate GAP executable is available, qLDPC prints instructions for installing the package for libgap before using the executable instead.
134
+ qLDPC uses `passagemath-gap` when available, so supported GAP operations do not launch a separate process.
135
+ The in-process binding requires Python 3.13 on a non-Windows platform.
136
+ If `passagemath-gap` is unavailable, qLDPC relies on a GAP executable in your `PATH` (possibly obtainable with `conda install -c conda-forge gap`), and a manual copy/paste fallback.
122
137
  GAP integration on Windows remains limited; see [issue #294](https://github.com/qLDPCOrg/qLDPC/issues/294).
123
138
 
124
139
  #### `sqetch` distance estimation
@@ -131,7 +146,7 @@ python -m pip install 'sqetch[gpu] @ git+https://github.com/a7b/yarn.git@e9ce9d0
131
146
 
132
147
  This command installs the upstream version of `sqetch` that qLDPC is tested against, with GPU support.
133
148
  `sqetch` is not published on PyPI, so it is not available as a qLDPC extra.
134
- For binary CSS codes, select it with `code.get_distance_bound(backend="sqetch")`.
149
+ For binary CSS codes, you can select this backend with `code.get_distance_bound(backend="sqetch")`.
135
150
 
136
151
  ### Troubleshooting
137
152
 
@@ -167,11 +182,9 @@ print("physical qubits:", len(code))
167
182
  print("logical qubits:", code.dimension)
168
183
  ```
169
184
 
170
- That object is ready for more than a parameter check: inspect its parity checks and logical operators, choose a decoder, estimate a logical error rate, or use it in a memory experiment.
171
-
172
185
  ### Go deeper: a subsystem lifted-product code
173
186
 
174
- For a more technical taste, the next example builds a parity-check matrix over the group algebra `GF(2)[C₅]`, lifts each group-ring entry to a 5×5 binary block, and hands the result to `SLPCode`.
187
+ The next example builds a parity-check matrix over the group algebra `GF(2)[C₅]`, lifts each group-ring entry to a 5×5 binary block, and hands the result to `SLPCode`.
175
188
  The [subsystem lifted-product construction](https://arxiv.org/abs/2404.18302) produces an 80-qubit CSS subsystem code with 5 logical qubits and 45 gauge qubits.
176
189
 
177
190
  ```python
@@ -38,14 +38,25 @@ python -m pip install qldpc
38
38
 
39
39
  ### Optional extras
40
40
 
41
- #### Relay-BP decoder
41
+ #### Decoders
42
42
 
43
- Install the optional Relay-BP decoder with:
43
+ qLDPC installs `ldpc` and `pymatching` by default, which include a host of decoders.
44
+ Install the optional decoder integrations published as qLDPC extras for your platform with:
45
+
46
+ ```bash
47
+ python -m pip install 'qldpc[decoders]'
48
+ ```
49
+
50
+ You can also use individual extras to install a specific optional decoder:
44
51
 
45
52
  ```bash
46
53
  python -m pip install 'qldpc[relay-bp]'
54
+ python -m pip install 'qldpc[tesseract]'
47
55
  ```
48
56
 
57
+ The [Tesseract](https://github.com/quantumlib/tesseract-decoder) decoder requires CPython 3.12–3.14 on macOS arm64 or Linux x86-64.
58
+ [Frontier](https://github.com/aleverrier/frontier) is not published on PyPI and is not included in an extra; see the [decoder guide](https://qldpc.readthedocs.io/decoders.html) for availability and build-time installation guidance.
59
+
49
60
  #### GAP integration
50
61
 
51
62
  Some algebra tools, code lookups, and distance estimates require [GAP](https://www.gap-system.org).
@@ -55,10 +66,9 @@ For the recommended in-process integration, install qLDPC with its maintained li
55
66
  python -m pip install 'qldpc[gap]'
56
67
  ```
57
68
 
58
- qLDPC uses `passagemath-gap` when it is installed, so supported GAP operations do not launch a separate process.
59
- If the extra is unavailable, qLDPC still supports a GAP executable on `PATH` (`conda install -c conda-forge gap` is one option) and retains its documented manual copy/paste fallback.
60
- If a package such as GUAVA or QDistRnd is absent from the in-process binding, qLDPC asks for permission to install it through GAP's PackageManager.
61
- If that attempt fails and a separate GAP executable is available, qLDPC prints instructions for installing the package for libgap before using the executable instead.
69
+ qLDPC uses `passagemath-gap` when available, so supported GAP operations do not launch a separate process.
70
+ The in-process binding requires Python 3.13 on a non-Windows platform.
71
+ If `passagemath-gap` is unavailable, qLDPC relies on a GAP executable in your `PATH` (possibly obtainable with `conda install -c conda-forge gap`), and a manual copy/paste fallback.
62
72
  GAP integration on Windows remains limited; see [issue #294](https://github.com/qLDPCOrg/qLDPC/issues/294).
63
73
 
64
74
  #### `sqetch` distance estimation
@@ -71,7 +81,7 @@ python -m pip install 'sqetch[gpu] @ git+https://github.com/a7b/yarn.git@e9ce9d0
71
81
 
72
82
  This command installs the upstream version of `sqetch` that qLDPC is tested against, with GPU support.
73
83
  `sqetch` is not published on PyPI, so it is not available as a qLDPC extra.
74
- For binary CSS codes, select it with `code.get_distance_bound(backend="sqetch")`.
84
+ For binary CSS codes, you can select this backend with `code.get_distance_bound(backend="sqetch")`.
75
85
 
76
86
  ### Troubleshooting
77
87
 
@@ -107,11 +117,9 @@ print("physical qubits:", len(code))
107
117
  print("logical qubits:", code.dimension)
108
118
  ```
109
119
 
110
- That object is ready for more than a parameter check: inspect its parity checks and logical operators, choose a decoder, estimate a logical error rate, or use it in a memory experiment.
111
-
112
120
  ### Go deeper: a subsystem lifted-product code
113
121
 
114
- For a more technical taste, the next example builds a parity-check matrix over the group algebra `GF(2)[C₅]`, lifts each group-ring entry to a 5×5 binary block, and hands the result to `SLPCode`.
122
+ The next example builds a parity-check matrix over the group algebra `GF(2)[C₅]`, lifts each group-ring entry to a 5×5 binary block, and hands the result to `SLPCode`.
115
123
  The [subsystem lifted-product construction](https://arxiv.org/abs/2404.18302) produces an 80-qubit CSS subsystem code with 5 logical qubits and 45 gauge qubits.
116
124
 
117
125
  ```python
@@ -4,7 +4,7 @@ requires = ["uv-build>=0.9.18,<0.13"]
4
4
 
5
5
  [project]
6
6
  name = "qldpc"
7
- version = "0.4.0"
7
+ version = "0.4.2.dev20261004"
8
8
  description = "Tools for constructing and analyzing quantum low density parity check (qLDPC) codes."
9
9
  readme = "README.md"
10
10
  keywords = [
@@ -53,6 +53,10 @@ name = "Michael A. Perlin"
53
53
  email = "mika.perlin@gmail.com"
54
54
 
55
55
  [project.optional-dependencies]
56
+ decoders = [
57
+ "qldpc[relay-bp]",
58
+ "qldpc[tesseract]; ((sys_platform=='darwin' and platform_machine=='arm64') or (sys_platform=='linux' and platform_machine=='x86_64')) and python_version<'3.15'",
59
+ ]
56
60
  dev = [
57
61
  "checks-superstaq>=0.5.67",
58
62
  "jupyter>=1.1.1",
@@ -62,7 +66,7 @@ dev = [
62
66
  "pytest-randomly>=3.10",
63
67
  "pytest-socket>=0.7",
64
68
  "python-lsp-server[all]>=1.14",
65
- "qldpc[docs,gap,relay-bp]",
69
+ "qldpc[decoders,docs,gap]",
66
70
  ]
67
71
  docs = [
68
72
  "ipython>=8",
@@ -71,8 +75,9 @@ docs = [
71
75
  "sphinx-autoapi>=3",
72
76
  "sphinx-rtd-theme>=2",
73
77
  ]
74
- gap = ["passagemath-gap==10.8.12; python_version<'3.15' and sys_platform!='win32'"]
78
+ gap = ["passagemath-gap==10.8.12; python_version=='3.13' and sys_platform!='win32'"]
75
79
  relay-bp = ["relay-bp[stim]==0.2.2"]
80
+ tesseract = ["tesseract-decoder==0.1.1.dev20260910235247; python_version>='3.12'"]
76
81
 
77
82
  [project.urls]
78
83
  Documentation = "https://qldpc.readthedocs.io/"
@@ -88,6 +93,11 @@ ci-lint = [
88
93
  "checks-superstaq>=0.5.67",
89
94
  "pyproject-fmt>=2",
90
95
  ]
96
+ ci-notebooks = [
97
+ "jupyter>=1.1.1",
98
+ "matplotlib>=3.10",
99
+ "pip>=24",
100
+ ]
91
101
  ci-test = [
92
102
  "checks-superstaq>=0.5.67",
93
103
  "pytest>=8",
@@ -136,7 +146,10 @@ show_error_codes = true
136
146
 
137
147
  [tool.pytest.ini_options]
138
148
  addopts = "--disable-socket"
139
- filterwarnings = ["ignore:(?s).*The problem is either infeasible or unbounded.*:UserWarning"]
149
+ filterwarnings = [
150
+ "error::DeprecationWarning",
151
+ "ignore:(?s).*The problem is either infeasible or unbounded.*:UserWarning",
152
+ ]
140
153
 
141
154
  [tool.coverage.run]
142
155
  include = ["./*"]
@@ -4,7 +4,7 @@ requires = [ "uv-build>=0.9.18,<0.13" ]
4
4
 
5
5
  [project]
6
6
  name = "qldpc"
7
- version = "0.4.0"
7
+ version = "0.4.2.dev20261004"
8
8
  description = "Tools for constructing and analyzing quantum low density parity check (qLDPC) codes."
9
9
  readme = "README.md"
10
10
  keywords = [
@@ -45,6 +45,13 @@ dependencies = [
45
45
  "sympy>=1.12",
46
46
  "tqdm>=4",
47
47
  ]
48
+ optional-dependencies.decoders = [
49
+ "qldpc[relay-bp]",
50
+ """\
51
+ qldpc[tesseract]; ((sys_platform=='darwin' and platform_machine=='arm64') or (sys_platform=='linux' and \
52
+ platform_machine=='x86_64')) and python_version<'3.15'\
53
+ """,
54
+ ]
48
55
  optional-dependencies.dev = [
49
56
  "checks-superstaq>=0.5.67",
50
57
  "jupyter>=1.1.1",
@@ -54,7 +61,7 @@ optional-dependencies.dev = [
54
61
  "pytest-randomly>=3.10", # tests that need reproducibility read its randomly_seed option
55
62
  "pytest-socket>=0.7", # implements the --disable-socket in [tool.pytest] addopts below
56
63
  "python-lsp-server[all]>=1.14",
57
- "qldpc[docs,gap,relay-bp]",
64
+ "qldpc[decoders,docs,gap]",
58
65
  ]
59
66
  optional-dependencies.docs = [
60
67
  "ipython>=8", # for nbsphinx's IPython.sphinxext.ipython_console_highlighting extension
@@ -64,9 +71,12 @@ optional-dependencies.docs = [
64
71
  "sphinx-rtd-theme>=2",
65
72
  ]
66
73
  optional-dependencies.gap = [
67
- "passagemath-gap==10.8.12; python_version<'3.15' and sys_platform!='win32'",
74
+ "passagemath-gap==10.8.12; python_version=='3.13' and sys_platform!='win32'",
68
75
  ]
69
76
  optional-dependencies.relay-bp = [ "relay-bp[stim]==0.2.2" ]
77
+ optional-dependencies.tesseract = [
78
+ "tesseract-decoder==0.1.1.dev20260910235247; python_version>='3.12'",
79
+ ]
70
80
  urls.Documentation = "https://qldpc.readthedocs.io/"
71
81
  urls.Issues = "https://github.com/qLDPCOrg/qLDPC/issues"
72
82
  urls.Repository = "https://github.com/qLDPCOrg/qLDPC"
@@ -80,6 +90,11 @@ ci-lint = [
80
90
  "checks-superstaq>=0.5.67",
81
91
  "pyproject-fmt>=2",
82
92
  ]
93
+ ci-notebooks = [
94
+ "jupyter>=1.1.1",
95
+ "matplotlib>=3.10",
96
+ "pip>=24",
97
+ ]
83
98
  ci-test = [
84
99
  "checks-superstaq>=0.5.67",
85
100
  "pytest>=8",
@@ -126,6 +141,7 @@ show_error_codes = true
126
141
  [tool.pytest]
127
142
  ini_options.addopts = "--disable-socket" # forbid tests from making network calls
128
143
  ini_options.filterwarnings = [
144
+ "error::DeprecationWarning", # tests use the modern API
129
145
  "ignore:(?s).*The problem is either infeasible or unbounded.*:UserWarning", # from cvxpy
130
146
  ]
131
147
 
@@ -0,0 +1,139 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+
3
+ """Helper function(s) for caching results."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import functools
8
+ import pathlib
9
+ import sys
10
+ import warnings
11
+ from collections.abc import Callable, Hashable
12
+ from typing import Generic, ParamSpec, TypeVar, cast
13
+
14
+ import diskcache
15
+ import platformdirs
16
+
17
+ Params = ParamSpec("Params")
18
+ Result = TypeVar("Result")
19
+
20
+
21
+ class CachedFunction(Generic[Params, Result]):
22
+ """A function whose results are cached to disk.
23
+
24
+ Calling the function retrieves its result from the cache if available, and otherwise computes
25
+ and caches the result. Calling CachedFunction.refresh ignores any existing cache entry,
26
+ recomputes the result, and saves the new result to the cache.
27
+ """
28
+
29
+ __wrapped__: Callable[Params, Result]
30
+
31
+ def __init__(
32
+ self,
33
+ function: Callable[Params, Result],
34
+ cache_name: str,
35
+ *,
36
+ cache_dir: pathlib.Path | str | None = None,
37
+ key_func: Callable[..., Hashable] | None = None,
38
+ ) -> None:
39
+ self._function = function
40
+ self._cache_name = cache_name
41
+ self._cache_dir = cache_dir
42
+ self._key_func = key_func
43
+ functools.update_wrapper(self, function)
44
+
45
+ def __call__(self, *args: Params.args, **kwargs: Params.kwargs) -> Result:
46
+ """Retrieve a result from the cache if available, and otherwise compute and cache it."""
47
+ cache = get_disk_cache(self._cache_name, cache_dir=self._cache_dir)
48
+ key = self._get_key(*args, **kwargs)
49
+ if key in cache:
50
+ return cast(Result, cache[key])
51
+ result = self._function(*args, **kwargs)
52
+ cache[key] = result
53
+ return result
54
+
55
+ def refresh(self, *args: Params.args, **kwargs: Params.kwargs) -> Result:
56
+ """Recompute the result for these arguments and overwrite the cached value."""
57
+ cache = get_disk_cache(self._cache_name, cache_dir=self._cache_dir)
58
+ key = self._get_key(*args, **kwargs)
59
+ result = self._function(*args, **kwargs)
60
+ cache[key] = result
61
+ return result
62
+
63
+ def _get_key(self, *args: Params.args, **kwargs: Params.kwargs) -> Hashable:
64
+ """Get the cache key for the given arguments."""
65
+ if self._key_func is not None:
66
+ return self._key_func(*args, **kwargs)
67
+ key = args + tuple(kwargs.items())
68
+ return key if len(key) != 1 else key[0] # unpack length-1 tuples
69
+
70
+ def __reduce__(self) -> str:
71
+ """Pickle by reference to the module-level name of the decorated function."""
72
+ return self._function.__qualname__
73
+
74
+
75
+ def get_disk_cache_path(
76
+ cache_name: str, *, cache_dir: pathlib.Path | str | None = None
77
+ ) -> pathlib.Path:
78
+ """Retrieve the path of a cache."""
79
+ cache_dir = cache_dir or pathlib.Path(platformdirs.user_cache_dir()) / "qldpc"
80
+ return pathlib.Path(cache_dir) / cache_name
81
+
82
+
83
+ def get_disk_cache(
84
+ cache_name: str, *, cache_dir: pathlib.Path | str | None = None
85
+ ) -> diskcache.Cache:
86
+ """Retrieve a dictionary-like cache object."""
87
+ if running_with_pytest():
88
+ return {}
89
+ return diskcache.Cache(get_disk_cache_path(cache_name, cache_dir=cache_dir))
90
+
91
+
92
+ def use_disk_cache(
93
+ cache_name: str,
94
+ *,
95
+ cache_dir: pathlib.Path | str | None = None,
96
+ key_func: Callable[..., Hashable] | None = None,
97
+ ) -> Callable[[Callable[Params, Result]], CachedFunction[Params, Result]]:
98
+ """Decorator to cache results to disk.
99
+
100
+ By default, the cache key is the tuple of positional arguments followed by (keyword, value)
101
+ pairs, unpacked if this tuple has length 1. A custom key_func, called with the same arguments
102
+ as the decorated function, overrides this default.
103
+
104
+ The decorated function has a .refresh method with the same signature, which ignores any
105
+ existing cache entry, recomputes the result, and saves the new result to the cache. Refreshing
106
+ only affects this cache, and does not clear other caches used internally by the function.
107
+
108
+ The decorator is intended for module-level functions: a decorated function is pickled by
109
+ reference to its qualified name, and it does not bind as a method.
110
+
111
+ Disk caching is bypassed when running with pytest.
112
+ """
113
+
114
+ def decorator(function: Callable[Params, Result]) -> CachedFunction[Params, Result]:
115
+ return CachedFunction(function, cache_name, cache_dir=cache_dir, key_func=key_func)
116
+
117
+ return decorator
118
+
119
+
120
+ def running_with_pytest() -> bool:
121
+ """Are we currently running with pytest?"""
122
+ return "pytest" in sys.modules
123
+
124
+
125
+ def clear_entry(
126
+ cache_name: str, key: Hashable, *, cache_dir: pathlib.Path | str | None = None
127
+ ) -> None:
128
+ """Clear an entry from a local cache."""
129
+ cache = get_disk_cache(cache_name, cache_dir=cache_dir)
130
+ if key in cache:
131
+ del cache[key]
132
+ else:
133
+ cache_path = get_disk_cache_path(cache_name, cache_dir=cache_dir)
134
+ warnings.warn(
135
+ f"Attempted to delete the entry '{key}' from the cache '{cache_name}'"
136
+ + ("" if cache_dir is None else f" (located at '{cache_path}')")
137
+ + ", but this entry does not exist.",
138
+ stacklevel=2,
139
+ )
@@ -0,0 +1,121 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+
3
+ """Unit tests for cache.py."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import copy
8
+ import pathlib
9
+ import pickle
10
+ import unittest.mock
11
+ from collections.abc import Hashable
12
+
13
+ import pytest
14
+
15
+ import qldpc.cache
16
+ import qldpc.external.codes
17
+
18
+
19
+ def test_pytest() -> None:
20
+ """Disk caching is bypassed when running under pytest."""
21
+ assert qldpc.cache.running_with_pytest()
22
+ assert qldpc.cache.get_disk_cache("test") == {}
23
+
24
+ calls = []
25
+
26
+ @qldpc.cache.use_disk_cache("test")
27
+ def test_func(value: int) -> int:
28
+ """Test docstring."""
29
+ calls.append(value)
30
+ return value
31
+
32
+ assert test_func.__doc__ == "Test docstring."
33
+ assert test_func(1) == test_func(1) == test_func.refresh(1) == 1
34
+ assert calls == [1, 1, 1]
35
+
36
+
37
+ def test_use_disk_cache(tmp_path: pathlib.Path) -> None:
38
+ """Cache function outputs."""
39
+
40
+ cache: dict[Hashable, int] = {}
41
+ with (
42
+ unittest.mock.patch("qldpc.cache.running_with_pytest", return_value=False),
43
+ unittest.mock.patch("diskcache.Cache", return_value=cache),
44
+ ):
45
+
46
+ @qldpc.cache.use_disk_cache("test_name")
47
+ def get_five(_: str) -> int:
48
+ return 5
49
+
50
+ # use cache to save/retrieve results
51
+ get_five("test_arg") # save results to cache
52
+ assert cache == {"test_arg": 5}
53
+ assert cache["test_arg"] == get_five("test_arg")
54
+
55
+ # post-process inputs to determine the cache key
56
+ @qldpc.cache.use_disk_cache("test_name", key_func=lambda _: None)
57
+ def get_six(_: str) -> int:
58
+ return 6
59
+
60
+ assert get_six("test_arg") == 6
61
+ assert cache == {"test_arg": 5, None: 6}
62
+
63
+ # delete an entry from the cache
64
+ qldpc.cache.clear_entry("test_name", None)
65
+ assert cache == {"test_arg": 5}
66
+
67
+ # raise a warning if trying to delete an entry that does not exist in the cache
68
+ with pytest.warns(UserWarning, match="entry does not exist"):
69
+ qldpc.cache.clear_entry("test_name", "some_key")
70
+ with pytest.warns(UserWarning, match="located at"):
71
+ qldpc.cache.clear_entry("test_name", "some_key", cache_dir=tmp_path)
72
+
73
+
74
+ def test_refresh() -> None:
75
+ """Refresh cached function outputs."""
76
+
77
+ cache: dict[Hashable, int] = {}
78
+ with (
79
+ unittest.mock.patch("qldpc.cache.running_with_pytest", return_value=False),
80
+ unittest.mock.patch("diskcache.Cache", return_value=cache),
81
+ ):
82
+ calls: list[tuple[int, int]] = []
83
+
84
+ @qldpc.cache.use_disk_cache("test_name")
85
+ def count(value: int, *, offset: int = 0) -> int:
86
+ calls.append((value, offset))
87
+ return len(calls)
88
+
89
+ # the first call computes a result, and the second call retrieves it from the cache
90
+ assert count(1) == count(1) == 1
91
+ assert calls == [(1, 0)]
92
+
93
+ # refreshing recomputes the result and overwrites the cache entry
94
+ assert count.refresh(1) == 2
95
+ assert cache == {1: 2}
96
+ assert count(1) == 2
97
+ assert calls == [(1, 0), (1, 0)]
98
+
99
+ # refreshing a missing entry computes and saves a result without warnings
100
+ assert count.refresh(2, offset=3) == 3
101
+ assert cache == {1: 2, (2, ("offset", 3)): 3}
102
+
103
+ # refreshing uses the custom cache key
104
+ @qldpc.cache.use_disk_cache("test_name", key_func=lambda value: f"key-{value}")
105
+ def double(value: int) -> int:
106
+ calls.append((value, 0))
107
+ return 2 * value
108
+
109
+ cache["key-4"] = 0
110
+ assert double(4) == 0
111
+ assert double.refresh(4) == 8
112
+ assert cache["key-4"] == 8
113
+ assert double(4) == 8
114
+
115
+
116
+ def test_pickle() -> None:
117
+ """Cached module-level functions are pickled by reference."""
118
+ function = qldpc.external.codes.get_classical_code
119
+ assert pickle.dumps(function)
120
+ assert copy.deepcopy(function) is function
121
+ assert function.__wrapped__.__name__ == "get_classical_code"
@@ -176,9 +176,7 @@ def test_erasure_counts_as_a_discard() -> None:
176
176
 
177
177
  # every error of weight one is decoded exactly, and every heavier error is erased, so the
178
178
  # logical error rate is exactly zero and the discard rate is 1 - 0.8**7 - 7 * 0.2 * 0.8**6
179
- decoder = decoders.SinterDecoder(
180
- decoder=decoders.lookup_table(max_weight=1, add_erasure_bit=True)
181
- )
179
+ decoder = decoders.SinterDecoder(decoder=decoders.lookup(max_weight=1, add_erasure_bit=True))
182
180
  logical_error_rate, discard_rate = circuits.get_logical_error_and_discard_rate(
183
181
  circuit, sinter_decoder=decoder, num_samples=1000
184
182
  )
@@ -191,9 +189,9 @@ def test_erasure_counts_as_a_discard() -> None:
191
189
 
192
190
  def compile_decoder_for_dem(
193
191
  self, dem: stim.DetectorErrorModel
194
- ) -> decoders.CompiledSinterDecoder:
192
+ ) -> decoders.sinter.CompiledSinterDecoder:
195
193
  compiled = super().compile_decoder_for_dem(dem)
196
- assert isinstance(compiled, decoders.CompiledTrivialDecoder)
194
+ assert isinstance(compiled, decoders.sinter.CompiledTrivialDecoder)
197
195
  compiled.packed_observable_size += 2
198
196
  return compiled
199
197
 
@@ -386,7 +386,10 @@ def _tableaus_are_equivalent_mod_paulis(tableau_1: stim.Tableau, tableau_2: stim
386
386
 
387
387
  @cache.use_disk_cache(
388
388
  "group_intersection",
389
- key_func=lambda xx, yy: (xx.hashable_generators(), yy.hashable_generators()),
389
+ key_func=lambda group_a, group_b: (
390
+ group_a.hashable_generators(),
391
+ group_b.hashable_generators(),
392
+ ),
390
393
  )
391
394
  def _sympy_group_intersection_generators(
392
395
  group_a: abstract.Group, group_b: abstract.Group
@@ -107,6 +107,17 @@ def test_transversal_group_degree() -> None:
107
107
  assert len(circuits.get_transversal_ops(code, ["H"])) == 0
108
108
 
109
109
 
110
+ def test_group_intersection_keywords() -> None:
111
+ """The cache key of a group intersection supports keyword arguments."""
112
+ group_a = abstract.SymmetricGroup(3)
113
+ group_b = abstract.CyclicGroup(3)
114
+ generators = transversal._sympy_group_intersection_generators(group_a, group_b)
115
+ assert generators # the cyclic group is a nontrivial subgroup of the symmetric group
116
+ assert generators == transversal._sympy_group_intersection_generators(
117
+ group_a=group_a, group_b=group_b
118
+ )
119
+
120
+
110
121
  def test_finding_circuit(
111
122
  pytestconfig: pytest.Config, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
112
123
  ) -> None: