{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# This cell is added by sphinx-gallery\n# It can be customized to whatever you like\n%matplotlib inline"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Tensor-network quantum circuits {#tn_circuits}\n===============================\n\n::: {.meta}\n:property=\\\"og:description\\\": This demonstration explains how to\nsimulate tensor-network quantum circuits. :property=\\\"og:image\\\":\n\n:::\n\n::: {.related}\ntutorial\\_variational\\_classifier Variational classifier\n:::\n\n*Authors: Diego Guala*^1^ *, Esther Cruz-Rico*^2^ *, Shaoming Zhang*^2^\n*, Juan Miguel Arrazola*^1^ *--- Posted: 29 March 2022. Last updated: 27\nJune 2022.*\n\n| ^1^ Xanadu, Toronto, ON, M5G 2C8, Canada\n| ^2^ BMW Group, Munich, Germany\n\nThis demonstration explains how to use PennyLane templates to design and\nimplement tensor-network quantum circuits as in Ref.. Tensor-network\nquantum circuits emulate the shape and connectivity of tensor networks\nsuch as matrix product states and tree tensor networks.\n\nWe begin with a short introduction to tensor networks and explain their\nrelationship to quantum circuits. Next, we illustrate how PennyLane\\'s\ntemplates make it easy to design, customize, and simulate these\ncircuits. Finally, we show how to use the circuits to learn to classify\nthe bars and stripes data set. This is a toy problem where the template\nlearns to recognize whether an image exhibits horizontal stripes or\nvertical bars.\n\nTensors and Tensor Networks\n---------------------------\n\nTensors are multi-dimensional arrays of numbers. Intuitively, they can\nbe interpreted as a generalization of scalars, vectors, and matrices.\nTensors can be described by their rank, indices, and the dimension of\nthe indices. The rank is the number of indices in a tensor \\-\\-- a\nscalar has rank zero, a vector has rank one, and a matrix has rank two.\nThe dimension of an index is the number of values that index can take.\nFor example, a vector with three elements has one index that can take\nthree values. This is vector is therefore a rank one tensor and its\nindex has dimension three.\n\nTo define tensor networks, it is important to first understand tensor\ncontraction. Two or more tensors can be contracted by summing over\nrepeated indices. In diagrammatic notation, the repeated indices appear\nas lines connecting tensors, as in the figure below. We see two tensors\nof rank two connected by one repeated index, $k$. The dimension of the\nrepeated index is called the bond dimension.\n\n![image](../demonstrations/tn_circuits/simple_tn_color.PNG){.align-center\nwidth=\"50.0%\"}\n\nThe contraction of the tensors above is equivalent to the standard\nmatrix multiplication formula and can be expressed as\n\n$$C_{ij} = \\sum_{k}A_{ik}B_{kj},$$\n\nwhere $C_{ij}$ denotes the entry for the $i$-th row and $j$-th column of\nthe product $C=AB$.\n\nA tensor network is a collection of tensors where a subset of all\nindices are contracted. As mentioned above, we can use diagrammatic\nnotation to specify which indices and tensors will be contracted\ntogether by connecting individual tensors with lines. Tensor networks\ncan represent complicated operations involving several tensors with many\nindices contracted in sophisticated patterns.\n\nTwo well-known tensor network architectures are matrix product states\n(MPS) and tree tensor networks (TTN). These follow specific patterns of\nconnections between tensors and can be extended to have many or few\nindices. Examples of these architectures with only a few tensors can be\nseen in the figure below. An MPS is shown on the left and a TTN on the\nright.\n\n![image](../demonstrations/tn_circuits/MPS_TTN_Color.PNG){.align-center\nwidth=\"50.0%\"}\n\nThese tensor networks are commonly used to efficiently represent certain\nmany-body quantum states. Every quantum circuit can be represented as a\ntensor network, with the bond dimension dependent on the width and\nconnectivity of the circuit. Moreover, one can design quantum circuits\nthat have the same connectivity as well-known tensor networks like MPS\nand TTN. We call these **tensor-network quantum circuits**. Note that\nthe connectivity of a tensor network is related to how entanglement is\ndistributed and how correlations spread in the resulting tensor-network\nquantum circuit. We therefore design circuits based on the tensor\nnetworks that best capture the information we want to extract.\n\nIn tensor-network quantum circuits, the tensor network architecture acts\nas a guideline for the shape of the quantum circuit. More specifically,\nthe tensors in the tensor networks above are replaced with unitary\noperations to obtain quantum circuits, as illustrated in the figure\nbelow.\n\n![image](../demonstrations/tn_circuits/MPS_TTN_Circuit_Color.PNG){.align-center\nwidth=\"70.0%\"}\n\nSince the unitary operations $U_1$ to $U_3$ are in principle completely\ngeneral, it is not always clear how to implement them with a specific\ngate set. Instead, we can replace the unitary operations with\nvariational quantum circuits determined by a specific template of\nchoice. The PennyLane tensor network templates allow us to do precisely\nthis: implement tensor-network quantum circuits with user-defined\ncircuit ansatze as the unitary operations. In this sense, just as a\ntemplate is a strategy for arranging parametrized gates, tensor-network\nquantum circuits are strategies for structuring circuit templates. They\ncan therefore be interpreted as templates of templates, i.e., as\nmeta-templates.\n\nPennyLane Implementation\n------------------------\n\nWe now demonstrate how to use PennyLane to build and simulate\ntensor-network quantum circuits.\n\nThe first step is to define the circuit that will be broadcast into the\ntensor network shape. We call this a block. The block defines a\nvariational quantum circuit that takes the position of tensors in the\nnetwork.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import pennylane as qml\nfrom pennylane import numpy as np\n\n\ndef block(weights, wires):\n qml.RX(weights[0], wires=wires[0])\n qml.RY(weights[1], wires=wires[1])\n qml.CNOT(wires=wires)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"With the block defined, we can build the full tensor-network quantum\ncircuit. The following code broadcasts the above block into the shape of\nan MPS tensor network and computes the expectation value of a Pauli Z\noperator on the bottom qubit.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"dev = qml.device(\"default.qubit\", wires=4)\n\n\n@qml.qnode(dev)\ndef circuit(template_weights):\n qml.MPS(\n wires=range(4),\n n_block_wires=2,\n block=block,\n n_params_block=2,\n template_weights=template_weights,\n )\n return qml.expval(qml.PauliZ(wires=3))\n\n\nnp.random.seed(1)\nweights = np.random.random(size=[3, 2])\nqml.drawer.use_style(\"black_white\")\nfig, ax = qml.draw_mpl(circuit, expansion_strategy=\"device\")(weights)\nfig.set_size_inches((6, 3))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Using the `~pennylane.MPS`{.interpreted-text role=\"class\"} template we\ncan easily change the block type, depth, and size. For example, the\nblock can contain a template like\n`~pennylane.StronglyEntanglingLayers`{.interpreted-text role=\"class\"},\nyielding a deeper block.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def deep_block(weights, wires):\n qml.StronglyEntanglingLayers(weights, wires)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can use the `~pennylane.MPS`{.interpreted-text role=\"class\"} template\nagain and simply set `n_params_block = 3` to suit the new block.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"dev = qml.device(\"default.qubit\", wires=4)\n\n\n@qml.qnode(dev)\ndef circuit(template_weights):\n qml.MPS(\n wires=range(4),\n n_block_wires=2,\n block=deep_block,\n n_params_block=3,\n template_weights=template_weights,\n )\n return qml.expval(qml.PauliZ(wires=3))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To ensure that the weights of the block and `template_weights` sent to\nthe `~pennylane.MPS`{.interpreted-text role=\"class\"} template are\ncompatible, we use the\n`~pennylane.StronglyEntanglingLayers.shape`{.interpreted-text\nrole=\"class\"} function and replicate the elemnts for the number of\nexpected blocks. Since this example will have three blocks, we replicate\nthe elements three times using `[list]*3`. The resulting circuit is\nillustrated in the figure below the code. Note that this circuit retains\nthe layout of an MPS, but each block is now a deeper circuit with more\ngates. Both this circuit and the previous circuit can be represented by\nan MPS with a bond dimension of two.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"shape = qml.StronglyEntanglingLayers.shape(n_layers=2, n_wires=2)\ntemplate_weights = [np.random.random(size=shape)] * 3\nfig, ax = qml.draw_mpl(circuit, expansion_strategy=\"device\")(template_weights)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In addition to deep blocks, we can easily expand to wider blocks with\nmore input wires. In the next example, we use the\n`~pennylane.SimplifiedTwoDesign`{.interpreted-text role=\"class\"}\ntemplate as the block.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def wide_block(weights, wires):\n qml.SimplifiedTwoDesign(initial_layer_weights=weights[0], weights=weights[1], wires=wires)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To implement this wider block, we can use the\n`~pennylane.MPS`{.interpreted-text role=\"class\"} template as before. To\naccount for the extra wires per block, we simply set the `n_block_wires`\nargument to a higher number. The figure below shows the resulting\ncircuit. Notice that, in the circuit diagram, gates are left-justified.\nTherefore parts of later blocks appear near the beginning of the\ncircuit. Furthermore, this circuit has a higher bond dimension than the\nprevious ones and would correspond to an MPS with a bond dimension of\nfour.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"dev = qml.device(\"default.qubit\", wires=8)\n\n\n@qml.qnode(dev)\ndef circuit(template_weights):\n qml.MPS(\n wires=range(8),\n n_block_wires=4,\n block=wide_block,\n n_params_block=2,\n template_weights=template_weights,\n )\n return qml.expval(qml.PauliZ(wires=7))\n\n\nshapes = qml.SimplifiedTwoDesign.shape(n_layers=1, n_wires=4)\nweights = [np.random.random(size=shape) for shape in shapes]\ntemplate_weights = [weights] * 3\nfig, ax = qml.draw_mpl(circuit, expansion_strategy=\"device\")(template_weights)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can also broadcast a block to the tree tensor network architecture by\nusing the `~pennylane.TTN`{.interpreted-text role=\"class\"} template.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def block(weights, wires):\n qml.RX(weights[0], wires=wires[0])\n qml.RX(weights[1], wires=wires[1])\n qml.CNOT(wires=wires)\n\n\ndev = qml.device(\"default.qubit\", wires=8)\n\n\n@qml.qnode(dev)\ndef circuit(template_weights):\n qml.TTN(\n wires=range(8),\n n_block_wires=2,\n block=block,\n n_params_block=2,\n template_weights=template_weights,\n )\n return qml.expval(qml.PauliZ(wires=7))\n\n\nweights = np.random.random(size=[7, 2])\nfig, ax = qml.draw_mpl(circuit, expansion_strategy=\"device\")(weights)\nfig.set_size_inches((4, 4))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Classifying the bars and stripes data set\n=========================================\n\nNext, we use a tensor-network quantum circuit to tackle a toy machine\nlearning problem. For this, we use the bars and stripes data set and\noptimize a parametrized circuit to label the images as either bars or\nstripes. The data set is composed of binary black and white images of\nsize $n \\times n$ pixels. In images that should receive the bars label,\nall pixels in any given column have the same color. In images with the\nstripes label, all pixels in any given row have the same color. The full\ndata set for $4\\times 4$ images is shown in the image below:\n\n![](../demonstrations/tn_circuits/BAS.png){.align-center height=\"300px\"}\n\nA quantum circuit that successfully performs this task accepts any image\nfrom the data set as input and outputs the correct label. We will\ntherefore choose a data encoding strategy that can record the input\nimage in a qubit register, a processing circuit that can analyze the\ndata, and a final measurement that can serve as a label of either\nstripes or bars.\n\nThe first step is to generate the bars and stripes data set. For\n$2\\times 2$ images, we can manually define the full data set, giving\nwhite pixels a value of 1 and black pixels a value of 0:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt\n\nBAS = [[1, 1, 0, 0], [0, 0, 1, 1], [1, 0, 1, 0], [0, 1, 0, 1]]\nj = 1\nplt.figure(figsize=[3, 3])\nfor i in BAS:\n plt.subplot(2, 2, j)\n j += 1\n plt.imshow(np.reshape(i, [2, 2]), cmap=\"gray\")\n plt.xticks([])\n plt.yticks([])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The next step is to define the parameterized quantum circuit that will\nbe trained to label the images. This involves determining the block and\nthe tensor-network architecture. For the block, a circuit consisting of\n`~pennylane.RY`{.interpreted-text role=\"class\"} rotations and\n`~pennylane.CNOT`{.interpreted-text role=\"class\"} gates suffices for\nthis simple data set.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def block(weights, wires):\n qml.RY(weights[0], wires=wires[0])\n qml.RY(weights[1], wires=wires[1])\n qml.CNOT(wires=wires)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As for the tensor-network architecture, we use the tree tensor-network\nquantum circuit. We use\n`~pennylane.BasisStatePreparation`{.interpreted-text role=\"class\"} to\nencode the input images. The following code implements the\n`~pennylane.BasisStatePreparation`{.interpreted-text role=\"class\"}\nencoding, followed by a `~pennylane.TTN`{.interpreted-text role=\"class\"}\ncircuit using the above `block`. Finally, we compute the expectation\nvalue of a `~pennylane.PauliZ`{.interpreted-text role=\"class\"}\nmeasurement as the output. The circuit diagram below shows the full\ncircuit. The `~pennylane.BasisStatePreparation`{.interpreted-text\nrole=\"class\"} encoding appears in the initial\n`~pennylane.PauliX`{.interpreted-text role=\"class\"} gates.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"dev = qml.device(\"default.qubit\", wires=4)\n\n\n@qml.qnode(dev)\ndef circuit(image, template_weights):\n qml.BasisStatePreparation(image, wires=range(4))\n qml.TTN(\n wires=range(4),\n n_block_wires=2,\n block=block,\n n_params_block=2,\n template_weights=template_weights,\n )\n return qml.expval(qml.PauliZ(wires=3))\n\n\nweights = np.random.random(size=[3, 2])\nfig, ax = qml.draw_mpl(circuit, expansion_strategy=\"device\")(BAS[0], weights)\nfig.set_size_inches((6, 3.5))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"When the output of the above circuit is less than zero, we label the\nimage \\\"stripes\\\", otherwise we label it \\\"bars\\\". Based on these\nlabels, we define a cost function to train the circuit. The cost\nfunction in the following code adds the expectation value result if the\nlabel should be negative and subtracts the result if the label should be\npositive. In other words, the cost will be minimized when the stripes\nimages output negative one and the bars images output positive one.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def costfunc(params):\n cost = 0\n for i in range(len(BAS)):\n if i < len(BAS) / 2:\n cost += circuit(BAS[i], params)\n else:\n cost -= circuit(BAS[i], params)\n return cost"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finally, we initialize the parameters and use PennyLane's built-in\noptimizer train the circuit over 100 iterations. This optimizer will\nattempt to minimize the cost function.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"params = np.random.random(size=[3, 2], requires_grad=True)\noptimizer = qml.GradientDescentOptimizer(stepsize=0.1)\n\nfor k in range(100):\n if k % 20 == 0:\n print(f\"Step {k}, cost: {costfunc(params)}\")\n params = optimizer.step(costfunc, params)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"With the circuit trained and the parameters stored in `params`, we can\nnow show the full circuits and the resulting output for each image.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"for image in BAS:\n fig, ax = qml.draw_mpl(circuit, expansion_strategy=\"device\")(image, params)\n plt.figure(figsize=[1.8, 1.8])\n plt.imshow(np.reshape(image, [2, 2]), cmap=\"gray\")\n plt.title(\n f\"Exp. Val. = {circuit(image,params):.0f};\"\n + f\" Label = {'Bars' if circuit(image,params)>0 else 'Stripes'}\",\n fontsize=8,\n )\n plt.xticks([])\n plt.yticks([])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The resulting labels are all correct. For images with stripes, the\ncircuit outputs an expectation value of minus one, corresponding to\nstripes and for images with bars the circuit outputs an expectation\nvalue of positive one, corresponding to bars.\n\nReferences {#tn_circuits_references}\n==========\n\nAbout the authors\n=================\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.15"
}
},
"nbformat": 4,
"nbformat_minor": 0
}