In [1]:
%matplotlib inline
import quimb as qu
import quimb.tensor as qtn
In [2]:
data = qu.bell_state('psi-').reshape(2, 2)
inds = ('k0', 'k1')
tags = {'KET'}
ket = qtn.Tensor(data, inds, tags)
ket
Out[2]:
In [3]:
X = qtn.Tensor(qu.pauli('X'), inds=('k0', 'b0'), tags={'PAULI', 'X', '0'})
Y = qtn.Tensor(qu.pauli('Y'), inds=('k1', 'b1'), tags={'PAULI', 'Y', '1'})
And finally, a random 'bra' to complete the inner product:
In [4]:
bra = qtn.Tensor(qu.rand_ket(4).reshape(2, 2), inds=('b0', 'b1'), tags={'BRA'})
In [5]:
TN = ket.H & X & Y & bra
print(TN)
In [6]:
TN.graph(color=['KET', 'PAULI', 'BRA'], figsize=(4, 4))
Note the tags can be used to identify both paulis at once. But they could also be uniquely identified using their 'X'
and 'Y'
tags respectively:
In [7]:
TN.graph(color=['KET', 'X', 'BRA', 'Y'], figsize=(4, 4))
In [8]:
TN ^ all
Out[8]:
Or if we just want to contract the paulis:
In [9]:
print(TN ^ 'PAULI')
Notice how the tags
of the Paulis have been combined on the new tensor.
The contraction order is optimized automatically using opt_einsum
, is cached,
and can easily handle hundreds of tensors (though it uses a greedy algorithm and
is not guaranteed to find the optimal path).
A cumulative contract allows a custom 'bubbling' order:
In [10]:
# "take KET, then contract X in, then contract BRA *and* Y in, etc..."
print(TN >> ['KET', 'X', ('BRA', 'Y')])
And a structured contract uses the tensor networks tagging structure (a string
format specifier like "I{}"
) to perform a cumulative contract automatically,
e.g. grouping the tensors of a MPS/MPO into segments of 10 sites.
This can be slightly quicker than finding the full contraction path.
When a TN has a structure, structured contractions can be used by specifying either an Ellipsis
:
``TN ^ ...`` # which means full, structured contract
or a slice
:
``TN ^ slice(100, 200)`` # which means a structured contract of those sites only
In [11]:
print((TN ^ 'PAULI'))
In [12]:
# select any tensors matching the 'KET' tag - here only 1
Tk = TN['KET']
# now split it, creating a new tensor network of 2 tensors
Tk_s = Tk.split(left_inds=['k0'])
# note new index created
print(Tk_s)
In [13]:
# remove the original KET tensor
del TN['KET']
# inplace add the split tensor network
TN &= Tk_s
# plot - should now have 5 tensors
TN.graph(color=['KET', 'PAULI', 'BRA'], figsize=(4, 4))
In [14]:
L = 10
# create the nodes, by default just the scalar 1.0
tensors = [qtn.Tensor() for _ in range(L)]
for i in range(L):
# add the physical indices, each of size 2
tensors[i].new_ind(f'k{i}', size=2)
# add bonds between neighbouring tensors, of size 7
tensors[i].new_bond(tensors[(i + 1) % L], size=7)
mps = qtn.TensorNetwork(tensors)
mps.graph()
In [15]:
# make a 5 qubit tensor state
dims = [2] * 5
data = qu.rand_ket(32).A.reshape(*dims)
inds=['k0', 'k1', 'k2', 'k3', 'k4']
psi = qtn.Tensor(data, inds=inds)
# find the inner product with itself
psi.H @ psi
Out[15]:
In this case, the conjugated copy psi.H
has the same outer indices as psi
and so the inner product is naturally formed.