ket-lang 0.10.4.2.dev1__tar.gz → 0.10.4.3.dev0__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 (119) hide show
  1. {ket_lang-0.10.4.2.dev1/src/ket_lang.egg-info → ket_lang-0.10.4.3.dev0}/PKG-INFO +1 -1
  2. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/__init__.py +1 -1
  3. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/expv.py +258 -25
  4. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/gates.py +44 -4
  5. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/operations.py +17 -2
  6. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/qulib/__init__.py +4 -2
  7. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0/src/ket_lang.egg-info}/PKG-INFO +1 -1
  8. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/LICENSE +0 -0
  9. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/MANIFEST.in +0 -0
  10. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/README.md +0 -0
  11. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/pyproject.toml +0 -0
  12. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/setup.cfg +0 -0
  13. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/setup.py +0 -0
  14. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/amazon.py +0 -0
  15. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/base/__init__.py +0 -0
  16. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/base/qasm.py +0 -0
  17. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/base/quant.py +0 -0
  18. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/__init__.py +0 -0
  19. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/kbw.py +0 -0
  20. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libket/__init__.py +0 -0
  21. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libket/execution.py +0 -0
  22. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/__init__.py +0 -0
  23. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/kbw/.git +0 -0
  24. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/kbw/.gitignore +0 -0
  25. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/kbw/.gitlab-ci.yml +0 -0
  26. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/kbw/.pre-commit-config.yaml +0 -0
  27. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/kbw/CHANGELOG.md +0 -0
  28. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/kbw/Cargo.toml +0 -0
  29. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/kbw/LICENSE +0 -0
  30. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/kbw/LICENSES/Apache-2.0.txt +0 -0
  31. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/kbw/README.md +0 -0
  32. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/kbw/REUSE.toml +0 -0
  33. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/kbw/build.rs +0 -0
  34. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/kbw/cbindgen.toml +0 -0
  35. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/kbw/examples/grover_batch.rs +0 -0
  36. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/kbw/examples/grover_live.rs +0 -0
  37. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/kbw/examples/grover_live_dv2.rs +0 -0
  38. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/kbw/kbw.h +0 -0
  39. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/kbw/src/bitwise.rs +0 -0
  40. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/kbw/src/block.rs +0 -0
  41. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/kbw/src/c_api.rs +0 -0
  42. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/kbw/src/convert.rs +0 -0
  43. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/kbw/src/dense.rs +0 -0
  44. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/kbw/src/dense_gpu/kernel.rs +0 -0
  45. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/kbw/src/dense_gpu/mod.rs +0 -0
  46. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/kbw/src/dense_v2.rs +0 -0
  47. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/kbw/src/lib.rs +0 -0
  48. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/kbw/src/quantum_execution.rs +0 -0
  49. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/kbw/src/sparse.rs +0 -0
  50. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/kbw/src/sparse_v2.rs +0 -0
  51. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/.git +0 -0
  52. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/.gitignore +0 -0
  53. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/.gitlab-ci.yml +0 -0
  54. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/.pre-commit-config.yaml +0 -0
  55. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/CHANGELOG.md +0 -0
  56. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/Cargo.toml +0 -0
  57. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/LICENSE +0 -0
  58. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/LICENSES/Apache-2.0.txt +0 -0
  59. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/README.md +0 -0
  60. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/REUSE.toml +0 -0
  61. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/build.rs +0 -0
  62. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/cbindgen.toml +0 -0
  63. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/examples/grover.rs +0 -0
  64. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/examples/qft.rs +0 -0
  65. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/libket.h +0 -0
  66. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/c_api/block.rs +0 -0
  67. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/c_api/execute.rs +0 -0
  68. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/c_api/mod.rs +0 -0
  69. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/c_api/process.rs +0 -0
  70. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/decompose/mod.rs +0 -0
  71. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/decompose/network.rs +0 -0
  72. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/decompose/su2.rs +0 -0
  73. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/decompose/u2.rs +0 -0
  74. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/decompose/util.rs +0 -0
  75. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/decompose/x/c2to4x.rs +0 -0
  76. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/decompose/x/mod.rs +0 -0
  77. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/decompose/x/single_aux.rs +0 -0
  78. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/decompose/x/v_chain.rs +0 -0
  79. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/error.rs +0 -0
  80. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/execution.rs +0 -0
  81. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/ir/block.rs +0 -0
  82. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/ir/gate.rs +0 -0
  83. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/ir/hamiltonian.rs +0 -0
  84. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/ir/macros.rs +0 -0
  85. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/ir/mod.rs +0 -0
  86. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/lib.rs +0 -0
  87. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/mapping/allocation.rs +0 -0
  88. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/mapping/dynamic_look_ahead.rs +0 -0
  89. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/mapping/graph.rs +0 -0
  90. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/mapping/mod.rs +0 -0
  91. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/matrix.rs +0 -0
  92. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/process/classical_shadow.rs +0 -0
  93. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/process/mod.rs +0 -0
  94. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/process/qwc.rs +0 -0
  95. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/qasm/ast.rs +0 -0
  96. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/qasm/executor.rs +0 -0
  97. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/qasm/gates.rs +0 -0
  98. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/qasm/mod.rs +0 -0
  99. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/qasm/openqasm.lalrpop +0 -0
  100. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/libs/libket/src/tests.rs +0 -0
  101. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/clib/wrapper.py +0 -0
  102. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/fermion.py +0 -0
  103. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/gateset.py +0 -0
  104. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/ibm.py +0 -0
  105. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/measurement.py +0 -0
  106. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/mps.py +0 -0
  107. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/npsim.py +0 -0
  108. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/qint.py +0 -0
  109. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/quantumstate.py +0 -0
  110. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/qulib/gates.py +0 -0
  111. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/qulib/ham.py +0 -0
  112. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/qulib/oracle.py +0 -0
  113. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket/qulib/prepare.py +0 -0
  114. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket_lang.egg-info/SOURCES.txt +0 -0
  115. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket_lang.egg-info/dependency_links.txt +0 -0
  116. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket_lang.egg-info/not-zip-safe +0 -0
  117. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket_lang.egg-info/requires.txt +0 -0
  118. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/src/ket_lang.egg-info/top_level.txt +0 -0
  119. {ket_lang-0.10.4.2.dev1 → ket_lang-0.10.4.3.dev0}/tests/test_fermion.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ket-lang
3
- Version: 0.10.4.2.dev1
3
+ Version: 0.10.4.3.dev0
4
4
  Summary: Ket quantum programming language interpreter and library
5
5
  Home-page: https://quantumket.org
6
6
  Author: Evandro Chagas Ribeiro da Rosa
@@ -83,7 +83,7 @@ from .fermion import *
83
83
  from .fermion import __all__ as all_fermion
84
84
  from . import qulib
85
85
 
86
- __version__ = "0.10.4.2.dev1"
86
+ __version__ = "0.10.4.3.dev0"
87
87
 
88
88
  __all__ = (
89
89
  all_base
@@ -22,7 +22,25 @@ Example:
22
22
  # Heisenberg XX + YY + ZZ Hamiltonian
23
23
  h = X(q[0])*X(q[1]) + Y(q[0])*Y(q[1]) + Z(q[0])*Z(q[1])
24
24
  ev = exp_value(h)
25
- print(ev.get()) # -3.0 for the singlet Bell state
25
+ print(ev.get()) # 1.0 for the Bell state |Φ+⟩
26
+
27
+ Hamiltonians can also be built without a process. ``with obs(n) as q:``
28
+ yields ``n`` placeholder qubits (:class:`~ket.expv.ObsQubits`). The result is
29
+ an *unbound* Hamiltonian that you bind to real qubits later, either with
30
+ :meth:`~ket.expv.Hamiltonian.bind` (or the call shorthand ``h(qubits)``) or by
31
+ passing the qubits to :func:`~ket.operations.exp_value` or
32
+ :func:`~ket.gates.evolve`:
33
+
34
+ .. code-block:: python
35
+
36
+ from ket import *
37
+ with obs(2) as q:
38
+ h = X(q[0])*X(q[1]) + Y(q[0])*Y(q[1]) + Z(q[0])*Z(q[1])
39
+
40
+ p = Process()
41
+ r = p.alloc(2)
42
+ CNOT(H(r[0]), r[1])
43
+ print(exp_value(h, r).get()) # same as exp_value(h(r))
26
44
  """
27
45
 
28
46
  from __future__ import annotations
@@ -34,6 +52,7 @@ from __future__ import annotations
34
52
 
35
53
  # pylint: disable=duplicate-code,protected-access
36
54
  import json
55
+ import operator
37
56
  from numbers import Number
38
57
  from itertools import product
39
58
  from functools import reduce
@@ -44,6 +63,7 @@ from .base import Parameter, Process, Quant
44
63
  from .clib.libket import HasProcess
45
64
 
46
65
  __all__ = [
66
+ "ObsQubits",
47
67
  "Pauli",
48
68
  "Hamiltonian",
49
69
  "commutator",
@@ -51,6 +71,95 @@ __all__ = [
51
71
  ]
52
72
 
53
73
 
74
+ class ObsQubits:
75
+ """Placeholder qubits for building Hamiltonians without a process.
76
+
77
+ Placeholder qubits are numbered ``0`` to ``n - 1``. A Hamiltonian built on
78
+ them is *unbound*: it supports all the usual algebra, but it must be bound
79
+ to qubits from a :class:`~ket.base.Process` before computing an
80
+ expectation value or evolving a state. Binding maps placeholder ``i`` to
81
+ the ``i``-th qubit of the given register.
82
+
83
+ .. note::
84
+ You usually get these from ``with obs(n) as q:`` rather than by
85
+ instantiating this class directly.
86
+
87
+ Example:
88
+
89
+ .. code-block:: python
90
+
91
+ from ket import *
92
+ with obs(3) as q:
93
+ h = sum(Z(q[i]) * Z(q[i + 1]) for i in range(2))
94
+
95
+ p = Process()
96
+ r = p.alloc(3)
97
+ ev = exp_value(h, r)
98
+
99
+ Args:
100
+ num_qubits: Number of placeholder qubits.
101
+ """
102
+
103
+ def __init__(self, num_qubits: int = 0, *, _qubits: list[int] | None = None):
104
+ self.qubits = list(range(num_qubits)) if _qubits is None else _qubits
105
+
106
+ @property
107
+ def ket_process(self) -> None:
108
+ """Always ``None``: placeholder qubits do not belong to a process."""
109
+ return None
110
+
111
+ def __getitem__(self, key) -> ObsQubits:
112
+ qubits = self.qubits.__getitem__(key)
113
+ return ObsQubits(_qubits=qubits if isinstance(qubits, list) else [qubits])
114
+
115
+ def __iter__(self):
116
+ for qubit in self.qubits:
117
+ yield ObsQubits(_qubits=[qubit])
118
+
119
+ def __reversed__(self) -> ObsQubits:
120
+ return ObsQubits(_qubits=list(reversed(self.qubits)))
121
+
122
+ def __len__(self) -> int:
123
+ return len(self.qubits)
124
+
125
+ def __add__(self, other: ObsQubits) -> ObsQubits:
126
+ if not isinstance(other, ObsQubits):
127
+ return NotImplemented
128
+ if any(qubit in other.qubits for qubit in self.qubits):
129
+ raise ValueError("Cannot concatenate qubits with overlapping indices")
130
+ return ObsQubits(_qubits=self.qubits + other.qubits)
131
+
132
+ def at(self, index: list[int]) -> ObsQubits:
133
+ """Return the placeholder qubits at the given positions."""
134
+ return ObsQubits(_qubits=[self.qubits[i] for i in index])
135
+
136
+ def __repr__(self) -> str:
137
+ return f"<Ket 'ObsQubits' {self.qubits}>"
138
+
139
+
140
+ def _check_same_process(lhs: HasProcess, rhs: HasProcess):
141
+ if lhs.ket_process is rhs.ket_process:
142
+ return
143
+ if lhs.ket_process is None or rhs.ket_process is None:
144
+ raise ValueError(
145
+ "Cannot combine bound and unbound operators; "
146
+ "bind the unbound one to qubits first"
147
+ )
148
+ raise ValueError("different Ket processes")
149
+
150
+
151
+ def _as_quant(qubits) -> Quant:
152
+ if not isinstance(qubits, Quant):
153
+ qubits = reduce(operator.add, qubits)
154
+ if not isinstance(qubits, Quant):
155
+ raise TypeError("Can only bind to qubits allocated from a Process")
156
+ return qubits
157
+
158
+
159
+ def _pid(process) -> str:
160
+ return "unbound" if process is None else f"pid={hex(id(process))}"
161
+
162
+
54
163
  class Pauli(HasProcess):
55
164
  """Pauli operator for Hamiltonian creation.
56
165
 
@@ -66,7 +175,9 @@ class Pauli(HasProcess):
66
175
 
67
176
  Args:
68
177
  pauli: Pauli operator type.
69
- qubits: Qubits to apply the Pauli operator to.
178
+ qubits: Qubits to apply the Pauli operator to. Either qubits from a
179
+ :class:`~ket.base.Process` or placeholder :class:`~ket.expv.ObsQubits`;
180
+ the latter creates an unbound operator (see :meth:`bind`).
70
181
  process: *For internal usage*. Quantum process, default is the process of the given qubits.
71
182
  pauli_list: *For internal usage*. List of Pauli operators.
72
183
  qubits_list: *For internal usage*. List of Qubit.
@@ -77,24 +188,23 @@ class Pauli(HasProcess):
77
188
  def __init__( # pylint: disable=too-many-arguments
78
189
  self,
79
190
  pauli: Literal["X", "Y", "Z", "I"] | None,
80
- qubits: Quant | None,
191
+ qubits: Quant | ObsQubits | None,
81
192
  *,
82
193
  _process: Process | None = None,
83
194
  _map: dict[int, str] | None = None,
84
195
  _coef: float | complex | None = None,
85
196
  ):
86
- if qubits is not None and not isinstance(qubits, Quant):
87
- qubits = reduce(Quant.__add__, qubits)
197
+ if qubits is not None and not isinstance(qubits, (Quant, ObsQubits)):
198
+ qubits = reduce(operator.add, qubits)
88
199
 
89
200
  if pauli is not None and qubits is not None:
90
201
  super().__init__(ket_process=qubits.ket_process)
91
202
  self.map = {q: pauli for q in qubits.qubits}
92
203
  self.coef = 1.0
93
204
  else:
94
- assert _process is not None and _map is not None and _coef is not None, (
95
- "If pauli and qubits are not provided, "
96
- "process, map, and coef must be provided."
97
- )
205
+ assert (
206
+ _map is not None and _coef is not None
207
+ ), "If pauli and qubits are not provided, map and coef must be provided."
98
208
  super().__init__(ket_process=_process)
99
209
  self.map = _map
100
210
  self.coef = _coef
@@ -118,8 +228,7 @@ class Pauli(HasProcess):
118
228
  if isinstance(other, Hamiltonian):
119
229
  return other.__mul__(self)
120
230
 
121
- if self.ket_process is not other.ket_process:
122
- raise ValueError("different Ket processes")
231
+ _check_same_process(self, other)
123
232
 
124
233
  if not set(self.map.keys()).isdisjoint(other.map.keys()):
125
234
  raise ValueError("Pauli operators act on the same qubits")
@@ -138,8 +247,7 @@ class Pauli(HasProcess):
138
247
  if isinstance(rhs, Hamiltonian):
139
248
  return Hamiltonian([self], self.ket_process) @ rhs
140
249
 
141
- if self.ket_process is not rhs.ket_process:
142
- raise ValueError("different Ket processes")
250
+ _check_same_process(self, rhs)
143
251
 
144
252
  qubits = {*self.map.keys(), *rhs.map.keys()}
145
253
  result_map = {}
@@ -195,8 +303,7 @@ class Pauli(HasProcess):
195
303
  if isinstance(other, Number):
196
304
  other = Pauli(None, None, _process=self.ket_process, _map={}, _coef=other)
197
305
 
198
- if self.ket_process is not other.ket_process:
199
- raise ValueError("different Ket processes")
306
+ _check_same_process(self, other)
200
307
 
201
308
  if isinstance(other, Hamiltonian):
202
309
  result = Hamiltonian([self], process=self.ket_process) + other
@@ -217,8 +324,57 @@ class Pauli(HasProcess):
217
324
  def __truediv__(self, other: float) -> Pauli:
218
325
  return self.__mul__(1.0 / other)
219
326
 
327
+ @property
328
+ def is_bound(self) -> bool:
329
+ """``True`` if the operator acts on qubits from a process."""
330
+ return self.ket_process is not None
331
+
332
+ @property
333
+ def num_qubits(self) -> int:
334
+ """Number of qubits needed to bind the operator.
335
+
336
+ For an unbound operator this is the highest placeholder index plus
337
+ one. For a bound operator it is the number of qubits it acts on.
338
+ """
339
+ if self.is_bound:
340
+ return len(self.map)
341
+ return max(self.map, default=-1) + 1
342
+
343
+ def bind(self, qubits: Quant) -> Pauli:
344
+ """Bind an unbound operator to qubits from a process.
345
+
346
+ Placeholder qubit ``i`` becomes ``qubits[i]``. Calling the operator,
347
+ ``pauli(qubits)``, is a shorthand for this method.
348
+
349
+ Args:
350
+ qubits: Qubits allocated from a :class:`~ket.base.Process`. Must
351
+ have at least :attr:`num_qubits` qubits.
352
+
353
+ Returns:
354
+ A new operator bound to the process of ``qubits``.
355
+
356
+ Raises:
357
+ ValueError: If the operator is already bound or ``qubits`` is too short.
358
+ """
359
+ if self.is_bound:
360
+ raise ValueError("Operator is already bound to a process")
361
+ qubits = _as_quant(qubits)
362
+ if len(qubits) < self.num_qubits:
363
+ raise ValueError(
364
+ f"Operator needs {self.num_qubits} qubits, but got {len(qubits)}"
365
+ )
366
+ return Pauli(
367
+ None,
368
+ None,
369
+ _process=qubits.ket_process,
370
+ _map={qubits.qubits[i]: p for i, p in self.map.items()},
371
+ _coef=self.coef,
372
+ )
373
+
374
+ __call__ = bind
375
+
220
376
  @staticmethod
221
- def x(qubits: Quant) -> Pauli:
377
+ def x(qubits: Quant | ObsQubits) -> Pauli:
222
378
  """Pauli X operator.
223
379
 
224
380
  Args:
@@ -227,7 +383,7 @@ class Pauli(HasProcess):
227
383
  return Pauli("X", qubits)
228
384
 
229
385
  @staticmethod
230
- def y(qubits: Quant) -> Pauli:
386
+ def y(qubits: Quant | ObsQubits) -> Pauli:
231
387
  """Pauli Y operator.
232
388
 
233
389
  Args:
@@ -236,7 +392,7 @@ class Pauli(HasProcess):
236
392
  return Pauli("Y", qubits)
237
393
 
238
394
  @staticmethod
239
- def z(qubits: Quant) -> Pauli:
395
+ def z(qubits: Quant | ObsQubits) -> Pauli:
240
396
  """Pauli Z operator.
241
397
 
242
398
  Args:
@@ -245,7 +401,7 @@ class Pauli(HasProcess):
245
401
  return Pauli("Z", qubits)
246
402
 
247
403
  @staticmethod
248
- def i(qubits: Quant) -> Pauli:
404
+ def i(qubits: Quant | ObsQubits) -> Pauli:
249
405
  """Pauli I operator.
250
406
 
251
407
  Args:
@@ -263,7 +419,7 @@ class Pauli(HasProcess):
263
419
  return f"{self.coef}" + ("*" + self._str_no_coef() if len(self.map) > 0 else "")
264
420
 
265
421
  def __repr__(self) -> str:
266
- return f"<Ket 'Pauli' {str(self)}, pid={hex(id(self.ket_process))}>"
422
+ return f"<Ket 'Pauli' {str(self)}, {_pid(self.ket_process)}>"
267
423
 
268
424
  def _repr_latex_no_math_(self) -> str: # pylint: disable=too-many-branches
269
425
  coef = self.coef
@@ -352,6 +508,20 @@ class Hamiltonian(HasProcess):
352
508
  xx = sum(-h * X(q[i]) for i in range(3))
353
509
  H = zz + xx
354
510
 
511
+ To build a Hamiltonian before allocating qubits, use ``with obs(n) as q:``.
512
+ The result is *unbound*; bind it with :meth:`bind`, the call shorthand
513
+ ``H(qubits)``, or by passing the qubits to
514
+ :func:`~ket.operations.exp_value` or :func:`~ket.gates.evolve`:
515
+
516
+ .. code-block:: python
517
+
518
+ with obs(3) as q:
519
+ H = sum(-Z(q[i]) * Z(q[i+1]) for i in range(2))
520
+
521
+ p = Process()
522
+ r = p.alloc(3)
523
+ exp_value(H, r) # same as exp_value(H(r))
524
+
355
525
  Supported arithmetic operators:
356
526
 
357
527
  - **Addition** (``+``): Combine two Hamiltonians or add a scalar constant.
@@ -376,8 +546,7 @@ class Hamiltonian(HasProcess):
376
546
  if isinstance(other, Pauli):
377
547
  other = Hamiltonian([other], self.ket_process)
378
548
 
379
- if self.ket_process is not other.ket_process:
380
- raise ValueError("different Ket processes")
549
+ _check_same_process(self, other)
381
550
 
382
551
  result = Hamiltonian(self.terms + other.terms, self.ket_process)
383
552
  result._filter()
@@ -390,13 +559,20 @@ class Hamiltonian(HasProcess):
390
559
  __radd__ = __add__
391
560
 
392
561
  def _filter(self):
562
+ # Merge like terms into copies, so the operands' terms are never modified.
393
563
  new_terms = {}
394
564
  for term in self.terms:
395
565
  str_term = term._str_no_coef()
396
566
  if all(c not in str_term for c in "XYZ"):
397
567
  str_term = ""
398
568
  if str_term not in new_terms:
399
- new_terms[str_term] = term
569
+ new_terms[str_term] = Pauli(
570
+ None,
571
+ None,
572
+ _process=term.ket_process,
573
+ _map=dict(term.map),
574
+ _coef=term.coef,
575
+ )
400
576
  else:
401
577
  new_terms[str_term].coef += term.coef
402
578
 
@@ -406,6 +582,8 @@ class Hamiltonian(HasProcess):
406
582
  if isinstance(other, Pauli):
407
583
  other = Hamiltonian([other], self.ket_process)
408
584
 
585
+ _check_same_process(self, other)
586
+
409
587
  result = Hamiltonian(
410
588
  [a @ b for a, b in product(self.terms, other.terms)],
411
589
  self.ket_process,
@@ -427,6 +605,7 @@ class Hamiltonian(HasProcess):
427
605
  )
428
606
 
429
607
  if isinstance(other, Hamiltonian):
608
+ _check_same_process(self, other)
430
609
  return Hamiltonian(
431
610
  [p * q for p, q in product(self.terms, other.terms)],
432
611
  process=self.ket_process,
@@ -461,10 +640,57 @@ class Hamiltonian(HasProcess):
461
640
  def __len__(self) -> int:
462
641
  return len(self.terms)
463
642
 
643
+ @property
644
+ def is_bound(self) -> bool:
645
+ """``True`` if the Hamiltonian acts on qubits from a process."""
646
+ return self.ket_process is not None
647
+
648
+ @property
649
+ def num_qubits(self) -> int:
650
+ """Number of qubits needed to bind the Hamiltonian.
651
+
652
+ For an unbound Hamiltonian this is the highest placeholder index plus
653
+ one. For a bound Hamiltonian it is the number of qubits it acts on.
654
+ """
655
+ if self.is_bound:
656
+ return len({q for term in self.terms for q in term.map})
657
+ return max((term.num_qubits for term in self.terms), default=0)
658
+
659
+ def bind(self, qubits: Quant) -> Hamiltonian:
660
+ """Bind an unbound Hamiltonian to qubits from a process.
661
+
662
+ Placeholder qubit ``i`` becomes ``qubits[i]``. Calling the Hamiltonian,
663
+ ``h(qubits)``, is a shorthand for this method, so an unbound
664
+ Hamiltonian can be passed anywhere a ``Callable[[Quant], Hamiltonian]``
665
+ is expected.
666
+
667
+ Args:
668
+ qubits: Qubits allocated from a :class:`~ket.base.Process`. Must
669
+ have at least :attr:`num_qubits` qubits.
670
+
671
+ Returns:
672
+ A new Hamiltonian bound to the process of ``qubits``.
673
+
674
+ Raises:
675
+ ValueError: If the Hamiltonian is already bound or ``qubits`` is too short.
676
+ """
677
+ if self.is_bound:
678
+ raise ValueError("Hamiltonian is already bound to a process")
679
+ qubits = _as_quant(qubits)
680
+ if len(qubits) < self.num_qubits:
681
+ raise ValueError(
682
+ f"Hamiltonian needs {self.num_qubits} qubits, but got {len(qubits)}"
683
+ )
684
+ return Hamiltonian(
685
+ [term.bind(qubits) for term in self.terms], qubits.ket_process
686
+ )
687
+
688
+ __call__ = bind
689
+
464
690
  def __repr__(self) -> str:
465
691
  return (
466
692
  f"<Ket 'Hamiltonian' {' + '.join(str(p) for p in self.terms)}, "
467
- f"pid={hex(id(self.ket_process))}>"
693
+ f"{_pid(self.ket_process)}>"
468
694
  )
469
695
 
470
696
  def _repr_latex_(self) -> str:
@@ -535,7 +761,14 @@ class ExpValue(HasProcess):
535
761
 
536
762
  pauli_map = {"X": "PauliX", "Y": "PauliY", "Z": "PauliZ"}
537
763
 
538
- def __init__(self, hamiltonian: Hamiltonian | Pauli):
764
+ def __init__(self, hamiltonian: Hamiltonian | Pauli, qubits: Quant | None = None):
765
+ if qubits is not None:
766
+ hamiltonian = hamiltonian.bind(qubits)
767
+ elif not hamiltonian.is_bound:
768
+ raise ValueError(
769
+ "Cannot compute the expectation value of an unbound Hamiltonian; "
770
+ "pass the qubits to exp_value or bind it first"
771
+ )
539
772
  super().__init__(ket_process=hamiltonian.ket_process)
540
773
  if isinstance(hamiltonian, Pauli):
541
774
  hamiltonian = Hamiltonian([hamiltonian], process=hamiltonian.ket_process)
@@ -67,7 +67,7 @@ from ket.clib.libket import search_process, API as libket, Block
67
67
  from .base import Process, Quant, Parameter
68
68
  from .operations import ctrl, cat, kron, around
69
69
 
70
- from .expv import Pauli, Hamiltonian
70
+ from .expv import Pauli, Hamiltonian, ObsQubits
71
71
 
72
72
  __all__ = [
73
73
  "I",
@@ -115,7 +115,7 @@ _build_obs = contextvars.ContextVar("build_obs", default=False)
115
115
 
116
116
 
117
117
  @contextmanager
118
- def obs():
118
+ def obs(num_qubits: int | None = None):
119
119
  r"""Context manager for constructing quantum observables in symbolic form.
120
120
 
121
121
  Inside a ``with obs():`` block, the Pauli gate functions
@@ -144,10 +144,34 @@ def obs():
144
144
  h_c = -0.5 * sum(1 - Z(i) * Z(j) for i, j in edges)
145
145
 
146
146
  ev = exp_value(h_c)
147
+
148
+ Pass ``num_qubits`` to build an observable before allocating any qubits.
149
+ The block then yields that many placeholder qubits
150
+ (:class:`~ket.expv.ObsQubits`) and the result is an *unbound*
151
+ Hamiltonian. Bind it to real qubits with
152
+ :meth:`~ket.expv.Hamiltonian.bind`, the call shorthand ``h_c(qubits)``,
153
+ or by passing the qubits to :func:`~ket.operations.exp_value` or
154
+ :func:`~ket.gates.evolve`:
155
+
156
+ .. code-block:: python
157
+
158
+ from ket import *
159
+
160
+ with obs(4) as q:
161
+ edges = [(q[0], q[1]), (q[1], q[2]), (q[2], q[3])]
162
+ h_c = -0.5 * sum(1 - Z(i) * Z(j) for i, j in edges)
163
+
164
+ p = Process()
165
+ r = p.alloc(4)
166
+ ev = exp_value(h_c, r) # same as exp_value(h_c(r))
167
+
168
+ Args:
169
+ num_qubits: Number of placeholder qubits to yield. If ``None``, the
170
+ block yields ``None`` and observables are built on process qubits.
147
171
  """
148
172
  token = _build_obs.set(True)
149
173
  try:
150
- yield
174
+ yield None if num_qubits is None else ObsQubits(num_qubits)
151
175
  finally:
152
176
  _build_obs.reset(token)
153
177
 
@@ -863,7 +887,7 @@ def _rzg(angle, qubits):
863
887
  _RGATE = {"X": RX, "Y": RY, "Z": RZ}
864
888
 
865
889
 
866
- def evolve(hamiltonian: Hamiltonian):
890
+ def evolve(hamiltonian: Hamiltonian | Pauli, qubits: Quant | None = None):
867
891
  r"""Time evolution :math:`e^{-iHt}` for a Hamiltonian.
868
892
 
869
893
  For each Pauli term :math:`c_k P_k` in the Hamiltonian, applies the
@@ -890,12 +914,28 @@ def evolve(hamiltonian: Hamiltonian):
890
914
 
891
915
  evolve(H_ising) # one Trotter step with t encoded in the coefficients
892
916
 
917
+ with obs(2) as v:
918
+ H_free = -1.0 * Z(v[0]) * Z(v[1]) - 0.5 * X(v[0])
919
+
920
+ evolve(H_free, q) # unbound Hamiltonian, same as evolve(H_free(q))
921
+
893
922
  Args:
894
923
  hamiltonian: The Hamiltonian to
895
924
  simulate. The coefficient of each term serves as the rotation
896
925
  angle.
926
+ qubits: Qubits to bind an unbound Hamiltonian to: placeholder ``i``
927
+ becomes ``qubits[i]``. Required for an unbound Hamiltonian and not
928
+ allowed for a bound one.
897
929
  """
898
930
 
931
+ if qubits is not None:
932
+ hamiltonian = hamiltonian.bind(qubits)
933
+ elif not hamiltonian.is_bound:
934
+ raise ValueError(
935
+ "Cannot evolve an unbound Hamiltonian; "
936
+ "pass the qubits to evolve or bind it first"
937
+ )
938
+
899
939
  process = hamiltonian.ket_process
900
940
  if isinstance(hamiltonian, Pauli):
901
941
  hamiltonian = Hamiltonian([hamiltonian], process)
@@ -754,7 +754,9 @@ def sample(qubits: Quant, shots: int = 2048) -> Samples:
754
754
  return Samples(qubits, int(shots), postprocessing)
755
755
 
756
756
 
757
- def exp_value(hamiltonian: Hamiltonian | Pauli) -> ExpValue:
757
+ def exp_value(
758
+ hamiltonian: Hamiltonian | Pauli, qubits: Quant | None = None
759
+ ) -> ExpValue:
758
760
  """Calculate the expectation value of a Hamiltonian for the current quantum state.
759
761
 
760
762
  Registers an expectation-value computation for the given Hamiltonian or
@@ -778,16 +780,29 @@ def exp_value(hamiltonian: Hamiltonian | Pauli) -> ExpValue:
778
780
  ev = exp_value(h)
779
781
  print(ev.get()) # ⟨Bell|XX|Bell⟩ = 1.0
780
782
 
783
+ An unbound Hamiltonian, built with ``with obs(n) as q:``, needs the
784
+ qubits to evaluate it on:
785
+
786
+ .. code-block:: python
787
+
788
+ with obs(2) as q:
789
+ h = X(q[0]) * X(q[1])
790
+ ev = exp_value(h, r) # same as exp_value(h(r))
791
+
781
792
  Args:
782
793
  hamiltonian:
783
794
  The observable to evaluate.
795
+ qubits:
796
+ Qubits to bind an unbound observable to: placeholder ``i``
797
+ becomes ``qubits[i]``. Required for an unbound observable and not
798
+ allowed for a bound one.
784
799
 
785
800
  Returns:
786
801
  A handle to the expectation value result.
787
802
  Access :attr:`~ket.expv.ExpValue.value` or call ``.get()`` to retrieve
788
803
  the result as a ``float``.
789
804
  """
790
- return ExpValue(hamiltonian)
805
+ return ExpValue(hamiltonian, qubits)
791
806
 
792
807
 
793
808
  def using_aux(unsafe: bool = False, **names):
@@ -35,7 +35,7 @@ from ..clib.libket.execution import LiveExecution, BatchExecution
35
35
  from ..base import Process, Quant
36
36
  from ..operations import dump, exp_value, measure
37
37
  from ..gates import H, CNOT, X, Z, obs
38
- from ..expv import Hamiltonian
38
+ from ..expv import Hamiltonian, Pauli
39
39
  from . import gates, prepare, oracle, ham
40
40
 
41
41
  try:
@@ -510,7 +510,7 @@ def pauli_string(
510
510
 
511
511
  Args:
512
512
  hamiltonian: A callable that takes a collection of qubits
513
- (``Quant``) and returns a ``Hamiltonian`` object.
513
+ (``Quant``) and returns a ``Hamiltonian`` or ``Pauli`` object.
514
514
  num_qubits: The total number of qubits the Hamiltonian acts on.
515
515
  reversed_qubits: If True, the order of the allocated qubits is reversed
516
516
  before being passed to the `hamiltonian` function. Defaults to False.
@@ -526,6 +526,8 @@ def pauli_string(
526
526
  if reversed_qubits:
527
527
  qubits = reversed(qubits)
528
528
  h = hamiltonian(qubits)
529
+ if isinstance(h, Pauli):
530
+ h = Hamiltonian([h], h.ket_process)
529
531
 
530
532
  coef = []
531
533
  pauli_str = []
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ket-lang
3
- Version: 0.10.4.2.dev1
3
+ Version: 0.10.4.3.dev0
4
4
  Summary: Ket quantum programming language interpreter and library
5
5
  Home-page: https://quantumket.org
6
6
  Author: Evandro Chagas Ribeiro da Rosa