Exact n-site measurement with opt_einsum#
yastn.tn.fpeps.EnvCTM.measure_nsite_exact() contracts the CTM environment
around the rectangular window spanned by the requested sites exactly, row by
row. yastn.tn.fpeps.EnvCTM.measure_nsite_exact_oe() computes the same
number by handing the whole window, corners, edges, ket, bra and operators,
to opt_einsum as one tensor network. The contraction path is optimized,
individual bonds can be unrolled (sliced) to bound peak memory, and the norm
and the numerator can be evaluated separately so that many operators on the same
window share one norm contraction.
- EnvCTM.measure_nsite_exact_oe(*operators, sites=None, unroll=None, checkpoint_loop=False, optimizer='default', devices=None, mp_workers_per_device=0, projectors=None, per_combo_path=False, combo_path_kwargs=None) float#
Memory-efficient version of
measure_nsite_exact()using opt_einsum contraction path optimization, optional block-sparse index unrolling, and checkpointing.For
DoublePepsTensorPEPS, ket, operator and bra of every site enter the network as separate tensors, with the fermionic crossings between them asnconswap pairs; edge middle legs are unfused to match.For single-layer PEPS, falls back to the fused double-layer approach.
Returns
<psi| O0_s0 ... |psi> / <psi|psi>. Seemeasure_nsite_norm_exact_oe()for the norm-only contraction andmeasure_nsite_numerator_exact_oe()for the unnormalized numerator – callers that share a single norm across multiple numerator evaluations should use those split functions to control the autograd graph lifetime explicitly.- Parameters:
operators (Sequence[yastn.Tensor] | yastn.tn.mps.MpsMpoOBC) – Local operators of
<O0_s0 O1_s1 ...>, one plain two-leg operator per site. Alternatively a single MPO with one tensor per site, e.g. fromyastn.tn.mps.generate_mpo(), which measures a sum of operator products in one contraction. Its chain issitesas listed, in any order and not necessarily adjacent; it applies no reordering sign of its own, the sign of each term being part of the MPO. See Operators as MPO tensors.sites (Sequence[tuple[int, int]]) – A list of sites [s0, s1, …] matching corresponding operators.
unroll (dict or None) – Dict mapping bond labels to
int(uniform slice size) orlist[SlicedLeg]. See Network layout, bond labels and unrolling for the bond-label scheme.checkpoint_loop (bool) – If
Trueandunrollis notNone, each unroll iteration is wrapped intorch.utils.checkpoint.checkpoint(), trading recomputation for lower peak memory.optimizer (str or opt_einsum.paths.PathOptimizer) – Contraction-path optimizer passed to
opt_einsum.contract_path()."default"(alsoNone,"dp","dynamic-programming") usesopt_einsum.DynamicProgramming(minimize="write", search_outer=False, cost_cap=True), which minimizes the size of the intermediates. Any other value accepted by opt_einsum, e.g."greedy"or"auto", is passed through unchanged.devices (Sequence[str] or None) – Devices to spread the contraction over, e.g.
["cuda:0", "cuda:1"].None(default) contracts on the device of the PEPS tensors. Withunroll, the slice combinations are dispatched across the devices, which needsmp_workers_per_device >= 1; a single device with one worker contracts serially there and moves the result back. Withoutunroll, the network is moved todevices[0]and contracted there.mp_workers_per_device (int) – Number of worker processes per device in the multiprocess pool that contracts the slice combinations.
0(default) disables multiprocessing and requiresdevicesto beNoneor the PEPS device.per_combo_path (bool) – Only used with
unroll. IfTrue, search a separate contraction path for every slice combination, tuned to its slice dimensions and cached by shape, instead of reusing one path for all combinations. DefaultFalse.combo_path_kwargs (dict or None) – Options of the per-combination path search when
per_combo_path=True; keys may includeoptimizer,memory_limit,namesandwho.None(default) means{"optimizer": optimizer}.
- EnvCTM.measure_nsite_norm_exact_oe(*, sites, unroll=None, checkpoint_loop=False, optimizer='default', devices=None, mp_workers_per_device=0, projectors=None, per_combo_path=False, combo_path_kwargs=None)#
Contract only the norm <psi|psi> over the bounding window of
sites.Same contraction backend and options as
measure_nsite_exact_oe(), withoperatorsomitted; operator-bond labels('opb', k)inunrollare ignored. Use this when sharing a single norm value across multiple numerator evaluations (seemeasure_nsite_numerator_exact_oe()).
- EnvCTM.measure_nsite_numerator_exact_oe(*operators, sites, unroll=None, checkpoint_loop=False, optimizer='default', devices=None, mp_workers_per_device=0, projectors=None, per_combo_path=False, combo_path_kwargs=None)#
Contract only the unnormalized numerator
sign * <psi| O0_s0 ... |psi>.Same contraction backend and options as
measure_nsite_exact_oe(); the result is not divided by the norm. The caller is responsible for dividing by<psi|psi>(typically obtained viameasure_nsite_norm_exact_oe()).
Network layout, bond labels and unrolling#
The contraction builds a tensor network over the Nx x Ny window
enclosing the requested sites: the four CTM corners TL, TR, BL, BR,
the edge tensors T[j], B[j], L[i], R[i], and one double-layer site
* per window position. Every edge of that network carries a tuple
label; the unroll dict refers to those labels. Coordinates i (row,
0 ... Nx-1) and j (column, 0 ... Ny-1) are window-local, not
absolute lattice positions, and follow the Site(x, y) = (row, col)
convention:
j=-1 j=0 j=1 j=Ny-1 j=Ny
: : : : :
i=-1 TL --h,-1,-1-- T[0] --h,-1,0-- T[1] -- ... -- h,-1,Ny-1 -- TR
| | | | |
v,0,-1 v,0,0 v,0,1 v,0,Ny-1 v,0,Ny
| | | | |
i=0 L[0]-h,0,-1-----*---h,0,0-------*--- ... --h,0,Ny-1-----R[0]
| | | | |
v,1,-1 v,1,0 v,1,1 v,1,Ny-1 v,1,Ny
| | | | |
i=1 L[1]-h,1,-1-----*---h,1,0-------*--- ... --h,1,Ny-1-----R[1]
: : : : :
| | | | |
v,Nx,-1 v,Nx,0 v,Nx,1 v,Nx,Ny-1 v,Nx,Ny
| | | | |
i=Nx BL --h,Nx,-1-- B[0] --h,Nx,0-- B[1] -- ... -- h,Nx,Ny-1 -- BR
Horizontal bonds
('h', i, j)run left to right between columnsjandj+1at rowi. Rowsi = -1andi = Nxare the boundary rows (chi bonds between edge tensors and corners);i = 0 ... Nx-1are PEPS rows;j = -1is the left-boundary column andj = Ny-1the right-boundary column for the chi bonds attached to the side edges.Vertical bonds
('v', i, j)run top to bottom in columnjbetween rowsi-1andi:i = 0connects the top row to the first PEPS row,i = Nxthe last PEPS row to the bottom row,i = 1 ... Nx-1are interior;j = -1andj = Nyare the left and right boundary columns.Ket / bra split. For
DoublePepsTensorPEPS every PEPS-row bond, i.e.('h', i, j)with0 <= i < Nxand all('v', i, j), carries two labels,(*, 'k')for the ket layer and(*, 'b')for the bra layer. An un-qualified label inunrollis expanded into both; a layer-qualified label like('v', 1, 1, 'k')slices only the ket side. Boundary (chi) bonds are single-label.Operator bonds
('opb', k)label the bond between MPO tensorsk-1andk(next section); they exist only in the numerator network and are ignored by the norm.
Examples for a 2 x 3 window (Nx = 2, Ny = 3):
# unroll the horizontal bond between columns 0 and 1 at the first PEPS
# row, one index at a time (an int is a uniform slice size):
unroll = {('h', 0, 0): 1}
# unroll the vertical bond in column 1 between rows 0 and 1:
unroll = {('v', 1, 1): 1}
# several bonds at once; a left-boundary (chi) bond:
unroll = {('h', 0, 0): 1, ('v', 1, 1): 1}
unroll = {('v', 0, -1): 1}
# one charge sector at a time:
from yastn.tensor.oe_blocksparse import make_sliced_legs
unroll = {('h', 0, 0): make_sliced_legs(leg)}
The ket, the operator and the bra of every site stay separate network tensors
(_build_ketbra_separate()), with the fermionic crossings between them as swap
pairs of the contraction.
Plain charged operators#
A product of plain operators \(O_{s_1} O_{s_2} \cdots O_{s_n}\), listed in any
order, is measured as an MPO of bond dimension one if any of the operators is charged
(a single \(c\) or \(c^\dagger\)): yastn.tn.mps.product_mpo() of the
operators. The bond between two neighbouring operators of the chain carries the total
charge of the operators after it, and the charge travels along the MPO bonds exactly
as the bonds of an MPO passed by the caller do (next section). Operators listed on the same site are multiplied, with the sign of
bringing them together. A product of operators of zero charge needs no bonds; they sit
on their sites as they are.
Operators as MPO tensors#
Building the MPO#
A sum of operator products on one set of sites,
can be measured in a single contraction instead of one contraction per term.
Build it with yastn.tn.mps.generate_mpo() and pass the MPO in place of the
plain operators:
import yastn.tn.mps as mps
H = mps.generate_mpo(mps.product_mpo(I, N=len(sites)),
[mps.Hterm(coeff, list(range(len(sites))), ops) for coeff, ops in terms])
value = env.measure_nsite_exact_oe(H, sites=sites)
The chain of the MPO is sites as listed: position k of an
yastn.tn.mps.Hterm is the operator acting on sites[k], and I is the
local identity, which fills the positions a term does not list. The sites may be
listed in any order, and neighbouring positions of the chain need not be
neighbouring sites of the lattice.
yastn.tn.mps.generate_mpo() returns the operator in the Fock basis: the
entries of its tensors are the matrix elements of \(O\), the string of the
chain among them. On the chain it is then an ordinary MPO, bonds running
straight from one tensor to the next and crossing nothing:
i0 i1 i2 i3
| | | |
+-+--+ +-+--+ +-+--+ +-+--+
| M0 |----1----| M1 |----2----| M2 |----3----| M3 |
+-+--+ +-+--+ +-+--+ +-+--+
| | | |
o0 o1 o2 o3
matrix element <o0 o1 o2 o3| O |i0 i1 i2 i3>
1, 2, 3 = network labels ('opb', 1), ('opb', 2), ('opb', 3)
In the window each bond joins its two MPO tensors directly. Its swap gates are
the ones the same operator would pick up if it were applied to the ket as a gate,
the way yastn.tn.fpeps.Peps.apply_gate_() applies an MPO in time evolution:
along a path of nearest-neighbour sites, each MPO tensor is contracted with the ket
of its site and each bond becomes part of the ket’s virtual leg towards the next site
of the path, with an identity on every site the path only passes. The measurement
keeps the MPO tensors separate from the ket instead, and gives each bond the swap
gates with the legs it would cross in that application.
What the measurement requires#
Either plain operators, one per listed site, or one MPO, passed as a single
yastn.tn.mps.MpsMpoOBC; the two kinds are never mixed in one call.Plain operators may list their sites in any order, and a site more than once. The product is taken as written, \(O_{s_1} O_{s_2} \cdots O_{s_n}\) with the rightmost operator acting first, whatever the lattice’s fermionic order.
The chain of an MPO is
sitesas listed:H[k]acts onsites[k]. Any order works, neighbouring positions of the chain need not be neighbouring sites, and each site appears once.The measurement applies no sign of its own to an MPO:
generate_mpoputs the sign of every term into the MPO, from the order in which that term lists its operators, with the same convention as plain operators.
A term may list its sites in any order, independently of the chain and of the other
terms: each operator takes the position of its site in the chain, and
generate_mpo sorts the term by those positions and multiplies it by the sign of
that commutation:
H = mps.generate_mpo(mps.product_mpo(I, N=len(sites)),
[mps.Hterm(coeff, [sites.index(s) for s in term_sites], term_ops)
for coeff, term_sites, term_ops in terms])
value = env.measure_nsite_exact_oe(H, sites=sites)
Operator bonds and unrolling#
The bond between MPO tensors k-1 and k is labelled ('opb', k).
It is unrolled like any other bond, e.g. unroll={('opb', 1): 2}, or one
charge sector at a time with
yastn.tensor.oe_blocksparse.make_sliced_legs(). The dimension-one bonds
at the two ends of the chain are dropped and have no label. A product of plain
charged operators has such bonds too, of dimension one, along its sites in the
order listed. The norm network contains no operator bonds, so these entries
are dropped when the norm is contracted.
Example#
Hopping in both directions plus density-density interaction on a diagonal pair of
sites of a fermionic PEPS with CTM environment env. The sites shared by all terms
are listed in sites, in any order. Every term names the site of each of its
operators; to pass the terms to yastn.tn.mps.generate_mpo(), those sites are
turned into positions in sites with sites.index:
import yastn
import yastn.tn.mps as mps
ops = yastn.operators.SpinlessFermions(sym='U1')
c, cp, n, I = ops.c(), ops.cp(), ops.n(), ops.I()
sites = [(1, 0), (0, 1)]
terms = [(-1.0, [(0, 1), (1, 0)], [cp, c]), # c+_(0,1) c_(1,0)
(-1.0, [(1, 0), (0, 1)], [cp, c]), # c+_(1,0) c_(0,1)
(0.5, [(0, 1), (1, 0)], [n, n])]
H = mps.generate_mpo(mps.product_mpo(I, N=len(sites)),
[mps.Hterm(co, [sites.index(s) for s in ss], oo) for co, ss, oo in terms])
norm = env.measure_nsite_norm_exact_oe(sites=sites)
num = env.measure_nsite_numerator_exact_oe(H, sites=sites, unroll={('opb', 1): 1})
value = num / norm
# the same as the plain measurements, each term with its sites as it lists them
ref = sum(co * env.measure_nsite_numerator_exact_oe(*oo, sites=ss) for co, ss, oo in terms) / norm
Listing sites in another order, with the same terms, gives the same value:
sites_r = [(0, 1), (1, 0)]
H_r = mps.generate_mpo(mps.product_mpo(I, N=len(sites_r)),
[mps.Hterm(co, [sites_r.index(s) for s in ss], oo) for co, ss, oo in terms])
value_r = env.measure_nsite_numerator_exact_oe(H_r, sites=sites_r) / norm # == value