def compute_gate_inverse(gate_label): if gate_label.name in gate_inverse.keys(): return _lbl.Label(gate_inverse[gate_label.name], gate_label.qubits) else: if gate_label.name == 'Gzr' or gate_label.name == 'Gczr': return _lbl.Label(gate_label.name, gate_label.qubits, args=(str(-1 * float(gate_label.args[0])), )) else: raise ValueError("Cannot invert gate with name {}".format( gate_label.name))
def p_expdstr_expop(p): '''expdstr : expable EXPOP INTEGER''' plbl = _lbl.Label(p[1]) # just for total sslbls if len(p[1]) > 0: p[0] = _lbl.CircuitLabel('', p[1], plbl.sslbls, p[3]), else: p[0] = () # special case of {}^power => remain empty
def make_label(s): if '!' in s: s, time = s.split( '!') # must be only two parts (only 1 exclamation pt) time = float(time) else: time = 0.0 if ';' in s: parts = s.split(';') parts2 = parts[-1].split(':') nm = parts[0] args = parts[1:-1] + [parts2[0]] sslbls = parts2[1:] else: parts = s.split(':') nm = parts[0] args = None sslbls = parts[1:] if len(sslbls) == 0: sslbls = None return _lbl.Label(nm, sslbls, time, args)
def p_layerable_subcircuit_expop(p): '''layerable : subcircuit EXPOP INTEGER''' plbl = _lbl.Label(p[1]) # just for total sslbls p[0] = _lbl.CircuitLabel('', p[1], plbl.sslbls, p[3]),
def p_layerable_subcircuit(p): '''layerable : subcircuit ''' plbl = _lbl.Label((p[1], )) # just for total sslbls p[0] = _lbl.CircuitLabel('', p[1], plbl.sslbls, 1),
def simulate_data(model_or_dataset, circuit_list, num_samples, sample_error="multinomial", seed=None, rand_state=None, alias_dict=None, collision_action="aggregate", record_zero_counts=True, comm=None, mem_limit=None, times=None): """ Creates a DataSet using the probabilities obtained from a model. Parameters ---------- model_or_dataset : Model or DataSet object The source of the underlying probabilities used to generate the data. If a Model, the model whose probabilities generate the data. If a DataSet, the data set whose frequencies generate the data. circuit_list : list of (tuples or Circuits) or ExperimentDesign or None Each tuple or Circuit contains operation labels and specifies a gate sequence whose counts are included in the returned DataSet. e.g. ``[ (), ('Gx',), ('Gx','Gy') ]`` If an :class:`ExperimentDesign`, then the design's `.all_circuits_needing_data` list is used as the circuit list. num_samples : int or list of ints or None The simulated number of samples for each circuit. This only has effect when ``sample_error == "binomial"`` or ``"multinomial"``. If an integer, all circuits have this number of total samples. If a list, integer elements specify the number of samples for the corresponding circuit. If ``None``, then `model_or_dataset` must be a :class:`~pygsti.objects.DataSet`, and total counts are taken from it (on a per-circuit basis). sample_error : string, optional What type of sample error is included in the counts. Can be: - "none" - no sample error: counts are floating point numbers such that the exact probabilty can be found by the ratio of count / total. - "clip" - no sample error, but clip probabilities to [0,1] so, e.g., counts are always positive. - "round" - same as "clip", except counts are rounded to the nearest integer. - "binomial" - the number of counts is taken from a binomial distribution. Distribution has parameters p = (clipped) probability of the circuit and n = number of samples. This can only be used when there are exactly two SPAM labels in model_or_dataset. - "multinomial" - counts are taken from a multinomial distribution. Distribution has parameters p_k = (clipped) probability of the gate string using the k-th SPAM label and n = number of samples. seed : int, optional If not ``None``, a seed for numpy's random number generator, which is used to sample from the binomial or multinomial distribution. rand_state : numpy.random.RandomState A RandomState object to generate samples from. Can be useful to set instead of `seed` if you want reproducible distribution samples across multiple random function calls but you don't want to bother with manually incrementing seeds between those calls. alias_dict : dict, optional A dictionary mapping single operation labels into tuples of one or more other operation labels which translate the given circuits before values are computed using `model_or_dataset`. The resulting Dataset, however, contains the *un-translated* circuits as keys. collision_action : {"aggregate", "keepseparate"} Determines how duplicate circuits are handled by the resulting `DataSet`. Please see the constructor documentation for `DataSet`. record_zero_counts : bool, optional Whether zero-counts are actually recorded (stored) in the returned DataSet. If False, then zero counts are ignored, except for potentially registering new outcome labels. comm : mpi4py.MPI.Comm, optional When not ``None``, an MPI communicator for distributing the computation across multiple processors and ensuring that the *same* dataset is generated on each processor. mem_limit : int, optional A rough memory limit in bytes which is used to determine job allocation when there are multiple processors. times : iterable, optional When not None, a list of time-stamps at which data should be sampled. `num_samples` samples will be simulated at each time value, meaning that each circuit in `circuit_list` will be evaluated with the given time value as its *start time*. Returns ------- DataSet A static data set filled with counts for the specified circuits. """ NTOL = 10 TOL = 10**-NTOL if isinstance(model_or_dataset, _ds.DataSet): dsGen = model_or_dataset gsGen = None dataset = _ds.DataSet( collision_action=collision_action, outcome_label_indices=dsGen.olIndex) # keep same outcome labels else: gsGen = model_or_dataset dsGen = None dataset = _ds.DataSet(collision_action=collision_action) if alias_dict: alias_dict = { _lbl.Label(ky): tuple((_lbl.Label(el) for el in val)) for ky, val in alias_dict.items() } # convert to use Labels from pygsti.protocols import ExperimentDesign as _ExperimentDesign if isinstance(circuit_list, _ExperimentDesign): circuit_list = circuit_list.all_circuits_needing_data if gsGen and times is None: if alias_dict is not None: trans_circuit_list = [ _gstrc.translate_circuit(s, alias_dict) for s in circuit_list ] else: trans_circuit_list = circuit_list all_probs = gsGen.bulk_probabilities(trans_circuit_list, comm=comm, mem_limit=mem_limit) else: trans_circuit_list = circuit_list if comm is None or comm.Get_rank() == 0: # only root rank computes if sample_error in ("binomial", "multinomial") and rand_state is None: rndm = _rndm.RandomState(seed) # ok if seed is None else: rndm = rand_state # can be None circuit_times = times if times is not None else ["N/A dummy"] count_lists = _collections.OrderedDict() for tm in circuit_times: #print("Time ", tm) #It would be nice to be able to do something like this (time dependent calc for all probs at ptic time) #if gsGen and times is not None: # all_probs = gsGen.bulk_probabilities(trans_circuit_list, comm=comm, mem_limit=mem_limit, time=tm) for k, (s, trans_s) in enumerate(zip(circuit_list, trans_circuit_list)): if gsGen: if times is None: ps = all_probs[trans_s] else: ps = gsGen.probabilities(trans_s, time=tm) if sample_error in ("binomial", "multinomial"): _adjust_probabilities_inbounds(ps, TOL) else: ps = _collections.OrderedDict([ (ol, frac) for ol, frac in dsGen[trans_s].fractions.items() ]) if gsGen and sample_error in ("binomial", "multinomial"): _adjust_unit_sum(ps, TOL) if num_samples is None and dsGen is not None: N = dsGen[ trans_s].total # use the number of samples from the generating dataset #Note: total() accounts for other intermediate-measurment branches automatically else: try: N = num_samples[ k] # try to treat num_samples as a list except: N = num_samples # if not indexable, num_samples should be a single number nWeightedSamples = N counts = _sample_distribution(ps, sample_error, nWeightedSamples, rndm) if s not in count_lists: count_lists[s] = [] count_lists[s].append(counts) if times is None: for s, counts_list in count_lists.items(): for counts_dict in counts_list: dataset.add_count_dict( s, counts_dict, record_zero_counts=record_zero_counts) else: for s, counts_list in count_lists.items(): dataset.add_series_data(s, counts_list, times, record_zero_counts=record_zero_counts) dataset.done_adding_data() if comm is not None: # broadcast to non-root procs dataset = comm.bcast(dataset if (comm.Get_rank() == 0) else None, root=0) return dataset
def create_mirror_circuit(circ, pspec, circ_type='clifford+zxzxz'): """ circ_type : clifford+zxzxz, cz(theta)+zxzxz """ n = circ.width d = circ.depth pauli_labels = ['I', 'X', 'Y', 'Z'] qubits = circ.line_labels _, gate_inverse = pspec.compute_one_qubit_gate_relations() gate_inverse.update( pspec.compute_multiqubit_inversion_relations()) # add multiQ inverse assert (circ_type in ( 'clifford+zxzxz', 'cz(theta)+zxzxz')), '{} not a valid circ_type!'.format(circ_type) def compute_gate_inverse(gate_label): if gate_label.name in gate_inverse.keys(): return _lbl.Label(gate_inverse[gate_label.name], gate_label.qubits) else: if gate_label.name == 'Gzr' or gate_label.name == 'Gczr': return _lbl.Label(gate_label.name, gate_label.qubits, args=(str(-1 * float(gate_label.args[0])), )) else: raise ValueError("Cannot invert gate with name {}".format( gate_label.name)) srep_dict = _symp.compute_internal_gate_symplectic_representations( gllist=['I', 'X', 'Y', 'Z']) # the `callable` part is a workaround to remove gates with args, defined by functions. srep_dict.update( pspec.compute_clifford_symplectic_reps( tuple((gn for gn, u in pspec.gate_unitaries.items() if not callable(u))))) if 'Gxpi2' in pspec.gate_names: xname = 'Gxpi2' elif 'Gc16' in pspec.gate_names: xname = 'Gc16' else: raise ValueError( ("There must be an X(pi/2) gate in the processor spec's gate set," " and it must be called Gxpi2 or Gc16!")) assert('Gzr' in pspec.gate_names), \ "There must be an Z(theta) gate in the processor spec's gate set, and it must be called Gzr!" zrotname = 'Gzr' if circ_type == 'cz(theta)+zxzxz': assert('Gczr' in pspec.gate_names), \ "There must be an controlled-Z(theta) gate in the processor spec's gate set, and it must be called Gczr!" czrotname = 'Gczr' Xpi2layer = [_lbl.Label(xname, q) for q in qubits] #make an editable copy of the circuit to add the inverse on to c = circ.copy(editable=True) #build the inverse d_ind = 0 while d_ind < d: layer = circ.layer(d - d_ind - 1) if len(layer) > 0 and layer[ 0].name == zrotname: # ask if it's a Zrot layer. # It's necessary for the whole layer to have Zrot gates #get the entire arbitrary 1q unitaries: Zrot-Xpi/2-Zrot-Xpi/2-Zrot current_layers = circ[d - d_ind - 5:d - d_ind] #recompile inverse of current layer for i in range(n): if n == 1: old_params = [(float(current_layers[0].args[0]), float(current_layers[2].args[0]), float(current_layers[4].args[0])) for i in range(n)] else: old_params = [(float(current_layers[0][i].args[0]), float(current_layers[2][i].args[0]), float(current_layers[4][i].args[0])) for i in range(n)] layer_new_params = [ _comp.inv_recompile_unitary(*p) for p in old_params ] theta1_layer = [ _lbl.Label(zrotname, qubits[i], args=(str(layer_new_params[i][0]), )) for i in range(len(layer_new_params)) ] theta2_layer = [ _lbl.Label(zrotname, qubits[i], args=(str(layer_new_params[i][1]), )) for i in range(len(layer_new_params)) ] theta3_layer = [ _lbl.Label(zrotname, qubits[i], args=(str(layer_new_params[i][2]), )) for i in range(len(layer_new_params)) ] #add to mirror circuit c.append_circuit_inplace( _cir.Circuit([theta3_layer], line_labels=circ.line_labels)) c.append_circuit_inplace( _cir.Circuit([Xpi2layer], line_labels=circ.line_labels)) c.append_circuit_inplace( _cir.Circuit([theta2_layer], line_labels=circ.line_labels)) c.append_circuit_inplace( _cir.Circuit([Xpi2layer], line_labels=circ.line_labels)) c.append_circuit_inplace( _cir.Circuit([theta1_layer], line_labels=circ.line_labels)) d_ind += 5 else: inverse_layer = [ compute_gate_inverse(gate_label) for gate_label in layer ] c.append_circuit_inplace( _cir.Circuit([inverse_layer], line_labels=circ.line_labels)) d_ind += 1 #now that we've built the simple mirror circuit, let's add pauli frame randomization d_ind = 0 mc = [] net_paulis = {q: 0 for q in qubits} d = c.depth correction_angles = { q: 0 for q in qubits } # corrections used in the cz(theta) case, which do nothing otherwise. while d_ind < d: layer = c.layer(d_ind) if len(layer) > 0 and layer[ 0].name == zrotname: # ask if it's a Zrot layer. #It's necessary for the whole layer to have Zrot gates #if the layer is 1Q unitaries, pauli randomize current_layers = c[d_ind:d_ind + 5] #generate random pauli new_paulis = {q: _np.random.randint(0, 4) for q in qubits} new_paulis_as_layer = [ _lbl.Label(pauli_labels[new_paulis[q]], q) for q in qubits ] net_paulis_as_layer = [ _lbl.Label(pauli_labels[net_paulis[q]], q) for q in qubits ] #compute new net pauli based on previous pauli net_pauli_numbers = _symp.find_pauli_number( _symp.symplectic_rep_of_clifford_circuit( _cir.Circuit(new_paulis_as_layer + net_paulis_as_layer, line_labels=circ.line_labels), srep_dict=srep_dict)[1]) # THIS WAS THE (THETA) VERSIONS #net_paulis_as_layer = [_lbl.Label(pauli_labels[net_paulis[q]], q) for q in qubits] #net_pauli_numbers = _symp.find_pauli_number(_symp.symplectic_rep_of_clifford_circuit(_cir.Circuit( # new_paulis_as_layer+net_paulis_as_layer), pspec=pspec)[1]) net_paulis = {qubits[i]: net_pauli_numbers[i] for i in range(n)} #depending on what the net pauli before the U gate is, might need to change parameters on the U gate # to commute the pauli through #recompile current layer to account for this and recompile with these paulis if n == 1: old_params_and_paulis = [ (float(current_layers[0].args[0]), float(current_layers[2].args[0]), float(current_layers[4].args[0]), net_paulis[qubits[i]], new_paulis[qubits[i]]) for i in range(n) ] else: old_params_and_paulis = [ (float(current_layers[0][i].args[0]), float(current_layers[2][i].args[0]), float(current_layers[4][i].args[0]), net_paulis[qubits[i]], new_paulis[qubits[i]]) for i in range(n) ] layer_new_params = [ _comp.pauli_frame_randomize_unitary(*p) for p in old_params_and_paulis ] #recompile any zrotation corrections from the previous Czr into the first zr of this layer. This correction # will be zero if there are no Czr gates (when it's clifford+zxzxz) theta1_layer = [ _lbl.Label(zrotname, qubits[i], args=(str(layer_new_params[i][0] + correction_angles[qubits[i]]), )) for i in range(len(layer_new_params)) ] theta2_layer = [ _lbl.Label(zrotname, qubits[i], args=(str(layer_new_params[i][1]), )) for i in range(len(layer_new_params)) ] theta3_layer = [ _lbl.Label(zrotname, qubits[i], args=(str(layer_new_params[i][2]), )) for i in range(len(layer_new_params)) ] #add to mirror circuit mc.append([theta1_layer]) mc.append([Xpi2layer]) mc.append([theta2_layer]) mc.append([Xpi2layer]) mc.append([theta3_layer]) d_ind += 5 # reset the correction angles. correction_angles = {q: 0 for q in qubits} else: if circ_type == 'clifford+zxzxz': net_paulis_as_layer = [ _lbl.Label(pauli_labels[net_paulis[qubits[i]]], qubits[i]) for i in range(n) ] circ_sandwich = _cir.Circuit( [layer, net_paulis_as_layer, layer], line_labels=circ.line_labels) net_paulis = { qubits[i]: pn for i, pn in enumerate( _symp.find_pauli_number( _symp.symplectic_rep_of_clifford_circuit( circ_sandwich, srep_dict=srep_dict)[1])) } mc.append(layer) #we need to account for how the net pauli changes when it gets passed through the clifford layers if circ_type == 'cz(theta)+zxzxz': quasi_inv_layer = [] #recompile layer taking into acount paulis for g in layer: if g.name == czrotname: #get the qubits, figure out net pauli on those qubits gate_qubits = g.qubits net_paulis_for_gate = (net_paulis[gate_qubits[0]], net_paulis[gate_qubits[1]]) theta = float(g.args[0]) if ((net_paulis_for_gate[0] % 3 != 0 and net_paulis_for_gate[1] % 3 == 0) or (net_paulis_for_gate[0] % 3 == 0 and net_paulis_for_gate[1] % 3 != 0)): theta *= -1 quasi_inv_layer.append( _lbl.Label(czrotname, gate_qubits, args=(str(theta), ))) #for each X or Y, do a Zrotation by -theta on the other qubit after the 2Q gate. for q in gate_qubits: if net_paulis[q] == 1 or net_paulis[q] == 2: for q2 in gate_qubits: if q2 != q: correction_angles[q2] += -1 * theta else: quasi_inv_layer.append( _lbl.Label(compute_gate_inverse(g))) #add to circuit mc.append([quasi_inv_layer]) #increment position in circuit d_ind += 1 #update the target pauli #pauli_layer = [_lbl.Label(pauli_labels[net_paulis[i]], qubits[i]) for i in range(len(qubits))] # The version from (THETA) pauli_layer = [_lbl.Label(pauli_labels[net_paulis[q]], q) for q in qubits] conjugation_circ = _cir.Circuit([pauli_layer], line_labels=circ.line_labels) telp_s, telp_p = _symp.symplectic_rep_of_clifford_circuit( conjugation_circ, srep_dict=srep_dict) # Calculate the bit string that this mirror circuit should output, from the final telescoped Pauli. target_bitstring = ''.join(['1' if p == 2 else '0' for p in telp_p[n:]]) mirror_circuit = _cir.Circuit(mc, line_labels=circ.line_labels) return mirror_circuit, target_bitstring
def _get_next_lbls(s, start, end, create_subcircuits, integerize_sslbls, segment, interlayer_marker): if s[start] == "(": i = start + 1 lbls_list = [] interlayer_marker_inds = [] while i < end and s[i] != ")": if s[i] == interlayer_marker: interlayer_marker_inds.append(len(lbls_list) - 1) i += 1 if i == end or s[i] == ")": break lbls, i, segment, _ = _get_next_lbls( s, i, end, create_subcircuits, integerize_sslbls, segment, interlayer_marker ) # don't recursively look for interlayer markers lbls_list.extend(lbls) if i == end: raise ValueError("mismatched parenthesis") i += 1 exponent, i = _parse_exponent(s, i, end) if exponent != 1 and len(interlayer_marker_inds) > 0: if exponent == 0: interlayer_marker_inds = () else: # exponent > 1 base_marker_inds = interlayer_marker_inds[:] # a new list for k in range(1, exponent): offset = len(lbls_list) * k interlayer_marker_inds.extend( map(lambda x: x + offset, base_marker_inds)) if create_subcircuits: if len(lbls_list) == 0: # special case of {}^power => remain empty return [], i, segment, () else: tmp = _lbl.Label( lbls_list ) # just for total sslbs - should probably do something faster return [ _lbl.CircuitLabel('', lbls_list, tmp.sslbls, exponent) ], i, segment, () else: return lbls_list * exponent, i, segment, interlayer_marker_inds elif s[start] == "[": # layer i = start + 1 lbls_list = [] while i < end and s[i] != "]": lbls, i, segment, _ = _get_next_lbls( s, i, end, create_subcircuits, integerize_sslbls, segment, interlayer_marker) # but don't actually look for marker lbls_list.extend(lbls) if i == end: raise ValueError("mismatched parenthesis") i += 1 exponent, i = _parse_exponent(s, i, end) if len(lbls_list) == 0: to_exponentiate = _lbl.LabelTupTup(()) elif len(lbls_list) > 1: time = max([l.time for l in lbls_list]) # create a layer label - a label of the labels within square brackets to_exponentiate = _lbl.LabelTupTup(tuple(lbls_list)) if (time == 0.0) \ else _lbl.LabelTupTupWithTime(tuple(lbls_list), time) else: to_exponentiate = lbls_list[0] return [to_exponentiate] * exponent, i, segment, () else: lbls, i, segment = _get_next_simple_lbl(s, start, end, integerize_sslbls, segment) exponent, i = _parse_exponent(s, i, end) return lbls * exponent, i, segment, ()