temfpy 0.1.0__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.
- temfpy/__init__.py +14 -0
- temfpy/_version.py +34 -0
- temfpy/gutzwiller.py +186 -0
- temfpy/iMPS.py +304 -0
- temfpy/pfaffian.py +2055 -0
- temfpy/schmidt_utils.py +319 -0
- temfpy/slater.py +1437 -0
- temfpy/testing.py +160 -0
- temfpy/utils.py +103 -0
- temfpy-0.1.0.dist-info/METADATA +94 -0
- temfpy-0.1.0.dist-info/RECORD +13 -0
- temfpy-0.1.0.dist-info/WHEEL +4 -0
- temfpy-0.1.0.dist-info/licenses/LICENSE +7 -0
temfpy/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import logging as _logging
|
|
2
|
+
|
|
3
|
+
# Try to get version from hatch-vcs generated file first, then fallback
|
|
4
|
+
try:
|
|
5
|
+
from ._version import __version__
|
|
6
|
+
except ImportError:
|
|
7
|
+
from importlib.metadata import version
|
|
8
|
+
__version__ = version("temfpy")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def setup_logging(level=_logging.INFO):
|
|
12
|
+
_logging.basicConfig(
|
|
13
|
+
level=level,
|
|
14
|
+
)
|
temfpy/_version.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"__version__",
|
|
6
|
+
"__version_tuple__",
|
|
7
|
+
"version",
|
|
8
|
+
"version_tuple",
|
|
9
|
+
"__commit_id__",
|
|
10
|
+
"commit_id",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
TYPE_CHECKING = False
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from typing import Tuple
|
|
16
|
+
from typing import Union
|
|
17
|
+
|
|
18
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
+
COMMIT_ID = Union[str, None]
|
|
20
|
+
else:
|
|
21
|
+
VERSION_TUPLE = object
|
|
22
|
+
COMMIT_ID = object
|
|
23
|
+
|
|
24
|
+
version: str
|
|
25
|
+
__version__: str
|
|
26
|
+
__version_tuple__: VERSION_TUPLE
|
|
27
|
+
version_tuple: VERSION_TUPLE
|
|
28
|
+
commit_id: COMMIT_ID
|
|
29
|
+
__commit_id__: COMMIT_ID
|
|
30
|
+
|
|
31
|
+
__version__ = version = '0.1.0'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 1, 0)
|
|
33
|
+
|
|
34
|
+
__commit_id__ = commit_id = None
|
temfpy/gutzwiller.py
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# Copyright (C) TeMFPy Developers, MIT license
|
|
2
|
+
r"Tools for Gutzwiller projecting MPS to a smaller on-site Hilbert space."
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
import warnings
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from tenpy import networks
|
|
10
|
+
import tenpy.linalg as npc
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def abrikosov_ph(
|
|
16
|
+
mps: networks.MPS,
|
|
17
|
+
*,
|
|
18
|
+
inplace: bool = False,
|
|
19
|
+
return_canonical: bool = True,
|
|
20
|
+
cutoff: float = 1e-12,
|
|
21
|
+
) -> None | networks.MPS:
|
|
22
|
+
r"""Gutzwiller projection from Abrikosov fermions to a spin-1/2 Hilbert space.
|
|
23
|
+
|
|
24
|
+
The input MPS is assumed to describe Abrikosov fermions, with the down
|
|
25
|
+
spins particle-hole transformed; i.e.:
|
|
26
|
+
|
|
27
|
+
.. math::
|
|
28
|
+
|
|
29
|
+
c_{i,\uparrow} := f_{i,\uparrow},
|
|
30
|
+
\qquad \qquad
|
|
31
|
+
c_{i,\downarrow} := f_{i,\downarrow}^\dagger.
|
|
32
|
+
|
|
33
|
+
Therefore, it must contain an even number of spinless fermion sites:
|
|
34
|
+
sites :math:`2i` and :math:`2i+1` represent modes :math:`c_{i\uparrow}` and
|
|
35
|
+
:math:`c_{i\downarrow}`, respectively.
|
|
36
|
+
These pairs are projected to a spin-1/2 Hilbert space using the following rules:
|
|
37
|
+
|
|
38
|
+
- Zero occupation → spin-down state
|
|
39
|
+
- Single occupation → unphysical states, dropped
|
|
40
|
+
- Double occupation → spin-up state
|
|
41
|
+
|
|
42
|
+
Therefore, depending on the conserved charge of the input MPS, only the following charge blocks
|
|
43
|
+
of the virtual legs are kept:
|
|
44
|
+
|
|
45
|
+
- ``'N'`` (particle number) → even ``'N'`` blocks → ``'S_z'`` conserved
|
|
46
|
+
- ``'parity'`` → even ``'parity'`` blocks → no conserved charge
|
|
47
|
+
|
|
48
|
+
Parameters
|
|
49
|
+
----------
|
|
50
|
+
mps:
|
|
51
|
+
MPS representing the wave function to be projected.
|
|
52
|
+
Must be of even length and every site must be an instance
|
|
53
|
+
of :class:`~tenpy.networks.site.FermionSite`.
|
|
54
|
+
inplace:
|
|
55
|
+
Whether to transform the original MPS in place.
|
|
56
|
+
return_canonical:
|
|
57
|
+
Whether to transform the output MPS to right canonical form.
|
|
58
|
+
cutoff:
|
|
59
|
+
Cutoff for Schmidt values to keep in the canonical form.
|
|
60
|
+
|
|
61
|
+
Returns
|
|
62
|
+
-------
|
|
63
|
+
The Gutzwiller projected ``mps``, if ``inplace`` is :obj:`False`.
|
|
64
|
+
|
|
65
|
+
Note
|
|
66
|
+
----
|
|
67
|
+
Currently, no symmetry quantum numbers other than fermion
|
|
68
|
+
number or parity can be handled.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
assert (
|
|
72
|
+
mps.L % 2 == 0
|
|
73
|
+
), "Odd-length MPS cannot represent an Abrikosov fermion Hilbert space"
|
|
74
|
+
# TODO: allow grouped sites which include a FermionicSite
|
|
75
|
+
assert isinstance(
|
|
76
|
+
mps.sites[0], networks.FermionSite
|
|
77
|
+
), f"All sites must be fermionic, found: {mps.sites[0]}"
|
|
78
|
+
|
|
79
|
+
def gen_leg_mask(leg: npc.charges.LegCharge) -> np.ndarray:
|
|
80
|
+
"""Generates a mask selecting the physical charge blocks for a given
|
|
81
|
+
fermionic leg.
|
|
82
|
+
|
|
83
|
+
The physical charge blocks depend on the conserved charge of the
|
|
84
|
+
given ``leg``:
|
|
85
|
+
|
|
86
|
+
- ``'N'`` → even particle charge blocks
|
|
87
|
+
- ``'parity'`` → even parity charge block
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
Parameters
|
|
91
|
+
----------
|
|
92
|
+
leg:
|
|
93
|
+
The fermionic leg for which the mask is generated.
|
|
94
|
+
|
|
95
|
+
Returns
|
|
96
|
+
-------
|
|
97
|
+
A boolean mask selecting the physical charge blocks
|
|
98
|
+
that can be used by :class:`~tenpy.networks.Array.iproject`.
|
|
99
|
+
|
|
100
|
+
"""
|
|
101
|
+
mask = (leg.to_qflat() % 2 == 0).ravel()
|
|
102
|
+
|
|
103
|
+
return mask
|
|
104
|
+
|
|
105
|
+
if not inplace:
|
|
106
|
+
mps = mps.copy()
|
|
107
|
+
logger.debug(f"Deep copied MPS before Gutzwiller projection.")
|
|
108
|
+
|
|
109
|
+
conserved_fermion = mps.sites[0].conserve
|
|
110
|
+
if conserved_fermion == "N":
|
|
111
|
+
conserved_spin = "Sz"
|
|
112
|
+
elif conserved_fermion == "parity":
|
|
113
|
+
conserved_spin = None
|
|
114
|
+
else:
|
|
115
|
+
raise ValueError(
|
|
116
|
+
f"FermionSite must conserve either 'N' or 'parity', found {conserved_fermion}"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# TeNPy bindings
|
|
120
|
+
spin_site = networks.SpinHalfSite(conserved_spin)
|
|
121
|
+
spin_leg = spin_site.leg
|
|
122
|
+
chinfo_s = spin_leg.chinfo
|
|
123
|
+
|
|
124
|
+
# We start by grouping neighboring sites
|
|
125
|
+
# This will result in LegPipe objects for all physical legs
|
|
126
|
+
mps.group_sites(2)
|
|
127
|
+
|
|
128
|
+
# The mask for the physical leg is independent of the site
|
|
129
|
+
mask_p = gen_leg_mask(mps._B[0].get_leg("p"))
|
|
130
|
+
|
|
131
|
+
for idx, B in enumerate(mps._B):
|
|
132
|
+
# Remove LegPipe structure
|
|
133
|
+
B.legs[B.get_leg_index("p")] = B.get_leg("p").to_LegCharge()
|
|
134
|
+
|
|
135
|
+
mask_vL = gen_leg_mask(B.get_leg("vL"))
|
|
136
|
+
mask_vR = gen_leg_mask(B.get_leg("vR"))
|
|
137
|
+
|
|
138
|
+
# Change the occupation number leg charges to spin charges
|
|
139
|
+
# --------------------------------------------------------
|
|
140
|
+
|
|
141
|
+
B.iproject([mask_vL, mask_p, mask_vR], ["vL", "p", "vR"])
|
|
142
|
+
|
|
143
|
+
# Change the occupation number leg charges to spin charges,
|
|
144
|
+
# if conserved.
|
|
145
|
+
# ------------------------------------------------------------
|
|
146
|
+
if conserved_spin is "Sz":
|
|
147
|
+
B.chinfo = chinfo_s
|
|
148
|
+
|
|
149
|
+
leg_vL, leg_p, leg_vR = [B.get_leg(label) for label in ["vL", "p", "vR"]]
|
|
150
|
+
|
|
151
|
+
leg_p.chinfo = chinfo_s
|
|
152
|
+
leg_p.charges = spin_leg.charges
|
|
153
|
+
|
|
154
|
+
leg_vL.chinfo = chinfo_s
|
|
155
|
+
leg_vL.charges -= idx
|
|
156
|
+
|
|
157
|
+
leg_vR.chinfo = chinfo_s
|
|
158
|
+
leg_vR.charges -= idx + 1
|
|
159
|
+
|
|
160
|
+
else: # None
|
|
161
|
+
B = B.drop_charge(charge="parity_N", chinfo=chinfo_s)
|
|
162
|
+
|
|
163
|
+
mps.chinfo = chinfo_s
|
|
164
|
+
mps.grouped = 1
|
|
165
|
+
mps.sites = [spin_site] * mps.L
|
|
166
|
+
|
|
167
|
+
# Transform into right canoncial form
|
|
168
|
+
mps.form = [None] * mps.L
|
|
169
|
+
mps._S = [None] * (mps.L + 1)
|
|
170
|
+
|
|
171
|
+
logger.info(
|
|
172
|
+
"Completed projection to spin-1/2 space. Conserved charge is now %s",
|
|
173
|
+
conserved_spin,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
if return_canonical:
|
|
177
|
+
mps.canonical_form(cutoff=cutoff)
|
|
178
|
+
logger.info("Transformed MPS to right canonical form")
|
|
179
|
+
else:
|
|
180
|
+
warnings.warn(
|
|
181
|
+
"The MPS is not in canonical form after Gutzwiller projection.\n"
|
|
182
|
+
"Consider setting 'return_canonical=True'",
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
if not inplace:
|
|
186
|
+
return mps
|
temfpy/iMPS.py
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
# Copyright (C) TeMFPy Developers, MIT license
|
|
2
|
+
r"""Tools for converting finite to infinite MPS."""
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
import warnings
|
|
6
|
+
from typing import NamedTuple
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
import tenpy.linalg.np_conserved as npc
|
|
11
|
+
from tenpy import networks as nw
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
_UNITARY_TOL = 1e-6
|
|
16
|
+
_SCHMIDT_TOL = 1e-6
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def overlap_schmidt(bra: nw.mps.MPS, ket: nw.mps.MPS, mode: str) -> npc.Array:
|
|
20
|
+
r"""Overlap or optimal basis rotation between two sets of Schmidt vectors.
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
bra:
|
|
25
|
+
Bra Schmidt vectors.
|
|
26
|
+
ket:
|
|
27
|
+
Ket Schmidt vectors.
|
|
28
|
+
mode:
|
|
29
|
+
If the overlap is between "left" or "right" Schmidt vectors.
|
|
30
|
+
|
|
31
|
+
Returns
|
|
32
|
+
-------
|
|
33
|
+
overlap: :class:`~tenpy.linalg.np_conserved.Array`
|
|
34
|
+
The overlaps :math:`(C_0)_{\alpha\beta} = \langle L_\alpha'|L_\beta \rangle`
|
|
35
|
+
or :math:`(D_0)_{\alpha\beta} = \langle R_\beta'|R_\alpha \rangle`,
|
|
36
|
+
depending on ``mode``.
|
|
37
|
+
|
|
38
|
+
Legs are labelled ``"vL"`` (incoming) and ``"vR"`` (outgoing).
|
|
39
|
+
"""
|
|
40
|
+
assert bra.L == ket.L, "The two MPS have different lengths."
|
|
41
|
+
mode = mode.lower()
|
|
42
|
+
|
|
43
|
+
# ---- Compute overlap ----
|
|
44
|
+
if mode == "left":
|
|
45
|
+
TM = nw.TransferMatrix(bra, ket, transpose=True, form="A")
|
|
46
|
+
elif mode == "right":
|
|
47
|
+
TM = nw.TransferMatrix(bra, ket, transpose=False, form="B", charge_sector=None)
|
|
48
|
+
else:
|
|
49
|
+
raise ValueError("`mode` must be either 'left' or 'right', got " + repr(mode))
|
|
50
|
+
|
|
51
|
+
# Identity matrix with legs matching the input legs of the transfer matrix
|
|
52
|
+
Id = npc.Array.from_ndarray([[1.0]], TM.pipe.legs, labels=TM.label_split)
|
|
53
|
+
|
|
54
|
+
# nw.TransferMatrix makes sure that the first legtwo_leg is always ingoing and
|
|
55
|
+
# the second leg is always outgoing.
|
|
56
|
+
# Therefore, it is safe to just set the labels here instead of using
|
|
57
|
+
# ireplace_labels.
|
|
58
|
+
overlap = TM.matvec(Id).iset_leg_labels(["vL", "vR"])
|
|
59
|
+
|
|
60
|
+
return overlap
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def basis_rotation(
|
|
64
|
+
overlap: npc.Array,
|
|
65
|
+
Schmidt_bra: np.ndarray,
|
|
66
|
+
Schmidt_ket: np.ndarray,
|
|
67
|
+
mode: str,
|
|
68
|
+
form: str = "B",
|
|
69
|
+
unitary_tol: float = _UNITARY_TOL,
|
|
70
|
+
schmidt_tol: float = _SCHMIDT_TOL,
|
|
71
|
+
) -> tuple[npc.Array, float, float]:
|
|
72
|
+
r"""Overlap or optimal basis rotation between two sets of Schmidt vectors.
|
|
73
|
+
|
|
74
|
+
Parameters
|
|
75
|
+
----------
|
|
76
|
+
overlap:
|
|
77
|
+
The overlaps :math:`(C_0)_{\alpha\beta} = \langle L_\alpha'|L_\beta \rangle`
|
|
78
|
+
or :math:`(D_0)_{\alpha\beta} = \langle R_\beta'|R_\alpha \rangle`,
|
|
79
|
+
depending on ``mode``.
|
|
80
|
+
|
|
81
|
+
Legs should be labelled ``"vL"`` (incoming) and ``"vR"`` (outgoing).
|
|
82
|
+
Schmidt_bra:
|
|
83
|
+
Schmidt values corresponding to the bra Schmidt vectors :math:`\langle L'|`
|
|
84
|
+
or :math:`\langle R'|`.
|
|
85
|
+
Schmidt_ket:
|
|
86
|
+
Schmidt values corresponding to the ket Schmidt vectors :math:`|L \rangle`
|
|
87
|
+
or :math:`|R \rangle`.
|
|
88
|
+
mode:
|
|
89
|
+
If the overlap is between "left" or "right" Schmidt vectors.
|
|
90
|
+
form:
|
|
91
|
+
Whether the basis rotation is to be used for a left ("A")
|
|
92
|
+
or a right ("B", default) canonical MPS tensor.
|
|
93
|
+
unitary_tol:
|
|
94
|
+
Highest allowed deviation from unitarity (weighted with Schmidt values)
|
|
95
|
+
in the overlaps before a warning is raised.
|
|
96
|
+
schmidt_tol:
|
|
97
|
+
Highest allowed mixing between unequal Schmidt value sectors
|
|
98
|
+
before a warning is raised.
|
|
99
|
+
|
|
100
|
+
Returns
|
|
101
|
+
-------
|
|
102
|
+
rotation_matrix: :class:`~tenpy.linalg.np_conserved.Array`
|
|
103
|
+
If :obj:`True`, the optimal unitary basis rotation matrix :math:`C` that
|
|
104
|
+
minimises the iMPS conversion error.
|
|
105
|
+
|
|
106
|
+
Legs are labelled ``"vL"`` (incoming) and ``"vR"`` (outgoing).
|
|
107
|
+
unitary_error: float
|
|
108
|
+
Deviation of the overlap matrix from unitarity,
|
|
109
|
+
measured as the square root of the trace of
|
|
110
|
+
:math:`S_{\rm ket} (C_0^\dagger C_0 - \mathbb{I}) S_{\rm ket}`.
|
|
111
|
+
schmidt_error: float
|
|
112
|
+
Degree of mixing of Schmidt vectors with unequal Schmidt values,
|
|
113
|
+
measured as the norm of either :math:`S_{\rm bra} C - C_0 S_{\rm ket}`
|
|
114
|
+
or :math:`(C - C_0) S_{\rm ket}`, depending on ``mode`` and ``form``.
|
|
115
|
+
"""
|
|
116
|
+
mode = mode.lower()
|
|
117
|
+
err = f"`mode` must be either 'left' or 'right', got {mode!r}"
|
|
118
|
+
assert mode in ["left", "right"], err
|
|
119
|
+
|
|
120
|
+
form = form.upper()
|
|
121
|
+
assert form in ["A", "B"], f"`form` must be either 'A' or 'B', got {form!r}"
|
|
122
|
+
|
|
123
|
+
# make what follows independent of mode
|
|
124
|
+
v_bra, v_ket = ("vL", "vR") if mode == "left" else ("vR", "vL")
|
|
125
|
+
|
|
126
|
+
# ---- Test unitarity ----
|
|
127
|
+
# C @ S_ket
|
|
128
|
+
C_Sk = overlap.scale_axis(Schmidt_ket, v_ket)
|
|
129
|
+
# unitary_error^2 = tr(S_ket^2 - S_ket @ C^dagger @ C @ S_ket)
|
|
130
|
+
unitary_error = (
|
|
131
|
+
np.sum(Schmidt_ket**2) - npc.inner(C_Sk, C_Sk, do_conj=True)
|
|
132
|
+
) ** 0.5
|
|
133
|
+
logging.info(f"{mode.capitalize()} deviation from unitary: {unitary_error:.4e}")
|
|
134
|
+
if unitary_error > unitary_tol:
|
|
135
|
+
warnings.warn(
|
|
136
|
+
f"\n{mode.capitalize()} overlap matrix deviates from unitarity by "
|
|
137
|
+
f"{unitary_error}.\n"
|
|
138
|
+
"Increasing the bond dimension may be useful."
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# ---- Convert to unitary rotation matrix ----
|
|
142
|
+
if (mode, form) in [("left", "A"), ("right", "B")]:
|
|
143
|
+
# Schmidt values are inserted into the mixed canonical form
|
|
144
|
+
# at this entanglement cut. => Orthogonal Procustes for S_bra @ C @ S_ket
|
|
145
|
+
U, _, V = npc.svd(C_Sk.scale_axis(Schmidt_bra, v_bra))
|
|
146
|
+
else:
|
|
147
|
+
# Schmidt values are inserted into the mixed canonical form
|
|
148
|
+
# far from this entanglement cut. => Orthogonal Procustes for C @ S_ket^2
|
|
149
|
+
U, _, V = npc.svd(C_Sk.scale_axis(Schmidt_ket, v_ket))
|
|
150
|
+
overlap = npc.tensordot(U, V, 1)
|
|
151
|
+
|
|
152
|
+
# ---- Test Schmidt value deviations ----
|
|
153
|
+
# As above, the difference is due to whether Schmidt values appear in
|
|
154
|
+
# the mixed canonical form at this cut or far away
|
|
155
|
+
if (mode, form) in [("left", "A"), ("right", "B")]:
|
|
156
|
+
Sb_C = overlap.scale_axis(Schmidt_bra, v_bra)
|
|
157
|
+
else:
|
|
158
|
+
# not just C_Sk bc `overlap` has changed
|
|
159
|
+
Sb_C = overlap.scale_axis(Schmidt_ket, v_ket)
|
|
160
|
+
|
|
161
|
+
schmidt_error = npc.norm(Sb_C - C_Sk)
|
|
162
|
+
logging.info(f"{mode.capitalize()} Schmidt value mixing: {schmidt_error:.4e}")
|
|
163
|
+
if schmidt_error > schmidt_tol:
|
|
164
|
+
warnings.warn(
|
|
165
|
+
f"\nMixing between unequal Schmidt value sectors on the {mode} side is\n"
|
|
166
|
+
f"{schmidt_error}. Increasing the number of sites may help."
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
return overlap, unitary_error, schmidt_error
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class iMPSError(NamedTuple):
|
|
173
|
+
"""Container of the approximation errors accrued by :func:`MPS_to_iMPS`."""
|
|
174
|
+
|
|
175
|
+
left_unitary: float
|
|
176
|
+
"""Deviation of left environment from unitarity."""
|
|
177
|
+
left_schmidt: float
|
|
178
|
+
"""Mixing between unequal Schmidt values by the left environment."""
|
|
179
|
+
right_unitary: float
|
|
180
|
+
"""Deviation of left environment from unitarity."""
|
|
181
|
+
right_schmidt: float
|
|
182
|
+
"""Mixing between unequal Schmidt values by the right environment."""
|
|
183
|
+
|
|
184
|
+
@property
|
|
185
|
+
def left_total(self) -> float:
|
|
186
|
+
"""Total approximation error of the left environment."""
|
|
187
|
+
return (self.left_schmidt**2 + self.left_unitary**2) ** 0.5
|
|
188
|
+
|
|
189
|
+
@property
|
|
190
|
+
def right_total(self) -> float:
|
|
191
|
+
"""Total approximation error of the right environment."""
|
|
192
|
+
return (self.right_schmidt**2 + self.right_unitary**2) ** 0.5
|
|
193
|
+
|
|
194
|
+
@property
|
|
195
|
+
def total_error(self) -> float:
|
|
196
|
+
"""Total approximation error."""
|
|
197
|
+
return np.linalg.norm(self)
|
|
198
|
+
|
|
199
|
+
def __repr__(self) -> str:
|
|
200
|
+
fields = [f" {f}={x:.8e}" for f, x in zip(self._fields, self) if x != 0]
|
|
201
|
+
if len(fields) == 0:
|
|
202
|
+
return "iMPSError()"
|
|
203
|
+
else:
|
|
204
|
+
return "iMPSError(\n" + (",\n".join(fields)) + "\n)"
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def MPS_to_iMPS(
|
|
208
|
+
mps_short: nw.MPS,
|
|
209
|
+
mps_long: nw.MPS,
|
|
210
|
+
sites_per_cell: int,
|
|
211
|
+
cut: int,
|
|
212
|
+
unitary_tol: float = _UNITARY_TOL,
|
|
213
|
+
schmidt_tol: float = _SCHMIDT_TOL,
|
|
214
|
+
) -> tuple[nw.MPS, iMPSError]:
|
|
215
|
+
"""Constructs an iMPS by comparing two finite MPS.
|
|
216
|
+
|
|
217
|
+
The two MPS are expected to represent the ground states of a gapped,
|
|
218
|
+
translation invariant Hamiltonian on two system sizes that differ by
|
|
219
|
+
one repeating unit cell.
|
|
220
|
+
|
|
221
|
+
For sufficiently large systems, therefore, they are of the form
|
|
222
|
+
|
|
223
|
+
.. code::
|
|
224
|
+
|
|
225
|
+
...(A...B)(A...B)...
|
|
226
|
+
...(A...B)(A...B)(A...B)...
|
|
227
|
+
|
|
228
|
+
up to gauge transformations. The repeating unit cell ``(A...B)`` is
|
|
229
|
+
extracted from the longer chain, and its gauge is fixed by comparing its
|
|
230
|
+
left and right environments to the Schmidt vectors of the shorter chain.
|
|
231
|
+
|
|
232
|
+
Parameters
|
|
233
|
+
----------
|
|
234
|
+
mps_short:
|
|
235
|
+
MPS of the shorter chain.
|
|
236
|
+
mps_long:
|
|
237
|
+
MPS of the longer chain.
|
|
238
|
+
sites_per_cell:
|
|
239
|
+
Size of the iMPS unit cell.
|
|
240
|
+
cut:
|
|
241
|
+
First site of the repeating unit cell in ``mps_long``.
|
|
242
|
+
unitary_tol:
|
|
243
|
+
Maximum deviation of the gauge rotation matrices from unitarity
|
|
244
|
+
before a warning is raised.
|
|
245
|
+
schmidt_tol:
|
|
246
|
+
Maximum mixing of unequal Schmidt values by the gauge rotation matrices
|
|
247
|
+
before a warning is raised.
|
|
248
|
+
|
|
249
|
+
Returns
|
|
250
|
+
-------
|
|
251
|
+
iMPS: :class:`~tenpy.networks.mps.MPS`
|
|
252
|
+
iMPS with unit cell size ``sites_per_cell``, constructed from the
|
|
253
|
+
additional unit cell of ``mps_long``.
|
|
254
|
+
validation_metric: :class:`iMPSError`
|
|
255
|
+
Errors introduced during the conversion.
|
|
256
|
+
"""
|
|
257
|
+
# preliminary checks
|
|
258
|
+
L_short, L_long = mps_short.L, mps_long.L
|
|
259
|
+
if L_short + sites_per_cell != L_long:
|
|
260
|
+
raise ValueError(
|
|
261
|
+
"The given two MPS must differ by one unit cell, got "
|
|
262
|
+
f"{L_long} - {L_short} != {sites_per_cell}"
|
|
263
|
+
)
|
|
264
|
+
if mps_short.chinfo != mps_long.chinfo:
|
|
265
|
+
raise ValueError("Incompatible ChargeInfo in the two MPS")
|
|
266
|
+
assert all(x is not None for x in mps_short.form), "mps_short is not canonical"
|
|
267
|
+
assert all(x is not None for x in mps_long.form), "mps_long is not canonical"
|
|
268
|
+
|
|
269
|
+
# Schmidt values in the short chain at the reference cut
|
|
270
|
+
S0 = mps_short.get_SL(cut)
|
|
271
|
+
|
|
272
|
+
# Left gauge fixing matrix C
|
|
273
|
+
bra = mps_short.extract_segment(0, cut - 1)
|
|
274
|
+
ket = mps_long.extract_segment(0, cut - 1)
|
|
275
|
+
S_ket = mps_long.get_SL(cut)
|
|
276
|
+
C = overlap_schmidt(bra, ket, mode="left")
|
|
277
|
+
C, left_unitary, left_schmidt = basis_rotation(
|
|
278
|
+
C, S0, S_ket, mode="left", unitary_tol=unitary_tol, schmidt_tol=schmidt_tol
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
# Right gauge fixing matrix D
|
|
282
|
+
bra = mps_short.extract_segment(cut, L_short - 1)
|
|
283
|
+
ket = mps_long.extract_segment(cut + sites_per_cell, L_long - 1)
|
|
284
|
+
S_ket = mps_long.get_SL(cut + sites_per_cell)
|
|
285
|
+
D = overlap_schmidt(bra, ket, mode="right")
|
|
286
|
+
D, right_unitary, right_schmidt = basis_rotation(
|
|
287
|
+
D, S0, S_ket, mode="right", unitary_tol=unitary_tol, schmidt_tol=schmidt_tol
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
# Extract middle section of MPS in right canonical form
|
|
291
|
+
sites = mps_long.sites[cut : cut + sites_per_cell]
|
|
292
|
+
tensors = [mps_long.get_B(cut + i, form="B") for i in range(sites_per_cell)]
|
|
293
|
+
schmidt_values = mps_long._S[cut + 1 : cut + sites_per_cell]
|
|
294
|
+
|
|
295
|
+
# Apply gauge unitaries to first and last tensor
|
|
296
|
+
tensors[0] = npc.tensordot(C, tensors[0], axes=["vR", "vL"])
|
|
297
|
+
tensors[-1] = npc.tensordot(tensors[-1], D, axes=["vR", "vL"])
|
|
298
|
+
|
|
299
|
+
# Set Schmidt values on both ends to that of the reference MPS
|
|
300
|
+
schmidt_values = [S0] + schmidt_values + [S0]
|
|
301
|
+
|
|
302
|
+
iMPS = nw.MPS(sites, tensors, schmidt_values, bc="infinite", form="B")
|
|
303
|
+
error = iMPSError(left_unitary, left_schmidt, right_unitary, right_schmidt)
|
|
304
|
+
return iMPS, error
|