<jupyter_start><jupyter_text>Contract a Grid CircuitShallow circuits on a planar grid with low-weight observables permit easy contraction.  Imports<jupyter_code>import numpy as np
import networkx as nx

import cirq
import quimb
import quimb.tensor as qtn
from cirq.contrib.svg import SVGCircuit

import cirq.contrib.quimb as ccq
%matplotlib inline
from matplotlib import pyplot as plt

import seaborn as sns
sns.set_style('ticks')

plt.rc('axes', labelsize=16, titlesize=16)
plt.rc('xtick', labelsize=14)
plt.rc('ytick', labelsize=14)
plt.rc('legend', fontsize=14, title_fontsize=16)
# theme colors
QBLUE = '#1967d2'
QRED = '#ea4335ff'
QGOLD = '#fbbc05ff'
QGREEN = '#34a853ff'

QGOLD2 = '#ffca28'
QBLUE2 = '#1e88e5'<jupyter_output><empty_output><jupyter_text>Make an example circuit topologyWe'll use entangling gates according to this topology and compute the value of an observable on the red nodes.<jupyter_code>width = 3
height = 4
graph = nx.grid_2d_graph(width, height)
rs = np.random.RandomState(52)
nx.set_edge_attributes(graph, name='weight',
                       values={e: np.round(rs.uniform(), 2) for e in graph.edges})

zz_inds = ((width//2, (height//2-1)), (width//2, (height//2)))
nx.draw_networkx(graph, 
                 pos={n:n for n in graph.nodes},
                 node_color=[QRED if node in zz_inds else QBLUE for node in graph.nodes])<jupyter_output><empty_output><jupyter_text>Circuit<jupyter_code>qubits = [cirq.GridQubit(*n) for n in graph]
circuit = cirq.Circuit(
    cirq.H.on_each(qubits),
    ccq.get_grid_moments(graph),
    cirq.Moment([cirq.rx(0.456).on_each(qubits)]),
)
SVGCircuit(circuit)<jupyter_output><empty_output><jupyter_text>Observable<jupyter_code>ZZ = cirq.Z(cirq.GridQubit(*zz_inds[0])) * cirq.Z(cirq.GridQubit(*zz_inds[1]))
ZZ<jupyter_output><empty_output><jupyter_text>The contractionThe value of the observable is $\langle 0 | U^\dagger (ZZ) U |0 \rangle$.<jupyter_code>tot_c = ccq.circuit_for_expectation_value(circuit, ZZ)
SVGCircuit(tot_c)<jupyter_output><empty_output><jupyter_text>We can simplify the circuitBy cancelling the "forwards" and "backwards" part of the circuit that are outside of the light-cone of the observable, we can reduce the number of gates to consider --- and sometimes the number of qubits involved at all. To see this in action, run the following cell and then keep re-running the following cell to watch gates disappear from the circuit.<jupyter_code>compressed_c = tot_c.copy()
print(len(list(compressed_c.all_operations())), len(compressed_c.all_qubits()))<jupyter_output><empty_output><jupyter_text>**(try re-running the following cell to watch the circuit get smaller)**<jupyter_code>ccq.MergeNQubitGates(n_qubits=2).optimize_circuit(compressed_c)
ccq.MergeNQubitGates(n_qubits=1).optimize_circuit(compressed_c)

cirq.DropNegligible(tolerance=1e-6).optimize_circuit(compressed_c)
cirq.DropEmptyMoments().optimize_circuit(compressed_c)
print(len(list(compressed_c.all_operations())), len(compressed_c.all_qubits()))
SVGCircuit(compressed_c)<jupyter_output><empty_output><jupyter_text>Utility function to fully-simplifyWe provide this utility function to fully simplify a circuit.<jupyter_code>ccq.simplify_expectation_value_circuit(tot_c)
SVGCircuit(tot_c)
# simplification might eliminate qubits entirely for large graphs and 
# shallow `p`, so re-get the current qubits.
qubits = sorted(tot_c.all_qubits())
print(len(qubits))<jupyter_output><empty_output><jupyter_text>Turn it into a Tensor NetoworkWe explicitly "cap" the tensor network with `<0..0|` bras so the entire thing contracts to the expectation value $\langle 0 | U^\dagger (ZZ) U |0 \rangle$.<jupyter_code>tensors, qubit_frontier, fix = ccq.circuit_to_tensors(
    circuit=tot_c, qubits=qubits)
end_bras = [
    qtn.Tensor(
        data=quimb.up().squeeze(),
        inds=(f'i{qubit_frontier[q]}_q{q}',),
        tags={'Q0', 'bra0'}) for q in qubits
]

tn = qtn.TensorNetwork(tensors + end_bras)
tn.graph(color=['Q0', 'Q1', 'Q2'])
plt.show()<jupyter_output><empty_output><jupyter_text>`rank_simplify` effectively folds together 1- and 2-qubit gatesIn practice, using this is faster than running the circuit optimizer to remove gates that cancel themselves, but please benchmark for your particular use case.<jupyter_code>tn.rank_simplify(inplace=True)
tn.graph(color=['Q0', 'Q1', 'Q2'])<jupyter_output><empty_output><jupyter_text>The tensor contraction path tells us how expensive this will be<jupyter_code>path_info = tn.contract(get='path-info')
path_info.opt_cost / int(3e9) # assuming 3gflop, in seconds
path_info.largest_intermediate * 128 / 8 / 1024 / 1024 / 1024 # gb<jupyter_output><empty_output><jupyter_text>Do the contraction<jupyter_code>zz = tn.contract(inplace=True)
zz = np.real_if_close(zz)
print(zz)<jupyter_output><empty_output><jupyter_text>Big Circuit<jupyter_code>width = 8
height = 8
graph = nx.grid_2d_graph(width, height)
rs = np.random.RandomState(52)
nx.set_edge_attributes(graph, name='weight',
                       values={e: np.round(rs.uniform(), 2) for e in graph.edges})

zz_inds = ((width//2, (height//2-1)), (width//2, (height//2)))
nx.draw_networkx(graph, 
                 pos={n:n for n in graph.nodes},
                 node_color=[QRED if node in zz_inds else QBLUE for node in graph.nodes])
qubits = [cirq.GridQubit(*n) for n in graph]
circuit = cirq.Circuit(
    cirq.H.on_each(qubits),
    ccq.get_grid_moments(graph),
    cirq.Moment([cirq.rx(0.456).on_each(qubits)]),
)
ZZ = cirq.Z(cirq.GridQubit(*zz_inds[0])) * cirq.Z(cirq.GridQubit(*zz_inds[1]))
ZZ
ccq.tensor_expectation_value(circuit, ZZ)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.12386545648691938, 'fraction_numerical': 0.019398469478554902, 'mean_word_length': 5.78743961352657, 'pattern_counts': {'":': 0, '<': 54, '<?xml version=': 0, '>': 53, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '13', 'dir': 'jupyter-structured-clean-dedup', 'id': '42341', 'license': 'Apache-2.0', 'n_tokens_mistral': 2150, 'n_tokens_neox': 2029, 'n_words': 453, 'path': 'cirq-core/cirq/contrib/quimb/Contract-a-Grid-Circuit.ipynb', 'repo_name': 'viathor/Cirq', 'size': '5619'}