ffsim 0.0.73__tar.gz → 0.0.74__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 (250) hide show
  1. {ffsim-0.0.73 → ffsim-0.0.74}/PKG-INFO +1 -1
  2. {ffsim-0.0.73 → ffsim-0.0.74}/docs/conf.py +1 -1
  3. {ffsim-0.0.73 → ffsim-0.0.74}/pyproject.toml +1 -1
  4. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/_lib.pyi +3 -0
  5. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/gates/orbital_rotation.py +3 -3
  6. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/linalg/double_factorized_decomposition.py +48 -0
  7. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/linalg/givens.py +4 -75
  8. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/operators/fermion_action.py +7 -1
  9. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/variational/ucj_spin_balanced.py +126 -0
  10. {ffsim-0.0.73 → ffsim-0.0.74}/src/gates/orbital_rotation.rs +9 -25
  11. {ffsim-0.0.73 → ffsim-0.0.74}/src/lib.rs +2 -0
  12. ffsim-0.0.74/src/linalg/givens.rs +148 -0
  13. ffsim-0.0.74/src/linalg/mod.rs +11 -0
  14. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/linalg/double_factorized_decomposition_test.py +5 -48
  15. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/variational/ucj_spin_balanced_test.py +32 -0
  16. {ffsim-0.0.73 → ffsim-0.0.74}/uv.lock +1 -1
  17. {ffsim-0.0.73 → ffsim-0.0.74}/.dockerignore +0 -0
  18. {ffsim-0.0.73 → ffsim-0.0.74}/.github/workflows/build-and-release.yml +0 -0
  19. {ffsim-0.0.73 → ffsim-0.0.74}/.github/workflows/check.yml +0 -0
  20. {ffsim-0.0.73 → ffsim-0.0.74}/.github/workflows/docker.yml +0 -0
  21. {ffsim-0.0.73 → ffsim-0.0.74}/.github/workflows/docs-dev.yml +0 -0
  22. {ffsim-0.0.73 → ffsim-0.0.74}/.github/workflows/docs-release.yml +0 -0
  23. {ffsim-0.0.73 → ffsim-0.0.74}/.github/workflows/test-latest-versions.yml +0 -0
  24. {ffsim-0.0.73 → ffsim-0.0.74}/.github/workflows/test-minimum-versions.yml +0 -0
  25. {ffsim-0.0.73 → ffsim-0.0.74}/.gitignore +0 -0
  26. {ffsim-0.0.73 → ffsim-0.0.74}/.pre-commit-config.yaml +0 -0
  27. {ffsim-0.0.73 → ffsim-0.0.74}/CONTRIBUTING.md +0 -0
  28. {ffsim-0.0.73 → ffsim-0.0.74}/Cargo.lock +0 -0
  29. {ffsim-0.0.73 → ffsim-0.0.74}/Cargo.toml +0 -0
  30. {ffsim-0.0.73 → ffsim-0.0.74}/Dockerfile +0 -0
  31. {ffsim-0.0.73 → ffsim-0.0.74}/LICENSE +0 -0
  32. {ffsim-0.0.73 → ffsim-0.0.74}/README.md +0 -0
  33. {ffsim-0.0.73 → ffsim-0.0.74}/compose.yaml +0 -0
  34. {ffsim-0.0.73 → ffsim-0.0.74}/docs/_templates/autosummary/class.rst +0 -0
  35. {ffsim-0.0.73 → ffsim-0.0.74}/docs/explanations/diag-coulomb-hamiltonian.ipynb +0 -0
  36. {ffsim-0.0.73 → ffsim-0.0.74}/docs/explanations/double-factorized.ipynb +0 -0
  37. {ffsim-0.0.73 → ffsim-0.0.74}/docs/explanations/fermion-operator.ipynb +0 -0
  38. {ffsim-0.0.73 → ffsim-0.0.74}/docs/explanations/hamiltonians.ipynb +0 -0
  39. {ffsim-0.0.73 → ffsim-0.0.74}/docs/explanations/index.md +0 -0
  40. {ffsim-0.0.73 → ffsim-0.0.74}/docs/explanations/lucj.ipynb +0 -0
  41. {ffsim-0.0.73 → ffsim-0.0.74}/docs/explanations/orbital-rotation.ipynb +0 -0
  42. {ffsim-0.0.73 → ffsim-0.0.74}/docs/explanations/protocols.ipynb +0 -0
  43. {ffsim-0.0.73 → ffsim-0.0.74}/docs/explanations/qubit-gate-decompositions.ipynb +0 -0
  44. {ffsim-0.0.73 → ffsim-0.0.74}/docs/explanations/state-vectors-and-gates.ipynb +0 -0
  45. {ffsim-0.0.73 → ffsim-0.0.74}/docs/explanations/variational-ansatzes.ipynb +0 -0
  46. {ffsim-0.0.73 → ffsim-0.0.74}/docs/generate_api_docs.py +0 -0
  47. {ffsim-0.0.73 → ffsim-0.0.74}/docs/how-to-guides/compute-expectation-values.ipynb +0 -0
  48. {ffsim-0.0.73 → ffsim-0.0.74}/docs/how-to-guides/entanglement-forging.ipynb +0 -0
  49. {ffsim-0.0.73 → ffsim-0.0.74}/docs/how-to-guides/index.md +0 -0
  50. {ffsim-0.0.73 → ffsim-0.0.74}/docs/how-to-guides/qiskit-circuits-sim.ipynb +0 -0
  51. {ffsim-0.0.73 → ffsim-0.0.74}/docs/how-to-guides/qiskit-lucj.ipynb +0 -0
  52. {ffsim-0.0.73 → ffsim-0.0.74}/docs/how-to-guides/qiskit-merge-orbital-rotations.ipynb +0 -0
  53. {ffsim-0.0.73 → ffsim-0.0.74}/docs/how-to-guides/qiskit-trotter.ipynb +0 -0
  54. {ffsim-0.0.73 → ffsim-0.0.74}/docs/how-to-guides/sample-from-state-vectors.ipynb +0 -0
  55. {ffsim-0.0.73 → ffsim-0.0.74}/docs/how-to-guides/sample-slater.ipynb +0 -0
  56. {ffsim-0.0.73 → ffsim-0.0.74}/docs/how-to-guides/simulate-trotter-hubbard.ipynb +0 -0
  57. {ffsim-0.0.73 → ffsim-0.0.74}/docs/how-to-guides/simulate-trotter-mol-ham.ipynb +0 -0
  58. {ffsim-0.0.73 → ffsim-0.0.74}/docs/how-to-guides/simulate-vqe.ipynb +0 -0
  59. {ffsim-0.0.73 → ffsim-0.0.74}/docs/index.md +0 -0
  60. {ffsim-0.0.73 → ffsim-0.0.74}/docs/install.md +0 -0
  61. {ffsim-0.0.73 → ffsim-0.0.74}/docs/tutorials/double-factorized-trotter.ipynb +0 -0
  62. {ffsim-0.0.73 → ffsim-0.0.74}/docs/tutorials/index.md +0 -0
  63. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/__init__.py +0 -0
  64. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/_cistring.py +0 -0
  65. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/_slow/__init__.py +0 -0
  66. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/_slow/contract/__init__.py +0 -0
  67. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/_slow/contract/diag_coulomb.py +0 -0
  68. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/_slow/contract/num_op_sum.py +0 -0
  69. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/_slow/fermion_operator.py +0 -0
  70. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/_slow/gates/__init__.py +0 -0
  71. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/_slow/gates/diag_coulomb.py +0 -0
  72. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/_slow/gates/num_op_sum.py +0 -0
  73. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/_slow/gates/orbital_rotation.py +0 -0
  74. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/contract/__init__.py +0 -0
  75. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/contract/diag_coulomb.py +0 -0
  76. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/contract/num_op_sum.py +0 -0
  77. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/contract/one_body.py +0 -0
  78. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/contract/two_body.py +0 -0
  79. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/gates/__init__.py +0 -0
  80. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/gates/basic_gates.py +0 -0
  81. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/gates/diag_coulomb.py +0 -0
  82. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/gates/num_op_sum.py +0 -0
  83. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/gates/quad_ham.py +0 -0
  84. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/hamiltonians/__init__.py +0 -0
  85. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/hamiltonians/diagonal_coulomb_hamiltonian.py +0 -0
  86. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/hamiltonians/double_factorized_hamiltonian.py +0 -0
  87. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/hamiltonians/molecular_hamiltonian.py +0 -0
  88. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/hamiltonians/single_factorized_hamiltonian.py +0 -0
  89. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/init_cache.py +0 -0
  90. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/linalg/__init__.py +0 -0
  91. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/linalg/linalg.py +0 -0
  92. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/linalg/predicates.py +0 -0
  93. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/linalg/util.py +0 -0
  94. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/molecular_data.py +0 -0
  95. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/operators/__init__.py +0 -0
  96. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/operators/common_operators.py +0 -0
  97. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/operators/coupled_cluster.py +0 -0
  98. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/operators/fermi_hubbard.py +0 -0
  99. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/operators/fermion_operator.py +0 -0
  100. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/optimize/__init__.py +0 -0
  101. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/optimize/_util.py +0 -0
  102. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/optimize/linear_method.py +0 -0
  103. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/protocols/__init__.py +0 -0
  104. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/protocols/apply_unitary_protocol.py +0 -0
  105. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/protocols/approximate_equality_protocol.py +0 -0
  106. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/protocols/diagonal_protocol.py +0 -0
  107. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/protocols/fermion_operator_protocol.py +0 -0
  108. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/protocols/linear_operator_protocol.py +0 -0
  109. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/protocols/trace_protocol.py +0 -0
  110. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/py.typed +0 -0
  111. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/qiskit/__init__.py +0 -0
  112. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/qiskit/gates/__init__.py +0 -0
  113. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/qiskit/gates/diag_coulomb.py +0 -0
  114. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/qiskit/gates/diag_coulomb_trotter_split_op.py +0 -0
  115. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/qiskit/gates/double_factorized_trotter.py +0 -0
  116. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/qiskit/gates/givens_ansatz.py +0 -0
  117. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/qiskit/gates/num_num_ansatz.py +0 -0
  118. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/qiskit/gates/num_op_sum.py +0 -0
  119. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/qiskit/gates/orbital_rotation.py +0 -0
  120. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/qiskit/gates/slater_determinant.py +0 -0
  121. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/qiskit/gates/ucj.py +0 -0
  122. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/qiskit/jordan_wigner.py +0 -0
  123. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/qiskit/sampler.py +0 -0
  124. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/qiskit/sim.py +0 -0
  125. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/qiskit/transpiler_passes/__init__.py +0 -0
  126. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/qiskit/transpiler_passes/drop_negligible.py +0 -0
  127. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/qiskit/transpiler_passes/merge_orbital_rotations.py +0 -0
  128. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/qiskit/transpiler_stages.py +0 -0
  129. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/qiskit/util.py +0 -0
  130. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/random/__init__.py +0 -0
  131. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/random/random.py +0 -0
  132. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/states/__init__.py +0 -0
  133. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/states/bitstring.py +0 -0
  134. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/states/dimensions.py +0 -0
  135. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/states/product_state_sum.py +0 -0
  136. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/states/rdm.py +0 -0
  137. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/states/sample_slater.py +0 -0
  138. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/states/slater.py +0 -0
  139. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/states/spin.py +0 -0
  140. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/states/states.py +0 -0
  141. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/states/wick.py +0 -0
  142. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/testing/__init__.py +0 -0
  143. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/testing/testing.py +0 -0
  144. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/trotter/__init__.py +0 -0
  145. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/trotter/_util.py +0 -0
  146. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/trotter/diagonal_coulomb_split_op.py +0 -0
  147. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/trotter/double_factorized.py +0 -0
  148. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/trotter/qdrift.py +0 -0
  149. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/variational/__init__.py +0 -0
  150. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/variational/givens.py +0 -0
  151. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/variational/hopgate.py +0 -0
  152. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/variational/multireference.py +0 -0
  153. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/variational/num_num.py +0 -0
  154. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/variational/orbital_optimization.py +0 -0
  155. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/variational/uccsd.py +0 -0
  156. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/variational/ucj_angles_spin_balanced.py +0 -0
  157. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/variational/ucj_spin_unbalanced.py +0 -0
  158. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/variational/ucj_spinless.py +0 -0
  159. {ffsim-0.0.73 → ffsim-0.0.74}/python/ffsim/variational/util.py +0 -0
  160. {ffsim-0.0.73 → ffsim-0.0.74}/src/contract/diag_coulomb.rs +0 -0
  161. {ffsim-0.0.73 → ffsim-0.0.74}/src/contract/mod.rs +0 -0
  162. {ffsim-0.0.73 → ffsim-0.0.74}/src/contract/num_op_sum.rs +0 -0
  163. {ffsim-0.0.73 → ffsim-0.0.74}/src/fermion_operator.rs +0 -0
  164. {ffsim-0.0.73 → ffsim-0.0.74}/src/gates/diag_coulomb.rs +0 -0
  165. {ffsim-0.0.73 → ffsim-0.0.74}/src/gates/mod.rs +0 -0
  166. {ffsim-0.0.73 → ffsim-0.0.74}/src/gates/num_op_sum.rs +0 -0
  167. {ffsim-0.0.73 → ffsim-0.0.74}/src/gates/phase_shift.rs +0 -0
  168. {ffsim-0.0.73 → ffsim-0.0.74}/src/jordan_wigner.rs +0 -0
  169. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/__init__.py +0 -0
  170. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/_slow/__init__.py +0 -0
  171. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/_slow/contract/__init__.py +0 -0
  172. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/_slow/contract/diag_coulomb_test.py +0 -0
  173. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/_slow/contract/num_op_sum_test.py +0 -0
  174. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/_slow/gates/__init__.py +0 -0
  175. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/_slow/gates/diag_coulomb_test.py +0 -0
  176. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/_slow/gates/num_op_sum_test.py +0 -0
  177. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/_slow/gates/orbital_rotation_test.py +0 -0
  178. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/contract/__init__.py +0 -0
  179. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/contract/diag_coulomb_test.py +0 -0
  180. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/contract/num_op_sum_test.py +0 -0
  181. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/contract/two_body_test.py +0 -0
  182. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/gates/__init__.py +0 -0
  183. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/gates/basic_gates_test.py +0 -0
  184. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/gates/diag_coulomb_test.py +0 -0
  185. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/gates/num_op_sum_test.py +0 -0
  186. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/gates/orbital_rotation_test.py +0 -0
  187. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/gates/quad_ham_test.py +0 -0
  188. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/hamiltonians/__init__.py +0 -0
  189. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/hamiltonians/diagonal_coulomb_hamiltonian_test.py +0 -0
  190. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/hamiltonians/double_factorized_hamiltonian_test.py +0 -0
  191. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/hamiltonians/molecular_hamiltonian_test.py +0 -0
  192. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/hamiltonians/single_factorized_hamiltonian_test.py +0 -0
  193. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/linalg/__init__.py +0 -0
  194. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/linalg/givens_test.py +0 -0
  195. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/linalg/linalg_test.py +0 -0
  196. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/linalg/util_test.py +0 -0
  197. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/molecular_data_test.py +0 -0
  198. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/operators/__init__.py +0 -0
  199. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/operators/common_operators_test.py +0 -0
  200. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/operators/coupled_cluster_test.py +0 -0
  201. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/operators/fermi_hubbard_test.py +0 -0
  202. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/operators/fermion_action_test.py +0 -0
  203. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/operators/fermion_operator_test.py +0 -0
  204. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/optimize/__init__.py +0 -0
  205. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/optimize/linear_method_test.py +0 -0
  206. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/protocols/__init__.py +0 -0
  207. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/protocols/trace_protocol_test.py +0 -0
  208. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/qiskit/__init__.py +0 -0
  209. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/qiskit/gates/__init__.py +0 -0
  210. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/qiskit/gates/diag_coulomb_test.py +0 -0
  211. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/qiskit/gates/diag_coulomb_trotter_split_op_test.py +0 -0
  212. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/qiskit/gates/double_factorized_trotter_test.py +0 -0
  213. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/qiskit/gates/givens_ansatz_test.py +0 -0
  214. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/qiskit/gates/num_num_ansatz_test.py +0 -0
  215. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/qiskit/gates/num_op_sum_test.py +0 -0
  216. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/qiskit/gates/orbital_rotation_test.py +0 -0
  217. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/qiskit/gates/slater_determinant_test.py +0 -0
  218. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/qiskit/gates/ucj_test.py +0 -0
  219. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/qiskit/jordan_wigner_test.py +0 -0
  220. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/qiskit/sampler_test.py +0 -0
  221. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/qiskit/sim_test.py +0 -0
  222. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/qiskit/transpiler_passes/__init__.py +0 -0
  223. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/qiskit/transpiler_passes/drop_negligible_test.py +0 -0
  224. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/qiskit/transpiler_passes/merge_orbital_rotations_test.py +0 -0
  225. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/qiskit/util_test.py +0 -0
  226. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/random_test.py +0 -0
  227. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/states/__init__.py +0 -0
  228. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/states/bitstring_test.py +0 -0
  229. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/states/rdm_test.py +0 -0
  230. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/states/sample_slater_test.py +0 -0
  231. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/states/slater_test.py +0 -0
  232. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/states/states_test.py +0 -0
  233. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/states/wick_test.py +0 -0
  234. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/test_data/orbital_rotation-0.npy +0 -0
  235. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/test_no_warnings.py +0 -0
  236. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/testing_test.py +0 -0
  237. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/trotter/__init__.py +0 -0
  238. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/trotter/diag_coulomb_split_op_test.py +0 -0
  239. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/trotter/double_factorized_test.py +0 -0
  240. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/trotter/qdrift_test.py +0 -0
  241. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/variational/__init__.py +0 -0
  242. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/variational/givens_test.py +0 -0
  243. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/variational/hopgate_test.py +0 -0
  244. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/variational/multireference_test.py +0 -0
  245. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/variational/num_num_test.py +0 -0
  246. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/variational/orbital_optimization_test.py +0 -0
  247. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/variational/uccsd_test.py +0 -0
  248. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/variational/ucj_angles_spin_balanced_test.py +0 -0
  249. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/variational/ucj_spin_unbalanced_test.py +0 -0
  250. {ffsim-0.0.73 → ffsim-0.0.74}/tests/python/variational/ucj_spinless_test.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ffsim
3
- Version: 0.0.73
3
+ Version: 0.0.74
4
4
  Classifier: License :: OSI Approved :: Apache Software License
5
5
  Classifier: Programming Language :: Python
6
6
  Classifier: Programming Language :: Python :: 3 :: Only
@@ -52,7 +52,7 @@ intersphinx_mapping = {
52
52
  "python": ("https://docs.python.org/3", None),
53
53
  "numpy": ("https://numpy.org/doc/stable", None),
54
54
  "scipy": ("https://docs.scipy.org/doc/scipy", None),
55
- "qiskit": ("https://docs.quantum.ibm.com/api/qiskit", None),
55
+ "qiskit": ("https://quantum.cloud.ibm.com/docs/api/qiskit", None),
56
56
  }
57
57
 
58
58
  # nbsphinx options (for tutorials)
@@ -5,7 +5,7 @@ build-backend = "maturin"
5
5
  [project]
6
6
  name = "ffsim"
7
7
  requires-python = ">=3.10"
8
- version = "0.0.73"
8
+ version = "0.0.74"
9
9
  description = "Faster simulation of fermionic quantum circuits."
10
10
  readme = "README.md"
11
11
  license = { file = "LICENSE" }
@@ -39,6 +39,9 @@ def apply_phase_shift_in_place(
39
39
  def apply_givens_rotation_in_place(
40
40
  vec: np.ndarray, c: float, s: complex, slice1: np.ndarray, slice2: np.ndarray
41
41
  ) -> None: ...
42
+ def givens_decomposition(
43
+ mat: np.ndarray,
44
+ ) -> tuple[list[tuple[float, complex, int, int]], np.ndarray]: ...
42
45
  def apply_num_op_sum_evolution_in_place(
43
46
  vec: np.ndarray,
44
47
  phases: np.ndarray,
@@ -20,7 +20,7 @@ import numpy as np
20
20
  from pyscf.fci import cistring
21
21
 
22
22
  from ffsim._lib import apply_givens_rotation_in_place, apply_phase_shift_in_place
23
- from ffsim.linalg import GivensRotation, givens_decomposition
23
+ from ffsim.linalg import givens_decomposition
24
24
 
25
25
 
26
26
  @overload
@@ -157,8 +157,8 @@ def _apply_orbital_rotation_spinful(
157
157
  def _get_givens_decomposition(
158
158
  mat: np.ndarray | tuple[np.ndarray | None, np.ndarray | None],
159
159
  ) -> tuple[
160
- tuple[list[GivensRotation], np.ndarray] | None,
161
- tuple[list[GivensRotation], np.ndarray] | None,
160
+ tuple[list[tuple[float, complex, int, int]], np.ndarray] | None,
161
+ tuple[list[tuple[float, complex, int, int]], np.ndarray] | None,
162
162
  ]:
163
163
  if isinstance(mat, np.ndarray) and mat.ndim == 2:
164
164
  decomp = givens_decomposition(mat)
@@ -1131,3 +1131,51 @@ def _double_factorized_t2_alpha_beta_explicit(
1131
1131
 
1132
1132
  def _quadrature(mat: np.ndarray, sign: int):
1133
1133
  return 0.5 * (1 - sign * 1j) * (mat + sign * 1j * mat.T.conj())
1134
+
1135
+
1136
+ def reconstruct_t2(
1137
+ diag_coulomb_mats: np.ndarray, orbital_rotations: np.ndarray, nocc: int
1138
+ ) -> np.ndarray:
1139
+ """Reconstruct t2 amplitudes from double factorization tensors."""
1140
+ return (
1141
+ 1j
1142
+ * contract(
1143
+ "kpq,kap,kip,kbq,kjq->ijab",
1144
+ diag_coulomb_mats,
1145
+ orbital_rotations,
1146
+ orbital_rotations.conj(),
1147
+ orbital_rotations,
1148
+ orbital_rotations.conj(),
1149
+ )[:nocc, :nocc, nocc:, nocc:]
1150
+ )
1151
+
1152
+
1153
+ def reconstruct_t2_alpha_beta(
1154
+ diag_coulomb_mats: np.ndarray,
1155
+ orbital_rotations: np.ndarray,
1156
+ norb: int,
1157
+ nocc_a: int,
1158
+ nocc_b: int,
1159
+ ) -> np.ndarray:
1160
+ """Reconstruct alpha-beta t2 amplitudes from double factorization tensors."""
1161
+ n_terms = diag_coulomb_mats.shape[0]
1162
+ expanded_diag_coulomb_mats = np.zeros((n_terms, 2 * norb, 2 * norb))
1163
+ expanded_orbital_rotations = np.zeros((n_terms, 2 * norb, 2 * norb), dtype=complex)
1164
+ for k in range(n_terms):
1165
+ (mat_aa, mat_ab, mat_bb) = diag_coulomb_mats[k]
1166
+ expanded_diag_coulomb_mats[k] = np.block([[mat_aa, mat_ab], [mat_ab.T, mat_bb]])
1167
+ orbital_rotation_a, orbital_rotation_b = orbital_rotations[k]
1168
+ expanded_orbital_rotations[k] = scipy.linalg.block_diag(
1169
+ orbital_rotation_a, orbital_rotation_b
1170
+ )
1171
+ return (
1172
+ 1j
1173
+ * contract(
1174
+ "kpq,kap,kip,kbq,kjq->ijab",
1175
+ expanded_diag_coulomb_mats,
1176
+ expanded_orbital_rotations,
1177
+ expanded_orbital_rotations.conj(),
1178
+ expanded_orbital_rotations,
1179
+ expanded_orbital_rotations.conj(),
1180
+ )[:nocc_a, norb : norb + nocc_b, nocc_a:norb, norb + nocc_b :]
1181
+ )
@@ -17,7 +17,8 @@ from typing import NamedTuple
17
17
 
18
18
  import numpy as np
19
19
  from scipy.linalg.blas import zrotg as zrotg_
20
- from scipy.linalg.lapack import zrot
20
+
21
+ from ffsim import _lib
21
22
 
22
23
 
23
24
  class GivensRotation(NamedTuple):
@@ -92,7 +93,7 @@ def zrotg(a: complex, b: complex, tol=1e-12) -> tuple[float, complex]:
92
93
 
93
94
  def givens_decomposition(
94
95
  mat: np.ndarray,
95
- ) -> tuple[list[GivensRotation], np.ndarray]:
96
+ ) -> tuple[list[tuple[float, complex, int, int]], np.ndarray]:
96
97
  r"""Givens rotation decomposition of a unitary matrix.
97
98
 
98
99
  The Givens rotation decomposition of an :math:`n \times n` unitary matrix :math:`U`
@@ -145,76 +146,4 @@ def givens_decomposition(
145
146
 
146
147
  .. _Clements et al., "Optimal design for universal multiport interferometers" (2016): https://doi.org/10.1364/OPTICA.3.001460
147
148
  """ # noqa: E501
148
- n, _ = mat.shape
149
- current_matrix = mat.astype(complex, copy=True)
150
- left_rotations = []
151
- right_rotations = []
152
-
153
- # compute left and right Givens rotations
154
- for i in range(n - 1):
155
- if i % 2 == 0:
156
- # rotate columns by right multiplication
157
- for j in range(i + 1):
158
- target_index = i - j
159
- row = n - j - 1
160
- if not cmath.isclose(current_matrix[row, target_index], 0.0):
161
- # zero out element at target index in given row
162
- c, s = zrotg(
163
- current_matrix[row, target_index + 1],
164
- current_matrix[row, target_index],
165
- )
166
- right_rotations.append(
167
- GivensRotation(c, s, target_index + 1, target_index)
168
- )
169
- (
170
- current_matrix[:, target_index + 1],
171
- current_matrix[:, target_index],
172
- ) = zrot(
173
- current_matrix[:, target_index + 1],
174
- current_matrix[:, target_index],
175
- c,
176
- s,
177
- )
178
- else:
179
- # rotate rows by left multiplication
180
- for j in range(i + 1):
181
- target_index = n - i + j - 1
182
- col = j
183
- if not cmath.isclose(current_matrix[target_index, col], 0.0):
184
- # zero out element at target index in given column
185
- c, s = zrotg(
186
- current_matrix[target_index - 1, col],
187
- current_matrix[target_index, col],
188
- )
189
- left_rotations.append(
190
- GivensRotation(c, s, target_index - 1, target_index)
191
- )
192
- (
193
- current_matrix[target_index - 1],
194
- current_matrix[target_index],
195
- ) = zrot(
196
- current_matrix[target_index - 1],
197
- current_matrix[target_index],
198
- c,
199
- s,
200
- )
201
-
202
- # convert left rotations to right rotations
203
- for c, s, i, j in reversed(left_rotations):
204
- c, s = zrotg(c * current_matrix[j, j], s.conjugate() * current_matrix[i, i])
205
- right_rotations.append(
206
- # clamp c between -1 and 1 to account for floating point error
207
- GivensRotation(min(1.0, max(-1.0, c)), -s.conjugate(), i, j)
208
- )
209
-
210
- givens_mat = np.array([[c, -s], [s.conjugate(), c]])
211
- givens_mat[:, 0] *= current_matrix[i, i]
212
- givens_mat[:, 1] *= current_matrix[j, j]
213
- c, s = zrotg(givens_mat[1, 1], givens_mat[1, 0])
214
- new_givens_mat = np.array([[c, s], [-s.conjugate(), c]])
215
- phase_matrix = givens_mat @ new_givens_mat
216
- current_matrix[i, i] = phase_matrix[0, 0]
217
- current_matrix[j, j] = phase_matrix[1, 1]
218
-
219
- # return decomposition
220
- return right_rotations, np.diagonal(current_matrix)
149
+ return _lib.givens_decomposition(np.asarray(mat, dtype=complex))
@@ -14,7 +14,13 @@ from typing import NamedTuple
14
14
 
15
15
 
16
16
  class FermionAction(NamedTuple):
17
- """A fermionic action."""
17
+ """A fermionic action.
18
+
19
+ Args:
20
+ action: ``False`` = destroy (annihilate), ``True`` = create
21
+ spin: ``False`` = alpha (spin up), ``True`` = beta (spin down)
22
+ orb: Index of the orbital to act on
23
+ """
18
24
 
19
25
  action: bool # False = destroy (annihilate), True = create
20
26
  spin: bool # False = alpha, True = beta
@@ -13,10 +13,12 @@
13
13
  from __future__ import annotations
14
14
 
15
15
  import itertools
16
+ import math
16
17
  from dataclasses import InitVar, dataclass
17
18
  from typing import cast
18
19
 
19
20
  import numpy as np
21
+ import pyscf.ci
20
22
 
21
23
  from ffsim import gates, linalg, protocols
22
24
  from ffsim.linalg.util import unitary_from_parameters, unitary_to_parameters
@@ -532,6 +534,130 @@ class UCJOpSpinBalanced(
532
534
  final_orbital_rotation=final_orbital_rotation,
533
535
  )
534
536
 
537
+ @staticmethod
538
+ def from_cisd_vec(
539
+ cisd_vec: np.ndarray,
540
+ *,
541
+ norb: int,
542
+ nocc: int,
543
+ c0_threshold: float = 1e-12,
544
+ n_reps: int | None = None,
545
+ interaction_pairs: tuple[
546
+ list[tuple[int, int]] | None, list[tuple[int, int]] | None
547
+ ]
548
+ | None = None,
549
+ tol: float = 1e-8,
550
+ optimize: bool = False,
551
+ method: str = "L-BFGS-B",
552
+ callback=None,
553
+ options: dict | None = None,
554
+ regularization: float = 0,
555
+ multi_stage_start: int | None = None,
556
+ multi_stage_step: int | None = None,
557
+ ) -> UCJOpSpinBalanced:
558
+ r"""Initialize the UCJ operator from a CISD vector.
559
+
560
+ The CISD amplitudes are converted to CC-style amplitudes using
561
+
562
+ .. math::
563
+
564
+ t_{1,ia} = \frac{c^{(1)}_{ia}}{c_0},
565
+ \qquad
566
+ t_{2,ijab} = \frac{c^{(2)}_{ijab}}{c_0} - \frac{t_{1,ia} t_{1,jb}}{2},
567
+
568
+ and the resulting amplitudes are then passed to
569
+ :meth:`from_t_amplitudes`.
570
+
571
+ Args:
572
+ cisd_vec: The CISD vector. This is a one-dimensional array containing the
573
+ reference coefficient :math:`c_0` in the first entry, followed by the
574
+ singles and then doubles coefficients.
575
+ norb: The number of spatial orbitals.
576
+ nocc: The number of occupied orbitals.
577
+ c0_threshold: Absolute value threshold for the reference coefficient. An
578
+ error is raised if the absolute value of :math:`c_0` is smaller than
579
+ this threshold.
580
+ n_reps: The number of ansatz repetitions. If not specified, then it is set
581
+ to the number of terms resulting from the double factorization of the
582
+ t2 amplitudes. If the specified number of repetitions is larger than the
583
+ number of terms resulting from the double factorization, then the ansatz
584
+ is padded with additional identity operators up to the specified number
585
+ of repetitions.
586
+ interaction_pairs: Optional restrictions on allowed orbital interactions
587
+ for the diagonal Coulomb operators.
588
+ If specified, `interaction_pairs` should be a pair of lists,
589
+ for alpha-alpha and alpha-beta interactions, in that order.
590
+ Either list can be substituted with ``None`` to indicate no restrictions
591
+ on interactions.
592
+ Each list should contain pairs of integers representing the orbitals
593
+ that are allowed to interact. These pairs can also be interpreted as
594
+ indices of diagonal Coulomb matrix entries that are allowed to be
595
+ nonzero.
596
+ Each integer pair must be upper triangular, that is, of the form
597
+ :math:`(i, j)` where :math:`i \leq j`.
598
+ tol: Tolerance for error in the double-factorized decomposition of the
599
+ converted t2 amplitudes.
600
+ The error is defined as the maximum absolute difference between
601
+ an element of the original tensor and the corresponding element of
602
+ the reconstructed tensor.
603
+ optimize: Whether to optimize the tensors returned by the decomposition to
604
+ to minimize the error in the factorization.
605
+ method: The optimization method. See the documentation of
606
+ `scipy.optimize.minimize`_ for possible values.
607
+ This argument is ignored if `optimize` is set to ``False``.
608
+ callback: Callback function for the optimization. See the documentation of
609
+ `scipy.optimize.minimize`_ for usage.
610
+ This argument is ignored if `optimize` is set to ``False``.
611
+ options: Options for the optimization. See the documentation of
612
+ `scipy.optimize.minimize`_ for usage.
613
+ This argument is ignored if `optimize` is set to ``False``.
614
+ regularization: See :func:`ffsim.linalg.double_factorized_t2` for a
615
+ description of this argument.
616
+ This argument is ignored if `optimize` is set to ``False``.
617
+ multi_stage_start: See :func:`ffsim.linalg.double_factorized_t2` for a
618
+ description of this argument.
619
+ This argument is ignored if `optimize` is set to ``False``.
620
+ multi_stage_step: See :func:`ffsim.linalg.double_factorized_t2` for a
621
+ description of this argument.
622
+ This argument is ignored if `optimize` is set to ``False``.
623
+
624
+ Returns:
625
+ The UCJ operator with parameters initialized from the CISD amplitudes.
626
+
627
+ Raises:
628
+ ValueError: The CISD reference coefficient is smaller than the
629
+ specified threshold c0_tol.
630
+
631
+ .. _scipy.optimize.minimize: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html
632
+ """
633
+ c0, c1, c2 = pyscf.ci.cisd.cisdvec_to_amplitudes(
634
+ cisd_vec, norb, nocc, copy=False
635
+ )
636
+
637
+ if math.isclose(c0, 0.0, abs_tol=c0_threshold):
638
+ raise ValueError(
639
+ f"CISD reference coefficient c0={c0} is smaller than the"
640
+ f"specified threshold, c0_tol={c0_threshold}."
641
+ )
642
+
643
+ t1 = c1 / c0
644
+ t2 = c2 / c0 - 0.5 * np.einsum("ia,jb->ijab", t1, t1)
645
+
646
+ return UCJOpSpinBalanced.from_t_amplitudes(
647
+ t2,
648
+ t1=t1,
649
+ n_reps=n_reps,
650
+ interaction_pairs=interaction_pairs,
651
+ tol=tol,
652
+ optimize=optimize,
653
+ method=method,
654
+ callback=callback,
655
+ options=options,
656
+ regularization=regularization,
657
+ multi_stage_start=multi_stage_start,
658
+ multi_stage_step=multi_stage_step,
659
+ )
660
+
535
661
  def _apply_unitary_(
536
662
  self, vec: np.ndarray, norb: int, nelec: int | tuple[int, int], copy: bool
537
663
  ) -> np.ndarray:
@@ -8,10 +8,6 @@
8
8
  // copyright notice, and modified files need to carry a notice indicating
9
9
  // that they have been altered from the originals.
10
10
 
11
- extern crate blas_src;
12
-
13
- use blas::zdrot;
14
- use blas::zscal;
15
11
  use numpy::Complex64;
16
12
  use numpy::PyReadonlyArray1;
17
13
 
@@ -36,11 +32,6 @@ pub fn apply_givens_rotation_in_place(
36
32
  let slice1 = slice1.as_array();
37
33
  let slice2 = slice2.as_array();
38
34
  let dim_b = vec.shape()[1];
39
- let dim_b_i32 = dim_b as i32;
40
- let s_abs = s.norm();
41
- let angle = s.arg();
42
- let phase = Complex64::new(angle.cos(), angle.sin());
43
- let phase_conj = phase.conj();
44
35
 
45
36
  let n_pairs = slice1.len();
46
37
  let n_threads = std::env::var("RAYON_NUM_THREADS")
@@ -61,9 +52,7 @@ pub fn apply_givens_rotation_in_place(
61
52
  let vec_ptr = vec.as_mut_ptr();
62
53
  for (&i, &j) in slice1_slice.iter().zip(slice2_slice) {
63
54
  unsafe {
64
- _apply_givens_rotation_to_pair(
65
- vec_ptr, i, j, dim_b, dim_b_i32, c, s_abs, phase, phase_conj,
66
- );
55
+ _apply_givens_rotation_to_pair(vec_ptr, i, j, dim_b, c, s);
67
56
  }
68
57
  }
69
58
  return;
@@ -85,9 +74,7 @@ pub fn apply_givens_rotation_in_place(
85
74
  let vec_ptr = vec_ptr as *mut Complex64;
86
75
  for (&i, &j) in slice1_chunk.iter().zip(slice2_chunk) {
87
76
  unsafe {
88
- _apply_givens_rotation_to_pair(
89
- vec_ptr, i, j, dim_b, dim_b_i32, c, s_abs, phase, phase_conj,
90
- );
77
+ _apply_givens_rotation_to_pair(vec_ptr, i, j, dim_b, c, s);
91
78
  }
92
79
  }
93
80
  });
@@ -96,23 +83,20 @@ pub fn apply_givens_rotation_in_place(
96
83
  }
97
84
 
98
85
  /// Apply Givens rotation to a pair of rows
99
- #[allow(clippy::too_many_arguments)]
100
86
  unsafe fn _apply_givens_rotation_to_pair(
101
87
  vec_ptr: *mut Complex64,
102
88
  i: usize,
103
89
  j: usize,
104
90
  dim_b: usize,
105
- dim_b_i32: i32,
106
91
  c: f64,
107
- s_abs: f64,
108
- phase: Complex64,
109
- phase_conj: Complex64,
92
+ s: Complex64,
110
93
  ) {
111
94
  let row_i = std::slice::from_raw_parts_mut(vec_ptr.add(i * dim_b), dim_b);
112
95
  let row_j = std::slice::from_raw_parts_mut(vec_ptr.add(j * dim_b), dim_b);
113
- // Use zdrot and zscal because zrot is not currently available
114
- // See https://github.com/qiskit-community/ffsim/issues/28
115
- zscal(dim_b_i32, phase_conj, row_i, 1);
116
- zdrot(dim_b_i32, row_i, 1, row_j, 1, c, s_abs);
117
- zscal(dim_b_i32, phase, row_i, 1);
96
+ for k in 0..dim_b {
97
+ let i_old = row_i[k];
98
+ let j_old = row_j[k];
99
+ row_i[k] = c * i_old + s * j_old;
100
+ row_j[k] = c * j_old - s.conj() * i_old;
101
+ }
118
102
  }
@@ -14,6 +14,7 @@ mod contract;
14
14
  mod fermion_operator;
15
15
  mod gates;
16
16
  mod jordan_wigner;
17
+ mod linalg;
17
18
 
18
19
  /// Python module exposing Rust extensions.
19
20
  #[pymodule]
@@ -38,6 +39,7 @@ fn _lib(m: &Bound<'_, PyModule>) -> PyResult<()> {
38
39
  gates::diag_coulomb::apply_diag_coulomb_evolution_in_place_z_rep,
39
40
  m
40
41
  )?)?;
42
+ m.add_function(wrap_pyfunction!(linalg::givens::givens_decomposition, m)?)?;
41
43
  m.add_function(wrap_pyfunction!(
42
44
  contract::diag_coulomb::contract_diag_coulomb_into_buffer_num_rep,
43
45
  m
@@ -0,0 +1,148 @@
1
+ // (C) Copyright IBM 2026
2
+ //
3
+ // This code is licensed under the Apache License, Version 2.0. You may
4
+ // obtain a copy of this license in the LICENSE.txt file in the root directory
5
+ // of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
6
+ //
7
+ // Any modifications or derivative works of this code must retain this
8
+ // copyright notice, and modified files need to carry a notice indicating
9
+ // that they have been altered from the originals.
10
+
11
+ use blas::zrotg as zrotg_blas;
12
+ use ndarray::Array2;
13
+ use numpy::{Complex64, IntoPyArray, PyArray1, PyReadonlyArray2};
14
+ use pyo3::exceptions::PyValueError;
15
+ use pyo3::prelude::*;
16
+
17
+ type GivensRotationTuple = (f64, Complex64, usize, usize);
18
+ type GivensDecompositionResult = (Vec<GivensRotationTuple>, Py<PyArray1<Complex64>>);
19
+
20
+ fn zrotg_safe(a: Complex64, b: Complex64, tol: f64) -> (f64, Complex64) {
21
+ if a.norm() <= tol {
22
+ return (0.0, Complex64::new(1.0, 0.0));
23
+ }
24
+ if b.norm() <= tol {
25
+ return (1.0, Complex64::new(0.0, 0.0));
26
+ }
27
+ let mut a_mut = a;
28
+ let mut c = 0.0;
29
+ let mut s = Complex64::new(0.0, 0.0);
30
+ unsafe {
31
+ zrotg_blas(&mut a_mut, b, &mut c, &mut s);
32
+ }
33
+ (c, s)
34
+ }
35
+
36
+ fn rotate_columns_in_place(
37
+ mat: &mut Array2<Complex64>,
38
+ col_x: usize,
39
+ col_y: usize,
40
+ c: f64,
41
+ s: Complex64,
42
+ ) {
43
+ let n_rows = mat.nrows();
44
+ for row in 0..n_rows {
45
+ let x_old = mat[[row, col_x]];
46
+ let y_old = mat[[row, col_y]];
47
+ mat[[row, col_x]] = c * x_old + s * y_old;
48
+ mat[[row, col_y]] = c * y_old - s.conj() * x_old;
49
+ }
50
+ }
51
+
52
+ fn rotate_rows_in_place(
53
+ mat: &mut Array2<Complex64>,
54
+ row_x: usize,
55
+ row_y: usize,
56
+ c: f64,
57
+ s: Complex64,
58
+ ) {
59
+ let n_cols = mat.ncols();
60
+ for col in 0..n_cols {
61
+ let x_old = mat[[row_x, col]];
62
+ let y_old = mat[[row_y, col]];
63
+ mat[[row_x, col]] = c * x_old + s * y_old;
64
+ mat[[row_y, col]] = c * y_old - s.conj() * x_old;
65
+ }
66
+ }
67
+
68
+ /// Givens rotation decomposition of a unitary matrix.
69
+ #[pyfunction]
70
+ pub fn givens_decomposition(
71
+ py: Python<'_>,
72
+ mat: PyReadonlyArray2<Complex64>,
73
+ ) -> PyResult<GivensDecompositionResult> {
74
+ let mat_view = mat.as_array();
75
+ let shape = mat_view.shape();
76
+ if shape[0] != shape[1] {
77
+ return Err(PyValueError::new_err("mat must be a square matrix"));
78
+ }
79
+ let n = shape[0];
80
+ let mut current_matrix: Array2<Complex64> = mat_view.to_owned();
81
+ let mut left_rotations: Vec<GivensRotationTuple> = Vec::new();
82
+ let mut right_rotations: Vec<GivensRotationTuple> = Vec::new();
83
+ let tol = 1e-12;
84
+
85
+ for i in 0..n.saturating_sub(1) {
86
+ if i % 2 == 0 {
87
+ for j in 0..=i {
88
+ let target_index = i - j;
89
+ let row = n - j - 1;
90
+ if current_matrix[[row, target_index]] != Complex64::new(0.0, 0.0) {
91
+ let (c, s) = zrotg_safe(
92
+ current_matrix[[row, target_index + 1]],
93
+ current_matrix[[row, target_index]],
94
+ tol,
95
+ );
96
+ right_rotations.push((c, s, target_index + 1, target_index));
97
+ rotate_columns_in_place(
98
+ &mut current_matrix,
99
+ target_index + 1,
100
+ target_index,
101
+ c,
102
+ s,
103
+ );
104
+ }
105
+ }
106
+ } else {
107
+ for j in 0..=i {
108
+ let target_index = n - i + j - 1;
109
+ let col = j;
110
+ if current_matrix[[target_index, col]] != Complex64::new(0.0, 0.0) {
111
+ let (c, s) = zrotg_safe(
112
+ current_matrix[[target_index - 1, col]],
113
+ current_matrix[[target_index, col]],
114
+ tol,
115
+ );
116
+ left_rotations.push((c, s, target_index - 1, target_index));
117
+ rotate_rows_in_place(&mut current_matrix, target_index - 1, target_index, c, s);
118
+ }
119
+ }
120
+ }
121
+ }
122
+
123
+ for &(c_left, s_left, i, j) in left_rotations.iter().rev() {
124
+ let (c, s) = zrotg_safe(
125
+ Complex64::new(c_left, 0.0) * current_matrix[[j, j]],
126
+ s_left.conj() * current_matrix[[i, i]],
127
+ tol,
128
+ );
129
+
130
+ right_rotations.push((c.clamp(-1.0, 1.0), -s.conj(), i, j));
131
+
132
+ let diag_i = current_matrix[[i, i]];
133
+ let diag_j = current_matrix[[j, j]];
134
+ let g00 = Complex64::new(c, 0.0) * diag_i;
135
+ let g01 = -s * diag_j;
136
+ let g10 = s.conj() * diag_i;
137
+ let g11 = Complex64::new(c, 0.0) * diag_j;
138
+ let (c_new, s_new) = zrotg_safe(g11, g10, tol);
139
+
140
+ let phase00 = g00 * Complex64::new(c_new, 0.0) + g01 * (-s_new.conj());
141
+ let phase11 = g10 * s_new + g11 * Complex64::new(c_new, 0.0);
142
+ current_matrix[[i, i]] = phase00;
143
+ current_matrix[[j, j]] = phase11;
144
+ }
145
+
146
+ let diagonal = current_matrix.diag().to_owned();
147
+ Ok((right_rotations, diagonal.into_pyarray(py).unbind()))
148
+ }
@@ -0,0 +1,11 @@
1
+ // (C) Copyright IBM 2026
2
+ //
3
+ // This code is licensed under the Apache License, Version 2.0. You may
4
+ // obtain a copy of this license in the LICENSE.txt file in the root directory
5
+ // of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
6
+ //
7
+ // Any modifications or derivative works of this code must retain this
8
+ // copyright notice, and modified files need to carry a notice indicating
9
+ // that they have been altered from the originals.
10
+
11
+ pub mod givens;