diff --git a/src/qibocal/protocols/__init__.py b/src/qibocal/protocols/__init__.py index 262203042..13418613f 100644 --- a/src/qibocal/protocols/__init__.py +++ b/src/qibocal/protocols/__init__.py @@ -72,6 +72,7 @@ chsh_pulses, correct_virtual_z_phases, correct_virtual_z_phases_signal, + mermin, optimize_two_qubit_gate, ) from .two_qubit_state_tomography import two_qubit_state_tomography @@ -149,5 +150,6 @@ "standard_rb_2q", "standard_rb_2q_inter", "optimize_two_qubit_gate", + "mermin", "ramsey_zz", ] diff --git a/src/qibocal/protocols/readout_mitigation_matrix.py b/src/qibocal/protocols/readout_mitigation_matrix.py index dc6ed3db8..ce11fd314 100644 --- a/src/qibocal/protocols/readout_mitigation_matrix.py +++ b/src/qibocal/protocols/readout_mitigation_matrix.py @@ -7,24 +7,18 @@ from qibo import gates from qibo.backends import GlobalBackend from qibo.models import Circuit -from qibolab import ExecutionParameters from qibolab.platform import Platform -from qibolab.pulses import PulseSequence from qibolab.qubits import QubitId from qibocal.auto.operation import Data, Parameters, Results, Routine from qibocal.auto.transpile import dummy_transpiler, execute_transpiled_circuit from qibocal.config import log -from .utils import calculate_frequencies - @dataclass class ReadoutMitigationMatrixParameters(Parameters): """ReadoutMitigationMatrix matrix inputs.""" - pulses: Optional[bool] = True - """Get readout mitigation matrix using pulses. If False gates will be used.""" nshots: Optional[int] = None """Number of shots.""" relaxation_time: Optional[int] = None @@ -37,10 +31,14 @@ class ReadoutMitigationMatrixResults(Results): field(default_factory=dict) ) """Readout mitigation matrices (inverse of measurement matrix).""" - measurement_matrix: dict[tuple[QubitId, ...], npt.NDArray[np.float64]] = field( - default_factory=dict - ) - """Matrix containing measurement matrices for each state.""" + + +ReadoutMitigationMatrixType = np.dtype( + [ + ("state", int), + ("frequency", np.float64), + ] +) @dataclass @@ -54,40 +52,6 @@ class ReadoutMitigationMatrixData(Data): data: dict = field(default_factory=dict) """Raw data acquited.""" - def add(self, qubits, state, freqs): - for result_state, freq in freqs.items(): - self.data[ - qubits - + ( - state, - result_state, - ) - ] = freq - - for basis in [format(i, f"0{len(qubits)}b") for i in range(2 ** len(qubits))]: - if ( - qubits - + ( - state, - basis, - ) - not in self.data - ): - self.data[ - qubits - + ( - state, - basis, - ) - ] = 0 - - def __getitem__(self, qubits): - return { - index: value - for index, value in self.data.items() - if qubits == list(index[: len(index) - 2]) - } - def _acquisition( params: ReadoutMitigationMatrixParameters, @@ -105,75 +69,52 @@ def _acquisition( nqubits = len(qubits) for i in range(2**nqubits): state = format(i, f"0{nqubits}b") - if params.pulses: - sequence = PulseSequence() - for q, bit in enumerate(state): - if bit == "1": - sequence.add( - platform.create_RX_pulse( - qubits[q], start=0, relative_phase=0 - ) - ) - measurement_start = sequence.finish - for q in range(len(state)): - MZ_pulse = platform.create_MZ_pulse( - qubits[q], start=measurement_start - ) - sequence.add(MZ_pulse) - results = platform.execute_pulse_sequence( - sequence, ExecutionParameters(nshots=params.nshots) - ) - data.add( - tuple(qubits), state, calculate_frequencies(results, tuple(qubits)) + c = Circuit( + nqubits, + ) + for q, bit in enumerate(state): + if bit == "1": + c.add(gates.X(q)) + c.add(gates.M(*range(nqubits))) + _, results = execute_transpiled_circuit( + c, qubits, backend, nshots=params.nshots, transpiler=transpiler + ) + frequencies = np.zeros(2 ** len(qubits)) + for i, freq in results.frequencies().items(): + frequencies[int(i, 2)] = freq + for freq in frequencies: + data.register_qubit( + ReadoutMitigationMatrixType, + (qubits), + dict( + state=np.array([int(state, 2)]), + frequency=freq, + ), ) - else: - c = Circuit( - platform.nqubits, - wire_names=[str(i) for i in range(platform.nqubits)], - ) - for q, bit in enumerate(state): - if bit == "1": - c.add(gates.X(qubits[q])) - c.add(gates.M(*[qubits[i] for i in range(len(state))])) - _, results = execute_transpiled_circuit( - c, qubit_map, backend, nshots=params.nshots, transpiler=transpiler - ) - data.add(tuple(qubits), state, dict(results.frequencies())) return data def _fit(data: ReadoutMitigationMatrixData) -> ReadoutMitigationMatrixResults: """Post processing for readout mitigation matrix protocol.""" readout_mitigation_matrix = {} - measurement_matrix = {} - for qubit in data.qubit_list: - qubit_data = data[qubit] - matrix = np.zeros((2 ** len(qubit), 2 ** len(qubit))) - computational_basis = [ - format(i, f"0{len(qubit)}b") for i in range(2 ** len(qubit)) - ] - for state in computational_basis: - column = np.zeros(2 ** len(qubit)) - qubit_state_data = { - index: value - for index, value in qubit_data.items() - if index[-2] == state - } - for index, value in qubit_state_data.items(): - column[(int(index[-1], 2))] = value / data.nshots - matrix[:, int(state, 2)] = np.flip(column) - - measurement_matrix[tuple(qubit)] = matrix.tolist() + for qubits in data.qubit_list: + qubit_data = data.data[tuple(qubits)] + mitigation_matrix = [] + for state in range(2 ** len(qubits)): + mitigation_matrix.append(qubit_data[qubit_data.state == state].frequency) + mitigation_matrix = np.vstack(mitigation_matrix) / data.nshots try: - readout_mitigation_matrix[tuple(qubit)] = np.linalg.inv(matrix).tolist() + readout_mitigation_matrix[tuple(qubits)] = np.linalg.inv( + mitigation_matrix + ).tolist() except np.linalg.LinAlgError as e: log.warning(f"ReadoutMitigationMatrix: the fitting was not succesful. {e}") - - return ReadoutMitigationMatrixResults( + res = ReadoutMitigationMatrixResults( readout_mitigation_matrix=readout_mitigation_matrix, - measurement_matrix=measurement_matrix, ) + return res + def _plot( data: ReadoutMitigationMatrixData, @@ -187,12 +128,12 @@ def _plot( computational_basis = [ format(i, f"0{len(target)}b") for i in range(2 ** len(target)) ] - z = fit.measurement_matrix[tuple(target)] - + measurement_matrix = np.linalg.inv(fit.readout_mitigation_matrix[tuple(target)]) + z = measurement_matrix fig = px.imshow( z, x=computational_basis, - y=computational_basis[::-1], + y=computational_basis, text_auto=True, labels={ "x": "Prepeared States", diff --git a/src/qibocal/protocols/two_qubit_interaction/__init__.py b/src/qibocal/protocols/two_qubit_interaction/__init__.py index e45930b33..befbb370a 100644 --- a/src/qibocal/protocols/two_qubit_interaction/__init__.py +++ b/src/qibocal/protocols/two_qubit_interaction/__init__.py @@ -1,5 +1,6 @@ from .chevron import chevron, chevron_signal from .chsh import chsh_circuits, chsh_pulses +from .mermin import mermin from .optimize import optimize_two_qubit_gate from .virtual_z_phases import correct_virtual_z_phases from .virtual_z_phases_signal import correct_virtual_z_phases_signal diff --git a/src/qibocal/protocols/two_qubit_interaction/chsh/circuits.py b/src/qibocal/protocols/two_qubit_interaction/chsh/circuits.py index cebe92346..898ab8536 100644 --- a/src/qibocal/protocols/two_qubit_interaction/chsh/circuits.py +++ b/src/qibocal/protocols/two_qubit_interaction/chsh/circuits.py @@ -7,7 +7,7 @@ from .utils import READOUT_BASIS -def create_bell_circuit(nqubits, qubits, theta=np.pi / 4, bell_state=0): +def create_bell_circuit(theta=np.pi / 4, bell_state=0): """Creates the circuit to generate the bell states and with a theta-measurement bell_state chooses the initial bell state for the test: 0 -> |00>+|11> @@ -17,24 +17,24 @@ def create_bell_circuit(nqubits, qubits, theta=np.pi / 4, bell_state=0): Native defaults to only using GPI2 and GPI gates. """ p = [0, 0] - c = Circuit(nqubits) - c.add(gates.H(qubits[0])) - c.add(gates.H(qubits[1])) - c.add(gates.CZ(qubits[0], qubits[1])) - c.add(gates.H(qubits[1])) + c = Circuit(2) + c.add(gates.H(0)) + c.add(gates.H(1)) + c.add(gates.CZ(0, 1)) + c.add(gates.H(1)) if bell_state == 1: - c.add(gates.Z(qubits[0])) + c.add(gates.Z(0)) elif bell_state == 2: - c.add(gates.Z(qubits[0])) - c.add(gates.X(qubits[0])) + c.add(gates.Z(0)) + c.add(gates.X(0)) elif bell_state == 3: - c.add(gates.X(qubits[0])) + c.add(gates.X(0)) - c.add(gates.RY(qubits[0], theta)) + c.add(gates.RY(0, theta)) return c, p -def create_bell_circuit_native(nqubits, qubits, theta=np.pi / 4, bell_state=0): +def create_bell_circuit_native(theta=np.pi / 4, bell_state=0): """Creates the circuit to generate the bell states and with a theta-measurement bell_state chooses the initial bell state for the test: 0 -> |00>+|11> @@ -44,35 +44,33 @@ def create_bell_circuit_native(nqubits, qubits, theta=np.pi / 4, bell_state=0): Native defaults to only using GPI2 and GPI gates. """ - c = Circuit(nqubits) + c = Circuit(2) p = [0, 0] - c.add(gates.GPI2(qubits[0], np.pi / 2)) - c.add(gates.GPI2(qubits[1], np.pi / 2)) - c.add(gates.CZ(qubits[0], qubits[1])) - c.add(gates.GPI2(qubits[1], -np.pi / 2)) + c.add(gates.GPI2(0, np.pi / 2)) + c.add(gates.GPI2(1, np.pi / 2)) + c.add(gates.CZ(0, 1)) + c.add(gates.GPI2(1, -np.pi / 2)) if bell_state == 0: p[0] += np.pi elif bell_state == 1: p[0] += 0 elif bell_state == 2: p[0] += 0 - c.add(gates.GPI2(qubits[0], p[0])) - c.add(gates.GPI2(qubits[0], p[0])) + c.add(gates.GPI2(0, p[0])) + c.add(gates.GPI2(0, p[0])) elif bell_state == 3: p[0] += np.pi - c.add(gates.GPI2(qubits[0], p[0])) - c.add(gates.GPI2(qubits[0], p[0])) + c.add(gates.GPI2(0, p[0])) + c.add(gates.GPI2(0, p[0])) - c.add(gates.GPI2(qubits[0], p[0])) + c.add(gates.GPI2(0, p[0])) p[0] += theta - c.add(gates.GPI2(qubits[0], p[0] + np.pi)) + c.add(gates.GPI2(0, p[0] + np.pi)) return c, p def create_chsh_circuits( - platform, - qubits, theta=np.pi / 4, bell_state=0, native=True, @@ -84,15 +82,14 @@ def create_chsh_circuits( """ create_bell = create_bell_circuit_native if native else create_bell_circuit chsh_circuits = {} - nqubits = platform.nqubits if platform else max(qubits) + 1 for basis in readout_basis: - c, p = create_bell(nqubits, qubits, theta, bell_state) + c, p = create_bell(theta, bell_state) for i, base in enumerate(basis): if base == "X": if native: - c.add(gates.GPI2(qubits[i], p[i] + np.pi / 2)) + c.add(gates.GPI2(i, p[i] + np.pi / 2)) else: - c.add(gates.H(qubits[i])) - c.add(gates.M(*qubits)) + c.add(gates.H(i)) + c.add(gates.M(0, 1)) chsh_circuits[basis] = c return chsh_circuits diff --git a/src/qibocal/protocols/two_qubit_interaction/chsh/protocol.py b/src/qibocal/protocols/two_qubit_interaction/chsh/protocol.py index 6adc6f147..f8a64c57b 100644 --- a/src/qibocal/protocols/two_qubit_interaction/chsh/protocol.py +++ b/src/qibocal/protocols/two_qubit_interaction/chsh/protocol.py @@ -76,14 +76,16 @@ class CHSHData(Data): def save(self, path: Path): """Saving data including mitigation matrix.""" - - np.savez( - path / f"{MITIGATION_MATRIX_FILE}.npz", - **{ - json.dumps((control, target)): self.mitigation_matrix[control, target] - for control, target, _, _, _ in self.data - }, - ) + if self.mitigation_matrix: + np.savez( + path / f"{MITIGATION_MATRIX_FILE}.npz", + **{ + json.dumps((control, target)): self.mitigation_matrix[ + control, target + ] + for control, target, _, _, _ in self.data + }, + ) super().save(path=path) @classmethod @@ -174,7 +176,7 @@ def _acquisition_pulses( if params.apply_error_mitigation: mitigation_data = mitigation_acquisition( - mitigation_params(pulses=True, nshots=params.nshots), platform, targets + mitigation_params(nshots=params.nshots), platform, targets ) mitigation_results = mitigation_fit(mitigation_data) @@ -223,10 +225,9 @@ def _acquisition_circuits( backend = GlobalBackend() backend.platform = platform transpiler = dummy_transpiler(backend) - qubit_map = [i for i in range(platform.nqubits)] if params.apply_error_mitigation: mitigation_data = mitigation_acquisition( - mitigation_params(pulses=False, nshots=params.nshots), platform, targets + mitigation_params(nshots=params.nshots), platform, targets ) mitigation_results = mitigation_fit(mitigation_data) for pair in targets: @@ -242,8 +243,6 @@ def _acquisition_circuits( for bell_state in params.bell_states: for theta in thetas: chsh_circuits = create_chsh_circuits( - platform, - qubits=pair, bell_state=bell_state, theta=theta, native=params.native, @@ -254,7 +253,7 @@ def _acquisition_circuits( nshots=params.nshots, transpiler=transpiler, backend=backend, - qubit_map=qubit_map, + qubit_map=pair, ) frequencies = result.frequencies() data.register_basis(pair, bell_state, basis, frequencies) diff --git a/src/qibocal/protocols/two_qubit_interaction/mermin/__init__.py b/src/qibocal/protocols/two_qubit_interaction/mermin/__init__.py new file mode 100644 index 000000000..5a4e9488d --- /dev/null +++ b/src/qibocal/protocols/two_qubit_interaction/mermin/__init__.py @@ -0,0 +1 @@ +from .protocol import mermin diff --git a/src/qibocal/protocols/two_qubit_interaction/mermin/protocol.py b/src/qibocal/protocols/two_qubit_interaction/mermin/protocol.py new file mode 100644 index 000000000..a4f764d02 --- /dev/null +++ b/src/qibocal/protocols/two_qubit_interaction/mermin/protocol.py @@ -0,0 +1,264 @@ +from dataclasses import dataclass, field +from typing import Optional + +import numpy as np +import numpy.typing as npt +import plotly.graph_objects as go +from qibolab import ExecutionParameters +from qibolab.platform import Platform +from qibolab.qubits import QubitId + +from qibocal.auto.operation import Data, Parameters, Results, Routine + +from ...readout_mitigation_matrix import readout_mitigation_matrix +from ...utils import STRING_TYPE, calculate_frequencies +from .pulses import create_mermin_sequences +from .utils import ( + compute_mermin, + get_mermin_coefficients, + get_mermin_polynomial, + get_readout_basis, +) + +PLOT_PADDING = 0.2 + + +@dataclass +class MerminParameters(Parameters): + """Mermin experiment input parameters.""" + + ntheta: int + """Number of angles probed linearly between 0 and 2 pi.""" + native: Optional[bool] = False + """If True a circuit will be created using only GPI2 and CZ gates.""" + apply_error_mitigation: Optional[bool] = False + """Error mitigation model""" + + +MerminType = np.dtype( + [ + ("theta", float), + ("basis", STRING_TYPE), + ("state", int), + ("frequency", int), + ] +) + + +@dataclass +class MerminData(Data): + """Mermin Data structure.""" + + thetas: list + """Angles probed.""" + data: dict[list[QubitId], npt.NDArray[MerminType]] = field(default_factory=dict) + """Raw data acquired.""" + mitigation_matrix: dict[list[QubitId], npt.NDArray[np.float64]] = field( + default_factory=dict + ) + """Mitigation matrix computed using the readout_mitigation_matrix protocol.""" + + @property + def targets(self): + return list(self.data) + + +@dataclass +class MerminResults(Results): + """Mermin Results class.""" + + mermin: dict[tuple[QubitId, ...], npt.NDArray[np.float64]] = field( + default_factory=dict + ) + """Raw Mermin value.""" + + mermin_mitigated: dict[tuple[QubitId, ...], npt.NDArray[np.float64]] = field( + default_factory=dict + ) + """Mitigated Mermin value.""" + + +def _acquisition( + params: MerminParameters, + platform: Platform, + targets: list[list[QubitId]], +) -> MerminData: + """Data acquisition for Mermin protocol using pulse sequences.""" + + thetas = np.linspace(0, 2 * np.pi, params.ntheta) + data = MerminData(thetas=thetas.tolist()) + if params.apply_error_mitigation: + mitigation_data, _ = readout_mitigation_matrix.acquisition( + readout_mitigation_matrix.parameters_type.load(dict(nshots=params.nshots)), + platform, + targets, + ) + + mitigation_results, _ = readout_mitigation_matrix.fit(mitigation_data) + data.mitigation_matrix = mitigation_results.readout_mitigation_matrix + platform.connect() + for qubits in targets: + mermin_polynomial = get_mermin_polynomial(len(qubits)) + readout_basis = get_readout_basis(mermin_polynomial) + + for theta in thetas: + mermin_sequences = create_mermin_sequences( + platform, qubits, readout_basis=readout_basis, theta=theta + ) + options = ExecutionParameters(nshots=params.nshots) + # TODO: use unrolling + for basis, sequence in mermin_sequences.items(): + results = platform.execute_pulse_sequence(sequence, options=options) + frequencies = calculate_frequencies(results, qubits) + for state, frequency in enumerate(frequencies.values()): + data.register_qubit( + MerminType, + tuple(qubits), + dict( + theta=np.array([theta]), + basis=np.array([basis]), + state=np.array([state]), + frequency=np.array([frequency]), + ), + ) + return data + + +def _fit(data: MerminData) -> MerminResults: + """Fitting for Mermin protocol.""" + targets = data.targets + results = {qubits: [] for qubits in targets} + mitigated_results = {qubits: [] for qubits in targets} + basis = np.unique(data.data[targets[0]].basis) + for qubits in targets: + mermin_polynomial = get_mermin_polynomial(len(qubits)) + mermin_coefficients = get_mermin_coefficients(mermin_polynomial) + + for theta in data.thetas: + qubit_data = data.data[qubits] + outputs = [] + mitigated_outputs = [] + for base in basis: + frequencies = np.zeros(2 ** len(qubits)) + data_filter = (qubit_data.basis == base) & (qubit_data.theta == theta) + filtered_data = qubit_data[data_filter] + state_freq = qubit_data[data_filter].frequency + for state, freq in zip(filtered_data.state, filtered_data.frequency): + frequencies[state] = freq + + outputs.append( + { + format(i, f"0{len(qubits)}b"): freq + for i, freq in enumerate(state_freq) + } + ) + + if data.mitigation_matrix: + mitigated_output = np.dot( + data.mitigation_matrix[qubits], + frequencies, + ) + mitigated_outputs.append( + { + format(i, f"0{len(qubits)}b"): freq + for i, freq in enumerate(mitigated_output) + } + ) + if data.mitigation_matrix: + mitigated_results[tuple(qubits)].append( + compute_mermin(mitigated_outputs, mermin_coefficients) + ) + results[tuple(qubits)].append(compute_mermin(outputs, mermin_coefficients)) + return MerminResults( + mermin=results, + mermin_mitigated=mitigated_results, + ) + + +def _plot(data: MerminData, fit: MerminResults, target): + """Plotting function for Mermin protocol.""" + figures = [] + + n_qubits = len(target) + classical_bound = 2 ** (n_qubits // 2) + quantum_bound = 2 ** ((n_qubits - 1) / 2) * (2 ** (n_qubits // 2)) + + fig = go.Figure( + layout_yaxis_range=[-quantum_bound - PLOT_PADDING, quantum_bound + PLOT_PADDING] + ) + if fit is not None: + fig.add_trace( + go.Scatter( + x=data.thetas, + y=fit.mermin[tuple(target)], + name="Bare", + ) + ) + if fit.mermin_mitigated: + fig.add_trace( + go.Scatter( + x=data.thetas, + y=fit.mermin_mitigated[tuple(target)], + name="Mitigated", + ) + ) + + fig.add_trace( + go.Scatter( + mode="lines", + x=data.thetas, + y=[+classical_bound] * len(data.thetas), + line_color="gray", + name="Classical limit", + line_dash="dash", + legendgroup="classic", + ) + ) + + fig.add_trace( + go.Scatter( + mode="lines", + x=data.thetas, + y=[-classical_bound] * len(data.thetas), + line_color="gray", + name="Classical limit", + legendgroup="classic", + line_dash="dash", + showlegend=False, + ) + ) + + fig.add_trace( + go.Scatter( + mode="lines", + x=data.thetas, + y=[+quantum_bound] * len(data.thetas), + line_color="gray", + name="Quantum limit", + legendgroup="quantum", + ) + ) + + fig.add_trace( + go.Scatter( + mode="lines", + x=data.thetas, + y=[-quantum_bound] * len(data.thetas), + line_color="gray", + name="Quantum limit", + legendgroup="quantum", + showlegend=False, + ) + ) + + fig.update_layout( + xaxis_title="Theta [rad]", + yaxis_title="Mermin polynomial value", + xaxis=dict(range=[min(data.thetas), max(data.thetas)]), + ) + figures.append(fig) + + return figures, "" + + +mermin = Routine(_acquisition, _fit, _plot) diff --git a/src/qibocal/protocols/two_qubit_interaction/mermin/pulses.py b/src/qibocal/protocols/two_qubit_interaction/mermin/pulses.py new file mode 100644 index 000000000..239f81d67 --- /dev/null +++ b/src/qibocal/protocols/two_qubit_interaction/mermin/pulses.py @@ -0,0 +1,84 @@ +from collections import defaultdict + +import numpy as np +from qibolab.pulses import PulseSequence + + +def create_mermin_sequence(platform, qubits, theta=None): + """Creates the pulse sequence to generate the bell states and with a theta-measurement""" + + nqubits = len(qubits) + if theta is None: + theta = ((nqubits - 1) * 0.25 * np.pi) % (2 * np.pi) + + virtual_z_phases = defaultdict(int) + sequence = PulseSequence() + + for qubit in qubits: + sequence.add( + platform.create_RX90_pulse( + qubit, start=0, relative_phase=virtual_z_phases[qubit] + np.pi / 2 + ) + ) + + # TODO: Not hardcode topology + + # qubits[0] needs to be the center qubit where everything is connected + for i in range(1, len(qubits)): + (cz_sequence1, cz_virtual_z_phases) = platform.create_CZ_pulse_sequence( + [qubits[0]] + [qubits[i]], sequence.finish + 8 # TODO: ask for the 8 + ) + sequence.add(cz_sequence1) + for qubit in cz_virtual_z_phases: + virtual_z_phases[qubit] += cz_virtual_z_phases[qubit] + + t = sequence.finish + 8 + + for i in range(1, len(qubits)): + sequence.add( + platform.create_RX90_pulse( + qubits[i], + start=t, + relative_phase=virtual_z_phases[qubits[i]] - np.pi / 2, + ) + ) + + virtual_z_phases[qubits[0]] -= theta + + return sequence, virtual_z_phases + + +def create_mermin_sequences(platform, qubits, readout_basis, theta): + """Creates the pulse sequences needed for the 4 measurement settings for chsh.""" + + mermin_sequences = {} + + for basis in readout_basis: + sequence, virtual_z_phases = create_mermin_sequence( + platform, qubits, theta=theta + ) + # t = sequence.finish + for i, base in enumerate(basis): + if base == "X": + sequence.add( + platform.create_RX90_pulse( + qubits[i], + start=sequence.finish, + relative_phase=virtual_z_phases[qubits[i]] + np.pi / 2, + ) + ) + if base == "Y": + sequence.add( + platform.create_RX90_pulse( + qubits[i], + start=sequence.finish, + relative_phase=virtual_z_phases[qubits[i]], + ) + ) + measurement_start = sequence.finish + + for qubit in qubits: + sequence.add(platform.create_MZ_pulse(qubit, start=measurement_start)) + + mermin_sequences[basis] = sequence + return mermin_sequences diff --git a/src/qibocal/protocols/two_qubit_interaction/mermin/utils.py b/src/qibocal/protocols/two_qubit_interaction/mermin/utils.py new file mode 100644 index 000000000..e0afdca28 --- /dev/null +++ b/src/qibocal/protocols/two_qubit_interaction/mermin/utils.py @@ -0,0 +1,44 @@ +from qibo.hamiltonians import SymbolicHamiltonian +from qibo.symbols import X, Y + + +def compute_mermin(frequencies, mermin_coefficients): + """Computes the chsh inequality out of the frequencies of the 4 circuits executed.""" + assert len(frequencies) == len(mermin_coefficients) + m = 0 + for j, freq in enumerate(frequencies): + for key in freq: + m += ( + mermin_coefficients[j] + * freq[key] + * (-1) ** (sum([int(key[k]) for k in range(len(key))])) + ) + nshots = sum(freq[x] for x in freq) + if nshots != 0: + return float(m / nshots) + + return 0 + + +def get_mermin_polynomial(n): + assert n > 1 + m0 = X(0) + m0p = Y(0) + for i in range(1, n): + mn = m0 * (X(i) + Y(i)) + m0p * (X(i) - Y(i)) + mnp = m0 * (Y(i) - X(i)) + m0p * (X(i) + Y(i)) + m0 = mn.expand() + m0p = mnp.expand() + m = m0 / 2 ** ((n - 1) // 2) + return SymbolicHamiltonian(m.expand()) + + +def get_readout_basis(mermin_polynomial): + return [ + "".join([factor.name[0] for factor in term.factors]) + for term in mermin_polynomial.terms + ] + + +def get_mermin_coefficients(mermin_polynomial): + return [term.coefficient.real for term in mermin_polynomial.terms] diff --git a/src/qibocal/protocols/utils.py b/src/qibocal/protocols/utils.py index a8513ef92..c806e5679 100644 --- a/src/qibocal/protocols/utils.py +++ b/src/qibocal/protocols/utils.py @@ -45,6 +45,7 @@ """Confidence interval used to clean outliers.""" DELAY_FIT_PERCENTAGE = 10 """Percentage of the first and last points used to fit the cable delay.""" +STRING_TYPE = "