示例#1
0
def make_rpe_alpha_str_lists_gx_gz(k_list):
    """
    Make alpha cosine and sine circuit lists for (approx) X pi/4 and Z pi/2 gates.

    These circuits are used to estimate alpha (Z rotation angle).

    Parameters
    ----------
    k_list : list of ints
        The list of "germ powers" to be used.  Typically successive powers of
        two; e.g. [1,2,4,8,16].

    Returns
    -------
    cosStrList : list of Circuits
        The list of "cosine strings" to be used for alpha estimation.
    sinStrList : list of Circuits
        The list of "sine strings" to be used for alpha estimation.
    """
    cosStrList = []
    sinStrList = []
    for k in k_list:
        cosStrList += [
            _Circuit(('Gi', 'Gx', 'Gx', 'Gz') + ('Gz', ) * k +
                     ('Gz', 'Gz', 'Gz', 'Gx', 'Gx'),
                     'GiGxGxGzGz^' + str(k) + 'GzGzGzGxGx')
        ]

        sinStrList += [
            _Circuit(('Gx', 'Gx', 'Gz', 'Gz') + ('Gz', ) * k +
                     ('Gz', 'Gz', 'Gz', 'Gx', 'Gx'),
                     'GxGxGzGzGz^' + str(k) + 'GzGzGzGxGx')
        ]

        #From RPEToolsNewNew.py
        ##cosStrList += [_Circuit(('Gi','Gx','Gx')+
        ##                                ('Gz',)*k +
        ##                                ('Gx','Gx'),
        ##                                'GiGxGxGz^'+str(k)+'GxGx')]
        #
        #
        #cosStrList += [_Circuit(('Gx','Gx')+
        #                                ('Gz',)*k +
        #                                ('Gx','Gx'),
        #                                'GxGxGz^'+str(k)+'GxGx')]
        #
        #
        #sinStrList += [_Circuit(('Gx','Gx')+
        #                                ('Gz',)*k +
        #                                ('Gz','Gx','Gx'),
        #                                'GxGxGz^'+str(k)+'GzGxGx')]

    return cosStrList, sinStrList
示例#2
0
def make_rpe_theta_str_lists_gx_gz(k_list):
    """
    Make theta cosine and sine circuit lists for (approx) X pi/4 and Z pi/2 gates.

    These circuits are used to estimate theta (X-Z axes angle).

    Parameters
    ----------
    k_list : list of ints
        The list of "germ powers" to be used.  Typically successive powers of
        two; e.g. [1,2,4,8,16].

    Returns
    -------
    thetaCosStrList : list of Circuits
        The list of "cosine strings" to be used for theta estimation.
    thetaSinStrList : list of Circuits
        The list of "sine strings" to be used for theta estimation.
    """
    thetaCosStrList = []
    thetaSinStrList = []

    for k in k_list:
        thetaCosStrList += [
            _Circuit(('Gz', 'Gx', 'Gx', 'Gx', 'Gx', 'Gz', 'Gz', 'Gx', 'Gx',
                      'Gx', 'Gx', 'Gz') * k + ('Gx', ) * 4,
                     '(GzGxGxGxGxGzGzGxGxGxGxGz)^' + str(k) + 'GxGxGxGx')
        ]

        thetaSinStrList += [
            _Circuit(
                ('Gx', 'Gx', 'Gz', 'Gz') +
                ('Gz', 'Gx', 'Gx', 'Gx', 'Gx', 'Gz', 'Gz', 'Gx', 'Gx', 'Gx',
                 'Gx', 'Gz') * k + ('Gx', ) * 4,
                '(GxGxGzGz)(GzGxGxGxGxGzGzGxGxGxGxGz)^' + str(k) + 'GxGxGxGx')
        ]

        #From RPEToolsNewNew.py
        #thetaCosStrList += [_Circuit(
        #       ('Gz','Gx','Gx','Gx','Gx','Gz','Gz','Gx','Gx','Gx','Gx','Gz')*k,
        #       '(GzGxGxGxGxGzGzGxGxGxGxGz)^'+str(k))]
        #
        #thetaSinStrList += [_Circuit(
        #       ('Gx','Gx')+
        #       ('Gz','Gx','Gx','Gx','Gx','Gz','Gz','Gx','Gx','Gx','Gx','Gz')*k,
        #       'GxGx(GzGxGxGxGxGzGzGxGxGxGxGz)^'+str(k))]

    return thetaCosStrList, thetaSinStrList
示例#3
0
    def clifford_compilation(self, qubit_labels=None):
        """
        Return the Clifford-compilation dictionary for this model pack.

        This is a dictionary whose keys are all the n-qubit Clifford gates, `"GcX"`, where
        `X` is an integer, and whose values are circuits (given as tuples of labels)
        specifying how to compile that Clifford out of the native gates.

        Parameters
        ----------
        qubit_labels : tuple, optional
            If not None, a tuple of the qubit labels to use in the returned circuits. If None,
            then the default labels are used, which are often the integers beginning with 0.

        Returns
        -------
        dict
        """
        if qubit_labels is None: qubit_labels = self._sslbls
        assert (len(qubit_labels) == len(
            self._sslbls)), "Wrong number of labels in: %s" % str(qubit_labels)
        return {
            clifford_name:
            _Circuit(_transform_circuit_tup(circuittup_of_native_gates,
                                            qubit_labels),
                     line_labels=qubit_labels)
            for clifford_name, circuittup_of_native_gates in
            self._clifford_compilation.items()
        }
示例#4
0
 def _indexed_circuitdict(self, prototype, index):
     if index is None: index = self._sslbls
     assert (len(index) == len(
         self._sslbls)), "Wrong number of labels in: %s" % str(index)
     if prototype is not None:
         return {
             _Circuit(_transform_circuit_tup(k, index), line_labels=index):
             val
             for k, val in prototype.items()
         }
示例#5
0
def make_rpe_epsilon_str_lists_gx_gz(k_list):
    """
    Make epsilon cosine and sine circuit lists for (approx) X pi/4 and Z pi/2 gates.

    These circuits are used to estimate epsilon (X rotation angle).

    Parameters
    ----------
    k_list : list of ints
        The list of "germ powers" to be used.  Typically successive powers of
        two; e.g. [1,2,4,8,16].

    Returns
    -------
    epsilonCosStrList : list of Circuits
        The list of "cosine strings" to be used for epsilon estimation.
    epsilonSinStrList : list of Circuits
        The list of "sine strings" to be used for epsilon estimation.
    """
    epsilonCosStrList = []
    epsilonSinStrList = []

    for k in k_list:
        epsilonCosStrList += [
            _Circuit(('Gx', ) * k + ('Gx', ) * 4, 'Gx^' + str(k) + 'GxGxGxGx')
        ]

        epsilonSinStrList += [
            _Circuit(('Gx', 'Gx', 'Gz', 'Gz') + ('Gx', ) * k + ('Gx', ) * 4,
                     'GxGxGzGzGx^' + str(k) + 'GxGxGxGx')
        ]

        #From RPEToolsNewNew.py
        #epsilonCosStrList += [_Circuit(('Gx',)*k,
        #                                       'Gx^'+str(k))]
        #
        #epsilonSinStrList += [_Circuit(('Gx','Gx')+('Gx',)*k,
        #                                       'GxGxGx^'+str(k))]

    return epsilonCosStrList, epsilonSinStrList
示例#6
0
 def _compute_unique_circuits(cls, circuits):
     first_copy = _collections.OrderedDict()
     to_unique = {}
     nUnique = 0
     for i, c in enumerate(circuits):
         if not isinstance(c, _Circuit):
             c = _Circuit(c)  # ensure all returned circuits are Circuits
         if c not in first_copy:
             first_copy[c] = to_unique[i] = nUnique
             nUnique += 1
         else:
             to_unique[i] = first_copy[c]
     unique_circuits = list(first_copy.keys(
     ))  # unique_circuits is in same order as in `circuits`
     return unique_circuits, to_unique
示例#7
0
def create_rpe_angle_circuit_lists(k_list, angle_name, rpeconfig_inst):
    """
    Make cosine and sine circuit lists.  These operation sequences are used to estimate the angle specified
    by angle_name ('alpha', 'epsilon', or 'theta')

    Parameters
    ----------
    k_list : list of ints
        The list of "germ powers" to be used.  Typically successive powers of
        two; e.g. [1,2,4,8,16].

    angle_name : string
        The angle to be deduced from these operation sequences.
        (Choices are 'alpha', 'epsilon', or 'theta')

    rpeconfig_inst : RPEconfig object
        Declares which model configuration RPE should be trying to fit;
        determines particular functions and values to be used.

    Returns
    -------
    cosStrList : list of Circuits
        The list of "cosine strings" to be used for alpha estimation.
    sinStrList : list of Circuits
        The list of "sine strings" to be used for alpha estimation.
    """

    #    rpeconfig_inst = rpeInstanceDict[rpeconfig_inst]

    if angle_name == 'alpha':
        cos_prep_tuple = rpeconfig_inst.alpha_cos_prep_tuple
        cos_prep_str = rpeconfig_inst.alpha_cos_prep_str
        cos_germ_tuple = rpeconfig_inst.alpha_cos_germ_tuple
        cos_germ_str = rpeconfig_inst.alpha_cos_germ_str
        cos_meas_tuple = rpeconfig_inst.alpha_cos_meas_tuple
        cos_meas_str = rpeconfig_inst.alpha_cos_meas_str
        sin_prep_tuple = rpeconfig_inst.alpha_sin_prep_tuple
        sin_prep_str = rpeconfig_inst.alpha_sin_prep_str
        sin_germ_tuple = rpeconfig_inst.alpha_sin_germ_tuple
        sin_germ_str = rpeconfig_inst.alpha_sin_germ_str
        sin_meas_tuple = rpeconfig_inst.alpha_sin_meas_tuple
        sin_meas_str = rpeconfig_inst.alpha_sin_meas_str

    elif angle_name == 'epsilon':
        cos_prep_tuple = rpeconfig_inst.epsilon_cos_prep_tuple
        cos_prep_str = rpeconfig_inst.epsilon_cos_prep_str
        cos_germ_tuple = rpeconfig_inst.epsilon_cos_germ_tuple
        cos_germ_str = rpeconfig_inst.epsilon_cos_germ_str
        cos_meas_tuple = rpeconfig_inst.epsilon_cos_meas_tuple
        cos_meas_str = rpeconfig_inst.epsilon_cos_meas_str
        sin_prep_tuple = rpeconfig_inst.epsilon_sin_prep_tuple
        sin_prep_str = rpeconfig_inst.epsilon_sin_prep_str
        sin_germ_tuple = rpeconfig_inst.epsilon_sin_germ_tuple
        sin_germ_str = rpeconfig_inst.epsilon_sin_germ_str
        sin_meas_tuple = rpeconfig_inst.epsilon_sin_meas_tuple
        sin_meas_str = rpeconfig_inst.epsilon_sin_meas_str

    elif angle_name == 'theta':
        cos_prep_tuple = rpeconfig_inst.theta_cos_prep_tuple
        cos_prep_str = rpeconfig_inst.theta_cos_prep_str
        cos_germ_tuple = rpeconfig_inst.theta_cos_germ_tuple
        cos_germ_str = rpeconfig_inst.theta_cos_germ_str
        cos_meas_tuple = rpeconfig_inst.theta_cos_meas_tuple
        cos_meas_str = rpeconfig_inst.theta_cos_meas_str
        sin_prep_tuple = rpeconfig_inst.theta_sin_prep_tuple
        sin_prep_str = rpeconfig_inst.theta_sin_prep_str
        sin_germ_tuple = rpeconfig_inst.theta_sin_germ_tuple
        sin_germ_str = rpeconfig_inst.theta_sin_germ_str
        sin_meas_tuple = rpeconfig_inst.theta_sin_meas_tuple
        sin_meas_str = rpeconfig_inst.theta_sin_meas_str

    else:
        raise Exception("Need valid angle!")

    cosStrList = []
    sinStrList = []
    for k in k_list:
        cosStrList += [
            _Circuit(cos_prep_tuple + cos_germ_tuple * k + cos_meas_tuple,
                     stringrep=cos_prep_str + '(' + cos_germ_str + ')^' +
                     str(k) + cos_meas_str)
        ]
        sinStrList += [
            _Circuit(sin_prep_tuple + sin_germ_tuple * k + sin_meas_tuple,
                     stringrep=sin_prep_str + '(' + sin_germ_str + ')^' +
                     str(k) + sin_meas_str)
        ]
    return cosStrList, sinStrList
示例#8
0
def crosstalk_detection_experiment2(
        pspec,
        lengths,
        circuits_per_length,
        circuit_population_sz,
        multiplier=3,
        idle_prob=0.1,
        structure='1Q',
        descriptor='A set of crosstalk detections experiments',
        verbosity=1):

    experiment_dict = {}
    experiment_dict['spec'] = {}
    experiment_dict['spec']['lengths'] = lengths
    experiment_dict['spec']['circuit_population_sz'] = circuit_population_sz
    experiment_dict['spec']['multiplier'] = multiplier
    experiment_dict['spec']['idle_prob'] = idle_prob
    experiment_dict['spec']['descriptor'] = descriptor
    experiment_dict['spec'][
        'createdby'] = 'extras.crosstalk.crosstalk_detection_experiment2'

    if isinstance(structure, str):
        assert (structure == '1Q'
                ), "The only default `structure` option is the string '1Q'"
        structure = tuple([(q, ) for q in pspec.qubit_labels])
        n = pspec.num_qubits
    else:
        assert(isinstance(structure, list) or isinstance(structure, tuple)), \
            "If not a string, `structure` must be a list or tuple."
        qubits_used = []
        for subsetQs in structure:
            assert (isinstance(subsetQs, list) or isinstance(
                subsetQs, tuple)), "SubsetQs must be a list or a tuple!"
            qubits_used = qubits_used + list(subsetQs)
            assert(len(set(qubits_used)) == len(qubits_used)), \
                "The qubits in the tuples/lists of `structure must all be unique!"

        assert(set(qubits_used).issubset(set(pspec.qubit_labels))), \
            "The qubits to benchmark must all be in the QubitProcessorSpec `pspec`!"
        n = len(qubits_used)

    experiment_dict['spec'][
        'circuits_per_length'] = circuits_per_length * multiplier * n

    experiment_dict['spec']['structure'] = structure
    experiment_dict['circuits'] = {}
    experiment_dict['settings'] = {}

    gates_available = list(pspec.models['target'].primitive_op_labels)
    gates_by_qubit = [[] for _ in range(0, n)]
    for i in range(0, len(gates_available)):
        for q in range(0, n):
            if gates_available[i].qubits == (q, ):
                gates_by_qubit[q].append(gates_available[i])

    for lnum, l in enumerate(lengths):

        # generate menu of circuits for each qubit
        circuit_menu = [[] for _ in range(0, n)]
        for q in range(0, n):
            d = len(gates_by_qubit[q])

            if d**l < circuit_population_sz:
                print((
                    '- Warning: circuit population specified too large for qubit {}'
                    ' -- there will be redundant circuits').format(q))

            for rep in range(0, circuit_population_sz):
                singleQcirc = []
                for j in range(0, l):
                    r = _np.random.randint(0, d)
                    singleQcirc.append(gates_by_qubit[q][r])
                circuit_menu[q].append(singleQcirc)

        if verbosity > 0:
            print(
                '- Sampling {} random circuits per qubit at length {} ({} of {} lengths)'
                .format(circuits_per_length, l, lnum + 1, len(lengths)))

        # loop over qubits (should generalize this to regions)
        cnt = 0
        for q in range(0, n):
            print('  - Qubit {} = '.format(q))

            # need circuits_per_length number of settings for this qubit
            for j in range(circuits_per_length):
                if verbosity > 0: print('     Circuit {}: ('.format(j), end='')

                # draw a setting for the central qubit (q)
                #qr = _np.random.randint(0,circuit_population_sz)

                # instead of randomly drawing a setting for the central qubit,
                #  iteratively choose each of the circuits in the menu
                qr = j

                # generate "multiplier" number of random circuits on the other qubits with qr setting
                #  on the central qubit
                for m in range(0, multiplier):
                    circuit = _Circuit(num_lines=0, editable=True)
                    settings = {}

                    for q1 in range(0, n):

                        if q1 == q:
                            # the setting for the central qubit is fixed
                            r = qr
                        else:
                            # draw a setting
                            r = _np.random.randint(0, circuit_population_sz)

                        settings[(
                            q1, )] = lnum * (circuit_population_sz + 1) + r + 1

                        singleQcircuit = _Circuit(num_lines=1,
                                                  line_labels=[q1],
                                                  editable=True)
                        for layer in range(0, l):
                            singleQcircuit.insert_layer(
                                circuit_menu[q1][r][layer], layer)
                        singleQcircuit.done_editing()

                        circuit.tensor_circuit(singleQcircuit)

                    experiment_dict['settings'][l, cnt] = settings

                    # for each line, except the central qubit, replace sequence with an idle
                    #  independently according to idle_prob
                    if idle_prob > 0:
                        for q1 in range(0, n):
                            if q1 != q:
                                idle = bool(_np.random.binomial(1, idle_prob))
                                if idle:
                                    circuit.replace_with_idling_line_inplace(
                                        q1)
                                    # Update the setting on that qubit to the idling setting
                                    #  (denoted by the length index)
                                    experiment_dict['settings'][l, cnt][(
                                        q1,
                                    )] = lnum * (circuit_population_sz + 1)
                                    if verbosity > 0:
                                        print('(Idled {}) '.format(q1), end='')

                    circuit.done_editing()
                    experiment_dict['circuits'][l, cnt] = circuit
                    cnt += 1

                    if verbosity > 0: print('{}, '.format(m), end='')

                if verbosity > 0: print(')')


#   How are settings labeled?
#       For each circuit length, we label the random circuits of that length by consecutive integers, with the all
#       idle being the first number in the range for that circuit length.
#
#       So if circuit_population_sz is 10, and lengths are [10,20,30] then the the labels are:
#
#       0 (idle of length 10),  1, ...., 10  [all length 10 circuits]
#       11 (idle of length 20), 12, ..., 21  [all length 20 circuits]
#       22 (idle of length 30), 23, ..., 32  [all length 30 circuits]
#
        print('cnt: {}'.format(cnt))

    return experiment_dict
示例#9
0
def create_lsgst_circuit_lists(op_label_src,
                               prep_fiducials,
                               meas_fiducials,
                               germs,
                               max_lengths,
                               fid_pairs=None,
                               trunc_scheme="whole germ powers",
                               nest=True,
                               keep_fraction=1,
                               keep_seed=None,
                               include_lgst=True,
                               op_label_aliases=None,
                               circuit_rules=None,
                               dscheck=None,
                               action_if_missing="raise",
                               germ_length_limits=None,
                               verbosity=0):
    """
    Create a set of long-sequence GST circuit lists (including structure).

    Constructs a series (a list) of circuit structures used by long-sequence
    GST (LSGST) algorithms.  If `include_lgst == True` then the starting
    structure contains the LGST strings, otherwise the starting structure is
    empty.  For each nonzero element of max_length_list, call it L, a set of
    circuits is created with the form:

    Case: trunc_scheme == 'whole germ powers':
      prep_fiducial + pygsti.circuits.repeat_with_max_length(germ,L) + meas_fiducial

    Case: trunc_scheme == 'truncated germ powers':
      prep_fiducial + pygsti.circuits.repeat_and_truncate(germ,L) + meas_fiducial

    Case: trunc_scheme == 'length as exponent':
      prep_fiducial + germ^L + meas_fiducial

    If nest == True, the above set is iteratively *added* (w/duplicates
    removed) to the current circuit structure to form a final structure for
    the given L.  This results in successively larger structures, each of which
    contains all the elements of previous-L structures.  If nest == False then
    the above set *is* the final structure for the given L.

    Parameters
    ----------
    op_label_src : list or Model
        List of operation labels to determine needed LGST circuits.  If a Model,
        then the model's gate and instrument labels are used. Only
        relevant when `include_lgst == True`.

    prep_fiducials : list of Circuits
        List of the preparation fiducial circuits, which follow state
        preparation.

    effect_fiducials : list of Circuits
        List of the measurement fiducial circuits, which precede
        measurement.

    germs : list of Circuits
        List of the germ circuits.

    max_lengths : list of ints
        List of maximum lengths. A zero value in this list has special
        meaning, and corresponds to the LGST circuits.

    fid_pairs : list of 2-tuples or dict, optional
        Specifies a subset of all fiducial string pairs (prepStr, effectStr)
        to be used in the circuit lists.  If a list, each element of
        fid_pairs is a (iPrepStr, iEffectStr) 2-tuple of integers, each
        indexing a string within prep_strs and effect_strs, respectively, so
        that prepStr = prep_strs[iPrepStr] and effectStr =
        effect_strs[iEffectStr].  If a dictionary, keys are germs (elements
        of germ_list) and values are lists of 2-tuples specifying the pairs
        to use for that germ.

    trunc_scheme : str, optional
        Truncation scheme used to interpret what the list of maximum lengths
        means. If unsure, leave as default. Allowed values are:

        - 'whole germ powers' -- germs are repeated an integer number of
          times such that the length is less than or equal to the max.
        - 'truncated germ powers' -- repeated germ string is truncated
          to be exactly equal to the max (partial germ at end is ok).
        - 'length as exponent' -- max. length is instead interpreted
          as the germ exponent (the number of germ repetitions).

    nest : boolean, optional
        If True, the returned circuit lists are "nested", meaning
        that each successive list of circuits contains all the gate
        strings found in previous lists (and usually some additional
        new ones).  If False, then the returned string list for maximum
        length == L contains *only* those circuits specified in the
        description above, and *not* those for previous values of L.

    keep_fraction : float, optional
        The fraction of fiducial pairs selected for each germ-power base
        string.  The default includes all fiducial pairs.  Note that
        for each germ-power the selected pairs are *different* random
        sets of all possible pairs (unlike fid_pairs, which specifies the
        *same* fiducial pairs for *all* same-germ base strings).  If
        fid_pairs is used in conjuction with keep_fraction, the pairs
        specified by fid_pairs are always selected, and any additional
        pairs are randomly selected.

    keep_seed : int, optional
        The seed used for random fiducial pair selection (only relevant
        when keep_fraction < 1).

    include_lgst : boolean, optional
        If true, then the starting list (only applicable when
        `nest == True`) is the list of LGST strings rather than the
        empty list.  This means that when `nest == True`, the LGST
        circuits will be included in all the lists.

    op_label_aliases : dictionary, optional
        Dictionary whose keys are operation label "aliases" and whose values are tuples
        corresponding to what that operation label should be expanded into before querying
        the dataset.  This information is stored within the returned circuit
        structures.  Defaults to the empty dictionary (no aliases defined)
        e.g. op_label_aliases['Gx^3'] = ('Gx','Gx','Gx')

    circuit_rules : list, optional
        A list of `(find,replace)` 2-tuples which specify string replacement
        rules.  Both `find` and `replace` are tuples of operation labels
        (or `Circuit` objects).

    dscheck : DataSet, optional
        A data set which is checked for each of the generated circuits. When
        a generated circuit is missing from this `DataSet`, action is taken
        according to `action_if_missing`.

    action_if_missing : {"raise","drop"}, optional
        The action to take when a generated circuit is missing from
        `dscheck` (only relevant when `dscheck` is not None).  "raise" causes
        a ValueError to be raised; "drop" causes the missing circuits to be
        dropped from the returned set.

    germ_length_limits : dict, optional
        A dictionary limiting the max-length values used for specific germs.
        Keys are germ circuits and values are integers.  For example, if
        this argument is `{('Gx',): 4}` and `max_length_list = [1,2,4,8,16]`,
        then the germ `('Gx',)` is only repeated using max-lengths of 1, 2,
        and 4 (whereas other germs use all the values in `max_length_list`).

    verbosity : int, optional
        The level of output to print to stdout.

    Returns
    -------
    list of PlaquetteGridCircuitStructure objects
        The i-th object corresponds to a circuit list containing repeated
        germs limited to length max_length_list[i].  If nest == True, then
        repeated germs limited to previous max-lengths are also included.
        Note that a "0" maximum-length corresponds to the LGST strings.
    """

    # ensure circuit lists have computed their string reps so addition produces "nice" strings for printing
    [germ.str for germ in germs]
    [c.str for c in prep_fiducials]
    [c.str for c in meas_fiducials]

    #print('Germs: ', germs)

    def filter_ds(circuits, ds, missing_lgst):
        if ds is None: return circuits[:]
        filtered_circuits = []
        for opstr in circuits:
            trans_opstr = _gsc.translate_circuit(opstr, op_label_aliases)
            if trans_opstr not in ds:
                missing_lgst.append(opstr)
            else:
                filtered_circuits.append(opstr)
        return filtered_circuits

    def add_to_plaquettes(pkey_dict, plaquette_dict, base_circuit, maxlen,
                          germ, power, fidpair_indices, ds, missing_list):
        """ Only create a new plaquette for a new base circuit; otherwise add to existing """
        if ds is not None:
            inds_to_remove = []
            for k, (i, j) in enumerate(fidpair_indices):
                el = prep_fiducials[i] + base_circuit + meas_fiducials[j]
                trans_el = _gsc.translate_circuit(el, op_label_aliases)
                if trans_el not in ds:
                    missing_list.append((prep_fiducials[i], germ, maxlen,
                                         meas_fiducials[j], el))
                    inds_to_remove.append(k)

            if len(inds_to_remove) > 0:
                fidpair_indices = fidpair_indices[:]  # copy
                for i in reversed(inds_to_remove):
                    del fidpair_indices[i]

        fidpairs = _collections.OrderedDict([((j, i), (prep_fiducials[i],
                                                       meas_fiducials[j]))
                                             for i, j in fidpair_indices])

        if base_circuit not in plaquette_dict:
            pkey_dict[base_circuit] = (maxlen, germ)
            if power is None:  # no well-defined power, so just make a fiducial-pair plaquette
                plaq = _FiducialPairPlaquette(base_circuit, fidpairs,
                                              len(meas_fiducials),
                                              len(prep_fiducials),
                                              op_label_aliases, circuit_rules)
            else:
                plaq = _GermFiducialPairPlaquette(germ, power, fidpairs,
                                                  len(meas_fiducials),
                                                  len(prep_fiducials),
                                                  op_label_aliases,
                                                  circuit_rules)
            plaquette_dict[base_circuit] = plaq
        else:
            #Add to existing plaquette (assume we don't need to change number of rows/cols of plaquette)
            existing_plaq = plaquette_dict[base_circuit]
            existing_circuits = set(existing_plaq.circuits)
            new_fidpairs = existing_plaq.fidpairs.copy()
            for (j, i), (prep, meas) in fidpairs.items():
                if prep + base_circuit + meas not in existing_circuits:
                    new_fidpairs[(j, i)] = (prep, meas)
            if power is None:  # no well-defined power, so just make a fiducial-pair plaquette
                plaquette_dict[base_circuit] = _FiducialPairPlaquette(
                    base_circuit, new_fidpairs, len(meas_fiducials),
                    len(prep_fiducials), op_label_aliases, circuit_rules)
            else:
                plaquette_dict[base_circuit] = _GermFiducialPairPlaquette(
                    germ, power, new_fidpairs, len(meas_fiducials),
                    len(prep_fiducials), op_label_aliases, circuit_rules)

    printer = _VerbosityPrinter.create_printer(verbosity)
    if germ_length_limits is None: germ_length_limits = {}

    if nest and include_lgst and len(max_lengths) > 0 and max_lengths[0] == 0:
        _warnings.warn(
            "Setting the first element of a max-length list to zero" +
            " to ensure the inclusion of LGST circuits has been" +
            " replaced by the `include_lgst` parameter which" +
            " defaults to `True`.  Thus, in most cases, you can" +
            " simply remove the leading 0 and start your" +
            " max-length list at 1 now." + "")

    from pygsti.processors.processorspec import QuditProcessorSpec as _QuditProcessorSpec
    from pygsti.models.model import OpModel as _OpModel
    if isinstance(op_label_src, _QuditProcessorSpec):
        opLabels = op_label_src.primitive_op_labels
    elif isinstance(op_label_src, _OpModel):
        opLabels = op_label_src.primitive_op_labels + op_label_src.primitive_instrument_labels
    else:
        opLabels = op_label_src

    lgst_list = _gsc.create_lgst_circuits(prep_fiducials, meas_fiducials,
                                          opLabels)
    if circuit_rules is not None:
        lgst_list = _manipulate_circuits(lgst_list, circuit_rules)

    allPossiblePairs = list(
        _itertools.product(range(len(prep_fiducials)),
                           range(len(meas_fiducials))))

    if keep_fraction < 1.0:
        rndm = _rndm.RandomState(keep_seed)  # ok if seed is None
        nPairs = len(prep_fiducials) * len(meas_fiducials)
        nPairsToKeep = int(round(float(keep_fraction) * nPairs))
    else:
        rndm = None

    fidpair_germ_power_keys = False
    if isinstance(fid_pairs, dict) or hasattr(fid_pairs, "keys"):
        fidPairDict = fid_pairs  # assume a dict of per-germ pairs
        if isinstance(list(fidPairDict.keys())[0], tuple):
            fidpair_germ_power_keys = True
    else:
        if fid_pairs is not None:  # assume fid_pairs is a list
            fidPairDict = {germ: fid_pairs for germ in germs}
        else:
            fidPairDict = None

    truncFn = _get_trunc_function(trunc_scheme)

    line_labels = germs[0].line_labels if len(germs) > 0 \
        else (prep_fiducials + meas_fiducials)[0].line_labels   # if an empty germ list, base line_labels off fiducials

    empty_germ = _Circuit((
    ), line_labels)  # , stringrep="{}@(%s)" % ','.join(map(str, line_labels)))
    if include_lgst and empty_germ not in germs: germs = [empty_germ] + germs

    if nest:
        #keep track of running quantities used to build circuit structures
        running_plaquette_keys = {
        }  # base-circuit => (maxlength, germ) key for final plaquette dict
        running_plaquettes = _collections.OrderedDict(
        )  # keep consistent ordering in produced circuit list.
        running_unindexed = []
        running_maxLens = []

    lsgst_structs = []  # list of circuit structures to return
    missing_list = []  # keep track of missing data if dscheck is given
    missing_lgst = [
    ]  # keep track of missing LGST circuits separately (for better error msgs)
    tot_circuits = 0

    if include_lgst and len(max_lengths) == 0:
        # Then we won't add LGST circuits during first iteration of loop below, so add them now
        unindexed = filter_ds(lgst_list, dscheck, missing_lgst)
        lsgst_structs.append(
            _PlaquetteGridCircuitStructure(
                {}, [],
                germs,
                "L",
                "germ",
                unindexed,
                op_label_aliases,
                circuit_weights_dict=None,
                additional_circuits_location='start',
                name=None))

    for i, maxLen in enumerate(max_lengths):

        if nest:  # add to running_* variables and pinch off a copy later on
            running_maxLens.append(maxLen)
            pkey = running_plaquette_keys
            plaquettes = running_plaquettes
            maxLens = running_maxLens
            unindexed = running_unindexed
        else:  # create a new cs for just this maxLen
            pkey = {
            }  # base-circuit => (maxlength, germ) key for final plaquette dict
            plaquettes = _collections.OrderedDict()
            maxLens = [maxLen]
            unindexed = []

        if maxLen == 0:
            # Special LGST case
            unindexed.extend(filter_ds(
                lgst_list, dscheck,
                missing_lgst))  # overlap w/plaquettes ok (removed later)
        else:
            if include_lgst and i == 0:  # first maxlen, so add LGST seqs as empty germ
                #Add LGST circuits as an empty-germ plaquette (and as unindexed circuits to include everything)
                #Note: no FPR on LGST strings
                add_to_plaquettes(pkey, plaquettes, empty_germ, maxLen,
                                  empty_germ, 1, allPossiblePairs, dscheck,
                                  missing_list)
                unindexed.extend(filter_ds(
                    lgst_list, dscheck,
                    missing_lgst))  # overlap w/plaquettes ok (removed later)
            #Typical case of germs repeated to maxLen using r_fn
            for ii, germ in enumerate(germs):
                if germ == empty_germ: continue  # handled specially above
                if maxLen > germ_length_limits.get(germ, 1e100): continue

                germ_power = truncFn(germ, maxLen)
                power = len(germ_power) // len(
                    germ)  # this *could* be the germ power
                if germ_power != germ * power:
                    power = None  # Signals there is no well-defined power

                if power == 0 and len(germ) != 0:
                    continue

                # Switch on fidpair dicts with germ or (germ, L) keys
                key = germ
                if fidpair_germ_power_keys:
                    key = (germ, maxLen)

                if rndm is None:
                    fiducialPairsThisIter = fidPairDict.get(key, allPossiblePairs) \
                        if fidPairDict is not None else allPossiblePairs
                    #if fiducialPairsThisIter==allPossiblePairs:
                    #    print('Couldn\'t find ', key, ' using allPossiblePairs')
                    #print('FiducialPairsThisIter: ', fiducialPairsThisIter)
                elif fidPairDict is not None:
                    pair_indx_tups = fidPairDict.get(key, allPossiblePairs)
                    remainingPairs = [(i, j)
                                      for i in range(len(prep_fiducials))
                                      for j in range(len(meas_fiducials))
                                      if (i, j) not in pair_indx_tups]
                    nPairsRemaining = len(remainingPairs)
                    nPairsToChoose = nPairsToKeep - len(pair_indx_tups)
                    nPairsToChoose = max(0, min(nPairsToChoose,
                                                nPairsRemaining))
                    assert (0 <= nPairsToChoose <= nPairsRemaining)
                    # FUTURE: issue warnings when clipping nPairsToChoose?

                    fiducialPairsThisIter = fidPairDict[key] + \
                        [remainingPairs[k] for k in
                         sorted(rndm.choice(nPairsRemaining, nPairsToChoose,
                                            replace=False))]

                else:  # rndm is not None and fidPairDict is None
                    assert (nPairsToKeep <= nPairs
                            )  # keep_fraction must be <= 1.0
                    fiducialPairsThisIter = \
                        [allPossiblePairs[k] for k in
                         sorted(rndm.choice(nPairs, nPairsToKeep, replace=False))]

                add_to_plaquettes(pkey, plaquettes, germ_power, maxLen, germ,
                                  power, fiducialPairsThisIter, dscheck,
                                  missing_list)

        if nest:
            # pinch off a copy of variables that were left as the running variables above
            maxLens = maxLens[:]
            plaquettes = plaquettes.copy()
            unindexed = unindexed[:]

        lsgst_structs.append(
            _PlaquetteGridCircuitStructure(
                _collections.OrderedDict([
                    (pkey[base], plaq) for base, plaq in plaquettes.items()
                ]),
                maxLens,
                germs,
                "L",
                "germ",
                unindexed,
                op_label_aliases,
                circuit_weights_dict=None,
                additional_circuits_location='start',
                name=None))
        tot_circuits += len(
            lsgst_structs[-1])  # only relevant for non-nested case

    if nest:  # then totStrs computation about overcounts -- just take string count of final stage
        tot_circuits = len(lsgst_structs[-1]) if len(lsgst_structs) > 0 else 0

    printer.log("--- Circuit Creation ---", 1)
    printer.log(" %d circuits created" % tot_circuits, 2)
    #print("Total Number of Circuits: ", tot_circuits)
    if dscheck:
        printer.log(
            " Dataset has %d entries: %d utilized, %d requested circuits were missing"
            % (len(dscheck), tot_circuits, len(missing_list)), 2)
    #print(len(missing_lgst))
    if len(missing_list) > 0 or len(missing_lgst) > 0:
        MAX = 10  # Maximum missing-seq messages to display
        missing_msgs = [("Prep: %s, Germ: %s, L: %d, Meas: %s, Circuit: %s" % tup) for tup in missing_list[0:MAX + 1]] \
            + ["LGST Seq: %s" % opstr for opstr in missing_lgst[0:MAX + 1]]
        if len(missing_list) > MAX or len(missing_lgst) > MAX:
            missing_msgs.append(" ... (more missing circuits not show) ... ")
        printer.log("The following circuits were missing from the dataset:", 4)
        printer.log("\n".join(missing_msgs), 4)
        if action_if_missing == "raise":
            raise ValueError("Missing data! %d missing circuits" %
                             len(missing_msgs))
        elif action_if_missing == "drop":
            pass
        else:
            raise ValueError("Invalid `action_if_missing` argument: %s" %
                             action_if_missing)

    for i, struct in enumerate(lsgst_structs):
        if nest:
            assert (struct.xs == max_lengths[0:i + 1]
                    )  # Make sure lengths are correct!
        else:
            assert (struct.xs == max_lengths[i:i + 1]
                    )  # Make sure lengths are correct!

    return lsgst_structs