Source code for qibocal.protocols.tomographies.state_tomography

import json
from copy import deepcopy
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional, Union

import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from qibo import Circuit, gates
from qibo.backends import NumpyBackend, construct_backend, matrices
from qibo.quantum_info import fidelity, partial_trace

from ...auto.operation import DATAFILE, Data, Parameters, QubitId, Results, Routine
from ...auto.transpile import dummy_transpiler, execute_transpiled_circuit
from ...calibration import CalibrationPlatform
from ..utils import table_dict, table_html

BASIS = ["X", "Y", "Z"]
"""Single qubit measurement basis."""
CIRCUIT_PATH = "circuit.json"
"""Path where circuit is stored."""


__all__ = ["state_tomography", "StateTomographyParameters", "plot_reconstruction"]


[docs] @dataclass class StateTomographyParameters(Parameters): """Tomography input parameters""" circuit: Optional[Union[str, Circuit]] = None """Circuit to prepare initial state. It can also be provided the path to a json file containing a serialized circuit. """ def __post_init__(self): if isinstance(self.circuit, str): raw = json.loads((Path.cwd() / self.circuit).read_text()) self.circuit = Circuit.from_dict(raw)
TomographyType = np.dtype( [ ("samples", np.int64), ] ) """Custom dtype for tomography.""" @dataclass class StateTomographyData(Data): """Tomography data""" targets: dict[QubitId, int] """Store targets order.""" circuit: Circuit """Circuit where tomography will be executed.""" data: dict[tuple[QubitId, str], np.int64] = field(default_factory=dict) """Hardware measurements.""" @property def params(self) -> dict: """Convert non-arrays attributes into dict.""" params = super().params params.pop("circuit") return params def save(self, path): super().save(path) (path / CIRCUIT_PATH).write_text(json.dumps(self.circuit.raw), encoding="utf-8") @classmethod def load(cls, path): circuit = Circuit.from_dict(json.loads((path / CIRCUIT_PATH).read_text())) data = super().load_data(path, DATAFILE) params = super().load_params(path, DATAFILE) return cls(data=data, circuit=circuit, targets=params["targets"]) @dataclass class StateTomographyResults(Results): """Tomography results""" measured_density_matrix_real: dict[QubitId, list] """Real part of measured density matrix.""" measured_density_matrix_imag: dict[QubitId, list] """Imaginary part of measured density matrix.""" target_density_matrix_real: dict[QubitId, list] """Real part of exact density matrix.""" target_density_matrix_imag: dict[QubitId, list] """Imaginary part of exact density matrix.""" fidelity: dict[QubitId, float] """State fidelity.""" def _acquisition( params: StateTomographyParameters, platform: CalibrationPlatform, targets: list[QubitId], ) -> StateTomographyData: """Acquisition protocol for single qubit state tomography experiment.""" if params.circuit is None: params.circuit = Circuit(len(targets)) backend = construct_backend("qibolab", platform=platform) transpiler = dummy_transpiler(backend) data = StateTomographyData( circuit=params.circuit, targets={target: i for i, target in enumerate(targets)} ) for basis in BASIS: basis_circuit = deepcopy(params.circuit) # FIXME: https://github.com/qiboteam/qibo/issues/1318 if basis != "Z": for i in range(len(targets)): basis_circuit.add(getattr(gates, basis)(i).basis_rotation()) basis_circuit.add(gates.M(*range(len(targets)))) _, results = execute_transpiled_circuit( basis_circuit, targets, backend, nshots=params.nshots, transpiler=transpiler, ) for i, target in enumerate(targets): data.register_qubit( TomographyType, (target, basis), dict( samples=np.array(results.samples()).T[i], ), ) return data def _fit(data: StateTomographyData) -> StateTomographyResults: """Post-processing for State tomography.""" measured_density_matrix_real = {} measured_density_matrix_imag = {} target_density_matrix_real = {} target_density_matrix_imag = {} fid = {} circuit = data.circuit circuit.density_matrix = True total_density_matrix = NumpyBackend().execute_circuit(circuit=circuit).state() for i, qubit in enumerate(data.targets): traced_qubits = [q for q in range(len(data.qubits)) if q != i] target_density_matrix = partial_trace(total_density_matrix, traced_qubits) x_exp = 1 - 2 * np.mean(data[qubit, "X"].samples) y_exp = 1 - 2 * np.mean(data[qubit, "Y"].samples) z_exp = 1 - 2 * np.mean(data[qubit, "Z"].samples) measured_density_matrix = 0.5 * ( matrices.I + matrices.X * x_exp + matrices.Y * y_exp + matrices.Z * z_exp ) measured_density_matrix_real[qubit] = np.real(measured_density_matrix).tolist() measured_density_matrix_imag[qubit] = np.imag(measured_density_matrix).tolist() target_density_matrix_real[qubit] = np.real(target_density_matrix).tolist() target_density_matrix_imag[qubit] = np.imag(target_density_matrix).tolist() fid[qubit] = fidelity( measured_density_matrix, target_density_matrix, ) return StateTomographyResults( measured_density_matrix_real=measured_density_matrix_real, measured_density_matrix_imag=measured_density_matrix_imag, target_density_matrix_real=target_density_matrix_real, target_density_matrix_imag=target_density_matrix_imag, fidelity=fid, ) def plot_parallelogram(a, e, pos_x, pos_y, **options): """Plotting single histogram in 3d plot.""" x, y, z = np.meshgrid( np.linspace(pos_x - a / 4, pos_x + a / 4, 2), np.linspace(pos_y - a / 4, pos_y + a / 4, 2), np.linspace(0, e, 2), ) x = x.flatten() y = y.flatten() z = z.flatten() return go.Mesh3d( x=x, y=y, z=z, alphahull=1, flatshading=True, lighting={"diffuse": 0.1, "specular": 2.0, "roughness": 0.5}, **options, ) def plot_rho(fig, zz, trace_options, figure_options, showlegend=None): """Plot density matrix""" values = list(range(len(zz))) x, y = np.meshgrid(values, values) xx = x.flatten() yy = y.flatten() zz = np.array(zz).ravel() showlegend_temp = False for x, y, z in zip(xx, yy, zz): if showlegend is None: showlegend_temp = bool(x == xx[-1] and y == yy[-1]) fig.add_trace( plot_parallelogram(1, z, x, y, showlegend=showlegend_temp, **trace_options), **figure_options, )
[docs] def plot_reconstruction(ideal, measured): """Plot 3D plot with reconstruction of ideal and measured density matrix.""" fig = make_subplots( rows=1, cols=2, start_cell="top-left", specs=[[{"type": "scatter3d"}, {"type": "scatter3d"}]], subplot_titles=( "Re(ρ)", "Im(ρ)", ), ) # computing limits for colorscale min_re, max_re = np.min(ideal.real), np.max(ideal.real) min_im, max_im = np.min(ideal.imag), np.max(ideal.imag) # add offset if np.abs(min_re - max_re) < 1e-5: min_re = min_re - 0.1 max_re = max_re + 0.1 if np.abs(min_im - max_im) < 1e-5: min_im = min_im - 0.1 max_im = max_im + 0.1 plot_rho( fig, measured.real, trace_options=dict( color="rgba(255,100,0,0.1)", name="experiment", legendgroup="experiment" ), figure_options=dict(row=1, col=1), ) plot_rho( fig, ideal.real, trace_options=dict( color="rgba(100,0,100,0.1)", name="simulation", legendgroup="simulation" ), figure_options=dict(row=1, col=1), ) plot_rho( fig, measured.imag, trace_options=dict( color="rgba(255,100,0,0.1)", name="experiment", legendgroup="experiment" ), figure_options=dict(row=1, col=2), showlegend=False, ) plot_rho( fig, ideal.imag, trace_options=dict( color="rgba(100,0,100,0.1)", name="simulation", legendgroup="simulation" ), figure_options=dict(row=1, col=2), showlegend=False, ) tickvals = list(range(len(ideal))) if len(tickvals) == 2: # single qubit tomography ticktext = ["{:01b}".format(i) for i in tickvals] else: # two qubit tomography ticktext = ["{:02b}".format(i) for i in tickvals] fig.update_scenes( xaxis=dict(tickvals=tickvals, ticktext=ticktext), yaxis=dict(tickvals=tickvals, ticktext=ticktext), zaxis=dict(range=[-1, 1]), ) return fig
def _plot(data: StateTomographyData, fit: StateTomographyResults, target: QubitId): """Plotting for state tomography""" if fit is None: return [], "" ideal = np.array(fit.target_density_matrix_real[target]) + 1j * np.array( fit.target_density_matrix_imag[target] ) measured = np.array(fit.measured_density_matrix_real[target]) + 1j * np.array( fit.measured_density_matrix_imag[target] ) fig = plot_reconstruction(ideal, measured) fitting_report = table_html( table_dict( target, [ "Fidelity", ], [ np.round(fit.fidelity[target], 4), ], ) ) return [fig], fitting_report state_tomography = Routine(_acquisition, _fit, _plot)