<jupyter_start><jupyter_text>Qubit picking with Loschmidt echoes       View on QuantumAI        Run in Google Colab        View source on GitHub        Download notebook   A Loschmidt echo circuit applies $UU^\dagger$ for some unitary $U$ and measures the probability of the ground state $p(0)$. In the noiseless case, $p(0) = 1$, and the deviation from this value gives some indication to the amount of noise in the processor. In particular, by fitting an exponential decay to the measured ground state probability vs. number of cycles (depth of $U$), we can estimate the gate error per cycle on a set of qubits. By varying this experiment over different qubit configurations, we can select the best configuration (lowest gate error per cycle) to run an experiment on. **Disclaimer**: The data shown in this tutorial is exemplary and does not reflect the performance of QCS in production.  Setup We first install Cirq then import packages we will use.<jupyter_code>try:
    import cirq
except ImportError:
    !pip install cirq --quiet
from typing import Optional, Sequence

import matplotlib.pyplot as plt
import numpy as np

import cirq
from cirq.experiments import random_rotations_between_grid_interaction_layers_circuit<jupyter_output><empty_output><jupyter_text>Next, we authorize to use the Quantum Computing Service.<jupyter_code># The Google Cloud Project id to use.
project_id = '' #@param {type:"string"}

if project_id == '':
    import os 
    if 'GOOGLE_CLOUD_PROJECT' not in os.environ:
        raise Exception("Please setup project_id in this cell or set the `GOOGLE_CLOUD_PROJECT` env var to your project id.")
    project_id = os.environ['GOOGLE_CLOUD_PROJECT']
else: 
    import os
    os.environ['GOOGLE_CLOUD_PROJECT'] = project_id

def authenticate_user():
    """Runs the user through the Colab OAuth process.

    Checks for Google Application Default Credentials and runs interactive login 
    if the notebook is executed in Colab. In case the notebook is executed in Jupyter notebook
    or other IPython runtimes, no interactive login is provided, it is assumed that the 
    `GOOGLE_APPLICATION_CREDENTIALS` env var is set or `gcloud auth application-default login`
    was executed already.

    For more information on using Application Default Credentials see 
    https://cloud.google.com/docs/authentication/production
    """
    in_colab = False
    try:
        from IPython import get_ipython
        in_colab = 'google.colab' in str(get_ipython())
    except: 
        # Notebook is not executed within IPython. Assuming external authentication.
        return 

    if in_colab: 
        from google.colab import auth      
        print("Getting OAuth2 credentials.")
        print("Press enter after entering the verification code.")
        auth.authenticate_user(clear_output=False)
        print("Authentication complete.")
    else: 
        print("Notebook is not executed with Colab, assuming Application Default Credentials are setup.") 

authenticate_user()

print("Successful authentication to Google Cloud.")<jupyter_output><empty_output><jupyter_text>Creating the circuits The function below creates a Loschmidt echo using a random circuit for $U$ on a given set of `qubits` for a given number of `cycles`. A `pause` can be optionally applied after $U$ and before $U^\dagger$.<jupyter_code>def create_loschmidt_echo_circuit(
    qubits: Sequence[cirq.GridQubit],
    cycles: int,
    twoq_gate: cirq.Gate = cirq.FSimGate(np.pi / 4, 0.0),
    pause: Optional[cirq.Duration] = None,
    seed: Optional[int] = None,
) -> cirq.Circuit:
    """Returns a Loschmidt echo circuit using a random unitary U.
    
    Args:
        qubits: Qubits to use.
        cycles: Depth of random rotations in the forward & reverse unitary.
        twoq_gate: Two-qubit gate to use.
        pause: Optional duration to pause for between U and U^\dagger.
        seed: Seed for circuit generation.
    """
    # Forward (U) operations.
    forward = random_rotations_between_grid_interaction_layers_circuit(
        qubits, 
        depth=cycles,
        two_qubit_op_factory=lambda a, b, _: twoq_gate.on(a, b),
        pattern=cirq.experiments.GRID_STAGGERED_PATTERN,
        single_qubit_gates=[cirq.PhasedXPowGate(phase_exponent=p, exponent=0.5)
                            for p in np.arange(-1.0, 1.0, 0.25)],
        seed=seed
    )

    # Optional pause.
    if pause is not None:
        wait = cirq.Moment(cirq.WaitGate(pause).on(q) for q in qubits)
    else:
        wait = []
    
    # Reverse (U^\dagger) operations.
    reverse = cirq.inverse(forward)

    # Measure all qubits.
    measure = cirq.measure(*qubits, key="z")

    return forward + wait + reverse + measure<jupyter_output><empty_output><jupyter_text>For example, we visualize a Loschmidt echo circuit below.<jupyter_code>"""See an example circuit."""
circuit = create_loschmidt_echo_circuit(
    qubits=cirq.GridQubit.square(2), cycles=2, pause=cirq.Duration(nanos=5.0)
)
circuit<jupyter_output><empty_output><jupyter_text>As mentioned, without noise all measurements should be $0$s. We verify this below by computing the ground state probability (or *survival probability*) on a noiseless simulator.<jupyter_code>"""Loschmidt echo benchmark on a simulator."""
# Simulate the circuit.
nreps = 20_000
res = cirq.Simulator().run(circuit, repetitions=nreps)

# Verify the survival probability is 1.0.
ground_state_prob = np.mean(np.sum(res.measurements["z"], axis=1) == 0)
print("Survival probability:", ground_state_prob)<jupyter_output>Survival probability: 1.0<jupyter_text>For convenience, we define a helper function to compute the ground state probability from a measurement result below.<jupyter_code>def to_ground_state_prob(result: cirq.Result) -> float:
    return np.mean(np.sum(result.measurements["z"], axis=1) == 0)<jupyter_output><empty_output><jupyter_text>Running the circuits We now create several Loschmidt echo circuits and run them on the Quantum Engine. The next cell sets various parameters including * `processor_id`, * list of `cycles` (depths) to use,* (optional) `pause`, * and the number of repetitions `nreps`. The `trials` parameter is the number of independent experiments to perform with the above parameters.<jupyter_code>"""Set parameters for Loschmidt echo benchmark."""
processor_id = "weber"
cycle_values = range(0, 80 + 1, 2)
pause = None
nreps = 20_000
trials = 10<jupyter_output><empty_output><jupyter_text>We now select several qubit configurations to run the Loschmidt echo experiment on. A good starting point for picking qubits is the calibration data.<jupyter_code>engine = cirq.google.Engine(project_id)

calibration = engine.get_processor(processor_id).get_current_calibration()
metric = "two_qubit_sqrt_iswap_gate_xeb_pauli_error_per_cycle"

_ = calibration.heatmap(metric).plot()<jupyter_output><empty_output><jupyter_text>Using this calibration information, we select several candidate sets of qubits to use. Note: We intentionally select one qubit configuration with a high calibration error to show this propagates through in our results. In practice, one would usually want all qubit configurations to have low errors to find the best one.<jupyter_code>"""Pick sets of qubits to run Loschmidt echoes on."""
qubit_sets_indices = [
    [(4, 7), (4, 8), (5, 8), (5, 7)],
    [(0, 5), (0, 6), (1, 6), (1, 5)],  # From the calibration, we expect this to be the worst configuration.
    [(2, 6), (2, 7), (3, 7), (3, 6)],
    [(7, 3), (7, 4), (8, 4), (8, 3)],
]

# Convert indices to grid qubits.
qubit_sets = [[cirq.GridQubit(*idx) for idx in qubit_indices] 
              for qubit_indices in qubit_sets_indices]<jupyter_output><empty_output><jupyter_text>We now run the Loschmidt echo circuits on each candidate set of qubits.<jupyter_code>"""Run the Loschmidt echo experiment."""
probs = []

for trial in range(trials):
    print("\r", f"Status: On trial {trial + 1} / {trials}", end="")

    # Create the batch of circuits.
    batch = [
        create_loschmidt_echo_circuit(qubits, cycles=c, pause=pause, seed=trial)
        for qubits in qubit_sets for c in cycle_values
    ]

    # Run the batch.
    results = engine.run_batch(
        programs=batch,
        processor_ids=[processor_id],
        repetitions=nreps,
        gate_set=cirq.google.SQRT_ISWAP_GATESET,
        params_list=[{}] * len(batch),
    )

    # Determine the ground state probability for each result.
    probs.append([to_ground_state_prob(res) for res in results])<jupyter_output>Status: On trial 10 / 10<jupyter_text>Plotting the results Below we plot the average survival probability on each qubit configuration.<jupyter_code># Average data over trials.
avg_probs = np.average(probs, axis=0)
std_probs = np.std(probs, axis=0)

# Plotting.
plt.figure(figsize=(7, 5))

step = len(cycle_values)
stop = len(avg_probs) // step
for i in range(stop):
    plt.errorbar(
        x=cycle_values, 
        y=avg_probs[i * step: (i + 1) * step], 
        yerr=std_probs[i * step: (i + 1) * step],
        capsize=5,
        lw=2,
        label=f"Qubit configuration {i}"
    )

plt.legend()
plt.ylabel("Survival probability")
plt.xlabel("Cycle")
plt.grid("on");<jupyter_output><empty_output><jupyter_text>The initial point (at zero cycles) reflects readout error, and the decay rate reflects the gate error per cycle. We fit an exponential to each curve to determine the gate error per cycle below. Note: To ensure good fit parameters are calculated, it is important to collect enough data such that the above curves reach their asymptote.<jupyter_code>"""Fit an exponential decay to the collected data."""
from scipy.optimize import curve_fit

def fit(cycle, a, f):
    return a * np.exp((f - 1.0) * cycle)


for i in range(stop):
    (a, f), _ = curve_fit(
        fit, 
        xdata=cycle_values, 
        ydata=avg_probs[i * step: (i + 1) * step],
    )
    print(f"Error/cycle on qubit configuration {i}: {round((1 - f) * 100, 2)}%")<jupyter_output>Error/cycle on qubit configuration 0: 3.49%
Error/cycle on qubit configuration 1: 6.45%
Error/cycle on qubit configuration 2: 4.46%
Error/cycle on qubit configuration 3: 3.87%
{'fraction_non_alphanumeric': 0.09332545776727702, 'fraction_numerical': 0.011321126205946053, 'mean_word_length': 3.9847890088321885, 'pattern_counts': {'":': 0, '<': 46, '<?xml version=': 0, '>': 48, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 5, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '12', 'dir': 'jupyter-structured-clean-dedup', 'id': '44241', 'license': 'Apache-2.0', 'n_tokens_mistral': 3242, 'n_tokens_neox': 3029, 'n_words': 1076, 'path': 'docs/tutorials/google/echoes.ipynb', 'repo_name': 'albertonavaa/Cirq', 'size': '10158'}