# Introduction to symbols

The parameterisable parts of a diagram are represented by symbols; these are instances of the lambeq.Symbol class. Let’s create a tensor diagram for a sentence:

Download code

[1]:

import warnings
warnings.filterwarnings('ignore')

from lambeq import AtomicType, BobcatParser, TensorAnsatz
from discopy import Dim

# Define atomic types
N = AtomicType.NOUN
S = AtomicType.SENTENCE

# Parse a sentence
parser = BobcatParser(verbose='suppress')
diagram = parser.sentence2diagram('John walks in the park')

# Apply a tensor ansatz
ansatz = TensorAnsatz({N: Dim(4), S: Dim(2)})
tensor_diagram = ansatz(diagram)
tensor_diagram.draw(figsize=(12,5), fontsize=12)


Note

Class Symbol inherits from class sympy.Symbol.

The symbols of the diagram can be accessed by the free_symbols attribute:

[2]:

tensor_diagram.free_symbols

[2]:

{John__n, in__s.r@n.r.r@n.r@s@n.l, park__n, the__n@n.l, walks__n.r@s}


Each symbol is associated with a specific size, which is defined from the applied ansatz.

[3]:

[(s, s.size) for s in tensor_diagram.free_symbols]

[3]:

[(walks__n.r@s, 8),
(the__n@n.l, 16),
(John__n, 4),
(in__s.r@n.r.r@n.r@s@n.l, 256),
(park__n, 4)]


For example, you see that preposition “in” has been assigned 256 dimensions, which is derived by multiplying the dimensions of each individual wire ($$2 \cdot 4 \cdot 4 \cdot 2 \cdot 4$$), nouns are assigned 4 dimensions, and the determiner 16 dimensions.

## Circuit symbols

We will now convert the original diagram into a quantum circuit and examine its parameters:

[4]:

from lambeq import IQPAnsatz

iqp_ansatz = IQPAnsatz({N: 1, S: 1}, n_layers=1)
circuit = iqp_ansatz(diagram)
circuit.draw(figsize=(12,8), fontsize=12)


Let’s see the symbols of the circuit and their sizes:

[5]:

[(s, s.size) for s in circuit.free_symbols]

[5]:

[(in__s.r@n.r.r@n.r@s@n.l_3, 1),
(walks__n.r@s_0, 1),
(park__n_0, 1),
(John__n_0, 1),
(in__s.r@n.r.r@n.r@s@n.l_0, 1),
(in__s.r@n.r.r@n.r@s@n.l_2, 1),
(park__n_2, 1),
(park__n_1, 1),
(John__n_2, 1),
(the__n@n.l_0, 1),
(in__s.r@n.r.r@n.r@s@n.l_1, 1),
(John__n_1, 1)]


Note that all sizes are equal to 1; this is because the parameters of the circuit are numbers, defining rotation angles on qubits.

## From symbols to tensors

In this section we will create actual tensors and associate them with the symbols of the diagram. In order to do this, we first need to fix the order of the symbols, since they are represented as a set. We can use sympy’s default_sort_key for this purpose.

[6]:

from sympy import default_sort_key

parameters = sorted(tensor_diagram.free_symbols, key=default_sort_key)


We will use numpy arrays for the tensors, initialised randomly:

[7]:

import numpy as np

tensors = [np.random.rand(p.size) for p in parameters]
print(tensors[0])

[0.69545749 0.70027778 0.79763124 0.40774577]


Associating the numpy arrays with the symbols in the diagram can be done by using the lambdify() method:

[8]:

tensor_diagram_np = tensor_diagram.lambdify(*parameters)(*tensors)
print("Before lambdify:", tensor_diagram.boxes[0].data)
print("After lambdify:", tensor_diagram_np.boxes[0].data)

Before lambdify: John__n
After lambdify: [0.69545749 0.70027778 0.79763124 0.40774577]


To contract the tensor network and compute a representation for the sentence, we will use eval().

[9]:

result = tensor_diagram_np.eval()
print(result)

Tensor(dom=Dim(1), cod=Dim(2), array=[13.4525461 , 12.83309003])


Note

The result is a 2-dimensional array, based on the fact that we have assigned a dimension of 2 to the sentence space when applying the ansatz.

The result is an instance of the discopy.tensor.Tensor class, and the array can be accessed via the array attribute.

[10]:

result.array

[10]:

array([13.4525461 , 12.83309003])