---
jupytext:
  text_representation:
    extension: .md
    format_name: myst
    format_version: 0.13
    jupytext_version: 1.17.3
kernelspec:
  display_name: Python 3 (ipykernel)
  language: python
  name: python3
---

# Circuit Lists
This tutorial will show you how to create lists of `Circuit` objects.  In some cases, we'll want to construct and use a list (or even a list of lists!) of `Circuit`s, so we've dedicated this tutorial to talking about "circuit lists".
We utilize the fact that `Circuit` objects behave and compose as tuples of layer-labels (see the [Circuit tutorial](Circuit)).

```{code-cell} ipython3
import pygsti
import pygsti.circuits as pc
```

```{code-cell} ipython3
from pygsti.modelpacks import smq1Q_XY #a standard model, packaged with related information
```

## List Construction Functions:  `pygsti.circuits.create_circuits`
You'll often be working with entire lists of `Circuit` objects which define some part of the experiments utilized by algorithms such as gate set tomography.  pyGSTi provides several functions for constructing circuit lists, which we not demonstrate.

The workhorse function is `pygsti.circuits.create_circuits`, which executes its positional arguments within a nested loop given by iterable keyword arguments.  That's a mouthful, so let's look at a few examples:

```{code-cell} ipython3
As = [('a1',),('a2',)]
Bs = [('b1','b2'), ('b3','b4')]

def rep2(x):
    return x+x

list1 = pc.create_circuits("a", a=As)
list2 = pc.create_circuits("a+b", a=As, b=Bs, order=['a','b'])
list3 = pc.create_circuits("R(a)+c", a=As, c=[('c',)], R=rep2)

print("list1 = %s" % list(map(tuple, list1)))
print("list2 = %s" % list2)
print("list3 = %s" % list(map(str,list3)))
```

Many of the operation sequences used by gate set tomography are composed of three parts.  A "preparation fiducial" sequence is followed by a "repeated germ" sequence, which is followed by a "measurement fiducial" sequence.  We won't get into why this structure is used, but simply use this fact to motivate looking at operation sequences of the form $f_1 + R(g) + f_2$, where the $f_1$ and $f_2$ fiducial sequences are simple short sequences are $R(g)$ is a possibly long sequence that is generated by repeating a short sequence $g$ called a "germ".

It is possible to generate "repeated germ" sequences in several ways using the functions **`pygsti.construction.repeat_`*xxx* **.  In modern GST, germ sequences are always repeated an *integer* number of times rather than being truncated to a precise length, so `repeat_with_max_length` is used instead of `repeat_and_truncate`.  Below we demonstrate the use of these functions.

```{code-cell} ipython3
print(pc.repeat_and_truncate(('A', 'B', 'C'), 5)) #args (x,N): repeat x until it is exactly length N

print(pc.repeat_with_max_length(('A', 'B', 'C'), 5)) #args (x,N): repeat x the maximum integer number of times so len(x) < N

print(pc.repeat_count_with_max_length(('A', 'B', 'C'), 5)) #args (x,N): the maximum integer number of times so len(x) < N
```

We can combine a repeated germ sequence between two fiducial sequences using `create_circuits`.  This demonstrates the power of the `create_circuits` to perform nested loops.  We also introduce the "bulk-conversion" function `circuit_list`, which creates a list of `Circuit` objects from a list of tuples.

```{code-cell} ipython3
fids  = pc.to_circuits( [ ('Gf0',), ('Gf1',)    ] ) #fiducial strings
germs = pc.to_circuits( [ ('G0',), ('G1a','G1b')] ) #germ strings

circuits1 = pc.create_circuits("f0+germ*e+f1", f0=fids, f1=fids,
                                       germ=germs, e=2, order=["germ","f0","f1"])
print("circuits1 = \n", "\n".join(map(str,circuits1)),"\n")

circuits2 = pc.create_circuits("f0+T(germ,N)+f1", f0=fids, f1=fids,
                                        germ=germs, N=3, T=pc.repeat_and_truncate,
                                        order=["germ","f0","f1"])

print("circuits2 = \n", "\n".join(map(str,circuits2)),"\n")

circuits3 = pc.create_circuits("f0+T(germ,N)+f1", f0=fids, f1=fids,
                                        germ=germs, N=3, T=pc.repeat_with_max_length,
                                        order=["germ","f0","f1"])
print("circuits3 = \n", "\n".join(map(str,circuits3)), "\n")
```

In addition to `create_circuits`, the **`pygsti.construction.list_`*xxx* ** functions provide ways of constructing common operation sequence lists.  The example below shows how to construct all possible operation sequences within a certain length range, as well as how to construct the set of operation sequences needed to run Linear Gate Set Tomography given a set of fiducial strings.

```{code-cell} ipython3
myGates = [ 'Gx', 'Gy' ]  #operation labels -- often just model.operations.keys()
allStringsInLengthRange = pc.list_all_circuits(myGates, minlength=0, maxlength=2)
print("\nAll strings using %s up to length 2 = \n" \
    % str(myGates), "\n".join(map(str,allStringsInLengthRange)))
```

```{code-cell} ipython3
myFiducialList = pc.to_circuits([ ('Gf1',), ('Gf2',) ])  #list of fiducials

lgstStrings = pc.create_lgst_circuits(myFiducialList,myFiducialList,myGates)

print("\nLGST strings = \n","\n".join(map(str,lgstStrings)))
```

## Manipulating `Circuits`
Sometimes it is useful to manipulate a `circuits` (or a list of them) via find & replace operations.  The `manipulate_circuit` and `manipulate_circuits` functions take as input a set of replacement "rules" and process one or more `circuits` objects accordingly.  For example, the rules

- ab $\rightarrow$ AB' (if B follows A, prime B)
- BA $\rightarrow$ B''A (if B precedes A, double-prime B)
- CA $\rightarrow$ CA' (if A follows C, prime A)
- BC $\rightarrow$ BC' (if C follows B, prime C)

are specified by the dictionary:

```{code-cell} ipython3
sequenceRules = [
        (("A", "B"), ("A", "B'")),
        (("B", "A"), ("B''", "A")),
        (("C", "A"), ("C", "A'")),
        (("B", "C"), ("B", "C'"))]
```

Will produce the output:
- BAB $\rightarrow$ B''AB'
- ABA $\rightarrow$ AB'A  (frustrated!)
- CAB $\rightarrow$ CA'B'
- ABC $\rightarrow$ AB'C'

```{code-cell} ipython3
from pygsti.circuits import Circuit
from pygsti.circuits import manipulate_circuit

print(manipulate_circuit(Circuit(tuple('BAB')), sequenceRules))
print(manipulate_circuit(Circuit(tuple('ABA')), sequenceRules))
print(manipulate_circuit(Circuit(tuple('CAB')), sequenceRules))
print(manipulate_circuit(Circuit(tuple('ABC')), sequenceRules))
```

```{code-cell} ipython3
# You can also process an entire list of operation sequences in bulk
orig_lst = pygsti.circuits.to_circuits([ tuple('BAB'), tuple('ABA'), tuple('CAB'), tuple('ABC')])
lst = pygsti.circuits.manipulate_circuits(orig_lst, sequenceRules)
print('\n'.join([str(s) for s in lst]))
```

## Gate Label "Aliases"
A similar but simpler type of manipulation called "operation label aliasing" is used in pyGSTi to map a operation label into another operation label **only for `DataSet` lookups**.  The mapping is similar to `manipulate_circuit`'s find & replace functionality, except that (at least currently) the string to find can be only a single operation label  (and so isn't even a string at all). The support for operation label aliasing within pyGSTi's algorithms aids in mapping many `Model` models onto the same data (often with simpler gate labelling).

```{code-cell} ipython3
#TODO: remove Aliasing or provide examples?
```
