# Symbolic compilation#

Motivation: in compilation, particularly of hybrid classical-quantum variational algorithms in which the structure of a circuit remains constant but the parameters of some gates change, it can be useful to compile using symbolic parameters and optimise the circuit without knowledge of what these parameters will be instantiated to afterwards.

In this tutorial, we will show how to compile a circuit containing mathematical symbols, and then instantiate the symbols afterwards. To do this, you need to have `pytket`

installed. Run:

`pip install pytket`

To begin, we will import the `Circuit`

and `Transform`

classes from `pytket`

, and the `fresh_symbol`

method from `pytket.circuit`

.

```
from pytket.circuit import Circuit, fresh_symbol
from pytket.transform import Transform
```

Now, we can construct a circuit containing symbols. You can ask for symbols by calling the `fresh_symbol`

method with a string as an argument. This string represents the preferred symbol name; if this has already been used elsewhere, an appropriate suffix of the form `_x`

, with `x`

a natural number, will be added to generate a new symbol, as shown below:

```
a = fresh_symbol("a")
a1 = fresh_symbol("a")
print(a)
print(a1)
```

```
a
a_1
```

We are going to make a circuit using just three ‘phase gadgets’: `Rz`

gates surrounded by ladders of `CX`

gates.

```
b = fresh_symbol("b")
circ = Circuit(4)
circ.CX(0, 1)
circ.CX(1, 2)
circ.CX(2, 3)
circ.Rz(a, 3)
circ.CX(2, 3)
circ.CX(1, 2)
circ.CX(0, 1)
circ.CX(3, 2)
circ.CX(2, 1)
circ.CX(1, 0)
circ.Rz(b, 0)
circ.CX(1, 0)
circ.CX(2, 1)
circ.CX(3, 2)
circ.CX(0, 1)
circ.CX(1, 2)
circ.CX(2, 3)
circ.Rz(0.5, 3)
circ.CX(2, 3)
circ.CX(1, 2)
circ.CX(0, 1)
```

```
[CX q[0], q[1]; CX q[1], q[2]; CX q[2], q[3]; Rz(a) q[3]; CX q[2], q[3]; CX q[1], q[2]; CX q[0], q[1]; CX q[3], q[2]; CX q[2], q[1]; CX q[1], q[0]; Rz(b) q[0]; CX q[1], q[0]; CX q[2], q[1]; CX q[0], q[1]; CX q[3], q[2]; CX q[1], q[2]; CX q[2], q[3]; Rz(0.5) q[3]; CX q[2], q[3]; CX q[1], q[2]; CX q[0], q[1]; ]
```

Now we can use the `render_circuit_jupyter`

method to display the circuit.

```
from pytket.circuit.display import render_circuit_jupyter
```

```
render_circuit_jupyter(circ)
```

Now let’s use a transform to shrink the circuit. For more detail on transforms, see the `transform_example`

notebook.

```
Transform.OptimisePhaseGadgets().apply(circ)
render_circuit_jupyter(circ)
```

Note that the type of gate has changed to `U1`

, but the phase gadgets have been successfully combined. The `U1`

gate is an IBM-specific gate that is equivalent to an `Rz`

.

We can now instantiate the symbols with some desired values. We make a dictionary, with each key a symbol name, and each value a double. Note that this value is in units of ‘half-turns’, in which a value of \(1\) corresponds to a rotation of \(\pi\).

Before instantiating our parameters we make a copy of the circuit, so that we can repeat the exercise without the need for recompilation.

```
symbol_circ = circ.copy()
```

```
symbol_dict = {a: 0.5, b: 0.75}
circ.symbol_substitution(symbol_dict)
```

```
render_circuit_jupyter(circ)
```

Because this symbol substitution was called on the copy, we still have our original symbolic circuit.

```
render_circuit_jupyter(symbol_circ)
```

Note: the expression tree for this symbolic expression is very small, consisting of only a couple of different operations, but tket is capable of handling large and complex expressions containing many different types of operation, such as trigonometric functions.

It is usually possible to instantiate a symbolic circuit with specific values that allow further optimisation: for example, if we had chosen \(a=1.5\) and \(b=0\), this circuit would be reduce to the identity. If there are likely to be many parameters set to trivial values (such as \(0\) or \(1\)), it can be beneficial to perform further optimisation after instantiation.