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 +53 -0
- fockit/_types.py +48 -0
- fockit/_version.py +24 -0
- fockit/algebra.py +275 -0
- fockit/constructors.py +280 -0
- fockit/converters.py +52 -0
- fockit/display.py +150 -0
- fockit/inspection.py +60 -0
- fockit/normal_order.py +142 -0
- fockit/operators.py +318 -0
- fockit/py.typed +0 -0
- fockit/simplify.py +89 -0
- fockit/solve/__init__.py +67 -0
- fockit/solve/basis.py +822 -0
- fockit/solve/diagonalize.py +96 -0
- fockit/solve/evolve.py +326 -0
- fockit/solve/matrix.py +88 -0
- fockit/solve/spaces.py +134 -0
- fockit/solve/thermal.py +196 -0
- fockit/transforms.py +151 -0
- fockit-0.0.2.dist-info/METADATA +83 -0
- fockit-0.0.2.dist-info/RECORD +24 -0
- fockit-0.0.2.dist-info/WHEEL +4 -0
- fockit-0.0.2.dist-info/licenses/LICENSE +21 -0
fockit/converters.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Converters from OpSum to external operator formats."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from fockit._types import as_complex
|
|
8
|
+
from fockit.algebra import FermionRules, Ladder
|
|
9
|
+
from fockit.operators import OpSum
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from triqs.operators import Operator as TriqsOperator
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def to_triqs(op: OpSum) -> TriqsOperator:
|
|
16
|
+
"""Convert a fermionic OpSum to a TRIQS Operator.
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
op : OpSum
|
|
21
|
+
Operator expression with ``FermionRules``.
|
|
22
|
+
|
|
23
|
+
Returns
|
|
24
|
+
-------
|
|
25
|
+
triqs.operators.Operator
|
|
26
|
+
|
|
27
|
+
"""
|
|
28
|
+
for mono in op.terms:
|
|
29
|
+
for single_op in mono.ops:
|
|
30
|
+
if not isinstance(single_op.rules, FermionRules):
|
|
31
|
+
name = type(single_op.rules).__name__
|
|
32
|
+
msg = f"to_triqs requires FermionRules, got {name}"
|
|
33
|
+
raise TypeError(msg)
|
|
34
|
+
|
|
35
|
+
from triqs.operators import Operator, c, c_dag
|
|
36
|
+
|
|
37
|
+
result = Operator()
|
|
38
|
+
for mono, coeff in op.terms.items():
|
|
39
|
+
c_val = as_complex(coeff)
|
|
40
|
+
term = c_val.real if c_val.imag == 0 else c_val
|
|
41
|
+
for ladder_op in mono.ops:
|
|
42
|
+
indices = (
|
|
43
|
+
ladder_op.mode
|
|
44
|
+
if isinstance(ladder_op.mode, tuple)
|
|
45
|
+
else (ladder_op.mode,)
|
|
46
|
+
)
|
|
47
|
+
if ladder_op.kind is Ladder.CREATE:
|
|
48
|
+
term = term * c_dag(*indices)
|
|
49
|
+
else:
|
|
50
|
+
term = term * c(*indices)
|
|
51
|
+
result = result + term
|
|
52
|
+
return result
|
fockit/display.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""Display and formatting for operator expressions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable, Hashable
|
|
6
|
+
|
|
7
|
+
from fockit._types import Coefficient
|
|
8
|
+
from fockit.algebra import (
|
|
9
|
+
BosonRules,
|
|
10
|
+
FermionRules,
|
|
11
|
+
Ladder,
|
|
12
|
+
Pauli,
|
|
13
|
+
PauliRules,
|
|
14
|
+
mode_sort_key,
|
|
15
|
+
)
|
|
16
|
+
from fockit.operators import Monomial, Op, OpSum, format_mode
|
|
17
|
+
|
|
18
|
+
# operators of other algebras show their kind as the symbol
|
|
19
|
+
_TEXT_SYMBOLS: dict[tuple[type, Hashable], str] = {
|
|
20
|
+
(FermionRules, Ladder.CREATE): "c†",
|
|
21
|
+
(FermionRules, Ladder.ANNIHILATE): "c",
|
|
22
|
+
(BosonRules, Ladder.CREATE): "b†",
|
|
23
|
+
(BosonRules, Ladder.ANNIHILATE): "b",
|
|
24
|
+
(PauliRules, Pauli.X): "σ_x",
|
|
25
|
+
(PauliRules, Pauli.Y): "σ_y",
|
|
26
|
+
(PauliRules, Pauli.Z): "σ_z",
|
|
27
|
+
}
|
|
28
|
+
_LATEX_SYMBOLS: dict[tuple[type, Hashable], str] = {
|
|
29
|
+
(FermionRules, Ladder.CREATE): r"c^{\dagger}",
|
|
30
|
+
(FermionRules, Ladder.ANNIHILATE): "c",
|
|
31
|
+
(BosonRules, Ladder.CREATE): r"b^{\dagger}",
|
|
32
|
+
(BosonRules, Ladder.ANNIHILATE): "b",
|
|
33
|
+
(PauliRules, Pauli.X): r"\sigma^{x}",
|
|
34
|
+
(PauliRules, Pauli.Y): r"\sigma^{y}",
|
|
35
|
+
(PauliRules, Pauli.Z): r"\sigma^{z}",
|
|
36
|
+
}
|
|
37
|
+
_LATEX_LABELS = {"up": r"\uparrow", "dn": r"\downarrow"}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def to_text(op: OpSum) -> str:
|
|
41
|
+
"""Format an OpSum as a human-readable string.
|
|
42
|
+
|
|
43
|
+
Parameters
|
|
44
|
+
----------
|
|
45
|
+
op : OpSum
|
|
46
|
+
|
|
47
|
+
Returns
|
|
48
|
+
-------
|
|
49
|
+
str
|
|
50
|
+
|
|
51
|
+
"""
|
|
52
|
+
return _join_terms(op, _op_text)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def to_latex(op: OpSum) -> str:
|
|
56
|
+
r"""Convert an operator expression to LaTeX.
|
|
57
|
+
|
|
58
|
+
Parameters
|
|
59
|
+
----------
|
|
60
|
+
op : OpSum
|
|
61
|
+
|
|
62
|
+
Returns
|
|
63
|
+
-------
|
|
64
|
+
str
|
|
65
|
+
|
|
66
|
+
"""
|
|
67
|
+
return _join_terms(op, _op_latex)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _join_terms(op: OpSum, format_op: Callable[[Op], str]) -> str:
|
|
71
|
+
"""Write the terms in display order, joined with + and -."""
|
|
72
|
+
if not op.terms:
|
|
73
|
+
return "0"
|
|
74
|
+
|
|
75
|
+
sorted_terms = sorted(
|
|
76
|
+
op.terms.items(), key=lambda item: _monomial_sort_key(item[0])
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
parts: list[str] = []
|
|
80
|
+
for mono, raw_coeff in sorted_terms:
|
|
81
|
+
coeff = _real_if_possible(raw_coeff)
|
|
82
|
+
if not mono.ops:
|
|
83
|
+
parts.append(str(coeff))
|
|
84
|
+
continue
|
|
85
|
+
|
|
86
|
+
ops_str = " ".join(format_op(single_op) for single_op in mono.ops)
|
|
87
|
+
|
|
88
|
+
coeff_str = _format_coeff(coeff)
|
|
89
|
+
if coeff_str == "":
|
|
90
|
+
parts.append(ops_str)
|
|
91
|
+
elif coeff_str == "-":
|
|
92
|
+
parts.append(f"-{ops_str}")
|
|
93
|
+
else:
|
|
94
|
+
parts.append(f"{coeff_str} {ops_str}")
|
|
95
|
+
|
|
96
|
+
result = parts[0]
|
|
97
|
+
for part in parts[1:]:
|
|
98
|
+
if part.startswith("-"):
|
|
99
|
+
result += f" - {part[1:]}"
|
|
100
|
+
else:
|
|
101
|
+
result += f" + {part}"
|
|
102
|
+
return result
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _op_text(op: Op) -> str:
|
|
106
|
+
"""Format as e.g. ``c†(0, up)``."""
|
|
107
|
+
symbol = _TEXT_SYMBOLS.get((type(op.rules), op.kind), op.kind)
|
|
108
|
+
return f"{symbol}({format_mode(op.mode)})"
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _op_latex(op: Op) -> str:
|
|
112
|
+
r"""Format as e.g. ``c^{\dagger}_{0, \uparrow}``."""
|
|
113
|
+
symbol = _LATEX_SYMBOLS.get((type(op.rules), op.kind), op.kind)
|
|
114
|
+
labels = op.mode if isinstance(op.mode, tuple) else (op.mode,)
|
|
115
|
+
subscript = ", ".join(_LATEX_LABELS.get(label, str(label)) for label in labels)
|
|
116
|
+
return f"{symbol}_{{{subscript}}}"
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _monomial_sort_key(mono: Monomial) -> tuple[object, ...]:
|
|
120
|
+
"""Sort key for deterministic term ordering in display."""
|
|
121
|
+
keys = []
|
|
122
|
+
for op in mono.ops:
|
|
123
|
+
keys.append((_kind_sort_key(op.kind), mode_sort_key(op.mode)))
|
|
124
|
+
return (len(mono.ops), tuple(keys))
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _kind_sort_key(kind: Hashable) -> tuple[object, ...]:
|
|
128
|
+
"""Sort key for operator kind within display ordering."""
|
|
129
|
+
if isinstance(kind, str):
|
|
130
|
+
return (2, kind)
|
|
131
|
+
return (3, repr(kind))
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _real_if_possible(coeff: Coefficient) -> Coefficient:
|
|
135
|
+
"""Show a complex number without its zero part, e.g. ``0.5`` or ``-0.5j``."""
|
|
136
|
+
if isinstance(coeff, complex):
|
|
137
|
+
if coeff.imag == 0:
|
|
138
|
+
return coeff.real
|
|
139
|
+
if coeff.real == 0:
|
|
140
|
+
return complex(0.0, coeff.imag)
|
|
141
|
+
return coeff
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _format_coeff(coeff: Coefficient) -> str:
|
|
145
|
+
"""Format a coefficient for display."""
|
|
146
|
+
if coeff == 1:
|
|
147
|
+
return ""
|
|
148
|
+
if coeff == -1:
|
|
149
|
+
return "-"
|
|
150
|
+
return str(coeff)
|
fockit/inspection.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Inspection utilities for operator expressions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Hashable
|
|
6
|
+
|
|
7
|
+
from fockit.operators import OpSum
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def modes(op: OpSum) -> set[Hashable]:
|
|
11
|
+
"""Return the set of all mode labels appearing in the operator.
|
|
12
|
+
|
|
13
|
+
Parameters
|
|
14
|
+
----------
|
|
15
|
+
op : OpSum
|
|
16
|
+
|
|
17
|
+
Returns
|
|
18
|
+
-------
|
|
19
|
+
set[Hashable]
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
result: set[Hashable] = set()
|
|
23
|
+
for mono in op.terms:
|
|
24
|
+
for ladder_op in mono.ops:
|
|
25
|
+
result.add(ladder_op.mode)
|
|
26
|
+
return result
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def degree(op: OpSum) -> int:
|
|
30
|
+
"""Maximum monomial length (number of ladder operators).
|
|
31
|
+
|
|
32
|
+
Returns 0 for the zero operator.
|
|
33
|
+
|
|
34
|
+
Parameters
|
|
35
|
+
----------
|
|
36
|
+
op : OpSum
|
|
37
|
+
|
|
38
|
+
Returns
|
|
39
|
+
-------
|
|
40
|
+
int
|
|
41
|
+
|
|
42
|
+
"""
|
|
43
|
+
if not op.terms:
|
|
44
|
+
return 0
|
|
45
|
+
return max(len(mono) for mono in op.terms)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def num_terms(op: OpSum) -> int:
|
|
49
|
+
"""Return the number of nonzero terms.
|
|
50
|
+
|
|
51
|
+
Parameters
|
|
52
|
+
----------
|
|
53
|
+
op : OpSum
|
|
54
|
+
|
|
55
|
+
Returns
|
|
56
|
+
-------
|
|
57
|
+
int
|
|
58
|
+
|
|
59
|
+
"""
|
|
60
|
+
return len(op.terms)
|
fockit/normal_order.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""Normal ordering algorithm.
|
|
2
|
+
|
|
3
|
+
Every algebra goes through the same path, using only its ``swap`` and
|
|
4
|
+
``is_normal_ordered_pair`` rules. Pauli strings could be multiplied faster site
|
|
5
|
+
by site, or stored as bitmasks, but that would treat one algebra differently
|
|
6
|
+
from the rest.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import functools
|
|
12
|
+
|
|
13
|
+
from fockit._types import Coefficient, as_coefficient
|
|
14
|
+
from fockit.algebra import SwapResult
|
|
15
|
+
from fockit.operators import Monomial, Op, OpSum
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def normal_order(op: OpSum) -> OpSum:
|
|
19
|
+
"""Normal-order an operator expression.
|
|
20
|
+
|
|
21
|
+
Operators of different algebras commute and are grouped by algebra.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
op : OpSum
|
|
26
|
+
The operator to normal-order.
|
|
27
|
+
|
|
28
|
+
Returns
|
|
29
|
+
-------
|
|
30
|
+
OpSum
|
|
31
|
+
The normal-ordered operator.
|
|
32
|
+
|
|
33
|
+
"""
|
|
34
|
+
terms: dict[Monomial, Coefficient] = {}
|
|
35
|
+
for mono, coeff in op.terms.items():
|
|
36
|
+
for ordered, c in _order(mono.ops, Monomial(())).items():
|
|
37
|
+
_accumulate(terms, ordered, as_coefficient(coeff * c))
|
|
38
|
+
return OpSum(terms)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def is_normal_ordered(op: OpSum) -> bool:
|
|
42
|
+
"""Check if an operator expression is in normal order.
|
|
43
|
+
|
|
44
|
+
Parameters
|
|
45
|
+
----------
|
|
46
|
+
op : OpSum
|
|
47
|
+
|
|
48
|
+
Returns
|
|
49
|
+
-------
|
|
50
|
+
bool
|
|
51
|
+
|
|
52
|
+
"""
|
|
53
|
+
for mono in op.terms:
|
|
54
|
+
ops = mono.ops
|
|
55
|
+
for i in range(len(ops) - 1):
|
|
56
|
+
if not _is_ordered(ops[i], ops[i + 1]):
|
|
57
|
+
return False
|
|
58
|
+
return True
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _is_ordered(left: Op, right: Op) -> bool:
|
|
62
|
+
if type(left.rules) is type(right.rules):
|
|
63
|
+
return left.rules.is_normal_ordered_pair(left, right)
|
|
64
|
+
return type(left.rules).__name__ < type(right.rules).__name__
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _swap(left: Op, right: Op) -> SwapResult:
|
|
68
|
+
if type(left.rules) is type(right.rules):
|
|
69
|
+
return left.rules.swap(left, right)
|
|
70
|
+
return SwapResult(terms=((1, (right, left)),))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _order(prefix: tuple[Op, ...], tail: Monomial) -> dict[Monomial, Coefficient]:
|
|
74
|
+
"""Normal-order ``prefix * tail``, where ``tail`` is already normal ordered."""
|
|
75
|
+
terms: dict[Monomial, Coefficient] = {tail: 1}
|
|
76
|
+
for op in reversed(prefix):
|
|
77
|
+
new: dict[Monomial, Coefficient] = {}
|
|
78
|
+
for t, c1 in terms.items():
|
|
79
|
+
for m, c2 in _insert(op, t):
|
|
80
|
+
_accumulate(new, m, as_coefficient(c1 * c2))
|
|
81
|
+
terms = new
|
|
82
|
+
return terms
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@functools.lru_cache(maxsize=2**16)
|
|
86
|
+
def _insert(op: Op, tail: Monomial) -> tuple[tuple[Monomial, Coefficient], ...]:
|
|
87
|
+
"""Normal-order ``op * tail``, where ``tail`` is already normal ordered.
|
|
88
|
+
|
|
89
|
+
``op`` moves right past plain exchanges, picking up only their sign. The
|
|
90
|
+
first swap that does anything else is expanded, and the operators ``op``
|
|
91
|
+
passed are put back in front.
|
|
92
|
+
"""
|
|
93
|
+
ops = tail.ops
|
|
94
|
+
sign: Coefficient = 1
|
|
95
|
+
i = 0
|
|
96
|
+
while i < len(ops) and not _is_ordered(op, ops[i]):
|
|
97
|
+
terms = _swap(op, ops[i]).terms
|
|
98
|
+
if len(terms) != 1 or terms[0][1] != (ops[i], op):
|
|
99
|
+
break
|
|
100
|
+
sign = as_coefficient(sign * terms[0][0])
|
|
101
|
+
i += 1
|
|
102
|
+
|
|
103
|
+
passed = ops[:i]
|
|
104
|
+
rest: dict[Monomial, Coefficient] = {}
|
|
105
|
+
if i == len(ops) or _is_ordered(op, ops[i]):
|
|
106
|
+
if not passed or _is_ordered(passed[-1], op):
|
|
107
|
+
return ((Monomial((*passed, op, *ops[i:])), sign),)
|
|
108
|
+
rest[Monomial((op, *ops[i:]))] = sign
|
|
109
|
+
else:
|
|
110
|
+
for coeff, repl in terms:
|
|
111
|
+
scale = as_coefficient(sign * coeff)
|
|
112
|
+
for m, c in _order(repl, Monomial(ops[i + 1 :])).items():
|
|
113
|
+
_accumulate(rest, m, as_coefficient(scale * c))
|
|
114
|
+
|
|
115
|
+
acc: dict[Monomial, Coefficient] = {}
|
|
116
|
+
for m, c in rest.items():
|
|
117
|
+
if not passed:
|
|
118
|
+
_accumulate(acc, m, c)
|
|
119
|
+
elif not m.ops or _is_ordered(passed[-1], m.ops[0]):
|
|
120
|
+
_accumulate(acc, Monomial(passed + m.ops), c)
|
|
121
|
+
else:
|
|
122
|
+
for ordered, c2 in _order(passed, m).items():
|
|
123
|
+
_accumulate(acc, ordered, as_coefficient(c * c2))
|
|
124
|
+
return tuple(acc.items())
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _accumulate(
|
|
128
|
+
acc: dict[Monomial, Coefficient], mono: Monomial, coeff: Coefficient
|
|
129
|
+
) -> None:
|
|
130
|
+
"""Add coeff * mono into the accumulator dict.
|
|
131
|
+
|
|
132
|
+
Parameters
|
|
133
|
+
----------
|
|
134
|
+
acc : dict[Monomial, Coefficient]
|
|
135
|
+
mono : Monomial
|
|
136
|
+
coeff : Coefficient
|
|
137
|
+
|
|
138
|
+
"""
|
|
139
|
+
if mono in acc:
|
|
140
|
+
acc[mono] = as_coefficient(acc[mono] + coeff)
|
|
141
|
+
else:
|
|
142
|
+
acc[mono] = coeff
|