fockit 0.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
fockit/__init__.py ADDED
@@ -0,0 +1,53 @@
1
+ """Symbolic operator algebra for fermions, bosons, and beyond."""
2
+
3
+ from fockit.constructors import (
4
+ b,
5
+ bdag,
6
+ c,
7
+ cdag,
8
+ n,
9
+ nb,
10
+ sigma_x,
11
+ sigma_y,
12
+ sigma_z,
13
+ sm,
14
+ sp,
15
+ )
16
+ from fockit.display import to_latex, to_text
17
+ from fockit.inspection import degree, modes, num_terms
18
+ from fockit.normal_order import is_normal_ordered, normal_order
19
+ from fockit.operators import (
20
+ identity,
21
+ zero,
22
+ )
23
+ from fockit.simplify import drop_zeros, simplify_coeffs
24
+ from fockit.transforms import anticommutator, commutator, hc, substitute_modes
25
+
26
+ __all__ = [
27
+ "anticommutator",
28
+ "b",
29
+ "bdag",
30
+ "c",
31
+ "cdag",
32
+ "commutator",
33
+ "degree",
34
+ "drop_zeros",
35
+ "hc",
36
+ "identity",
37
+ "is_normal_ordered",
38
+ "modes",
39
+ "n",
40
+ "nb",
41
+ "normal_order",
42
+ "num_terms",
43
+ "sigma_x",
44
+ "sigma_y",
45
+ "sigma_z",
46
+ "simplify_coeffs",
47
+ "sm",
48
+ "sp",
49
+ "substitute_modes",
50
+ "to_latex",
51
+ "to_text",
52
+ "zero",
53
+ ]
fockit/_types.py ADDED
@@ -0,0 +1,48 @@
1
+ """Shared typing helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Hashable
6
+ from typing import Protocol, SupportsComplex, SupportsFloat, SupportsIndex, cast
7
+
8
+ type Mode = Hashable
9
+
10
+
11
+ class SymbolicCoefficient(Protocol):
12
+ """Protocol for symbolic coefficient objects."""
13
+
14
+ def __hash__(self) -> int: ...
15
+
16
+ def __add__(self, other: object, /) -> object: ...
17
+
18
+ def __radd__(self, other: object, /) -> object: ...
19
+
20
+ def __sub__(self, other: object, /) -> object: ...
21
+
22
+ def __rsub__(self, other: object, /) -> object: ...
23
+
24
+ def __mul__(self, other: object, /) -> object: ...
25
+
26
+ def __rmul__(self, other: object, /) -> object: ...
27
+
28
+ def __neg__(self) -> object: ...
29
+
30
+ def __abs__(self) -> object: ...
31
+
32
+ def __eq__(self, other: object, /) -> bool: ...
33
+
34
+ def conjugate(self) -> object: ...
35
+
36
+
37
+ type Coefficient = int | float | complex | SymbolicCoefficient
38
+ type ComplexConvertible = complex | SupportsComplex | SupportsFloat | SupportsIndex
39
+
40
+
41
+ def as_coefficient(value: object) -> Coefficient:
42
+ """Cast a coefficient result back to ``Coefficient``."""
43
+ return cast(Coefficient, value)
44
+
45
+
46
+ def as_complex(value: Coefficient) -> complex:
47
+ """Convert a coefficient to ``complex``."""
48
+ return complex(cast(ComplexConvertible, value))
fockit/_version.py ADDED
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '0.0.2'
22
+ __version_tuple__ = version_tuple = (0, 0, 2)
23
+
24
+ __commit_id__ = commit_id = None
fockit/algebra.py ADDED
@@ -0,0 +1,275 @@
1
+ """Algebra rules protocol and concrete implementations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Hashable
6
+ from dataclasses import dataclass, replace
7
+ from enum import StrEnum
8
+ from typing import TYPE_CHECKING, Protocol, runtime_checkable
9
+
10
+ from fockit._types import Coefficient
11
+
12
+ if TYPE_CHECKING:
13
+ from fockit.operators import Op
14
+
15
+
16
+ class Ladder(StrEnum):
17
+ """Operator kinds for fermions and bosons."""
18
+
19
+ CREATE = "+"
20
+ ANNIHILATE = "-"
21
+
22
+
23
+ class Pauli(StrEnum):
24
+ """Operator kinds for Pauli matrices."""
25
+
26
+ X = "x"
27
+ Y = "y"
28
+ Z = "z"
29
+
30
+
31
+ @dataclass(frozen=True)
32
+ class SwapResult:
33
+ """Result of swapping two adjacent operators.
34
+
35
+ Each entry in ``terms`` is a ``(Coefficient, tuple[Op, ...])`` pair,
36
+ where the tuple replaces the original two-operator subsequence.
37
+
38
+ Parameters
39
+ ----------
40
+ terms : tuple[tuple[Coefficient, tuple[Op, ...]], ...]
41
+ The replacement terms.
42
+
43
+ Notes
44
+ -----
45
+ For the normal ordering algorithm to terminate, each replacement must
46
+ either shorten the pair (length 0 or 1) or reduce the number of
47
+ inversions (length 2).
48
+
49
+ """
50
+
51
+ terms: tuple[tuple[Coefficient, tuple[Op, ...]], ...]
52
+
53
+
54
+ @runtime_checkable
55
+ class AlgebraRules(Protocol):
56
+ """Protocol defining the (anti)commutation relations for an algebra.
57
+
58
+ Methods only ever receive operators of this algebra. Operators of
59
+ different algebras commute.
60
+
61
+ Notes
62
+ -----
63
+ Implementations MUST define ``__eq__`` and ``__hash__`` so that two
64
+ instances representing the same algebra compare equal and are usable
65
+ as ``lru_cache`` keys.
66
+
67
+ """
68
+
69
+ def swap(self, left: Op, right: Op) -> SwapResult:
70
+ """Define what happens when ``left`` and ``right`` are exchanged.
71
+
72
+ Parameters
73
+ ----------
74
+ left : Op
75
+ right : Op
76
+
77
+ Returns
78
+ -------
79
+ SwapResult
80
+
81
+ """
82
+ ...
83
+
84
+ def is_normal_ordered_pair(self, left: Op, right: Op) -> bool:
85
+ """Return True if ``(left, right)`` is already in normal order.
86
+
87
+ Parameters
88
+ ----------
89
+ left : Op
90
+ right : Op
91
+
92
+ Returns
93
+ -------
94
+ bool
95
+
96
+ """
97
+ ...
98
+
99
+ def dagger(self, op: Op) -> Op:
100
+ """Return the Hermitian conjugate of a single operator.
101
+
102
+ Parameters
103
+ ----------
104
+ op : Op
105
+
106
+ Returns
107
+ -------
108
+ Op
109
+
110
+ """
111
+ ...
112
+
113
+ def __hash__(self) -> int:
114
+ """Return hash for use as ``lru_cache`` key."""
115
+ ...
116
+
117
+
118
+ class FermionRules:
119
+ """Canonical anticommutation relations: {c_a, c†_b} = delta_{ab}."""
120
+
121
+ def swap(self, left: Op, right: Op) -> SwapResult:
122
+ """Apply fermion swap rules."""
123
+ if left == right:
124
+ return SwapResult(terms=()) # c c = 0
125
+ swapped = ((-1, (right, left)),)
126
+ if (
127
+ left.kind is Ladder.ANNIHILATE
128
+ and right.kind is Ladder.CREATE
129
+ and left.mode == right.mode
130
+ ):
131
+ return SwapResult(terms=((1, ()), *swapped))
132
+ return SwapResult(terms=swapped)
133
+
134
+ def is_normal_ordered_pair(self, left: Op, right: Op) -> bool:
135
+ """Check if a pair is in fermionic normal order.
136
+
137
+ Creators left, canonical mode order within blocks.
138
+ """
139
+ if left == right:
140
+ return False
141
+ if left.kind is Ladder.CREATE and right.kind is Ladder.ANNIHILATE:
142
+ return True
143
+ if left.kind is Ladder.ANNIHILATE and right.kind is Ladder.CREATE:
144
+ return False
145
+ return mode_sort_key(left.mode) <= mode_sort_key(right.mode)
146
+
147
+ def dagger(self, op: Op) -> Op:
148
+ """Flip creation ↔ annihilation."""
149
+ flipped = Ladder.CREATE if op.kind is Ladder.ANNIHILATE else Ladder.ANNIHILATE
150
+ return replace(op, kind=flipped)
151
+
152
+ def __eq__(self, other: object) -> bool: # noqa: D105
153
+ return isinstance(other, FermionRules)
154
+
155
+ def __hash__(self) -> int: # noqa: D105
156
+ return hash(type(self))
157
+
158
+ def __repr__(self) -> str: # noqa: D105
159
+ return "FermionRules()"
160
+
161
+
162
+ class BosonRules:
163
+ """Canonical commutation relations: [b_a, b†_b] = delta_{ab}."""
164
+
165
+ def swap(self, left: Op, right: Op) -> SwapResult:
166
+ """Apply boson swap rules."""
167
+ swapped = ((+1, (right, left)),)
168
+ if (
169
+ left.kind is Ladder.ANNIHILATE
170
+ and right.kind is Ladder.CREATE
171
+ and left.mode == right.mode
172
+ ):
173
+ return SwapResult(terms=((1, ()), *swapped))
174
+ return SwapResult(terms=swapped)
175
+
176
+ def is_normal_ordered_pair(self, left: Op, right: Op) -> bool:
177
+ """Check normal ordering for bosonic operators."""
178
+ if left.kind is Ladder.CREATE and right.kind is Ladder.ANNIHILATE:
179
+ return True
180
+ if left.kind is Ladder.ANNIHILATE and right.kind is Ladder.CREATE:
181
+ return False
182
+ return mode_sort_key(left.mode) <= mode_sort_key(right.mode)
183
+
184
+ def dagger(self, op: Op) -> Op:
185
+ """Flip creation ↔ annihilation."""
186
+ flipped = Ladder.CREATE if op.kind is Ladder.ANNIHILATE else Ladder.ANNIHILATE
187
+ return replace(op, kind=flipped)
188
+
189
+ def __eq__(self, other: object) -> bool: # noqa: D105
190
+ return isinstance(other, BosonRules)
191
+
192
+ def __hash__(self) -> int: # noqa: D105
193
+ return hash(type(self))
194
+
195
+ def __repr__(self) -> str: # noqa: D105
196
+ return "BosonRules()"
197
+
198
+
199
+ _PAULI_PRODUCT: dict[tuple[Hashable, Hashable], tuple[complex, Hashable]] = {
200
+ (Pauli.X, Pauli.Y): (1j, Pauli.Z),
201
+ (Pauli.Y, Pauli.X): (-1j, Pauli.Z),
202
+ (Pauli.Y, Pauli.Z): (1j, Pauli.X),
203
+ (Pauli.Z, Pauli.Y): (-1j, Pauli.X),
204
+ (Pauli.Z, Pauli.X): (1j, Pauli.Y),
205
+ (Pauli.X, Pauli.Z): (-1j, Pauli.Y),
206
+ }
207
+
208
+
209
+ class PauliRules:
210
+ """Pauli algebra: σ_a σ_b = δ_ab + i ε_abc σ_c on a site, commute across sites."""
211
+
212
+ def swap(self, left: Op, right: Op) -> SwapResult:
213
+ """Commute across sites, multiply out on the same site."""
214
+ if left.mode != right.mode:
215
+ return SwapResult(terms=((+1, (right, left)),))
216
+ if left.kind == right.kind:
217
+ return SwapResult(terms=((1, ()),))
218
+ phase, kind = _PAULI_PRODUCT[(left.kind, right.kind)]
219
+ return SwapResult(terms=((phase, (replace(left, kind=kind),)),))
220
+
221
+ def is_normal_ordered_pair(self, left: Op, right: Op) -> bool:
222
+ """Sites in mode order, at most one operator per site."""
223
+ if left.mode == right.mode:
224
+ return False
225
+ return mode_sort_key(left.mode) <= mode_sort_key(right.mode)
226
+
227
+ def dagger(self, op: Op) -> Op:
228
+ """Pauli operators are Hermitian: dagger is identity."""
229
+ return op
230
+
231
+ def __eq__(self, other: object) -> bool: # noqa: D105
232
+ return isinstance(other, PauliRules)
233
+
234
+ def __hash__(self) -> int: # noqa: D105
235
+ return hash(type(self))
236
+
237
+ def __repr__(self) -> str: # noqa: D105
238
+ return "PauliRules()"
239
+
240
+
241
+ FERMION = FermionRules()
242
+ BOSON = BosonRules()
243
+ PAULI = PauliRules()
244
+
245
+
246
+ def mode_sort_key(mode: Hashable) -> tuple[object, ...]:
247
+ """Canonical sort key for modes.
248
+
249
+ Stratifies by type first, then compares within type.
250
+
251
+ Parameters
252
+ ----------
253
+ mode : Hashable
254
+ The mode label.
255
+
256
+ Returns
257
+ -------
258
+ tuple
259
+ A universally comparable sort key.
260
+
261
+ Notes
262
+ -----
263
+ ``bool`` is a subclass of ``int``, so ``True`` and ``1`` produce
264
+ identical sort keys. Avoid using ``bool`` values as modes.
265
+
266
+ """
267
+ match mode:
268
+ case int():
269
+ return (0, mode)
270
+ case str():
271
+ return (1, mode)
272
+ case tuple():
273
+ return (2, *(mode_sort_key(m) for m in mode))
274
+ case _:
275
+ return (3, repr(mode))
fockit/constructors.py ADDED
@@ -0,0 +1,280 @@
1
+ """Constructor functions for operator expressions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Hashable
6
+
7
+ from fockit.algebra import BOSON, FERMION, PAULI, AlgebraRules, Ladder, Pauli
8
+ from fockit.operators import Monomial, Op, OpSum
9
+
10
+
11
+ def _mode(*labels: Hashable) -> Hashable:
12
+ return labels if len(labels) > 1 else labels[0]
13
+
14
+
15
+ def create(*labels: Hashable, rules: AlgebraRules = FERMION) -> OpSum:
16
+ """Creation operator with the given mode label(s).
17
+
18
+ If one label given: mode is that label.
19
+ If multiple labels given: mode is the tuple of labels.
20
+
21
+ Parameters
22
+ ----------
23
+ *labels : Hashable
24
+ Mode label components.
25
+ rules : AlgebraRules, optional
26
+ Algebra rules. Default: ``FERMION``.
27
+
28
+ Returns
29
+ -------
30
+ OpSum
31
+
32
+ """
33
+ return OpSum({Monomial(ops=(Op(_mode(*labels), Ladder.CREATE, rules),)): 1})
34
+
35
+
36
+ def annihilate(*labels: Hashable, rules: AlgebraRules = FERMION) -> OpSum:
37
+ """Annihilation operator.
38
+
39
+ Parameters
40
+ ----------
41
+ *labels : Hashable
42
+ Mode label components.
43
+ rules : AlgebraRules, optional
44
+ Algebra rules. Default: ``FERMION``.
45
+
46
+ Returns
47
+ -------
48
+ OpSum
49
+
50
+ """
51
+ return OpSum({Monomial(ops=(Op(_mode(*labels), Ladder.ANNIHILATE, rules),)): 1})
52
+
53
+
54
+ def number(*labels: Hashable, rules: AlgebraRules = FERMION) -> OpSum:
55
+ """Return the number operator create† * annihilate.
56
+
57
+ Parameters
58
+ ----------
59
+ *labels : Hashable
60
+ Mode label components.
61
+ rules : AlgebraRules, optional
62
+ Algebra rules. Default: ``FERMION``.
63
+
64
+ Returns
65
+ -------
66
+ OpSum
67
+
68
+ """
69
+ return create(*labels, rules=rules) * annihilate(*labels, rules=rules)
70
+
71
+
72
+ def cdag(*labels: Hashable) -> OpSum:
73
+ """Fermionic creation operator c†.
74
+
75
+ Parameters
76
+ ----------
77
+ *labels : Hashable
78
+ Mode label components.
79
+
80
+ Returns
81
+ -------
82
+ OpSum
83
+
84
+ """
85
+ return create(*labels, rules=FERMION)
86
+
87
+
88
+ def c(*labels: Hashable) -> OpSum:
89
+ """Fermionic annihilation operator c.
90
+
91
+ Parameters
92
+ ----------
93
+ *labels : Hashable
94
+ Mode label components.
95
+
96
+ Returns
97
+ -------
98
+ OpSum
99
+
100
+ """
101
+ return annihilate(*labels, rules=FERMION)
102
+
103
+
104
+ def n(*labels: Hashable) -> OpSum:
105
+ """Fermionic number operator n = c† c.
106
+
107
+ Parameters
108
+ ----------
109
+ *labels : Hashable
110
+ Mode label components.
111
+
112
+ Returns
113
+ -------
114
+ OpSum
115
+
116
+ """
117
+ return number(*labels, rules=FERMION)
118
+
119
+
120
+ def bdag(*labels: Hashable) -> OpSum:
121
+ """Bosonic creation operator b†.
122
+
123
+ Parameters
124
+ ----------
125
+ *labels : Hashable
126
+ Mode label components.
127
+
128
+ Returns
129
+ -------
130
+ OpSum
131
+
132
+ """
133
+ return create(*labels, rules=BOSON)
134
+
135
+
136
+ def b(*labels: Hashable) -> OpSum:
137
+ """Bosonic annihilation operator b.
138
+
139
+ Parameters
140
+ ----------
141
+ *labels : Hashable
142
+ Mode label components.
143
+
144
+ Returns
145
+ -------
146
+ OpSum
147
+
148
+ """
149
+ return annihilate(*labels, rules=BOSON)
150
+
151
+
152
+ def nb(*labels: Hashable) -> OpSum:
153
+ """Bosonic number operator n_b = b† b.
154
+
155
+ Parameters
156
+ ----------
157
+ *labels : Hashable
158
+ Mode label components.
159
+
160
+ Returns
161
+ -------
162
+ OpSum
163
+
164
+ """
165
+ return number(*labels, rules=BOSON)
166
+
167
+
168
+ def sp(*labels: Hashable) -> OpSum:
169
+ """Pauli raising operator σ+ = (σx + iσy)/2.
170
+
171
+ Parameters
172
+ ----------
173
+ *labels : Hashable
174
+ Mode label components.
175
+
176
+ Returns
177
+ -------
178
+ OpSum
179
+
180
+ """
181
+ return 0.5 * sigma_x(*labels) + 0.5j * sigma_y(*labels)
182
+
183
+
184
+ def sm(*labels: Hashable) -> OpSum:
185
+ """Pauli lowering operator σ- = (σx - iσy)/2.
186
+
187
+ Parameters
188
+ ----------
189
+ *labels : Hashable
190
+ Mode label components.
191
+
192
+ Returns
193
+ -------
194
+ OpSum
195
+
196
+ """
197
+ return 0.5 * sigma_x(*labels) - 0.5j * sigma_y(*labels)
198
+
199
+
200
+ def sigma_plus(*labels: Hashable) -> OpSum:
201
+ """Pauli raising operator σ+.
202
+
203
+ Parameters
204
+ ----------
205
+ *labels : Hashable
206
+ Mode label components.
207
+
208
+ Returns
209
+ -------
210
+ OpSum
211
+
212
+ """
213
+ return sp(*labels)
214
+
215
+
216
+ def sigma_minus(*labels: Hashable) -> OpSum:
217
+ """Pauli lowering operator σ-.
218
+
219
+ Parameters
220
+ ----------
221
+ *labels : Hashable
222
+ Mode label components.
223
+
224
+ Returns
225
+ -------
226
+ OpSum
227
+
228
+ """
229
+ return sm(*labels)
230
+
231
+
232
+ def sigma_x(*labels: Hashable) -> OpSum:
233
+ """Pauli σ_x operator.
234
+
235
+ Parameters
236
+ ----------
237
+ *labels : Hashable
238
+ Mode label components.
239
+
240
+ Returns
241
+ -------
242
+ OpSum
243
+
244
+ """
245
+ mode = _mode(*labels)
246
+ return OpSum({Monomial(ops=(Op(mode, Pauli.X, PAULI),)): 1})
247
+
248
+
249
+ def sigma_y(*labels: Hashable) -> OpSum:
250
+ """Pauli σ_y operator.
251
+
252
+ Parameters
253
+ ----------
254
+ *labels : Hashable
255
+ Mode label components.
256
+
257
+ Returns
258
+ -------
259
+ OpSum
260
+
261
+ """
262
+ mode = _mode(*labels)
263
+ return OpSum({Monomial(ops=(Op(mode, Pauli.Y, PAULI),)): 1})
264
+
265
+
266
+ def sigma_z(*labels: Hashable) -> OpSum:
267
+ """Pauli σ_z operator.
268
+
269
+ Parameters
270
+ ----------
271
+ *labels : Hashable
272
+ Mode label components.
273
+
274
+ Returns
275
+ -------
276
+ OpSum
277
+
278
+ """
279
+ mode = _mode(*labels)
280
+ return OpSum({Monomial(ops=(Op(mode, Pauli.Z, PAULI),)): 1})