From 7b2bfc13fe114966ae1dfb18bfef9e06a2c61e92 Mon Sep 17 00:00:00 2001 From: knzwnao Date: Thu, 20 Jan 2022 01:11:14 +0900 Subject: [PATCH 01/46] cleanup old PR #460 - Add hamiltonian tomography experiment module - Add base HEAT framework and analysis to the module - Add API documentation --- docs/apidocs/index.rst | 1 + docs/apidocs/mod_hamiltonian.rst | 6 + qiskit_experiments/library/__init__.py | 6 + .../library/hamiltonian/__init__.py | 64 +++++ .../library/hamiltonian/heat_analysis.py | 144 +++++++++++ .../library/hamiltonian/heat_base.py | 233 ++++++++++++++++++ .../library/hamiltonian/heat_zx.py | 215 ++++++++++++++++ 7 files changed, 669 insertions(+) create mode 100644 docs/apidocs/mod_hamiltonian.rst create mode 100644 qiskit_experiments/library/hamiltonian/__init__.py create mode 100644 qiskit_experiments/library/hamiltonian/heat_analysis.py create mode 100644 qiskit_experiments/library/hamiltonian/heat_base.py create mode 100644 qiskit_experiments/library/hamiltonian/heat_zx.py diff --git a/docs/apidocs/index.rst b/docs/apidocs/index.rst index b06897e4f8..b23965c685 100644 --- a/docs/apidocs/index.rst +++ b/docs/apidocs/index.rst @@ -30,3 +30,4 @@ Experiment Modules mod_randomized_benchmarking mod_tomography mod_quantum_volume + mod_hamiltonian diff --git a/docs/apidocs/mod_hamiltonian.rst b/docs/apidocs/mod_hamiltonian.rst new file mode 100644 index 0000000000..9fbf5e00cd --- /dev/null +++ b/docs/apidocs/mod_hamiltonian.rst @@ -0,0 +1,6 @@ +.. _qiskit-experiments-hamiltonian: + +.. automodule:: qiskit_experiments.library.hamiltonian + :no-members: + :no-inherited-members: + :no-special-members: diff --git a/qiskit_experiments/library/__init__.py b/qiskit_experiments/library/__init__.py index 08dd8b22dc..d65e734d2a 100644 --- a/qiskit_experiments/library/__init__.py +++ b/qiskit_experiments/library/__init__.py @@ -71,6 +71,10 @@ ~characterization.RamseyXY ~characterization.FineFrequency ~characterization.ReadoutAngle + ~hamiltonian.ZXHeat + ~hamiltonian.ZX90HeatXError + ~hamiltonian.ZX90HeatYError + ~hamiltonian.ZX90HeatZError .. _calibration: @@ -143,6 +147,7 @@ class instance to manage parameters and pulse schedules. from .tomography import StateTomography, ProcessTomography from .quantum_volume import QuantumVolume from .mitigation import ReadoutMitigationExperiment +from .hamiltonian import ZXHeat, ZX90HeatXError, ZX90HeatYError, ZX90HeatZError # Experiment Sub-modules from . import calibration @@ -150,3 +155,4 @@ class instance to manage parameters and pulse schedules. from . import randomized_benchmarking from . import tomography from . import quantum_volume +from . import hamiltonian diff --git a/qiskit_experiments/library/hamiltonian/__init__.py b/qiskit_experiments/library/hamiltonian/__init__.py new file mode 100644 index 0000000000..afb001138e --- /dev/null +++ b/qiskit_experiments/library/hamiltonian/__init__.py @@ -0,0 +1,64 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +=============================================================================================== +Hamiltonian Tomography Experiments (:mod:`qiskit_experiments.library.hamiltonian`) +=============================================================================================== + +.. currentmodule:: qiskit_experiments.library.hamiltonian + +This module provides a set of experiments to characterize Hamiltonian level +properties of qubits or control sequences. + +HEAT Experiments +================ + +HEAT stands for `Hamiltonian Error Amplifying Tomography` which amplifies the +dynamics of entangler along the interrogated axis on the target qubit with +the conventional error amplification (ping-pong) technique. + +These are the base experiment classes for developer to write own experiments. + +.. autosummary:: + :toctree: ../stubs/ + :template: autosummary/experiment.rst + + HeatElement + BatchHeatHelper + +HEAT for ZX Hamiltonian +----------------------- + +.. autosummary:: + :toctree: ../stubs/ + :template: autosummary/experiment.rst + + ZXHeat + ZX90HeatXError + ZX90HeatYError + ZX90HeatZError + +HEAT Analysis +------------- + +.. autosummary:: + :toctree: ../stubs/ + :template: autosummary/analysis.rst + + HeatElementAnalysis + HeatAnalysis + +""" + +from .heat_base import HeatElement, BatchHeatHelper +from .heat_zx import ZXHeat, ZX90HeatXError, ZX90HeatYError, ZX90HeatZError +from .heat_analysis import HeatElementAnalysis, HeatAnalysis diff --git a/qiskit_experiments/library/hamiltonian/heat_analysis.py b/qiskit_experiments/library/hamiltonian/heat_analysis.py new file mode 100644 index 0000000000..46d0f1d2a0 --- /dev/null +++ b/qiskit_experiments/library/hamiltonian/heat_analysis.py @@ -0,0 +1,144 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +Analysis for HEAT experiments. +""" + +from typing import List + +import numpy as np + +from qiskit_experiments.curve_analysis import ErrorAmplificationAnalysis +from qiskit_experiments.exceptions import AnalysisError +from qiskit_experiments.framework import ( + CompositeAnalysis, + ExperimentData, + AnalysisResultData, + Options, + FitVal, +) + + +class HeatElementAnalysis(ErrorAmplificationAnalysis): + """An analysis class for HEAT experiment to define the fixed parameters. + + # section: overview + + This is standard error amplification analysis. + + # section: see_also + qiskit_experiments.curve_analysis.ErrorAmplificationAnalysis + """ + + __fixed_parameters__ = ["angle_per_gate", "phase_offset", "amp"] + + @classmethod + def _default_options(cls) -> Options: + """Default analysis options.""" + options = super()._default_options() + options.angle_per_gate = np.pi + options.phase_offset = np.pi / 2 + options.amp = 1.0 + + return options + + +class HeatAnalysis(CompositeAnalysis): + r"""A composite error amplification analysis to get unitary error coefficients. + + # section: fit_model + + This analysis takes two error amplification experiment results performed with + different control qubit state to distinguish the local rotation term from + the controlled rotation term amplified along a specific error axis. + + This analysis takes a set of `d_theta` parameters from child error amplification results + which might be represented by a unique name in the child experiment data. + With these fit parameters, two Hamiltonian coefficients will be computed as + + .. math:: + + A_{I\beta} = \frac{1}{2}\left( d\theta_{\beta 0} + d\theta_{\beta 1} \right) \\ + + A_{Z\beta} = \frac{1}{2}\left( d\theta_{\beta 0} - d\theta_{\beta 1} \right) + + where, :math:`\beta \in [X, Y, Z]` is one of single qubit Pauli term, + :math:`d\theta_{\beta k}` is `d_theta` parameter extracted from the HEAT experiment + with the control qubit state :math:`|k\rangle \in [|0\rangle, |1\rangle]`. + + # section: see_also + qiskit_experiments.library.hamiltonian.HeatElementAnalysis + + """ + + def __init__(self, fit_params: List[str], out_params: List[str]): + """Create new HEAT analysis. + + Args: + fit_params: Name of error parameters for each amplification sequence. + out_params: Name of Hamiltonian coefficients. + """ + super(HeatAnalysis, self).__init__() + + if len(fit_params) != 2: + raise AnalysisError( + f"{self.__class__.__name__} assumes two fit parameters extracted from " + "a set of experiments with different control qubit state input. " + f"{len(fit_params)} input parameter names are specified." + ) + self.fit_params = fit_params + + if len(out_params) != 2: + raise AnalysisError( + f"{self.__class__.__name__} assumes two output parameters computed with " + "a set of experiment results with different control qubit state input. " + f"{len(out_params)} output parameter names are specified." + ) + self.out_params = out_params + + def _run_analysis(self, experiment_data: ExperimentData): + + # wait for child experiments to complete + super()._run_analysis(experiment_data) + + # extract d_theta parameters + fit_results = [] + for i, pname in enumerate(self.fit_params): + fit_results.append( + experiment_data.child_data(i).analysis_results(pname) + ) + + # Check data quality + is_good_quality = all(r.quality == "good" for r in fit_results) + + # Compute unitary terms + ib = (fit_results[0].value.value + fit_results[1].value.value) / 2 + zb = (fit_results[0].value.value - fit_results[1].value.value) / 2 + + # Compute new variance + sigma = np.sqrt( + fit_results[0].value.stderr ** 2 + fit_results[1].value.stderr ** 2 + ) + + estimate_ib = AnalysisResultData( + name=self.out_params[0], + value=FitVal(value=ib, stderr=sigma, unit="rad"), + quality="good" if is_good_quality else "bad", + ) + + estimate_zb = AnalysisResultData( + name=self.out_params[1], + value=FitVal(value=zb, stderr=sigma, unit="rad"), + quality="good" if is_good_quality else "bad", + ) + + return [estimate_ib, estimate_zb], None diff --git a/qiskit_experiments/library/hamiltonian/heat_base.py b/qiskit_experiments/library/hamiltonian/heat_base.py new file mode 100644 index 0000000000..f81486da22 --- /dev/null +++ b/qiskit_experiments/library/hamiltonian/heat_base.py @@ -0,0 +1,233 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +Base Class for general Hamiltonian Error Amplifying Tomography experiments. +""" + +from abc import ABC +from typing import List, Tuple, Optional + +from qiskit import circuit, QuantumCircuit +from qiskit.providers import Backend + +from qiskit_experiments.framework import BaseExperiment, BatchExperiment, Options +from .heat_analysis import HeatElementAnalysis, HeatAnalysis + + +class HeatElement(BaseExperiment): + """Base class of HEAT experiment elements. + + # section: overview + + Hamiltonian error amplifying tomography (HEAT) is designed to amplify + the dynamics of entangler circuit on the target qubit along a specific axis. + + The basic form of HEAT circuit is represented as follows. + + .. parsed-literal:: + + (xN) + ┌───────┐ ░ ┌───────┐┌───────┐ ░ ┌───────┐ + q_0: ┤0 ├─░─┤0 ├┤0 ├─░─┤0 ├─── + │ prep │ ░ │ heat ││ echo │ ░ │ meas │┌─┐ + q_1: ┤1 ├─░─┤1 ├┤1 ├─░─┤1 ├┤M├ + └───────┘ ░ └───────┘└───────┘ ░ └───────┘└╥┘ + c: 1/═══════════════════════════════════════════╩═ + 0 + + The circuit in middle is repeated by ``N`` times to amplify the Hamiltonian + coefficients along interrogated axis on the target qubit. The ``prep`` circuit + is carefully chosen based on the generator of ``heat`` gate to investigate, + and the ``echo`` and ``meas`` circuit depend on the axis of error to amplify. + Only target qubit is measured following to the projection by ``meas`` circuit. + + The amplified response may consist of the contribution of from the local and + controlled rotation terms. Thus, usually multiple error amplification experiments + with different control qubit states are combined to distinguish the terms in the analysis. + + The ``heat`` gate is a special gate kind to represent + the entangler pulse sequence of interest, thus one must provide the definition of it + through the backend or custom transpiler configuration, i.e. instruction schedule map. + This gate name can be overridden via the experiment option of this experiment. + + # section: note + + This class is usually not exposed to end users. + Developer of new HEAT experiment must design amplification sequence and + instantiate the class implicitly in the batch experiment. + The :class:`BatchHeatHelper` provides a convenient wrapper class of + the :class:`qiskit_experiments.framework.BatchExperiment` for implementing a + typical HEAT experiment. + + # section: analysis_ref + :py:class:`HeatElementAnalysis` + + # section: reference + .. ref_arxiv:: 1 2007.02925 + """ + + def __init__( + self, + qubits: Tuple[int, int], + prep_circ: QuantumCircuit, + echo_circ: QuantumCircuit, + meas_circ: QuantumCircuit, + backend: Optional[Backend] = None, + **kwargs + ): + """Create new HEAT sub experiment. + + Args: + qubits: Index of control and target qubit, respectively. + prep_circ: A circuit to prepare qubit before the echo sequence. + echo_circ: A circuit to selectively amplify the specific error term. + meas_circ: A circuit to project target qubit onto the basis of interest. + backend: Optional, the backend to run the experiment on. + + Keyword Args: + See :meth:`experiment_options` for details. + """ + super().__init__(qubits=qubits, backend=backend, analysis=HeatElementAnalysis()) + self.set_experiment_options(**kwargs) + + # These are not user configurable options. Be frozen once assigned. + self._prep_circuit = prep_circ + self._echo_circuit = echo_circ + self._meas_circuit = meas_circ + + @classmethod + def _default_experiment_options(cls) -> Options: + """Default experiment options. + + Experiment Options: + repetitions (Sequence[int]): A list of the number of echo repetitions. + cr_gate (Gate): A gate instance representing the entangler sequence. + """ + options = super()._default_experiment_options() + options.repetitions = list(range(21)) + options.heat_gate = circuit.Gate("heat", num_qubits=2, params=[]) + + return options + + @classmethod + def _default_transpile_options(cls) -> Options: + """Default transpile options.""" + options = super()._default_transpile_options() + options.basis_gates = ["sx", "x", "rz", "heat"] + options.optimization_level = 1 + + return options + + def circuits(self, backend: Optional[Backend] = None) -> List[QuantumCircuit]: + opt = self.experiment_options + + circs = list() + for repetition in opt.repetitions: + circ = circuit.QuantumCircuit(2, 1) + circ.compose(self._prep_circuit, qubits=[0, 1], inplace=True) + circ.barrier() + for _ in range(repetition): + circ.append(self.experiment_options.heat_gate, [0, 1]) + circ.compose(self._echo_circuit, qubits=[0, 1], inplace=True) + circ.barrier() + circ.compose(self._meas_circuit, qubits=[0, 1], inplace=True) + circ.measure(1, 0) + + # add metadata + circ.metadata = { + "experiment_type": self.experiment_type, + "qubits": self.physical_qubits, + "xval": repetition, + } + + circs.append(circ) + + return circs + + +class BatchHeatHelper(BatchExperiment, ABC): + """A wrapper class of ``BatchExperiment`` to implement HEAT experiment. + + # section: overview + + This is a helper class for experiment developers of the HEAT experiment. + This class overrides :meth:`set_experiment_options` and :meth:`set_transpile_options` + methods of :class:`BatchExperiment` so that it can override options of + subsequence amplification experiments to run them on the same set up. + From end users, this experiment seems as if a single HEAT experiment. + + # section: analysis_ref + :py:class:`HeatAnalysis` + """ + + def __init__( + self, + heat_experiments: List[HeatElement], + heat_analysis: HeatAnalysis, + backend: Optional[Backend] = None, + ): + """Create new HEAT experiment. + + Args: + heat_experiments: A list of error amplification sequence that might be + implemented as :class:``HeatElement`` instance. + heat_analysis: HEAT analysis instance. + backend: Optional, the backend to run the experiment on. + """ + super().__init__(experiments=heat_experiments, backend=backend, analysis=heat_analysis) + + @classmethod + def _default_experiment_options(cls) -> Options: + """Default experiment options. + + Experiment Options: + repetitions (Sequence[int]): A list of the number of echo repetitions. + cr_gate (Gate): A gate instance representing the entangler sequence. + """ + options = super()._default_experiment_options() + options.repetitions = list(range(21)) + options.heat_gate = circuit.Gate("heat", num_qubits=2, params=[]) + + return options + + def set_experiment_options(self, **fields): + """Set the analysis options for :meth:`run` method. + + Args: + fields: The fields to update the options + """ + # propagate options through all nested amplification experiments. + for comp_exp in self.component_experiment(): + comp_exp.set_experiment_options(**fields) + + super().set_experiment_options(**fields) + + @classmethod + def _default_transpile_options(cls) -> Options: + """Default transpile options.""" + options = super()._default_transpile_options() + options.basis_gates = ["sx", "x", "rz", "heat"] + options.optimization_level = 1 + + return options + + def set_transpile_options(self, **fields): + """Set the transpiler options for :meth:`run` method. + + Args: + fields: The fields to update the options + """ + # propagate options through all nested amplification experiments. + for comp_exp in self.component_experiment(): + comp_exp.set_transpile_options(**fields) + + super().set_transpile_options(**fields) diff --git a/qiskit_experiments/library/hamiltonian/heat_zx.py b/qiskit_experiments/library/hamiltonian/heat_zx.py new file mode 100644 index 0000000000..678c66d28b --- /dev/null +++ b/qiskit_experiments/library/hamiltonian/heat_zx.py @@ -0,0 +1,215 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +HEAT experiments for ZX Hamiltonian. +""" + +from typing import Tuple, Optional + +import numpy as np +from qiskit.circuit import QuantumCircuit +from qiskit.providers import Backend + +from .heat_base import BatchHeatHelper, HeatElement +from .heat_analysis import HeatAnalysis + + +class ZXHeat(BatchHeatHelper): + """HEAT experiment for the ZX-type entangler. + + # section: overview + This experiment is designed to amplify the error contained in the + ZX-type generator, a typical Hamiltonian implemented + by a cross resonance drive, which is a foundation of the CNOT gate. + + The preparation circuit locks target qubit state along X-axis, + in which the state is insensitive to the controlled X rotation + imposed by the drive with the ZX-type generator. + + The echo circuit refocuses ZX rotation to identity (II) then applies + a pi-pulse along the interrogated error axis. X error and Y error are + amplified outward the X-Y plane to draw a ping-pong pattern with the + state flip by the pi-pulse echo, while Z error is amplified outward + the X-Z plane in the same manner. + Measurement is projected onto Y-axis in this case. + Because the echoing axis are anti-commute with other Pauli terms, + errors in other axes are cancelled out to reduce rotation + in the interrogated axis. + This enables to selectively amplify the Hamiltonian dynamics in the specific axis. + + .. parsed-literal:: + (xN) + ░ ┌───────┐ ░ + q_0: ────────────░─┤0 ├────────────────────────────░───────── + ┌─────────┐ ░ │ heat │┌────────────────┐┌───────┐ ░ ┌───┐┌─┐ + q_1: ┤ Rα(π/2) ├─░─┤1 ├┤ Rx(-1.0*angle) ├┤ Rβ(π) ├─░─┤ γ ├┤M├ + └─────────┘ ░ └───────┘└────────────────┘└───────┘ ░ └───┘└╥┘ + c: 1/═══════════════════════════════════════════════════════════╩═ + 0 + + (xN) + ┌───┐ ░ ┌───────┐ ░ + q_0: ───┤ X ├────░─┤0 ├───────────────────────░───────── + ┌──┴───┴──┐ ░ │ heat │┌───────────┐┌───────┐ ░ ┌───┐┌─┐ + q_1: ┤ Rα(π/2) ├─░─┤1 ├┤ Rx(angle) ├┤ Rβ(π) ├─░─┤ γ ├┤M├ + └─────────┘ ░ └───────┘└───────────┘└───────┘ ░ └───┘└╥┘ + c: 1/══════════════════════════════════════════════════════╩═ + 0 + + ZX-HEAT experiments are performed with combination of two + error amplification experiments shown above, where :math:`\\alpha, \\beta, \\gamma` + depend on the interrogated error axis, namely, + (``X``, ``X``, ``I``), (``Y``, ``Y``, ``I``), (``Y``, ``Z``, ``Rx(pi/2)``) + for amplifying X, Y, Z axis, respectively. + The circuit in middle is repeated by ``N`` times for the error amplification. + + # section: note + The ``heat`` gate is a special gate to represent the entangler pulse sequence. + This gate is usually not provided by the backend, and thus user must provide + the pulse definition to run this experiment. + This pulse sequence should be pre-calibrated to roughly implement the + ZX(angle) evolution otherwise selective amplification doesn't work properly. + + # section: see_also + HeatElement + + # section: analysis_ref + :py:class:`HeatAnalysis` + + # section: reference + .. ref_arxiv:: 1 2007.02925 + """ + + def __init__( + self, + qubits: Tuple[int, int], + error_axis: str, + backend: Optional[Backend] = None, + angle: Optional[float] = np.pi/2, + ): + """Create new HEAT experiment for the entangler of ZX generator. + + Args: + qubits: Index of control and target qubit, respectively. + error_axis: String representation of axis that amplifies the error, + either one of "x", "y", "z". + backend: Optional, the backend to run the experiment on. + angle: Angle of controlled rotation, which defaults to pi/2. + """ + + amplification_exps = [] + for control in (0, 1): + prep = QuantumCircuit(2) + echo = QuantumCircuit(2) + meas = QuantumCircuit(2) + + if control: + prep.x(0) + echo.rx(angle, 1) + else: + echo.rx(-angle, 1) + + if error_axis == "x": + prep.rx(np.pi/2, 1) + echo.rx(np.pi, 1) + elif error_axis == "y": + prep.ry(np.pi/2, 1) + echo.ry(np.pi, 1) + elif error_axis == "z": + prep.ry(np.pi/2, 1) + echo.rz(np.pi, 1) + meas.rx(np.pi/2, 1) + else: + raise ValueError(f"Invalid error term {error_axis}.") + + exp = HeatElement( + qubits=qubits, + prep_circ=prep, + echo_circ=echo, + meas_circ=meas, + backend=backend, + ) + amplification_exps.append(exp) + + analysis = HeatAnalysis( + fit_params=[f"d_heat_{error_axis}0", f"d_heat_{error_axis}1"], + out_params=[f"A_I{error_axis.upper()}", f"A_Z{error_axis.upper()}"], + ) + + super(ZXHeat, self).__init__( + heat_experiments=amplification_exps, + heat_analysis=analysis, + backend=backend, + ) + + +class ZX90HeatXError(ZXHeat): + """HEAT experiment for X error amplification for ZX(pi/2) Hamiltonian. + + # section: see_also + :py:class:`ZXHeat` + """ + + def __init__(self, qubits: [int, int], backend: Optional[Backend] = None): + """Create new experiment. + + qubits: Index of control and target qubit, respectively. + backend: Optional, the backend to run the experiment on. + """ + super(ZX90HeatXError, self).__init__( + qubits=qubits, + error_axis="x", + backend=backend, + angle=np.pi / 2, + ) + + +class ZX90HeatYError(ZXHeat): + """HEAT experiment for Y error amplification for ZX(pi/2) Hamiltonian. + + # section: see_also + :py:class:`ZXHeat` + """ + + def __init__(self, qubits: [int, int], backend: Optional[Backend] = None): + """Create new experiment. + + qubits: Index of control and target qubit, respectively. + backend: Optional, the backend to run the experiment on. + """ + super(ZX90HeatYError, self).__init__( + qubits=qubits, + error_axis="y", + backend=backend, + angle=np.pi/2, + ) + + +class ZX90HeatZError(ZXHeat): + """HEAT experiment for Z error amplification for ZX(pi/2) Hamiltonian. + + # section: see_also + :py:class:`ZXHeat` + """ + + def __init__(self, qubits: [int, int], backend: Optional[Backend] = None): + """Create new experiment. + + qubits: Index of control and target qubit, respectively. + backend: Optional, the backend to run the experiment on. + """ + super(ZX90HeatZError, self).__init__( + qubits=qubits, + error_axis="z", + backend=backend, + angle=np.pi / 2, + ) From fccf94c9aef96de4a4d2c647179da123798ae44f Mon Sep 17 00:00:00 2001 From: knzwnao Date: Thu, 20 Jan 2022 01:23:01 +0900 Subject: [PATCH 02/46] write release note --- .../add-heat-experiment-047a73818407e733.yaml | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 releasenotes/notes/add-heat-experiment-047a73818407e733.yaml diff --git a/releasenotes/notes/add-heat-experiment-047a73818407e733.yaml b/releasenotes/notes/add-heat-experiment-047a73818407e733.yaml new file mode 100644 index 0000000000..85c2331856 --- /dev/null +++ b/releasenotes/notes/add-heat-experiment-047a73818407e733.yaml @@ -0,0 +1,22 @@ +--- +features: + - | + New experiment module :mod:`qiskit_experiments.library.hamiltonian` + is added. This module contains experiments to characterize low level + system properties, such as device Hamiltonian. + - | + The `Hamiltonian Error Amplifying Tomography` (HEAT) experiment has been + added. This experiment consists of the base class + :class:`~qiskit_experiments.library.hamiltonian.BatchHeatHelper` + and :class:`~qiskit_experiments.library.hamiltonian.HeatElement` + for the experiment developers to implement HEAT experiment + for arbitrary generator and for specific interrogated error axis. + + The HEAT experiment specific to the ZX-type generator + :class:`~qiskit_experiments.library.hamiltonian.ZXHeat` is also added + along with pre-configured experiments for + X (:class:`~qiskit_experiments.library.hamiltonian.ZX90HeatXError`), + Y (:class:`~qiskit_experiments.library.hamiltonian.ZX90HeatYError`), + Z (:class:`~qiskit_experiments.library.hamiltonian.ZX90HeatZError`) + error axis. These experiments are typically used to characterize + the Cross Resonance gate Hamiltonian implementing the CNOT gate. From 6e64321a5f3430ca268e49558ed9c1e46bca3a33 Mon Sep 17 00:00:00 2001 From: knzwnao Date: Thu, 20 Jan 2022 15:04:08 +0900 Subject: [PATCH 03/46] serialization unittest --- .../library/hamiltonian/__init__.py | 4 +- .../library/hamiltonian/heat_base.py | 18 +++- .../library/hamiltonian/heat_zx.py | 9 +- test/base.py | 4 +- test/hamiltonian/__init__.py | 13 +++ test/hamiltonian/test_heat.py | 92 +++++++++++++++++++ 6 files changed, 129 insertions(+), 11 deletions(-) create mode 100644 test/hamiltonian/__init__.py create mode 100644 test/hamiltonian/test_heat.py diff --git a/qiskit_experiments/library/hamiltonian/__init__.py b/qiskit_experiments/library/hamiltonian/__init__.py index afb001138e..d7b11a43f1 100644 --- a/qiskit_experiments/library/hamiltonian/__init__.py +++ b/qiskit_experiments/library/hamiltonian/__init__.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2021. +# (C) Copyright IBM 2022. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -11,7 +11,7 @@ # that they have been altered from the originals. """ =============================================================================================== -Hamiltonian Tomography Experiments (:mod:`qiskit_experiments.library.hamiltonian`) +Hamiltonian Characterization Experiments (:mod:`qiskit_experiments.library.hamiltonian`) =============================================================================================== .. currentmodule:: qiskit_experiments.library.hamiltonian diff --git a/qiskit_experiments/library/hamiltonian/heat_base.py b/qiskit_experiments/library/hamiltonian/heat_base.py index f81486da22..db3781e3df 100644 --- a/qiskit_experiments/library/hamiltonian/heat_base.py +++ b/qiskit_experiments/library/hamiltonian/heat_base.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2021. +# (C) Copyright IBM 2022. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -20,6 +20,7 @@ from qiskit.providers import Backend from qiskit_experiments.framework import BaseExperiment, BatchExperiment, Options +from qiskit_experiments.curve_analysis import ParameterRepr from .heat_analysis import HeatElementAnalysis, HeatAnalysis @@ -82,6 +83,7 @@ def __init__( echo_circ: QuantumCircuit, meas_circ: QuantumCircuit, backend: Optional[Backend] = None, + parameter_name: Optional[str] = "d_theta", **kwargs ): """Create new HEAT sub experiment. @@ -92,11 +94,17 @@ def __init__( echo_circ: A circuit to selectively amplify the specific error term. meas_circ: A circuit to project target qubit onto the basis of interest. backend: Optional, the backend to run the experiment on. + parameter_name: A name that represents angle from fitting. Keyword Args: See :meth:`experiment_options` for details. """ - super().__init__(qubits=qubits, backend=backend, analysis=HeatElementAnalysis()) + analysis = HeatElementAnalysis() + analysis.set_options( + result_parameters=[ParameterRepr("d_theta", parameter_name, "rad")] + ) + + super().__init__(qubits=qubits, backend=backend, analysis=analysis) self.set_experiment_options(**kwargs) # These are not user configurable options. Be frozen once assigned. @@ -183,7 +191,11 @@ def __init__( heat_analysis: HEAT analysis instance. backend: Optional, the backend to run the experiment on. """ - super().__init__(experiments=heat_experiments, backend=backend, analysis=heat_analysis) + super().__init__(experiments=heat_experiments, backend=backend) + + # override analysis. we expect the instance is initialized with + # parameter names specific to child amplification experiments. + self.analysis = heat_analysis @classmethod def _default_experiment_options(cls) -> Options: diff --git a/qiskit_experiments/library/hamiltonian/heat_zx.py b/qiskit_experiments/library/hamiltonian/heat_zx.py index 678c66d28b..1e7b6c639d 100644 --- a/qiskit_experiments/library/hamiltonian/heat_zx.py +++ b/qiskit_experiments/library/hamiltonian/heat_zx.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2021. +# (C) Copyright IBM 2022. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -137,6 +137,7 @@ def __init__( echo_circ=echo, meas_circ=meas, backend=backend, + parameter_name=f"d_heat_{error_axis}{control}", ) amplification_exps.append(exp) @@ -156,7 +157,7 @@ class ZX90HeatXError(ZXHeat): """HEAT experiment for X error amplification for ZX(pi/2) Hamiltonian. # section: see_also - :py:class:`ZXHeat` + ZXHeat """ def __init__(self, qubits: [int, int], backend: Optional[Backend] = None): @@ -177,7 +178,7 @@ class ZX90HeatYError(ZXHeat): """HEAT experiment for Y error amplification for ZX(pi/2) Hamiltonian. # section: see_also - :py:class:`ZXHeat` + ZXHeat """ def __init__(self, qubits: [int, int], backend: Optional[Backend] = None): @@ -198,7 +199,7 @@ class ZX90HeatZError(ZXHeat): """HEAT experiment for Z error amplification for ZX(pi/2) Hamiltonian. # section: see_also - :py:class:`ZXHeat` + ZXHeat """ def __init__(self, qubits: [int, int], backend: Optional[Backend] = None): diff --git a/test/base.py b/test/base.py index 7bf3bbdc74..99bb6484ce 100644 --- a/test/base.py +++ b/test/base.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2021. +# (C) Copyright IBM 2022. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -18,7 +18,7 @@ import numpy as np from qiskit.test import QiskitTestCase -from qiskit_experiments.framework import ExperimentDecoder, ExperimentEncoder +from qiskit_experiments.framework import ExperimentDecoder, ExperimentEncoder, BaseExperiment class QiskitExperimentsTestCase(QiskitTestCase): diff --git a/test/hamiltonian/__init__.py b/test/hamiltonian/__init__.py new file mode 100644 index 0000000000..43fbcfb7e2 --- /dev/null +++ b/test/hamiltonian/__init__.py @@ -0,0 +1,13 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2022. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Test cases for Hamiltonian characterization.""" diff --git a/test/hamiltonian/test_heat.py b/test/hamiltonian/test_heat.py new file mode 100644 index 0000000000..62a9400f24 --- /dev/null +++ b/test/hamiltonian/test_heat.py @@ -0,0 +1,92 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2022. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +Test for the HEAT experiment +""" +from test.base import QiskitExperimentsTestCase + +from qiskit import circuit, quantum_info as qi +from qiskit_experiments.library.hamiltonian import HeatElement, BatchHeatHelper, HeatAnalysis +from qiskit_experiments.library import ZXHeat + + +class TestHeatBase(QiskitExperimentsTestCase): + """Test for base classes.""" + + @staticmethod + def _create_fake_amplifier(prep_seed, echo_seed, meas_seed, pname): + prep = circuit.QuantumCircuit(2) + prep.compose(qi.random_unitary(4, seed=prep_seed).to_instruction(), inplace=True) + + echo = circuit.QuantumCircuit(2) + echo.compose(qi.random_unitary(4, seed=echo_seed).to_instruction(), inplace=True) + + meas = circuit.QuantumCircuit(2) + meas.compose(qi.random_unitary(4, seed=meas_seed).to_instruction(), inplace=True) + + exp = HeatElement( + qubits=(0, 1), + prep_circ=prep, + echo_circ=echo, + meas_circ=meas, + parameter_name=pname, + ) + + return exp + + def test_element_experiment_config(self): + """Test converting to and from config works""" + exp = self._create_fake_amplifier(123, 456, 789, "test") + + loaded_exp = HeatElement.from_config(exp.config()) + self.assertNotEqual(exp, loaded_exp) + self.assertTrue(self.experiments_equiv(exp, loaded_exp)) + + def test_element_roundtrip_serializable(self): + """Test round trip JSON serialization""" + exp = self._create_fake_amplifier(123, 456, 789, "test") + + self.assertRoundTripSerializable(exp, self.experiments_equiv) + + def test_experiment_config(self): + """Test converting to and from config works""" + ampl1 = self._create_fake_amplifier(123, 456, 789, "i1") + ampl2 = self._create_fake_amplifier(987, 654, 321, "i2") + analysis = HeatAnalysis(fit_params=["i1", "i2"], out_params=["o1", "o2"]) + exp = BatchHeatHelper(heat_experiments=[ampl1, ampl2], heat_analysis=analysis) + + loaded_exp = BatchHeatHelper.from_config(exp.config()) + self.assertNotEqual(exp, loaded_exp) + self.assertTrue(self.experiments_equiv(exp, loaded_exp)) + + def test_roundtrip_serializable(self): + """Test round trip JSON serialization""" + ampl1 = self._create_fake_amplifier(123, 456, 789, "i1") + ampl2 = self._create_fake_amplifier(987, 654, 321, "i2") + analysis = HeatAnalysis(fit_params=["i1", "i2"], out_params=["o1", "o2"]) + exp = BatchHeatHelper(heat_experiments=[ampl1, ampl2], heat_analysis=analysis) + + self.assertRoundTripSerializable(exp, self.experiments_equiv) + + def test_analysis_config(self): + """Test converting analysis to and from config works""" + analysis = HeatAnalysis(fit_params=["i1", "i2"], out_params=["o1", "o2"]) + loaded = HeatAnalysis.from_config(analysis.config()) + self.assertNotEqual(analysis, loaded) + self.assertEqual(analysis.config(), loaded.config()) + + + + + + From 49a7845df7b25fe72dd968845c445c3a20a7a95a Mon Sep 17 00:00:00 2001 From: knzwnao Date: Mon, 24 Jan 2022 10:40:56 +0900 Subject: [PATCH 04/46] fix projection angle of Z measure --- qiskit_experiments/library/hamiltonian/heat_zx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit_experiments/library/hamiltonian/heat_zx.py b/qiskit_experiments/library/hamiltonian/heat_zx.py index 1e7b6c639d..2166b65bdc 100644 --- a/qiskit_experiments/library/hamiltonian/heat_zx.py +++ b/qiskit_experiments/library/hamiltonian/heat_zx.py @@ -68,7 +68,7 @@ class ZXHeat(BatchHeatHelper): ZX-HEAT experiments are performed with combination of two error amplification experiments shown above, where :math:`\\alpha, \\beta, \\gamma` depend on the interrogated error axis, namely, - (``X``, ``X``, ``I``), (``Y``, ``Y``, ``I``), (``Y``, ``Z``, ``Rx(pi/2)``) + (``X``, ``X``, ``I``), (``Y``, ``Y``, ``I``), (``Y``, ``Z``, ``Rx(-pi/2)``) for amplifying X, Y, Z axis, respectively. The circuit in middle is repeated by ``N`` times for the error amplification. @@ -127,7 +127,7 @@ def __init__( elif error_axis == "z": prep.ry(np.pi/2, 1) echo.rz(np.pi, 1) - meas.rx(np.pi/2, 1) + meas.rx(-np.pi/2, 1) else: raise ValueError(f"Invalid error term {error_axis}.") From d32444f358af9ac517ae2eefad1432c671b25196 Mon Sep 17 00:00:00 2001 From: knzwnao Date: Tue, 25 Jan 2022 13:29:36 +0900 Subject: [PATCH 05/46] add unittest for running experiments and minor doc fix --- .../library/hamiltonian/heat_analysis.py | 13 +- .../library/hamiltonian/heat_base.py | 8 +- .../library/hamiltonian/heat_zx.py | 68 +++--- .../add-heat-experiment-047a73818407e733.yaml | 6 +- test/base.py | 2 +- test/hamiltonian/test_heat.py | 222 +++++++++++++++++- 6 files changed, 267 insertions(+), 52 deletions(-) diff --git a/qiskit_experiments/library/hamiltonian/heat_analysis.py b/qiskit_experiments/library/hamiltonian/heat_analysis.py index 46d0f1d2a0..307f8dbd5f 100644 --- a/qiskit_experiments/library/hamiltonian/heat_analysis.py +++ b/qiskit_experiments/library/hamiltonian/heat_analysis.py @@ -86,8 +86,11 @@ def __init__(self, fit_params: List[str], out_params: List[str]): Args: fit_params: Name of error parameters for each amplification sequence. out_params: Name of Hamiltonian coefficients. + + Raises: + AnalysisError: When size of ``fit_params`` or ``out_params`` are not 2. """ - super(HeatAnalysis, self).__init__() + super().__init__() if len(fit_params) != 2: raise AnalysisError( @@ -113,9 +116,7 @@ def _run_analysis(self, experiment_data: ExperimentData): # extract d_theta parameters fit_results = [] for i, pname in enumerate(self.fit_params): - fit_results.append( - experiment_data.child_data(i).analysis_results(pname) - ) + fit_results.append(experiment_data.child_data(i).analysis_results(pname)) # Check data quality is_good_quality = all(r.quality == "good" for r in fit_results) @@ -125,9 +126,7 @@ def _run_analysis(self, experiment_data: ExperimentData): zb = (fit_results[0].value.value - fit_results[1].value.value) / 2 # Compute new variance - sigma = np.sqrt( - fit_results[0].value.stderr ** 2 + fit_results[1].value.stderr ** 2 - ) + sigma = np.sqrt(fit_results[0].value.stderr ** 2 + fit_results[1].value.stderr ** 2) estimate_ib = AnalysisResultData( name=self.out_params[0], diff --git a/qiskit_experiments/library/hamiltonian/heat_base.py b/qiskit_experiments/library/hamiltonian/heat_base.py index db3781e3df..1acbfb1cb7 100644 --- a/qiskit_experiments/library/hamiltonian/heat_base.py +++ b/qiskit_experiments/library/hamiltonian/heat_base.py @@ -84,7 +84,7 @@ def __init__( meas_circ: QuantumCircuit, backend: Optional[Backend] = None, parameter_name: Optional[str] = "d_theta", - **kwargs + **kwargs, ): """Create new HEAT sub experiment. @@ -100,9 +100,7 @@ def __init__( See :meth:`experiment_options` for details. """ analysis = HeatElementAnalysis() - analysis.set_options( - result_parameters=[ParameterRepr("d_theta", parameter_name, "rad")] - ) + analysis.set_options(result_parameters=[ParameterRepr("d_theta", parameter_name, "rad")]) super().__init__(qubits=qubits, backend=backend, analysis=analysis) self.set_experiment_options(**kwargs) @@ -135,7 +133,7 @@ def _default_transpile_options(cls) -> Options: return options - def circuits(self, backend: Optional[Backend] = None) -> List[QuantumCircuit]: + def circuits(self) -> List[QuantumCircuit]: opt = self.experiment_options circs = list() diff --git a/qiskit_experiments/library/hamiltonian/heat_zx.py b/qiskit_experiments/library/hamiltonian/heat_zx.py index 2166b65bdc..c5e8a792d9 100644 --- a/qiskit_experiments/library/hamiltonian/heat_zx.py +++ b/qiskit_experiments/library/hamiltonian/heat_zx.py @@ -31,20 +31,21 @@ class ZXHeat(BatchHeatHelper): ZX-type generator, a typical Hamiltonian implemented by a cross resonance drive, which is a foundation of the CNOT gate. - The preparation circuit locks target qubit state along X-axis, - in which the state is insensitive to the controlled X rotation - imposed by the drive with the ZX-type generator. - The echo circuit refocuses ZX rotation to identity (II) then applies a pi-pulse along the interrogated error axis. X error and Y error are - amplified outward the X-Y plane to draw a ping-pong pattern with the + amplified outward the X-Y plane to draw a ping-pong pattern with state flip by the pi-pulse echo, while Z error is amplified outward the X-Z plane in the same manner. - Measurement is projected onto Y-axis in this case. - Because the echoing axis are anti-commute with other Pauli terms, - errors in other axes are cancelled out to reduce rotation - in the interrogated axis. + Measurement is projected onto Y-axis in this setup. + Because the echoed axis are anti-commute with other Pauli terms, + errors in other axes are cancelled out to reduce rotation in the interrogated axis. This enables to selectively amplify the Hamiltonian dynamics in the specific axis. + Note that we have always nonzero X rotation imparted by the significant ZX term, + the error along Y and Z axis are skewed by the nonzero commutator term. + This yields slight mismatch in the estimated coefficients with the generator Hamiltonian, + however this matters less when the expected magnitude of the error is small. + On the other hand, the error in the X axis is straightforward + because this is commute with the ZX term of the generator. .. parsed-literal:: (xN) @@ -80,7 +81,7 @@ class ZXHeat(BatchHeatHelper): ZX(angle) evolution otherwise selective amplification doesn't work properly. # section: see_also - HeatElement + qiskit_experiments.library.hamiltonian.HeatElement # section: analysis_ref :py:class:`HeatAnalysis` @@ -94,7 +95,7 @@ def __init__( qubits: Tuple[int, int], error_axis: str, backend: Optional[Backend] = None, - angle: Optional[float] = np.pi/2, + angle: Optional[float] = np.pi / 2, ): """Create new HEAT experiment for the entangler of ZX generator. @@ -104,6 +105,9 @@ def __init__( either one of "x", "y", "z". backend: Optional, the backend to run the experiment on. angle: Angle of controlled rotation, which defaults to pi/2. + + Raises: + ValueError: When ``error_axis`` is not one of "x", "y", "z". """ amplification_exps = [] @@ -119,15 +123,15 @@ def __init__( echo.rx(-angle, 1) if error_axis == "x": - prep.rx(np.pi/2, 1) + prep.rx(np.pi / 2, 1) echo.rx(np.pi, 1) elif error_axis == "y": - prep.ry(np.pi/2, 1) + prep.ry(np.pi / 2, 1) echo.ry(np.pi, 1) elif error_axis == "z": - prep.ry(np.pi/2, 1) + prep.ry(np.pi / 2, 1) echo.rz(np.pi, 1) - meas.rx(-np.pi/2, 1) + meas.rx(-np.pi / 2, 1) else: raise ValueError(f"Invalid error term {error_axis}.") @@ -146,7 +150,7 @@ def __init__( out_params=[f"A_I{error_axis.upper()}", f"A_Z{error_axis.upper()}"], ) - super(ZXHeat, self).__init__( + super().__init__( heat_experiments=amplification_exps, heat_analysis=analysis, backend=backend, @@ -157,16 +161,16 @@ class ZX90HeatXError(ZXHeat): """HEAT experiment for X error amplification for ZX(pi/2) Hamiltonian. # section: see_also - ZXHeat + qiskit_experiments.library.hamiltonian.ZXHeat """ - def __init__(self, qubits: [int, int], backend: Optional[Backend] = None): + def __init__(self, qubits: Tuple[int, int], backend: Optional[Backend] = None): """Create new experiment. - qubits: Index of control and target qubit, respectively. - backend: Optional, the backend to run the experiment on. + qubits: Index of control and target qubit, respectively. + backend: Optional, the backend to run the experiment on. """ - super(ZX90HeatXError, self).__init__( + super().__init__( qubits=qubits, error_axis="x", backend=backend, @@ -178,20 +182,20 @@ class ZX90HeatYError(ZXHeat): """HEAT experiment for Y error amplification for ZX(pi/2) Hamiltonian. # section: see_also - ZXHeat + qiskit_experiments.library.hamiltonian.ZXHeat """ - def __init__(self, qubits: [int, int], backend: Optional[Backend] = None): + def __init__(self, qubits: Tuple[int, int], backend: Optional[Backend] = None): """Create new experiment. - qubits: Index of control and target qubit, respectively. - backend: Optional, the backend to run the experiment on. + qubits: Index of control and target qubit, respectively. + backend: Optional, the backend to run the experiment on. """ - super(ZX90HeatYError, self).__init__( + super().__init__( qubits=qubits, error_axis="y", backend=backend, - angle=np.pi/2, + angle=np.pi / 2, ) @@ -199,16 +203,16 @@ class ZX90HeatZError(ZXHeat): """HEAT experiment for Z error amplification for ZX(pi/2) Hamiltonian. # section: see_also - ZXHeat + qiskit_experiments.library.hamiltonian.ZXHeat """ - def __init__(self, qubits: [int, int], backend: Optional[Backend] = None): + def __init__(self, qubits: Tuple[int, int], backend: Optional[Backend] = None): """Create new experiment. - qubits: Index of control and target qubit, respectively. - backend: Optional, the backend to run the experiment on. + qubits: Index of control and target qubit, respectively. + backend: Optional, the backend to run the experiment on. """ - super(ZX90HeatZError, self).__init__( + super().__init__( qubits=qubits, error_axis="z", backend=backend, diff --git a/releasenotes/notes/add-heat-experiment-047a73818407e733.yaml b/releasenotes/notes/add-heat-experiment-047a73818407e733.yaml index 85c2331856..b85fc290b5 100644 --- a/releasenotes/notes/add-heat-experiment-047a73818407e733.yaml +++ b/releasenotes/notes/add-heat-experiment-047a73818407e733.yaml @@ -2,15 +2,17 @@ features: - | New experiment module :mod:`qiskit_experiments.library.hamiltonian` - is added. This module contains experiments to characterize low level + has been added. This module collects experiments to characterize low level system properties, such as device Hamiltonian. - | The `Hamiltonian Error Amplifying Tomography` (HEAT) experiment has been added. This experiment consists of the base class :class:`~qiskit_experiments.library.hamiltonian.BatchHeatHelper` and :class:`~qiskit_experiments.library.hamiltonian.HeatElement` - for the experiment developers to implement HEAT experiment + for the experiment developers to implement own HEAT experiment for arbitrary generator and for specific interrogated error axis. + This can be realized by designing proper initialization circuit, + echo sequence, and measurement axis against the interrogated error axis. The HEAT experiment specific to the ZX-type generator :class:`~qiskit_experiments.library.hamiltonian.ZXHeat` is also added diff --git a/test/base.py b/test/base.py index d90992b07a..541eff724b 100644 --- a/test/base.py +++ b/test/base.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2022. +# (C) Copyright IBM 2021, 2022. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory diff --git a/test/hamiltonian/test_heat.py b/test/hamiltonian/test_heat.py index 62a9400f24..e64bcbe78d 100644 --- a/test/hamiltonian/test_heat.py +++ b/test/hamiltonian/test_heat.py @@ -15,9 +15,35 @@ """ from test.base import QiskitExperimentsTestCase +import scipy.linalg as la +import numpy as np + +from ddt import ddt, data, unpack + from qiskit import circuit, quantum_info as qi +from qiskit.providers.aer import AerSimulator from qiskit_experiments.library.hamiltonian import HeatElement, BatchHeatHelper, HeatAnalysis -from qiskit_experiments.library import ZXHeat +from qiskit_experiments.library import ZX90HeatXError, ZX90HeatYError, ZX90HeatZError +from qiskit_experiments.framework import BatchExperiment + + +class HeatExperimentsTestCase: + """Base class for HEAT experiment test.""" + + backend = AerSimulator() + + @staticmethod + def create_heat_gate(generator): + """Helper function to create HEAT gate for Aer simulator.""" + unitary = la.expm(-1j * generator) + + gate_decomp = circuit.QuantumCircuit(2) + gate_decomp.unitary(unitary, [0, 1]) + + heat_gate = circuit.Gate(f"heat_{hash(unitary.tobytes())}", 2, []) + heat_gate.add_decomposition(gate_decomp) + + return heat_gate class TestHeatBase(QiskitExperimentsTestCase): @@ -25,6 +51,7 @@ class TestHeatBase(QiskitExperimentsTestCase): @staticmethod def _create_fake_amplifier(prep_seed, echo_seed, meas_seed, pname): + """Helper method to generate fake experiment.""" prep = circuit.QuantumCircuit(2) prep.compose(qi.random_unitary(4, seed=prep_seed).to_instruction(), inplace=True) @@ -50,13 +77,13 @@ def test_element_experiment_config(self): loaded_exp = HeatElement.from_config(exp.config()) self.assertNotEqual(exp, loaded_exp) - self.assertTrue(self.experiments_equiv(exp, loaded_exp)) + self.assertTrue(self.json_equiv(exp, loaded_exp)) def test_element_roundtrip_serializable(self): """Test round trip JSON serialization""" exp = self._create_fake_amplifier(123, 456, 789, "test") - self.assertRoundTripSerializable(exp, self.experiments_equiv) + self.assertRoundTripSerializable(exp, self.json_equiv) def test_experiment_config(self): """Test converting to and from config works""" @@ -67,7 +94,7 @@ def test_experiment_config(self): loaded_exp = BatchHeatHelper.from_config(exp.config()) self.assertNotEqual(exp, loaded_exp) - self.assertTrue(self.experiments_equiv(exp, loaded_exp)) + self.assertTrue(self.json_equiv(exp, loaded_exp)) def test_roundtrip_serializable(self): """Test round trip JSON serialization""" @@ -76,7 +103,7 @@ def test_roundtrip_serializable(self): analysis = HeatAnalysis(fit_params=["i1", "i2"], out_params=["o1", "o2"]) exp = BatchHeatHelper(heat_experiments=[ampl1, ampl2], heat_analysis=analysis) - self.assertRoundTripSerializable(exp, self.experiments_equiv) + self.assertRoundTripSerializable(exp, self.json_equiv) def test_analysis_config(self): """Test converting analysis to and from config works""" @@ -85,8 +112,193 @@ def test_analysis_config(self): self.assertNotEqual(analysis, loaded) self.assertEqual(analysis.config(), loaded.config()) + def test_create_circuit(self): + """Test HEAT circuit generation.""" + prep = circuit.QuantumCircuit(2) + prep.x(0) + prep.ry(np.pi / 2, 1) + + echo = circuit.QuantumCircuit(2) + echo.z(1) + + meas = circuit.QuantumCircuit(2) + meas.rx(np.pi / 2, 1) + + exp = HeatElement( + qubits=(0, 1), + prep_circ=prep, + echo_circ=echo, + meas_circ=meas, + parameter_name="testing", + ) + exp.set_experiment_options(repetitions=[2]) + + # check also overriding of amplified parameter name + self.assertEqual(exp.analysis.options.result_parameters[0].repr, "testing") + + heat_circ = exp.circuits()[0] + + ref_circ = circuit.QuantumCircuit(2, 1) + ref_circ.x(0) + ref_circ.ry(np.pi / 2, 1) + ref_circ.barrier() + ref_circ.append(exp.experiment_options.heat_gate, [0, 1]) + ref_circ.z(1) + ref_circ.barrier() + ref_circ.append(exp.experiment_options.heat_gate, [0, 1]) + ref_circ.z(1) + ref_circ.barrier() + ref_circ.rx(np.pi / 2, 1) + ref_circ.measure(1, 0) + + self.assertEqual(heat_circ, ref_circ) + + +@ddt +class TestZXHeat(QiskitExperimentsTestCase, HeatExperimentsTestCase): + """Test ZX Heat experiment.""" + + @staticmethod + def create_generator( + angle=np.pi / 2, + e_zx=0.0, + e_zy=0.0, + e_zz=0.0, + e_ix=0.0, + e_iy=0.0, + e_iz=0.0, + ): + """Create generator Hamiltonian represented by numpy array.""" + generator_ham = ( + 0.5 + * ( + (angle + e_zx) * qi.Operator.from_label("XZ") + + e_zy * qi.Operator.from_label("YZ") + + e_zz * qi.Operator.from_label("ZZ") + + e_ix * qi.Operator.from_label("XI") + + e_iy * qi.Operator.from_label("YI") + + e_iz * qi.Operator.from_label("ZI") + ).data + ) + + return generator_ham + + @data( + [0.08, -0.01], + [-0.05, 0.13], + [0.15, 0.02], + [-0.04, -0.02], + [0.0, 0.12], + [0.12, 0.0], + ) + @unpack + def test_x_error_amplification(self, e_zx, e_ix): + """Test for X error amplification.""" + exp = ZX90HeatXError(qubits=(0, 1), backend=self.backend) + generator = self.create_generator(e_zx=e_zx, e_ix=e_ix) + gate = self.create_heat_gate(generator) + exp.set_experiment_options(heat_gate=gate) + exp.set_transpile_options(basis_gates=["x", "sx", "rz", "unitary"]) + + exp_data = exp.run().block_for_results() + + self.assertAlmostEqual(exp_data.analysis_results("A_IX").value.value, e_ix, delta=0.01) + self.assertAlmostEqual(exp_data.analysis_results("A_ZX").value.value, e_zx, delta=0.01) + + @data( + [0.02, -0.01], + [-0.05, 0.03], + [0.03, 0.02], + [-0.04, -0.01], + [0.0, 0.01], + [0.01, 0.0], + ) + @unpack + def test_y_error_amplification(self, e_zy, e_iy): + """Test for Y error amplification.""" + exp = ZX90HeatYError(qubits=(0, 1), backend=self.backend) + generator = self.create_generator(e_zy=e_zy, e_iy=e_iy) + gate = self.create_heat_gate(generator) + exp.set_experiment_options(heat_gate=gate) + exp.set_transpile_options(basis_gates=["x", "sx", "rz", "unitary"]) + + exp_data = exp.run().block_for_results() + + # larger error torelance to allow commutator term + self.assertAlmostEqual(exp_data.analysis_results("A_IY").value.value, e_iy, delta=0.05) + self.assertAlmostEqual(exp_data.analysis_results("A_ZY").value.value, e_zy, delta=0.05) + + @data( + [0.02, -0.01], + [-0.05, 0.03], + [0.03, 0.02], + [-0.04, -0.01], + [0.0, 0.01], + [0.01, 0.0], + ) + @unpack + def test_z_error_amplification(self, e_zz, e_iz): + """Test for Z error amplification.""" + exp = ZX90HeatZError(qubits=(0, 1), backend=self.backend) + generator = self.create_generator(e_zz=e_zz, e_iz=e_iz) + gate = self.create_heat_gate(generator) + exp.set_experiment_options(heat_gate=gate) + exp.set_transpile_options(basis_gates=["x", "sx", "rz", "unitary"]) + + exp_data = exp.run().block_for_results() + + # larger error torelance to allow commutator term + self.assertAlmostEqual(exp_data.analysis_results("A_IZ").value.value, e_iz, delta=0.05) + self.assertAlmostEqual(exp_data.analysis_results("A_ZZ").value.value, e_zz, delta=0.05) + + @data(123, 456) + def test_pseudo_calibration(self, seed): + """Test calibration with HEAT. + + This is somewhat of an integration test that covers multiple aspects of the experiment. + + The protocol of this test is as follows: + + First, this generates random Hamiltonian with multiple finite error terms. + Then errors in every axis is measured by three HEAT experiments as + a batch experiment, then inferred error values are subtracted from the + actual errors randomly determined. Repeating this eventually converges into + zero-ish errors in all axes if HEAT experiments work correctly. + + This checks if experiment sequence is designed correctly, and also checks + if HEAT experiment can be batched. + Note that HEAT itself is a batch experiment of amplifications. + """ + np.random.seed(seed) + coeffs = np.random.normal(0, 0.03, 6) + terms = ["e_zx", "e_zy", "e_zz", "e_ix", "e_iy", "e_iz"] + + errors_dict = dict(zip(terms, coeffs)) + + for _ in range(10): + generator = self.create_generator(**errors_dict) + gate = self.create_heat_gate(generator) + + exp_x = ZX90HeatXError(qubits=(0, 1)) + exp_x.set_experiment_options(heat_gate=gate) + exp_x.set_transpile_options(basis_gates=["x", "sx", "rz", "unitary"]) + exp_y = ZX90HeatYError(qubits=(0, 1)) + exp_y.set_experiment_options(heat_gate=gate) + exp_y.set_transpile_options(basis_gates=["x", "sx", "rz", "unitary"]) + exp_z = ZX90HeatZError(qubits=(0, 1)) + exp_z.set_experiment_options(heat_gate=gate) + exp_z.set_transpile_options(basis_gates=["x", "sx", "rz", "unitary"]) + exp = BatchExperiment([exp_x, exp_y, exp_z], backend=self.backend) + exp_data = exp.run().block_for_results() + for n, tp in enumerate(["x", "y", "z"]): + a_zp = exp_data.child_data(n).analysis_results(f"A_Z{tp.upper()}") + a_ip = exp_data.child_data(n).analysis_results(f"A_I{tp.upper()}") + errors_dict[f"e_z{tp}"] -= a_zp.value.value + errors_dict[f"e_i{tp}"] -= a_ip.value.value + for v in errors_dict.values(): + self.assertAlmostEqual(v, 0.0, delta=0.003) From c0e0da54b7eb2e12fb61c099f4334a4afb1707de Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 27 Jan 2022 15:37:51 +0900 Subject: [PATCH 06/46] Update qiskit_experiments/library/hamiltonian/__init__.py Co-authored-by: Daniel J. Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit_experiments/library/hamiltonian/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qiskit_experiments/library/hamiltonian/__init__.py b/qiskit_experiments/library/hamiltonian/__init__.py index d7b11a43f1..775701284f 100644 --- a/qiskit_experiments/library/hamiltonian/__init__.py +++ b/qiskit_experiments/library/hamiltonian/__init__.py @@ -16,8 +16,7 @@ .. currentmodule:: qiskit_experiments.library.hamiltonian -This module provides a set of experiments to characterize Hamiltonian level -properties of qubits or control sequences. +This module provides a set of experiments to characterize qubit Hamiltonians. HEAT Experiments ================ From 423dad4c080c33ec223ca559b39992d2d747accd Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 27 Jan 2022 15:38:00 +0900 Subject: [PATCH 07/46] Update qiskit_experiments/library/hamiltonian/__init__.py Co-authored-by: Daniel J. Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit_experiments/library/hamiltonian/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qiskit_experiments/library/hamiltonian/__init__.py b/qiskit_experiments/library/hamiltonian/__init__.py index 775701284f..d2b16c3485 100644 --- a/qiskit_experiments/library/hamiltonian/__init__.py +++ b/qiskit_experiments/library/hamiltonian/__init__.py @@ -22,8 +22,9 @@ ================ HEAT stands for `Hamiltonian Error Amplifying Tomography` which amplifies the -dynamics of entangler along the interrogated axis on the target qubit with -the conventional error amplification (ping-pong) technique. +dynamics of an entangling gate along a specified axis of the target qubit. Here, +errors are typically amplified by repeating a sequence of gates which results in +a ping-pong pattern when measuring the qubit population. These are the base experiment classes for developer to write own experiments. From 8adcca2ca3cb42dbafd926f90174cd349844e24c Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 27 Jan 2022 15:38:14 +0900 Subject: [PATCH 08/46] Update qiskit_experiments/library/hamiltonian/heat_analysis.py Co-authored-by: Daniel J. Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit_experiments/library/hamiltonian/heat_analysis.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qiskit_experiments/library/hamiltonian/heat_analysis.py b/qiskit_experiments/library/hamiltonian/heat_analysis.py index 307f8dbd5f..9108c9aa40 100644 --- a/qiskit_experiments/library/hamiltonian/heat_analysis.py +++ b/qiskit_experiments/library/hamiltonian/heat_analysis.py @@ -58,8 +58,9 @@ class HeatAnalysis(CompositeAnalysis): # section: fit_model This analysis takes two error amplification experiment results performed with - different control qubit state to distinguish the local rotation term from - the controlled rotation term amplified along a specific error axis. + different states of the control qubit to distinguish local rotations (such as IX) from + controlled rotations (such as ZX). These rotations are amplified along an + experiment-specific error axis. This analysis takes a set of `d_theta` parameters from child error amplification results which might be represented by a unique name in the child experiment data. From 942e558e8166e17379bd0b0add92ef1491787c55 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 27 Jan 2022 15:38:59 +0900 Subject: [PATCH 09/46] Update qiskit_experiments/library/hamiltonian/heat_analysis.py Co-authored-by: Daniel J. Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit_experiments/library/hamiltonian/heat_analysis.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit_experiments/library/hamiltonian/heat_analysis.py b/qiskit_experiments/library/hamiltonian/heat_analysis.py index 9108c9aa40..b59fa072d5 100644 --- a/qiskit_experiments/library/hamiltonian/heat_analysis.py +++ b/qiskit_experiments/library/hamiltonian/heat_analysis.py @@ -72,9 +72,9 @@ class HeatAnalysis(CompositeAnalysis): A_{Z\beta} = \frac{1}{2}\left( d\theta_{\beta 0} - d\theta_{\beta 1} \right) - where, :math:`\beta \in [X, Y, Z]` is one of single qubit Pauli term, - :math:`d\theta_{\beta k}` is `d_theta` parameter extracted from the HEAT experiment - with the control qubit state :math:`|k\rangle \in [|0\rangle, |1\rangle]`. + where, :math:`\beta \in [X, Y, Z]` is a single-qubit Pauli term, and + :math:`d\theta_{\beta k}` is an angle error ``d_theta`` extracted from the HEAT experiment + with the control qubit in state :math:`|k\rangle \in [|0\rangle, |1\rangle]`. # section: see_also qiskit_experiments.library.hamiltonian.HeatElementAnalysis From 4f70f6eb9cf44480b64edc2b45a010217f3e9358 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 27 Jan 2022 15:40:20 +0900 Subject: [PATCH 10/46] Update qiskit_experiments/library/hamiltonian/heat_base.py Co-authored-by: Daniel J. Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit_experiments/library/hamiltonian/heat_base.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/qiskit_experiments/library/hamiltonian/heat_base.py b/qiskit_experiments/library/hamiltonian/heat_base.py index 1acbfb1cb7..4062322de8 100644 --- a/qiskit_experiments/library/hamiltonian/heat_base.py +++ b/qiskit_experiments/library/hamiltonian/heat_base.py @@ -45,11 +45,11 @@ class HeatElement(BaseExperiment): c: 1/═══════════════════════════════════════════╩═ 0 - The circuit in middle is repeated by ``N`` times to amplify the Hamiltonian - coefficients along interrogated axis on the target qubit. The ``prep`` circuit - is carefully chosen based on the generator of ``heat`` gate to investigate, - and the ``echo`` and ``meas`` circuit depend on the axis of error to amplify. - Only target qubit is measured following to the projection by ``meas`` circuit. + The circuit in the middle is repeated ``N`` times to amplify the Hamiltonian + coefficients along a specific axis of the target qubit. The ``prep`` circuit is + carefully chosen based on the generator of the ``heat`` gate under consideration. + The ``echo`` and ``meas`` circuits depend on the axis of the error to amplify. + Only the target qubit is measured following to the projection in the ``meas`` circuit. The amplified response may consist of the contribution of from the local and controlled rotation terms. Thus, usually multiple error amplification experiments From 9cc247f17567abf4edbeed4798fba629b970f739 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 27 Jan 2022 15:41:57 +0900 Subject: [PATCH 11/46] Update qiskit_experiments/library/hamiltonian/heat_base.py Co-authored-by: Daniel J. Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit_experiments/library/hamiltonian/heat_base.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/qiskit_experiments/library/hamiltonian/heat_base.py b/qiskit_experiments/library/hamiltonian/heat_base.py index 4062322de8..28bdd368f7 100644 --- a/qiskit_experiments/library/hamiltonian/heat_base.py +++ b/qiskit_experiments/library/hamiltonian/heat_base.py @@ -51,9 +51,10 @@ class HeatElement(BaseExperiment): The ``echo`` and ``meas`` circuits depend on the axis of the error to amplify. Only the target qubit is measured following to the projection in the ``meas`` circuit. - The amplified response may consist of the contribution of from the local and - controlled rotation terms. Thus, usually multiple error amplification experiments - with different control qubit states are combined to distinguish the terms in the analysis. + The measured target-qubit population containing the amplified error typically has contributions + from both local (e.g. IZ) and controlled rotations (e.g. ZX). Thus, multiple error amplification + experiments with different control qubit states are usually combined to distinguish local from + controlled rotations. The ``heat`` gate is a special gate kind to represent the entangler pulse sequence of interest, thus one must provide the definition of it From e8a11f73a843e709c82bb87a588a445a842860e8 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 27 Jan 2022 15:43:26 +0900 Subject: [PATCH 12/46] Update qiskit_experiments/library/hamiltonian/heat_base.py Co-authored-by: Daniel J. Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit_experiments/library/hamiltonian/heat_base.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit_experiments/library/hamiltonian/heat_base.py b/qiskit_experiments/library/hamiltonian/heat_base.py index 28bdd368f7..9e3fa67156 100644 --- a/qiskit_experiments/library/hamiltonian/heat_base.py +++ b/qiskit_experiments/library/hamiltonian/heat_base.py @@ -56,10 +56,10 @@ class HeatElement(BaseExperiment): experiments with different control qubit states are usually combined to distinguish local from controlled rotations. - The ``heat`` gate is a special gate kind to represent - the entangler pulse sequence of interest, thus one must provide the definition of it - through the backend or custom transpiler configuration, i.e. instruction schedule map. - This gate name can be overridden via the experiment option of this experiment. + The ``heat`` gate is a custom gate representing the entangling pulse sequence. + One must thus provide its definition through the backend or a custom transpiler + configuration, i.e. with the instruction schedule map. This gate name can be overridden + via the experiment options. # section: note From 00a991a050aeff116620caa6807ab68dc99db47b Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 27 Jan 2022 15:44:34 +0900 Subject: [PATCH 13/46] Update qiskit_experiments/library/hamiltonian/heat_base.py Co-authored-by: Daniel J. Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit_experiments/library/hamiltonian/heat_base.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/qiskit_experiments/library/hamiltonian/heat_base.py b/qiskit_experiments/library/hamiltonian/heat_base.py index 9e3fa67156..6e269169ba 100644 --- a/qiskit_experiments/library/hamiltonian/heat_base.py +++ b/qiskit_experiments/library/hamiltonian/heat_base.py @@ -166,11 +166,11 @@ class BatchHeatHelper(BatchExperiment, ABC): # section: overview - This is a helper class for experiment developers of the HEAT experiment. - This class overrides :meth:`set_experiment_options` and :meth:`set_transpile_options` - methods of :class:`BatchExperiment` so that it can override options of - subsequence amplification experiments to run them on the same set up. - From end users, this experiment seems as if a single HEAT experiment. + This is a helper class for experiment developers of HEAT experiments. + This class overrides the :meth:`set_experiment_options` and :meth:`set_transpile_options` + methods of :class:`BatchExperiment` such that they set the options of the + individual amplification sub-experiments to the same values. Therefore, from + end user's perspective, this experiment behaves as if a single HEAT experiment. # section: analysis_ref :py:class:`HeatAnalysis` From 7c49da113d0c9e7c84cf4f1cb6ed73df75fbc3ac Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 27 Jan 2022 15:45:10 +0900 Subject: [PATCH 14/46] Update qiskit_experiments/library/hamiltonian/heat_base.py Co-authored-by: Daniel J. Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit_experiments/library/hamiltonian/heat_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_experiments/library/hamiltonian/heat_base.py b/qiskit_experiments/library/hamiltonian/heat_base.py index 6e269169ba..7156730586 100644 --- a/qiskit_experiments/library/hamiltonian/heat_base.py +++ b/qiskit_experiments/library/hamiltonian/heat_base.py @@ -202,7 +202,7 @@ def _default_experiment_options(cls) -> Options: Experiment Options: repetitions (Sequence[int]): A list of the number of echo repetitions. - cr_gate (Gate): A gate instance representing the entangler sequence. + heat_gate (Gate): A gate instance representing the entangling sequence. """ options = super()._default_experiment_options() options.repetitions = list(range(21)) From 4a3c6458eaddaace79d1cda9f81bead3b7990804 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 27 Jan 2022 15:45:40 +0900 Subject: [PATCH 15/46] Update qiskit_experiments/library/hamiltonian/heat_zx.py Co-authored-by: Daniel J. Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit_experiments/library/hamiltonian/heat_zx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_experiments/library/hamiltonian/heat_zx.py b/qiskit_experiments/library/hamiltonian/heat_zx.py index c5e8a792d9..cbea2c4063 100644 --- a/qiskit_experiments/library/hamiltonian/heat_zx.py +++ b/qiskit_experiments/library/hamiltonian/heat_zx.py @@ -29,7 +29,7 @@ class ZXHeat(BatchHeatHelper): # section: overview This experiment is designed to amplify the error contained in the ZX-type generator, a typical Hamiltonian implemented - by a cross resonance drive, which is a foundation of the CNOT gate. + by a cross-resonance drive, which is typically used to create a CNOT gate. The echo circuit refocuses ZX rotation to identity (II) then applies a pi-pulse along the interrogated error axis. X error and Y error are From ca871ac28426d152899f4387f528368d171b1f85 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 27 Jan 2022 15:46:11 +0900 Subject: [PATCH 16/46] Update qiskit_experiments/library/hamiltonian/heat_zx.py Co-authored-by: Daniel J. Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit_experiments/library/hamiltonian/heat_zx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit_experiments/library/hamiltonian/heat_zx.py b/qiskit_experiments/library/hamiltonian/heat_zx.py index cbea2c4063..581e89eab2 100644 --- a/qiskit_experiments/library/hamiltonian/heat_zx.py +++ b/qiskit_experiments/library/hamiltonian/heat_zx.py @@ -31,8 +31,8 @@ class ZXHeat(BatchHeatHelper): ZX-type generator, a typical Hamiltonian implemented by a cross-resonance drive, which is typically used to create a CNOT gate. - The echo circuit refocuses ZX rotation to identity (II) then applies - a pi-pulse along the interrogated error axis. X error and Y error are + The echo circuit refocuses the ZX rotation to the identity (II) and then applies + a pi-pulse along the interrogated error axis. X errors and Y errors are amplified outward the X-Y plane to draw a ping-pong pattern with state flip by the pi-pulse echo, while Z error is amplified outward the X-Z plane in the same manner. From 8ced3c2f8169661db407cb7b9c2bb534e78987d3 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 27 Jan 2022 15:47:02 +0900 Subject: [PATCH 17/46] Update qiskit_experiments/library/hamiltonian/heat_zx.py Co-authored-by: Daniel J. Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit_experiments/library/hamiltonian/heat_zx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_experiments/library/hamiltonian/heat_zx.py b/qiskit_experiments/library/hamiltonian/heat_zx.py index 581e89eab2..0d643aae3a 100644 --- a/qiskit_experiments/library/hamiltonian/heat_zx.py +++ b/qiskit_experiments/library/hamiltonian/heat_zx.py @@ -37,7 +37,7 @@ class ZXHeat(BatchHeatHelper): state flip by the pi-pulse echo, while Z error is amplified outward the X-Z plane in the same manner. Measurement is projected onto Y-axis in this setup. - Because the echoed axis are anti-commute with other Pauli terms, + Because the echoed axis anti-commute with other Pauli terms, errors in other axes are cancelled out to reduce rotation in the interrogated axis. This enables to selectively amplify the Hamiltonian dynamics in the specific axis. Note that we have always nonzero X rotation imparted by the significant ZX term, From 52a14394d3b4411c0bcbc3eed81bc1a1367a31ab Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 27 Jan 2022 15:47:43 +0900 Subject: [PATCH 18/46] Update qiskit_experiments/library/hamiltonian/heat_zx.py Co-authored-by: Daniel J. Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit_experiments/library/hamiltonian/heat_zx.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit_experiments/library/hamiltonian/heat_zx.py b/qiskit_experiments/library/hamiltonian/heat_zx.py index 0d643aae3a..09c0f5c346 100644 --- a/qiskit_experiments/library/hamiltonian/heat_zx.py +++ b/qiskit_experiments/library/hamiltonian/heat_zx.py @@ -74,9 +74,9 @@ class ZXHeat(BatchHeatHelper): The circuit in middle is repeated by ``N`` times for the error amplification. # section: note - The ``heat`` gate is a special gate to represent the entangler pulse sequence. - This gate is usually not provided by the backend, and thus user must provide - the pulse definition to run this experiment. + The ``heat`` gate represents the entangling pulse sequence. + This gate is usually not provided by the backend, and users must thus provide + the pulse schedule to run this experiment. This pulse sequence should be pre-calibrated to roughly implement the ZX(angle) evolution otherwise selective amplification doesn't work properly. From 42f07683bae5dba95ca96c36f4e671bf5d1ffdb9 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 27 Jan 2022 15:49:06 +0900 Subject: [PATCH 19/46] Update releasenotes/notes/add-heat-experiment-047a73818407e733.yaml Co-authored-by: Daniel J. Egger <38065505+eggerdj@users.noreply.github.com> --- releasenotes/notes/add-heat-experiment-047a73818407e733.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/notes/add-heat-experiment-047a73818407e733.yaml b/releasenotes/notes/add-heat-experiment-047a73818407e733.yaml index b85fc290b5..9b369bff3b 100644 --- a/releasenotes/notes/add-heat-experiment-047a73818407e733.yaml +++ b/releasenotes/notes/add-heat-experiment-047a73818407e733.yaml @@ -9,7 +9,7 @@ features: added. This experiment consists of the base class :class:`~qiskit_experiments.library.hamiltonian.BatchHeatHelper` and :class:`~qiskit_experiments.library.hamiltonian.HeatElement` - for the experiment developers to implement own HEAT experiment + class for experiment developers to implement their own HEAT experiment for arbitrary generator and for specific interrogated error axis. This can be realized by designing proper initialization circuit, echo sequence, and measurement axis against the interrogated error axis. From 16fb9eac9f3f9ddfb36cd08dd9eaf28f4e99c53c Mon Sep 17 00:00:00 2001 From: knzwnao Date: Thu, 27 Jan 2022 23:43:01 +0900 Subject: [PATCH 20/46] document overhaul for ZXHeat class --- .../library/hamiltonian/__init__.py | 20 ++-- .../library/hamiltonian/heat_analysis.py | 28 +++-- .../library/hamiltonian/heat_base.py | 7 +- .../library/hamiltonian/heat_zx.py | 107 +++++++++++++++--- 4 files changed, 121 insertions(+), 41 deletions(-) diff --git a/qiskit_experiments/library/hamiltonian/__init__.py b/qiskit_experiments/library/hamiltonian/__init__.py index d2b16c3485..5738ca0c2b 100644 --- a/qiskit_experiments/library/hamiltonian/__init__.py +++ b/qiskit_experiments/library/hamiltonian/__init__.py @@ -26,15 +26,6 @@ errors are typically amplified by repeating a sequence of gates which results in a ping-pong pattern when measuring the qubit population. -These are the base experiment classes for developer to write own experiments. - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/experiment.rst - - HeatElement - BatchHeatHelper - HEAT for ZX Hamiltonian ----------------------- @@ -57,6 +48,17 @@ HeatElementAnalysis HeatAnalysis +HEAT Base Classes +----------------- + +.. autosummary:: + :toctree: ../stubs/ + :template: autosummary/experiment.rst + + HeatElement + BatchHeatHelper + + """ from .heat_base import HeatElement, BatchHeatHelper diff --git a/qiskit_experiments/library/hamiltonian/heat_analysis.py b/qiskit_experiments/library/hamiltonian/heat_analysis.py index b59fa072d5..9310fa48bd 100644 --- a/qiskit_experiments/library/hamiltonian/heat_analysis.py +++ b/qiskit_experiments/library/hamiltonian/heat_analysis.py @@ -13,7 +13,7 @@ Analysis for HEAT experiments. """ -from typing import List +from typing import Tuple import numpy as np @@ -57,10 +57,11 @@ class HeatAnalysis(CompositeAnalysis): # section: fit_model + Heat experiment amplifies the dynamics of the entangling gate along the + experiment-specific error axis on the target qubit Bloch sphere. This analysis takes two error amplification experiment results performed with - different states of the control qubit to distinguish local rotations (such as IX) from - controlled rotations (such as ZX). These rotations are amplified along an - experiment-specific error axis. + different states of the control qubit to distinguish the contribution + of local term (such as IX) from non-local term (such as ZX). This analysis takes a set of `d_theta` parameters from child error amplification results which might be represented by a unique name in the child experiment data. @@ -78,10 +79,15 @@ class HeatAnalysis(CompositeAnalysis): # section: see_also qiskit_experiments.library.hamiltonian.HeatElementAnalysis + qiskit_experiments.curve_analysis.ErrorAmplificationAnalysis """ - def __init__(self, fit_params: List[str], out_params: List[str]): + def __init__( + self, + fit_params: Tuple[str, str], + out_params: Tuple[str, str], + ): """Create new HEAT analysis. Args: @@ -99,7 +105,7 @@ def __init__(self, fit_params: List[str], out_params: List[str]): "a set of experiments with different control qubit state input. " f"{len(fit_params)} input parameter names are specified." ) - self.fit_params = fit_params + self._fit_params = fit_params if len(out_params) != 2: raise AnalysisError( @@ -107,7 +113,7 @@ def __init__(self, fit_params: List[str], out_params: List[str]): "a set of experiment results with different control qubit state input. " f"{len(out_params)} output parameter names are specified." ) - self.out_params = out_params + self._out_params = out_params def _run_analysis(self, experiment_data: ExperimentData): @@ -116,7 +122,7 @@ def _run_analysis(self, experiment_data: ExperimentData): # extract d_theta parameters fit_results = [] - for i, pname in enumerate(self.fit_params): + for i, pname in enumerate(self._fit_params): fit_results.append(experiment_data.child_data(i).analysis_results(pname)) # Check data quality @@ -130,15 +136,15 @@ def _run_analysis(self, experiment_data: ExperimentData): sigma = np.sqrt(fit_results[0].value.stderr ** 2 + fit_results[1].value.stderr ** 2) estimate_ib = AnalysisResultData( - name=self.out_params[0], + name=self._out_params[0], value=FitVal(value=ib, stderr=sigma, unit="rad"), quality="good" if is_good_quality else "bad", ) estimate_zb = AnalysisResultData( - name=self.out_params[1], + name=self._out_params[1], value=FitVal(value=zb, stderr=sigma, unit="rad"), quality="good" if is_good_quality else "bad", ) - return [estimate_ib, estimate_zb], None + return [estimate_ib, estimate_zb], [] diff --git a/qiskit_experiments/library/hamiltonian/heat_base.py b/qiskit_experiments/library/hamiltonian/heat_base.py index 7156730586..7df2a8d8be 100644 --- a/qiskit_experiments/library/hamiltonian/heat_base.py +++ b/qiskit_experiments/library/hamiltonian/heat_base.py @@ -18,7 +18,6 @@ from qiskit import circuit, QuantumCircuit from qiskit.providers import Backend - from qiskit_experiments.framework import BaseExperiment, BatchExperiment, Options from qiskit_experiments.curve_analysis import ParameterRepr from .heat_analysis import HeatElementAnalysis, HeatAnalysis @@ -95,7 +94,9 @@ def __init__( echo_circ: A circuit to selectively amplify the specific error term. meas_circ: A circuit to project target qubit onto the basis of interest. backend: Optional, the backend to run the experiment on. - parameter_name: A name that represents angle from fitting. + parameter_name: A name of :math:`d\\theta` parameter from the + amplification fit. The fit parameter is represented by this name + in the analysis result. Keyword Args: See :meth:`experiment_options` for details. @@ -117,7 +118,7 @@ def _default_experiment_options(cls) -> Options: Experiment Options: repetitions (Sequence[int]): A list of the number of echo repetitions. - cr_gate (Gate): A gate instance representing the entangler sequence. + heat_gate (Gate): A gate instance representing the entangler sequence. """ options = super()._default_experiment_options() options.repetitions = list(range(21)) diff --git a/qiskit_experiments/library/hamiltonian/heat_zx.py b/qiskit_experiments/library/hamiltonian/heat_zx.py index 09c0f5c346..05f98a9a2d 100644 --- a/qiskit_experiments/library/hamiltonian/heat_zx.py +++ b/qiskit_experiments/library/hamiltonian/heat_zx.py @@ -24,30 +24,20 @@ class ZXHeat(BatchHeatHelper): - """HEAT experiment for the ZX-type entangler. + r"""HEAT experiment for the ZX-type entangler. # section: overview This experiment is designed to amplify the error contained in the ZX-type generator, a typical Hamiltonian implemented by a cross-resonance drive, which is typically used to create a CNOT gate. - The echo circuit refocuses the ZX rotation to the identity (II) and then applies - a pi-pulse along the interrogated error axis. X errors and Y errors are - amplified outward the X-Y plane to draw a ping-pong pattern with - state flip by the pi-pulse echo, while Z error is amplified outward - the X-Z plane in the same manner. - Measurement is projected onto Y-axis in this setup. - Because the echoed axis anti-commute with other Pauli terms, - errors in other axes are cancelled out to reduce rotation in the interrogated axis. - This enables to selectively amplify the Hamiltonian dynamics in the specific axis. - Note that we have always nonzero X rotation imparted by the significant ZX term, - the error along Y and Z axis are skewed by the nonzero commutator term. - This yields slight mismatch in the estimated coefficients with the generator Hamiltonian, - however this matters less when the expected magnitude of the error is small. - On the other hand, the error in the X axis is straightforward - because this is commute with the ZX term of the generator. + The experimental circuits are prepared as follows for different + interrogated error axis specified by the experiment parameter ``error_axis``. .. parsed-literal:: + +       prep heat    echo   meas + (xN) ░ ┌───────┐ ░ q_0: ────────────░─┤0 ├────────────────────────────░───────── @@ -67,11 +57,92 @@ class ZXHeat(BatchHeatHelper): 0 ZX-HEAT experiments are performed with combination of two - error amplification experiments shown above, where :math:`\\alpha, \\beta, \\gamma` + error amplification experiments shown above, where :math:`\alpha, \beta, \gamma` depend on the interrogated error axis, namely, (``X``, ``X``, ``I``), (``Y``, ``Y``, ``I``), (``Y``, ``Z``, ``Rx(-pi/2)``) for amplifying X, Y, Z axis, respectively. - The circuit in middle is repeated by ``N`` times for the error amplification. + The circuit in the middle is repeated by N times for the error amplification. + + For example, we amplify the X error in the simplified ``heat`` gate Hamiltonian + + .. math:: + + Ht = \frac{\Omega_{ZX}(t) ZX + \Delta_{IX}(t) IX}{2}. + + From the BCH formula we can derive a unitary evolution of the Hamiltonian + + .. math:: + + U = A_{II} II + A_{IX} IX + A_{ZX} ZX + A_{ZI} ZI. + + Since we have known control qubit state throughout the echo sequence, + we can compute partial unitary on the target qubit, namely, + :math:`U_{j} = A_{Ij} I + A_{Xj} X` for the control qubit state :math:`|j\rangle`. + Here :math:`A_{Ij} =\cos \theta_j` and :math:`A_{Xj} =-i \sin \theta_j`. + This form is exactly identical to the unitary of :math:`R_X(\theta_j)` gate, + with :math:`\theta_0 =\Delta_{IX} + \Omega_{ZX}` and + :math:`\theta_1 =\Delta_{IX} - \Omega_{ZX}`. + Given we calibrated the gate to have :math:`\Omega_{ZX} = \phi + \Delta_{ZX}` + so that :math:`\phi` corresponds to the experiment parameter ``angle``, + or the angle of the controlled rotation we want, + e.g. :math:`\phi = \pi/2` for the CNOT gate. + The total evolution during the echo sequence will be expressed by + :math:`R_X(\pi + \Delta_{ZX} \pm \Delta_{IX})` for the control qubit state + 0 and 1, respectively. + + In the echo circuit, the non-local ZX rotation by :math:`\phi` is undone by + applying :math:`R_X(\mp \phi)` with sign depending on the control qubit state, + thus only rotation error :math:`\Delta_{ZX}` from the target + angle :math:`\phi` is selectively amplified. + Repeating this sequence N times forms a typical ping-pong oscillation pattern + in the measured target qubit population, + which may be fit by :math:`P(N) = \cos(N (d\theta_j + \pi) + \phi_{\rm offset})`, + where :math:`d\theta_j = \Delta_{ZX}\pm \Delta_{IX}`. + By combining error amplification fit parameter :math:`d\theta_j` for + different control qubit states, we can resolve local (IX) and non-local (ZX) + dynamics of the Hamiltonian of interest. + + In this pulse sequence, the pi-pulse echo is applied to the target qubit + in the same axis with the interrogated error axis. + This cancels out the errors in other axes since the errors anti-commute with the echo, + e.g. :math:`XYX = -Y`, while the error in the interrogated axis are accumulated. + This is the trick how the sequence selectively amplifies the error axis. + + However, strictly speaking, non-X error terms :math:`{\cal P}` also anti-commute + with the primary :math:`ZX` term of the Hamiltonian, and + they are skewed by the significant nonzero commutator :math:`[ZX, {\cal P}]`. + Thus this sequence pattern might underestimate the coefficients in non-X axes. + Usually this is less impactful if the errors of interest are sufficiently small, + but you should keep this in mind. + + # section: example + This experiment requires you to provide the pulse definition of the ``heat`` gate. + This gate should implement the ZX Hamiltonian with rotation angle :math:`\phi`. + This might be done in the following workflow. + + .. code-block:: python + + from qiskit import pulse + from qiskit.test.mock import FakeJakarta + from qiskit_experiments.library import ZXHeat + + backend = FakeJakarta() + qubits = 0, 1 + + # Write pulse schedule implementing ZX Hamiltonian + heat_pulse = pulse.GaussianSquare(100, 1, 10, 5) + + with pulse.build(backend) as heat_sched: + pulse.play(heat_pulse, pulse.control_channels(*qubits)[0]) + + # Map schedule to the gate + my_inst_map = backend.defaults().instruction_schedule_map + my_inst_map.add("heat", qubits, heat_sched) + + # Set up experiment + heat_exp = ZXHeat(qubits, error_axis="x", backend=backend) + heat_exp.set_transpile_options(inst_map=my_inst_map) + heat_exp.run() # section: note The ``heat`` gate represents the entangling pulse sequence. From 617503659701cbb326199adb4ca455f95a911473 Mon Sep 17 00:00:00 2001 From: knzwnao Date: Fri, 28 Jan 2022 00:00:21 +0900 Subject: [PATCH 21/46] document overhaul for HeatElement class --- .../library/hamiltonian/heat_base.py | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/qiskit_experiments/library/hamiltonian/heat_base.py b/qiskit_experiments/library/hamiltonian/heat_base.py index 7df2a8d8be..e430c948fa 100644 --- a/qiskit_experiments/library/hamiltonian/heat_base.py +++ b/qiskit_experiments/library/hamiltonian/heat_base.py @@ -29,7 +29,7 @@ class HeatElement(BaseExperiment): # section: overview Hamiltonian error amplifying tomography (HEAT) is designed to amplify - the dynamics of entangler circuit on the target qubit along a specific axis. + the dynamics of entangler circuit on a qubit along a specific axis. The basic form of HEAT circuit is represented as follows. @@ -45,15 +45,20 @@ class HeatElement(BaseExperiment): 0 The circuit in the middle is repeated ``N`` times to amplify the Hamiltonian - coefficients along a specific axis of the target qubit. The ``prep`` circuit is + coefficients along a specific axis of the second qubit. The ``prep`` circuit is carefully chosen based on the generator of the ``heat`` gate under consideration. The ``echo`` and ``meas`` circuits depend on the axis of the error to amplify. - Only the target qubit is measured following to the projection in the ``meas`` circuit. - - The measured target-qubit population containing the amplified error typically has contributions - from both local (e.g. IZ) and controlled rotations (e.g. ZX). Thus, multiple error amplification - experiments with different control qubit states are usually combined to distinguish local from - controlled rotations. + Only the second qubit is measured following to the projection in the ``meas`` circuit. + + The measured qubit population containing the amplified error typically has contributions + from both local (e.g. IZ) and non-local rotations (e.g. ZX). + Thus, multiple error amplification experiments with different control qubit states + are usually combined to resolve these rotation terms. + This experiment just provides a single error amplification sequence, and therefore + you must combine multiple instances instantiated with different ``prep``, ``echo``, + and ``meas`` circuits designed to resolve error terms. + This class can be wrapped with hard-coded circuits to define new experiment class + to provide HEAT experiment with respect to the error axis and Hamiltonian of interest. The ``heat`` gate is a custom gate representing the entangling pulse sequence. One must thus provide its definition through the backend or a custom transpiler From 532f855547868f7b1e71a1490eab2a5c7132e76a Mon Sep 17 00:00:00 2001 From: knzwnao Date: Fri, 28 Jan 2022 00:15:59 +0900 Subject: [PATCH 22/46] black --- qiskit_experiments/library/hamiltonian/heat_base.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit_experiments/library/hamiltonian/heat_base.py b/qiskit_experiments/library/hamiltonian/heat_base.py index e430c948fa..a723c54d97 100644 --- a/qiskit_experiments/library/hamiltonian/heat_base.py +++ b/qiskit_experiments/library/hamiltonian/heat_base.py @@ -60,9 +60,9 @@ class HeatElement(BaseExperiment): This class can be wrapped with hard-coded circuits to define new experiment class to provide HEAT experiment with respect to the error axis and Hamiltonian of interest. - The ``heat`` gate is a custom gate representing the entangling pulse sequence. - One must thus provide its definition through the backend or a custom transpiler - configuration, i.e. with the instruction schedule map. This gate name can be overridden + The ``heat`` gate is a custom gate representing the entangling pulse sequence. + One must thus provide its definition through the backend or a custom transpiler + configuration, i.e. with the instruction schedule map. This gate name can be overridden via the experiment options. # section: note From e379b49062b923f159e197b5715aacf8494956d3 Mon Sep 17 00:00:00 2001 From: knzwnao Date: Fri, 28 Jan 2022 01:53:56 +0900 Subject: [PATCH 23/46] update test tolerance --- test/hamiltonian/test_heat.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/test/hamiltonian/test_heat.py b/test/hamiltonian/test_heat.py index e64bcbe78d..c204b7b3ed 100644 --- a/test/hamiltonian/test_heat.py +++ b/test/hamiltonian/test_heat.py @@ -225,8 +225,16 @@ def test_y_error_amplification(self, e_zy, e_iy): exp_data = exp.run().block_for_results() # larger error torelance to allow commutator term - self.assertAlmostEqual(exp_data.analysis_results("A_IY").value.value, e_iy, delta=0.05) - self.assertAlmostEqual(exp_data.analysis_results("A_ZY").value.value, e_zy, delta=0.05) + self.assertAlmostEqual( + exp_data.analysis_results("A_IY").value.value, + e_iy, + delta=max(0.8 * abs(e_iy), 0.01), + ) + self.assertAlmostEqual( + exp_data.analysis_results("A_ZY").value.value, + e_zy, + delta=max(0.8 * abs(e_zy), 0.01), + ) @data( [0.02, -0.01], @@ -248,8 +256,16 @@ def test_z_error_amplification(self, e_zz, e_iz): exp_data = exp.run().block_for_results() # larger error torelance to allow commutator term - self.assertAlmostEqual(exp_data.analysis_results("A_IZ").value.value, e_iz, delta=0.05) - self.assertAlmostEqual(exp_data.analysis_results("A_ZZ").value.value, e_zz, delta=0.05) + self.assertAlmostEqual( + exp_data.analysis_results("A_IZ").value.value, + e_iz, + delta=max(0.8 * abs(e_iz), 0.01), + ) + self.assertAlmostEqual( + exp_data.analysis_results("A_ZZ").value.value, + e_zz, + delta=max(0.8 * abs(e_zz), 0.01), + ) @data(123, 456) def test_pseudo_calibration(self, seed): @@ -301,4 +317,4 @@ def test_pseudo_calibration(self, seed): errors_dict[f"e_i{tp}"] -= a_ip.value.value for v in errors_dict.values(): - self.assertAlmostEqual(v, 0.0, delta=0.003) + self.assertAlmostEqual(v, 0.0, delta=0.005) From c7318b821aa89da6245e95aa561b3bf2f4803f0e Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Mon, 31 Jan 2022 13:49:28 +0900 Subject: [PATCH 24/46] Update qiskit_experiments/library/hamiltonian/heat_zx.py Co-authored-by: Will Shanks --- qiskit_experiments/library/hamiltonian/heat_zx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_experiments/library/hamiltonian/heat_zx.py b/qiskit_experiments/library/hamiltonian/heat_zx.py index 05f98a9a2d..c6d70b7ba1 100644 --- a/qiskit_experiments/library/hamiltonian/heat_zx.py +++ b/qiskit_experiments/library/hamiltonian/heat_zx.py @@ -106,7 +106,7 @@ class ZXHeat(BatchHeatHelper): in the same axis with the interrogated error axis. This cancels out the errors in other axes since the errors anti-commute with the echo, e.g. :math:`XYX = -Y`, while the error in the interrogated axis are accumulated. - This is the trick how the sequence selectively amplifies the error axis. + This is the trick of how the sequence selectively amplifies the error axis. However, strictly speaking, non-X error terms :math:`{\cal P}` also anti-commute with the primary :math:`ZX` term of the Hamiltonian, and From 9a59715e5ca83bde43d0b23bcb58ed7b415cc871 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Mon, 31 Jan 2022 13:49:36 +0900 Subject: [PATCH 25/46] Update qiskit_experiments/library/hamiltonian/heat_zx.py Co-authored-by: Will Shanks --- qiskit_experiments/library/hamiltonian/heat_zx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_experiments/library/hamiltonian/heat_zx.py b/qiskit_experiments/library/hamiltonian/heat_zx.py index c6d70b7ba1..1de851053e 100644 --- a/qiskit_experiments/library/hamiltonian/heat_zx.py +++ b/qiskit_experiments/library/hamiltonian/heat_zx.py @@ -105,7 +105,7 @@ class ZXHeat(BatchHeatHelper): In this pulse sequence, the pi-pulse echo is applied to the target qubit in the same axis with the interrogated error axis. This cancels out the errors in other axes since the errors anti-commute with the echo, - e.g. :math:`XYX = -Y`, while the error in the interrogated axis are accumulated. + e.g. :math:`XYX = -Y`, while the error in the interrogated axis is accumulated. This is the trick of how the sequence selectively amplifies the error axis. However, strictly speaking, non-X error terms :math:`{\cal P}` also anti-commute From ef8c56cd73adcca739e8d4586d48e3b9b2c7de18 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Mon, 31 Jan 2022 13:49:43 +0900 Subject: [PATCH 26/46] Update qiskit_experiments/library/hamiltonian/heat_zx.py Co-authored-by: Will Shanks --- qiskit_experiments/library/hamiltonian/heat_zx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_experiments/library/hamiltonian/heat_zx.py b/qiskit_experiments/library/hamiltonian/heat_zx.py index 1de851053e..fc810f7b3b 100644 --- a/qiskit_experiments/library/hamiltonian/heat_zx.py +++ b/qiskit_experiments/library/hamiltonian/heat_zx.py @@ -103,7 +103,7 @@ class ZXHeat(BatchHeatHelper): dynamics of the Hamiltonian of interest. In this pulse sequence, the pi-pulse echo is applied to the target qubit - in the same axis with the interrogated error axis. + around the same axis as the interrogated error. This cancels out the errors in other axes since the errors anti-commute with the echo, e.g. :math:`XYX = -Y`, while the error in the interrogated axis is accumulated. This is the trick of how the sequence selectively amplifies the error axis. From 30f9200d784dbc39af254a984446fa0bc8024d33 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Mon, 31 Jan 2022 13:49:51 +0900 Subject: [PATCH 27/46] Update qiskit_experiments/library/hamiltonian/heat_zx.py Co-authored-by: Will Shanks --- qiskit_experiments/library/hamiltonian/heat_zx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit_experiments/library/hamiltonian/heat_zx.py b/qiskit_experiments/library/hamiltonian/heat_zx.py index fc810f7b3b..35d954e11b 100644 --- a/qiskit_experiments/library/hamiltonian/heat_zx.py +++ b/qiskit_experiments/library/hamiltonian/heat_zx.py @@ -98,8 +98,8 @@ class ZXHeat(BatchHeatHelper): in the measured target qubit population, which may be fit by :math:`P(N) = \cos(N (d\theta_j + \pi) + \phi_{\rm offset})`, where :math:`d\theta_j = \Delta_{ZX}\pm \Delta_{IX}`. - By combining error amplification fit parameter :math:`d\theta_j` for - different control qubit states, we can resolve local (IX) and non-local (ZX) + By combining error amplification fit parameters :math:`d\theta_j` for + different control qubit states :math:`j`, we can resolve the local (IX) and non-local (ZX) dynamics of the Hamiltonian of interest. In this pulse sequence, the pi-pulse echo is applied to the target qubit From c4333fbf8d01a4506264fff7084aa9f59c6c39de Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Mon, 31 Jan 2022 13:49:57 +0900 Subject: [PATCH 28/46] Update qiskit_experiments/library/hamiltonian/heat_zx.py Co-authored-by: Will Shanks --- qiskit_experiments/library/hamiltonian/heat_zx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit_experiments/library/hamiltonian/heat_zx.py b/qiskit_experiments/library/hamiltonian/heat_zx.py index 35d954e11b..0057b9c3c2 100644 --- a/qiskit_experiments/library/hamiltonian/heat_zx.py +++ b/qiskit_experiments/library/hamiltonian/heat_zx.py @@ -81,8 +81,8 @@ class ZXHeat(BatchHeatHelper): Here :math:`A_{Ij} =\cos \theta_j` and :math:`A_{Xj} =-i \sin \theta_j`. This form is exactly identical to the unitary of :math:`R_X(\theta_j)` gate, with :math:`\theta_0 =\Delta_{IX} + \Omega_{ZX}` and - :math:`\theta_1 =\Delta_{IX} - \Omega_{ZX}`. - Given we calibrated the gate to have :math:`\Omega_{ZX} = \phi + \Delta_{ZX}` + :math:`\theta_1 =\Delta_{IX} - \Omega_{ZX}`, + given we calibrated the gate to have :math:`\Omega_{ZX} = \phi + \Delta_{ZX}` so that :math:`\phi` corresponds to the experiment parameter ``angle``, or the angle of the controlled rotation we want, e.g. :math:`\phi = \pi/2` for the CNOT gate. From 65248582ed13fd07dbd58a20b7cb7a90a3f9c848 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Mon, 31 Jan 2022 13:50:04 +0900 Subject: [PATCH 29/46] Update qiskit_experiments/library/hamiltonian/heat_zx.py Co-authored-by: Will Shanks --- qiskit_experiments/library/hamiltonian/heat_zx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_experiments/library/hamiltonian/heat_zx.py b/qiskit_experiments/library/hamiltonian/heat_zx.py index 0057b9c3c2..9a19e8a6ae 100644 --- a/qiskit_experiments/library/hamiltonian/heat_zx.py +++ b/qiskit_experiments/library/hamiltonian/heat_zx.py @@ -76,7 +76,7 @@ class ZXHeat(BatchHeatHelper): U = A_{II} II + A_{IX} IX + A_{ZX} ZX + A_{ZI} ZI. Since we have known control qubit state throughout the echo sequence, - we can compute partial unitary on the target qubit, namely, + we can compute a partial unitary on the target qubit, namely, :math:`U_{j} = A_{Ij} I + A_{Xj} X` for the control qubit state :math:`|j\rangle`. Here :math:`A_{Ij} =\cos \theta_j` and :math:`A_{Xj} =-i \sin \theta_j`. This form is exactly identical to the unitary of :math:`R_X(\theta_j)` gate, From cf474458146c7832b725cb36be5aedb893dabaf8 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Mon, 31 Jan 2022 13:50:10 +0900 Subject: [PATCH 30/46] Update qiskit_experiments/library/hamiltonian/heat_zx.py Co-authored-by: Will Shanks --- qiskit_experiments/library/hamiltonian/heat_zx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_experiments/library/hamiltonian/heat_zx.py b/qiskit_experiments/library/hamiltonian/heat_zx.py index 9a19e8a6ae..f74c32fdcb 100644 --- a/qiskit_experiments/library/hamiltonian/heat_zx.py +++ b/qiskit_experiments/library/hamiltonian/heat_zx.py @@ -75,7 +75,7 @@ class ZXHeat(BatchHeatHelper): U = A_{II} II + A_{IX} IX + A_{ZX} ZX + A_{ZI} ZI. - Since we have known control qubit state throughout the echo sequence, + Since we have a known control qubit state throughout the echo sequence, we can compute a partial unitary on the target qubit, namely, :math:`U_{j} = A_{Ij} I + A_{Xj} X` for the control qubit state :math:`|j\rangle`. Here :math:`A_{Ij} =\cos \theta_j` and :math:`A_{Xj} =-i \sin \theta_j`. From 077d217ff716a47345dcc887d62853b2acb971a9 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Mon, 31 Jan 2022 13:50:18 +0900 Subject: [PATCH 31/46] Update qiskit_experiments/library/hamiltonian/heat_zx.py Co-authored-by: Will Shanks --- qiskit_experiments/library/hamiltonian/heat_zx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_experiments/library/hamiltonian/heat_zx.py b/qiskit_experiments/library/hamiltonian/heat_zx.py index f74c32fdcb..226ee22d8e 100644 --- a/qiskit_experiments/library/hamiltonian/heat_zx.py +++ b/qiskit_experiments/library/hamiltonian/heat_zx.py @@ -32,7 +32,7 @@ class ZXHeat(BatchHeatHelper): by a cross-resonance drive, which is typically used to create a CNOT gate. The experimental circuits are prepared as follows for different - interrogated error axis specified by the experiment parameter ``error_axis``. + interrogated error axes specified by the experiment parameter ``error_axis``. .. parsed-literal:: From a773faae35b83abae6290f52f0f4ed2bef6b48a6 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Mon, 31 Jan 2022 13:50:29 +0900 Subject: [PATCH 32/46] Update qiskit_experiments/library/hamiltonian/heat_base.py Co-authored-by: Will Shanks --- qiskit_experiments/library/hamiltonian/heat_base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit_experiments/library/hamiltonian/heat_base.py b/qiskit_experiments/library/hamiltonian/heat_base.py index a723c54d97..08c72abd0d 100644 --- a/qiskit_experiments/library/hamiltonian/heat_base.py +++ b/qiskit_experiments/library/hamiltonian/heat_base.py @@ -68,8 +68,8 @@ class HeatElement(BaseExperiment): # section: note This class is usually not exposed to end users. - Developer of new HEAT experiment must design amplification sequence and - instantiate the class implicitly in the batch experiment. + The developer of a new HEAT experiment must design the amplification sequences and + create instances of this class implicitly in the batch experiment. The :class:`BatchHeatHelper` provides a convenient wrapper class of the :class:`qiskit_experiments.framework.BatchExperiment` for implementing a typical HEAT experiment. From 9819c16a15e22bd49f1365ae3a663cc9a38d1bef Mon Sep 17 00:00:00 2001 From: knzwnao Date: Mon, 31 Jan 2022 14:05:32 +0900 Subject: [PATCH 33/46] update test tolerance to be more precise --- test/hamiltonian/test_heat.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/test/hamiltonian/test_heat.py b/test/hamiltonian/test_heat.py index c204b7b3ed..bc53135768 100644 --- a/test/hamiltonian/test_heat.py +++ b/test/hamiltonian/test_heat.py @@ -224,16 +224,13 @@ def test_y_error_amplification(self, e_zy, e_iy): exp_data = exp.run().block_for_results() - # larger error torelance to allow commutator term + # The factor 0.7 is estimated from numerical analysis, which comes from ZX commutator term. + # Note that this number may depend on magnitude of coefficients. self.assertAlmostEqual( - exp_data.analysis_results("A_IY").value.value, - e_iy, - delta=max(0.8 * abs(e_iy), 0.01), + exp_data.analysis_results("A_IY").value.value, 0.7 * e_iy, delta=0.01 ) self.assertAlmostEqual( - exp_data.analysis_results("A_ZY").value.value, - e_zy, - delta=max(0.8 * abs(e_zy), 0.01), + exp_data.analysis_results("A_ZY").value.value, 0.7 * e_zy, delta=0.01 ) @data( @@ -255,16 +252,13 @@ def test_z_error_amplification(self, e_zz, e_iz): exp_data = exp.run().block_for_results() - # larger error torelance to allow commutator term + # The factor 0.7 is estimated from numerical analysis, which comes from ZX commutator term. + # Note that this number may depend on magnitude of coefficients. self.assertAlmostEqual( - exp_data.analysis_results("A_IZ").value.value, - e_iz, - delta=max(0.8 * abs(e_iz), 0.01), + exp_data.analysis_results("A_IZ").value.value, 0.7 * e_iz, delta=0.01 ) self.assertAlmostEqual( - exp_data.analysis_results("A_ZZ").value.value, - e_zz, - delta=max(0.8 * abs(e_zz), 0.01), + exp_data.analysis_results("A_ZZ").value.value, 0.7 * e_zz, delta=0.01 ) @data(123, 456) From 1ceff093754486e5d72b45a55f05e259fee27c4b Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Mon, 31 Jan 2022 14:24:18 +0900 Subject: [PATCH 34/46] Update heat_zx.py --- qiskit_experiments/library/hamiltonian/heat_zx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_experiments/library/hamiltonian/heat_zx.py b/qiskit_experiments/library/hamiltonian/heat_zx.py index 226ee22d8e..aa79b2cfba 100644 --- a/qiskit_experiments/library/hamiltonian/heat_zx.py +++ b/qiskit_experiments/library/hamiltonian/heat_zx.py @@ -108,7 +108,7 @@ class ZXHeat(BatchHeatHelper): e.g. :math:`XYX = -Y`, while the error in the interrogated axis is accumulated. This is the trick of how the sequence selectively amplifies the error axis. - However, strictly speaking, non-X error terms :math:`{\cal P}` also anti-commute + However, strictly speaking, non-X error terms :math:`{\cal P}` do not commute with the primary :math:`ZX` term of the Hamiltonian, and they are skewed by the significant nonzero commutator :math:`[ZX, {\cal P}]`. Thus this sequence pattern might underestimate the coefficients in non-X axes. From e1036408eb45e07cd7e7aecab5ab6226c015041c Mon Sep 17 00:00:00 2001 From: knzwnao Date: Wed, 2 Feb 2022 22:15:19 +0900 Subject: [PATCH 35/46] udpate analysis constructor and remove BatchHeatHelper This commit updates the logic of HeatAnalysis constructor according to #633 (now we can instantiate composite analysis with component analyses). Also BatchHeatHelper class is removed and replaced with decorators. --- .../framework/composite/__init__.py | 2 + .../composite/composite_experiment.py | 69 +++++++++++- .../library/hamiltonian/__init__.py | 3 +- .../library/hamiltonian/heat_analysis.py | 15 ++- .../library/hamiltonian/heat_base.py | 102 +----------------- .../library/hamiltonian/heat_zx.py | 49 ++++++--- test/hamiltonian/test_heat.py | 65 +++++------ 7 files changed, 158 insertions(+), 147 deletions(-) diff --git a/qiskit_experiments/framework/composite/__init__.py b/qiskit_experiments/framework/composite/__init__.py index d308f3f38c..fcffda439e 100644 --- a/qiskit_experiments/framework/composite/__init__.py +++ b/qiskit_experiments/framework/composite/__init__.py @@ -18,3 +18,5 @@ # Composite experiment classes from .parallel_experiment import ParallelExperiment from .batch_experiment import BatchExperiment + +from .composite_experiment import sync_experiment_options, sync_transpile_options diff --git a/qiskit_experiments/framework/composite/composite_experiment.py b/qiskit_experiments/framework/composite/composite_experiment.py index 955ef9bedd..a8f38eb4f2 100644 --- a/qiskit_experiments/framework/composite/composite_experiment.py +++ b/qiskit_experiments/framework/composite/composite_experiment.py @@ -13,8 +13,9 @@ Composite Experiment abstract base class. """ -from typing import List, Sequence, Optional, Union +from typing import List, Sequence, Optional, Union, Type, Callable from abc import abstractmethod +import functools import warnings from qiskit.providers.backend import Backend from qiskit_experiments.framework import BaseExperiment, ExperimentData @@ -131,3 +132,69 @@ def _postprocess_transpiled_circuits(self, circuits, **run_options): for expr in self._experiments: if not isinstance(expr, CompositeExperiment): expr._postprocess_transpiled_circuits(circuits, **run_options) + + +def sync_transpile_options( + composite_cls: Type[CompositeExperiment], +) -> Type[CompositeExperiment]: + """A class decorator that overrides the transpile option setter method. + + This method overrides the behavior of :meth:`set_transpile_options` method. + The option values set to the composite instance + will be propagated through all component experiments. + + Args: + composite_cls: CompositeExperiment subclass to decorate. + + Returns: + Composite experiment that implements option synchronization. + """ + if not issubclass(composite_cls, CompositeExperiment): + raise TypeError("Class is not composite experiment. Cannot override method.") + + options_setter = getattr(composite_cls, "set_transpile_options") + + @functools.wraps(options_setter) + def sync_opts(instance, **fields): + options_setter(instance, **fields) + # set the same options to component experiments + for comp in instance.component_experiment(): + comp.set_transpile_options(**fields) + + # override set method + setattr(composite_cls, "set_transpile_options", sync_opts) + + return composite_cls + + +def sync_experiment_options( + composite_cls: Type[CompositeExperiment], +) -> Type[CompositeExperiment]: + """A class decorator that overrides the experiment option setter method. + + This method overrides the behavior of :meth:`set_experiment_options` method. + The option values set to the composite instance + will be propagated through all component experiments. + + Args: + composite_cls: CompositeExperiment subclass to decorate. + + Returns: + Composite experiment that implements option synchronization. + """ + if not issubclass(composite_cls, CompositeExperiment): + raise TypeError("Class is not composite experiment. Cannot override method.") + + options_setter = getattr(composite_cls, "set_experiment_options") + + @functools.wraps(options_setter) + def sync_opts(instance, **fields): + options_setter(instance, **fields) + # set the same options to component experiments + for comp in instance.component_experiment(): + comp.set_experiment_options(**fields) + + # override set method + setattr(composite_cls, "set_experiment_options", sync_opts) + + return composite_cls diff --git a/qiskit_experiments/library/hamiltonian/__init__.py b/qiskit_experiments/library/hamiltonian/__init__.py index 5738ca0c2b..caf110a25e 100644 --- a/qiskit_experiments/library/hamiltonian/__init__.py +++ b/qiskit_experiments/library/hamiltonian/__init__.py @@ -56,11 +56,10 @@ :template: autosummary/experiment.rst HeatElement - BatchHeatHelper """ -from .heat_base import HeatElement, BatchHeatHelper +from .heat_base import HeatElement from .heat_zx import ZXHeat, ZX90HeatXError, ZX90HeatYError, ZX90HeatZError from .heat_analysis import HeatElementAnalysis, HeatAnalysis diff --git a/qiskit_experiments/library/hamiltonian/heat_analysis.py b/qiskit_experiments/library/hamiltonian/heat_analysis.py index 9310fa48bd..3869ad5a0e 100644 --- a/qiskit_experiments/library/hamiltonian/heat_analysis.py +++ b/qiskit_experiments/library/hamiltonian/heat_analysis.py @@ -17,7 +17,7 @@ import numpy as np -from qiskit_experiments.curve_analysis import ErrorAmplificationAnalysis +from qiskit_experiments.curve_analysis import ErrorAmplificationAnalysis, ParameterRepr from qiskit_experiments.exceptions import AnalysisError from qiskit_experiments.framework import ( CompositeAnalysis, @@ -97,15 +97,12 @@ def __init__( Raises: AnalysisError: When size of ``fit_params`` or ``out_params`` are not 2. """ - super().__init__() - if len(fit_params) != 2: raise AnalysisError( f"{self.__class__.__name__} assumes two fit parameters extracted from " "a set of experiments with different control qubit state input. " f"{len(fit_params)} input parameter names are specified." ) - self._fit_params = fit_params if len(out_params) != 2: raise AnalysisError( @@ -113,6 +110,16 @@ def __init__( "a set of experiment results with different control qubit state input. " f"{len(out_params)} output parameter names are specified." ) + + analyses = [] + for fit_parm in fit_params: + sub_analysis = HeatElementAnalysis() + sub_analysis.set_options(result_parameters=[ParameterRepr("d_theta", fit_parm, "rad")]) + analyses.append(sub_analysis) + + super().__init__(analyses=analyses) + + self._fit_params = fit_params self._out_params = out_params def _run_analysis(self, experiment_data: ExperimentData): diff --git a/qiskit_experiments/library/hamiltonian/heat_base.py b/qiskit_experiments/library/hamiltonian/heat_base.py index 08c72abd0d..dd3b468c95 100644 --- a/qiskit_experiments/library/hamiltonian/heat_base.py +++ b/qiskit_experiments/library/hamiltonian/heat_base.py @@ -13,14 +13,13 @@ Base Class for general Hamiltonian Error Amplifying Tomography experiments. """ -from abc import ABC from typing import List, Tuple, Optional from qiskit import circuit, QuantumCircuit from qiskit.providers import Backend -from qiskit_experiments.framework import BaseExperiment, BatchExperiment, Options -from qiskit_experiments.curve_analysis import ParameterRepr -from .heat_analysis import HeatElementAnalysis, HeatAnalysis + +from qiskit_experiments.framework import BaseExperiment, Options +from .heat_analysis import HeatElementAnalysis class HeatElement(BaseExperiment): @@ -70,9 +69,6 @@ class HeatElement(BaseExperiment): This class is usually not exposed to end users. The developer of a new HEAT experiment must design the amplification sequences and create instances of this class implicitly in the batch experiment. - The :class:`BatchHeatHelper` provides a convenient wrapper class of - the :class:`qiskit_experiments.framework.BatchExperiment` for implementing a - typical HEAT experiment. # section: analysis_ref :py:class:`HeatElementAnalysis` @@ -88,7 +84,6 @@ def __init__( echo_circ: QuantumCircuit, meas_circ: QuantumCircuit, backend: Optional[Backend] = None, - parameter_name: Optional[str] = "d_theta", **kwargs, ): """Create new HEAT sub experiment. @@ -99,17 +94,11 @@ def __init__( echo_circ: A circuit to selectively amplify the specific error term. meas_circ: A circuit to project target qubit onto the basis of interest. backend: Optional, the backend to run the experiment on. - parameter_name: A name of :math:`d\\theta` parameter from the - amplification fit. The fit parameter is represented by this name - in the analysis result. Keyword Args: See :meth:`experiment_options` for details. """ - analysis = HeatElementAnalysis() - analysis.set_options(result_parameters=[ParameterRepr("d_theta", parameter_name, "rad")]) - - super().__init__(qubits=qubits, backend=backend, analysis=analysis) + super().__init__(qubits=qubits, backend=backend, analysis=HeatElementAnalysis()) self.set_experiment_options(**kwargs) # These are not user configurable options. Be frozen once assigned. @@ -165,86 +154,3 @@ def circuits(self) -> List[QuantumCircuit]: circs.append(circ) return circs - - -class BatchHeatHelper(BatchExperiment, ABC): - """A wrapper class of ``BatchExperiment`` to implement HEAT experiment. - - # section: overview - - This is a helper class for experiment developers of HEAT experiments. - This class overrides the :meth:`set_experiment_options` and :meth:`set_transpile_options` - methods of :class:`BatchExperiment` such that they set the options of the - individual amplification sub-experiments to the same values. Therefore, from - end user's perspective, this experiment behaves as if a single HEAT experiment. - - # section: analysis_ref - :py:class:`HeatAnalysis` - """ - - def __init__( - self, - heat_experiments: List[HeatElement], - heat_analysis: HeatAnalysis, - backend: Optional[Backend] = None, - ): - """Create new HEAT experiment. - - Args: - heat_experiments: A list of error amplification sequence that might be - implemented as :class:``HeatElement`` instance. - heat_analysis: HEAT analysis instance. - backend: Optional, the backend to run the experiment on. - """ - super().__init__(experiments=heat_experiments, backend=backend) - - # override analysis. we expect the instance is initialized with - # parameter names specific to child amplification experiments. - self.analysis = heat_analysis - - @classmethod - def _default_experiment_options(cls) -> Options: - """Default experiment options. - - Experiment Options: - repetitions (Sequence[int]): A list of the number of echo repetitions. - heat_gate (Gate): A gate instance representing the entangling sequence. - """ - options = super()._default_experiment_options() - options.repetitions = list(range(21)) - options.heat_gate = circuit.Gate("heat", num_qubits=2, params=[]) - - return options - - def set_experiment_options(self, **fields): - """Set the analysis options for :meth:`run` method. - - Args: - fields: The fields to update the options - """ - # propagate options through all nested amplification experiments. - for comp_exp in self.component_experiment(): - comp_exp.set_experiment_options(**fields) - - super().set_experiment_options(**fields) - - @classmethod - def _default_transpile_options(cls) -> Options: - """Default transpile options.""" - options = super()._default_transpile_options() - options.basis_gates = ["sx", "x", "rz", "heat"] - options.optimization_level = 1 - - return options - - def set_transpile_options(self, **fields): - """Set the transpiler options for :meth:`run` method. - - Args: - fields: The fields to update the options - """ - # propagate options through all nested amplification experiments. - for comp_exp in self.component_experiment(): - comp_exp.set_transpile_options(**fields) - - super().set_transpile_options(**fields) diff --git a/qiskit_experiments/library/hamiltonian/heat_zx.py b/qiskit_experiments/library/hamiltonian/heat_zx.py index 226ee22d8e..6be2cb290a 100644 --- a/qiskit_experiments/library/hamiltonian/heat_zx.py +++ b/qiskit_experiments/library/hamiltonian/heat_zx.py @@ -16,14 +16,18 @@ from typing import Tuple, Optional import numpy as np -from qiskit.circuit import QuantumCircuit +from qiskit.circuit import QuantumCircuit, Gate from qiskit.providers import Backend -from .heat_base import BatchHeatHelper, HeatElement +from .heat_base import HeatElement from .heat_analysis import HeatAnalysis +from qiskit_experiments.framework import Options, composite -class ZXHeat(BatchHeatHelper): + +@composite.sync_experiment_options +@composite.sync_transpile_options +class ZXHeat(composite.BatchExperiment): r"""HEAT experiment for the ZX-type entangler. # section: overview @@ -212,20 +216,41 @@ def __init__( echo_circ=echo, meas_circ=meas, backend=backend, - parameter_name=f"d_heat_{error_axis}{control}", ) amplification_exps.append(exp) - analysis = HeatAnalysis( - fit_params=[f"d_heat_{error_axis}0", f"d_heat_{error_axis}1"], - out_params=[f"A_I{error_axis.upper()}", f"A_Z{error_axis.upper()}"], + heat_analysis = HeatAnalysis( + fit_params=(f"d_heat_{error_axis}0", f"d_heat_{error_axis}1"), + out_params=(f"A_I{error_axis.upper()}", f"A_Z{error_axis.upper()}"), ) - super().__init__( - heat_experiments=amplification_exps, - heat_analysis=analysis, - backend=backend, - ) + super().__init__(experiments=amplification_exps, backend=backend) + + # override analysis. + self.analysis = heat_analysis + + @classmethod + def _default_experiment_options(cls) -> Options: + """Default experiment options. + + Experiment Options: + repetitions (Sequence[int]): A list of the number of echo repetitions. + heat_gate (Gate): A gate instance representing the entangling sequence. + """ + options = super()._default_experiment_options() + options.repetitions = list(range(21)) + options.heat_gate = Gate("heat", num_qubits=2, params=[]) + + return options + + @classmethod + def _default_transpile_options(cls) -> Options: + """Default transpile options.""" + options = super()._default_transpile_options() + options.basis_gates = ["sx", "x", "rz", "heat"] + options.optimization_level = 1 + + return options class ZX90HeatXError(ZXHeat): diff --git a/test/hamiltonian/test_heat.py b/test/hamiltonian/test_heat.py index bc53135768..63216a8386 100644 --- a/test/hamiltonian/test_heat.py +++ b/test/hamiltonian/test_heat.py @@ -22,7 +22,7 @@ from qiskit import circuit, quantum_info as qi from qiskit.providers.aer import AerSimulator -from qiskit_experiments.library.hamiltonian import HeatElement, BatchHeatHelper, HeatAnalysis +from qiskit_experiments.library.hamiltonian import HeatElement, HeatAnalysis from qiskit_experiments.library import ZX90HeatXError, ZX90HeatYError, ZX90HeatZError from qiskit_experiments.framework import BatchExperiment @@ -50,7 +50,7 @@ class TestHeatBase(QiskitExperimentsTestCase): """Test for base classes.""" @staticmethod - def _create_fake_amplifier(prep_seed, echo_seed, meas_seed, pname): + def _create_fake_amplifier(prep_seed, echo_seed, meas_seed): """Helper method to generate fake experiment.""" prep = circuit.QuantumCircuit(2) prep.compose(qi.random_unitary(4, seed=prep_seed).to_instruction(), inplace=True) @@ -66,14 +66,13 @@ def _create_fake_amplifier(prep_seed, echo_seed, meas_seed, pname): prep_circ=prep, echo_circ=echo, meas_circ=meas, - parameter_name=pname, ) return exp def test_element_experiment_config(self): """Test converting to and from config works""" - exp = self._create_fake_amplifier(123, 456, 789, "test") + exp = self._create_fake_amplifier(123, 456, 789) loaded_exp = HeatElement.from_config(exp.config()) self.assertNotEqual(exp, loaded_exp) @@ -81,33 +80,13 @@ def test_element_experiment_config(self): def test_element_roundtrip_serializable(self): """Test round trip JSON serialization""" - exp = self._create_fake_amplifier(123, 456, 789, "test") - - self.assertRoundTripSerializable(exp, self.json_equiv) - - def test_experiment_config(self): - """Test converting to and from config works""" - ampl1 = self._create_fake_amplifier(123, 456, 789, "i1") - ampl2 = self._create_fake_amplifier(987, 654, 321, "i2") - analysis = HeatAnalysis(fit_params=["i1", "i2"], out_params=["o1", "o2"]) - exp = BatchHeatHelper(heat_experiments=[ampl1, ampl2], heat_analysis=analysis) - - loaded_exp = BatchHeatHelper.from_config(exp.config()) - self.assertNotEqual(exp, loaded_exp) - self.assertTrue(self.json_equiv(exp, loaded_exp)) - - def test_roundtrip_serializable(self): - """Test round trip JSON serialization""" - ampl1 = self._create_fake_amplifier(123, 456, 789, "i1") - ampl2 = self._create_fake_amplifier(987, 654, 321, "i2") - analysis = HeatAnalysis(fit_params=["i1", "i2"], out_params=["o1", "o2"]) - exp = BatchHeatHelper(heat_experiments=[ampl1, ampl2], heat_analysis=analysis) + exp = self._create_fake_amplifier(123, 456, 789) self.assertRoundTripSerializable(exp, self.json_equiv) def test_analysis_config(self): """Test converting analysis to and from config works""" - analysis = HeatAnalysis(fit_params=["i1", "i2"], out_params=["o1", "o2"]) + analysis = HeatAnalysis(fit_params=("i1", "i2"), out_params=("o1", "o2")) loaded = HeatAnalysis.from_config(analysis.config()) self.assertNotEqual(analysis, loaded) self.assertEqual(analysis.config(), loaded.config()) @@ -129,13 +108,9 @@ def test_create_circuit(self): prep_circ=prep, echo_circ=echo, meas_circ=meas, - parameter_name="testing", ) exp.set_experiment_options(repetitions=[2]) - # check also overriding of amplified parameter name - self.assertEqual(exp.analysis.options.result_parameters[0].repr, "testing") - heat_circ = exp.circuits()[0] ref_circ = circuit.QuantumCircuit(2, 1) @@ -183,6 +158,36 @@ def create_generator( return generator_ham + def test_transpile_options_sync(self): + """Test if transpile option set to composite can update all component experiments.""" + exp = ZX90HeatXError(qubits=(0, 1), backend=self.backend) + basis_exp0 = exp.component_experiment(0).transpile_options.basis_gates + basis_exp1 = exp.component_experiment(1).transpile_options.basis_gates + self.assertListEqual(basis_exp0, ["sx", "x", "rz", "heat"]) + self.assertListEqual(basis_exp1, ["sx", "x", "rz", "heat"]) + + # override from composite + exp.set_transpile_options(basis_gates=["sx", "x", "rz", "my_heat"]) + new_basis_exp0 = exp.component_experiment(0).transpile_options.basis_gates + new_basis_exp1 = exp.component_experiment(1).transpile_options.basis_gates + self.assertListEqual(new_basis_exp0, ["sx", "x", "rz", "my_heat"]) + self.assertListEqual(new_basis_exp1, ["sx", "x", "rz", "my_heat"]) + + def test_experiment_options_sync(self): + """Test if experiment option set to composite can update all component experiments.""" + exp = ZX90HeatXError(qubits=(0, 1), backend=self.backend) + reps_exp0 = exp.component_experiment(0).experiment_options.repetitions + reps_exp1 = exp.component_experiment(1).experiment_options.repetitions + self.assertListEqual(reps_exp0, list(range(21))) + self.assertListEqual(reps_exp1, list(range(21))) + + # override from composite + exp.set_experiment_options(repetitions=[1, 2, 3]) + new_reps_exp0 = exp.component_experiment(0).experiment_options.repetitions + new_reps_exp1 = exp.component_experiment(1).experiment_options.repetitions + self.assertListEqual(new_reps_exp0, [1, 2, 3]) + self.assertListEqual(new_reps_exp1, [1, 2, 3]) + @data( [0.08, -0.01], [-0.05, 0.13], From 280cae167072dd6ea55e56313baa2ab3882ff889 Mon Sep 17 00:00:00 2001 From: knzwnao Date: Thu, 3 Feb 2022 02:58:57 +0900 Subject: [PATCH 36/46] fix lint --- .../framework/composite/composite_experiment.py | 8 +++++++- qiskit_experiments/library/hamiltonian/heat_zx.py | 5 ++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/qiskit_experiments/framework/composite/composite_experiment.py b/qiskit_experiments/framework/composite/composite_experiment.py index a8f38eb4f2..9b8925dbb3 100644 --- a/qiskit_experiments/framework/composite/composite_experiment.py +++ b/qiskit_experiments/framework/composite/composite_experiment.py @@ -13,7 +13,7 @@ Composite Experiment abstract base class. """ -from typing import List, Sequence, Optional, Union, Type, Callable +from typing import List, Sequence, Optional, Union, Type from abc import abstractmethod import functools import warnings @@ -148,6 +148,9 @@ def sync_transpile_options( Returns: Composite experiment that implements option synchronization. + + Raises: + TypeError: When class is not subclass of :class:`CompositeExperiment` """ if not issubclass(composite_cls, CompositeExperiment): raise TypeError("Class is not composite experiment. Cannot override method.") @@ -181,6 +184,9 @@ def sync_experiment_options( Returns: Composite experiment that implements option synchronization. + + Raises: + TypeError: When class is not subclass of :class:`CompositeExperiment` """ if not issubclass(composite_cls, CompositeExperiment): raise TypeError("Class is not composite experiment. Cannot override method.") diff --git a/qiskit_experiments/library/hamiltonian/heat_zx.py b/qiskit_experiments/library/hamiltonian/heat_zx.py index cc9f8fe90c..4ed79cdc9d 100644 --- a/qiskit_experiments/library/hamiltonian/heat_zx.py +++ b/qiskit_experiments/library/hamiltonian/heat_zx.py @@ -19,10 +19,9 @@ from qiskit.circuit import QuantumCircuit, Gate from qiskit.providers import Backend -from .heat_base import HeatElement -from .heat_analysis import HeatAnalysis - from qiskit_experiments.framework import Options, composite +from .heat_analysis import HeatAnalysis +from .heat_base import HeatElement @composite.sync_experiment_options From 4f7cd81515353ab9526882ed1fc944ebf795ca0c Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Wed, 9 Feb 2022 07:18:32 +0900 Subject: [PATCH 37/46] Update qiskit_experiments/library/hamiltonian/heat_base.py Co-authored-by: Daniel J. Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit_experiments/library/hamiltonian/heat_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_experiments/library/hamiltonian/heat_base.py b/qiskit_experiments/library/hamiltonian/heat_base.py index dd3b468c95..87337efc7d 100644 --- a/qiskit_experiments/library/hamiltonian/heat_base.py +++ b/qiskit_experiments/library/hamiltonian/heat_base.py @@ -47,7 +47,7 @@ class HeatElement(BaseExperiment): coefficients along a specific axis of the second qubit. The ``prep`` circuit is carefully chosen based on the generator of the ``heat`` gate under consideration. The ``echo`` and ``meas`` circuits depend on the axis of the error to amplify. - Only the second qubit is measured following to the projection in the ``meas`` circuit. + Only the second qubit is measured. The measured qubit population containing the amplified error typically has contributions from both local (e.g. IZ) and non-local rotations (e.g. ZX). From 5c40d7d1fe91c9a7ef62dbb8f98e6138a90bf374 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Wed, 9 Feb 2022 07:18:44 +0900 Subject: [PATCH 38/46] Update qiskit_experiments/library/hamiltonian/heat_base.py Co-authored-by: Daniel J. Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit_experiments/library/hamiltonian/heat_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_experiments/library/hamiltonian/heat_base.py b/qiskit_experiments/library/hamiltonian/heat_base.py index 87337efc7d..ff03df31f5 100644 --- a/qiskit_experiments/library/hamiltonian/heat_base.py +++ b/qiskit_experiments/library/hamiltonian/heat_base.py @@ -55,7 +55,7 @@ class HeatElement(BaseExperiment): are usually combined to resolve these rotation terms. This experiment just provides a single error amplification sequence, and therefore you must combine multiple instances instantiated with different ``prep``, ``echo``, - and ``meas`` circuits designed to resolve error terms. + and ``meas`` circuits designed to resolve the different error terms. This class can be wrapped with hard-coded circuits to define new experiment class to provide HEAT experiment with respect to the error axis and Hamiltonian of interest. From 4882e9e8f4f8cedbec8d419e49ef6f7910cbfad7 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Wed, 9 Feb 2022 07:19:07 +0900 Subject: [PATCH 39/46] Update qiskit_experiments/library/hamiltonian/heat_base.py Co-authored-by: Daniel J. Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit_experiments/library/hamiltonian/heat_base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit_experiments/library/hamiltonian/heat_base.py b/qiskit_experiments/library/hamiltonian/heat_base.py index ff03df31f5..81b8f84f57 100644 --- a/qiskit_experiments/library/hamiltonian/heat_base.py +++ b/qiskit_experiments/library/hamiltonian/heat_base.py @@ -56,8 +56,8 @@ class HeatElement(BaseExperiment): This experiment just provides a single error amplification sequence, and therefore you must combine multiple instances instantiated with different ``prep``, ``echo``, and ``meas`` circuits designed to resolve the different error terms. - This class can be wrapped with hard-coded circuits to define new experiment class - to provide HEAT experiment with respect to the error axis and Hamiltonian of interest. + This class can be wrapped with hard-coded circuits to define new experiment classes + to provide HEAT experiments for different error axes and Hamiltonians of interest. The ``heat`` gate is a custom gate representing the entangling pulse sequence. One must thus provide its definition through the backend or a custom transpiler From a62cc8f7492334a44dd46226bab2b6498b05a838 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Wed, 9 Feb 2022 07:23:05 +0900 Subject: [PATCH 40/46] Update qiskit_experiments/library/hamiltonian/heat_zx.py Co-authored-by: Daniel J. Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit_experiments/library/hamiltonian/heat_zx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_experiments/library/hamiltonian/heat_zx.py b/qiskit_experiments/library/hamiltonian/heat_zx.py index 4ed79cdc9d..fceabc582e 100644 --- a/qiskit_experiments/library/hamiltonian/heat_zx.py +++ b/qiskit_experiments/library/hamiltonian/heat_zx.py @@ -59,7 +59,7 @@ class ZXHeat(composite.BatchExperiment): c: 1/══════════════════════════════════════════════════════╩═ 0 - ZX-HEAT experiments are performed with combination of two + ZX-HEAT experiments are performed by the combination of the two error amplification experiments shown above, where :math:`\alpha, \beta, \gamma` depend on the interrogated error axis, namely, (``X``, ``X``, ``I``), (``Y``, ``Y``, ``I``), (``Y``, ``Z``, ``Rx(-pi/2)``) From 5d4945eacfac15dfafa3f080cdf179ab14dc8e4b Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Wed, 9 Feb 2022 07:23:17 +0900 Subject: [PATCH 41/46] Update qiskit_experiments/library/hamiltonian/heat_zx.py Co-authored-by: Daniel J. Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit_experiments/library/hamiltonian/heat_zx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit_experiments/library/hamiltonian/heat_zx.py b/qiskit_experiments/library/hamiltonian/heat_zx.py index fceabc582e..2b6da33ef0 100644 --- a/qiskit_experiments/library/hamiltonian/heat_zx.py +++ b/qiskit_experiments/library/hamiltonian/heat_zx.py @@ -102,8 +102,8 @@ class ZXHeat(composite.BatchExperiment): which may be fit by :math:`P(N) = \cos(N (d\theta_j + \pi) + \phi_{\rm offset})`, where :math:`d\theta_j = \Delta_{ZX}\pm \Delta_{IX}`. By combining error amplification fit parameters :math:`d\theta_j` for - different control qubit states :math:`j`, we can resolve the local (IX) and non-local (ZX) - dynamics of the Hamiltonian of interest. + different control qubit states :math:`j`, we can differentiate the local (IX) from the + non-local (ZX) dynamics. In this pulse sequence, the pi-pulse echo is applied to the target qubit around the same axis as the interrogated error. From c9081051e68362db6ab2e895e09b7fa146f740f1 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Wed, 9 Feb 2022 07:23:24 +0900 Subject: [PATCH 42/46] Update qiskit_experiments/library/hamiltonian/heat_zx.py Co-authored-by: Daniel J. Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit_experiments/library/hamiltonian/heat_zx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_experiments/library/hamiltonian/heat_zx.py b/qiskit_experiments/library/hamiltonian/heat_zx.py index 2b6da33ef0..bfe762235e 100644 --- a/qiskit_experiments/library/hamiltonian/heat_zx.py +++ b/qiskit_experiments/library/hamiltonian/heat_zx.py @@ -109,7 +109,7 @@ class ZXHeat(composite.BatchExperiment): around the same axis as the interrogated error. This cancels out the errors in other axes since the errors anti-commute with the echo, e.g. :math:`XYX = -Y`, while the error in the interrogated axis is accumulated. - This is the trick of how the sequence selectively amplifies the error axis. + This is how the sequence selectively amplifies the error axis. However, strictly speaking, non-X error terms :math:`{\cal P}` do not commute with the primary :math:`ZX` term of the Hamiltonian, and From 995a009807dd34d5201621aa911dec1cf263f0ab Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 10 Feb 2022 03:21:15 +0900 Subject: [PATCH 43/46] Update qiskit_experiments/library/hamiltonian/heat_zx.py Co-authored-by: Daniel J. Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit_experiments/library/hamiltonian/heat_zx.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit_experiments/library/hamiltonian/heat_zx.py b/qiskit_experiments/library/hamiltonian/heat_zx.py index bfe762235e..eb8c6a62ee 100644 --- a/qiskit_experiments/library/hamiltonian/heat_zx.py +++ b/qiskit_experiments/library/hamiltonian/heat_zx.py @@ -93,10 +93,10 @@ class ZXHeat(composite.BatchExperiment): :math:`R_X(\pi + \Delta_{ZX} \pm \Delta_{IX})` for the control qubit state 0 and 1, respectively. - In the echo circuit, the non-local ZX rotation by :math:`\phi` is undone by - applying :math:`R_X(\mp \phi)` with sign depending on the control qubit state, - thus only rotation error :math:`\Delta_{ZX}` from the target - angle :math:`\phi` is selectively amplified. + In the echo circuit, the non-local ZX rotation of angle :math:`\phi` is undone by + the :math:`R_X(\mp \phi)` rotation whose sign depends on the control qubit state. + Therefore, the rotation error :math:`\Delta_{ZX}` from the target + angle :math:`\phi` is amplified. Repeating this sequence N times forms a typical ping-pong oscillation pattern in the measured target qubit population, which may be fit by :math:`P(N) = \cos(N (d\theta_j + \pi) + \phi_{\rm offset})`, From c400186f4c6aa2a783b682dd16a1de1a9104e46a Mon Sep 17 00:00:00 2001 From: knzwnao Date: Thu, 10 Feb 2022 15:37:39 +0900 Subject: [PATCH 44/46] remove usage of fitval --- .../library/hamiltonian/heat_analysis.py | 14 +++----- test/hamiltonian/test_heat.py | 32 ++++++++++++------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/qiskit_experiments/library/hamiltonian/heat_analysis.py b/qiskit_experiments/library/hamiltonian/heat_analysis.py index 3869ad5a0e..8cafcc0592 100644 --- a/qiskit_experiments/library/hamiltonian/heat_analysis.py +++ b/qiskit_experiments/library/hamiltonian/heat_analysis.py @@ -24,7 +24,6 @@ ExperimentData, AnalysisResultData, Options, - FitVal, ) @@ -135,23 +134,18 @@ def _run_analysis(self, experiment_data: ExperimentData): # Check data quality is_good_quality = all(r.quality == "good" for r in fit_results) - # Compute unitary terms - ib = (fit_results[0].value.value + fit_results[1].value.value) / 2 - zb = (fit_results[0].value.value - fit_results[1].value.value) / 2 - - # Compute new variance - sigma = np.sqrt(fit_results[0].value.stderr ** 2 + fit_results[1].value.stderr ** 2) - estimate_ib = AnalysisResultData( name=self._out_params[0], - value=FitVal(value=ib, stderr=sigma, unit="rad"), + value=(fit_results[0].value + fit_results[1].value) / 2, quality="good" if is_good_quality else "bad", + extra={"unit": "rad"}, ) estimate_zb = AnalysisResultData( name=self._out_params[1], - value=FitVal(value=zb, stderr=sigma, unit="rad"), + value=(fit_results[0].value - fit_results[1].value) / 2, quality="good" if is_good_quality else "bad", + extra={"unit": "rad"}, ) return [estimate_ib, estimate_zb], [] diff --git a/test/hamiltonian/test_heat.py b/test/hamiltonian/test_heat.py index 63216a8386..0c6ab7e44f 100644 --- a/test/hamiltonian/test_heat.py +++ b/test/hamiltonian/test_heat.py @@ -205,10 +205,15 @@ def test_x_error_amplification(self, e_zx, e_ix): exp.set_experiment_options(heat_gate=gate) exp.set_transpile_options(basis_gates=["x", "sx", "rz", "unitary"]) - exp_data = exp.run().block_for_results() + exp_data = exp.run() + self.assertExperimentDone(exp_data) - self.assertAlmostEqual(exp_data.analysis_results("A_IX").value.value, e_ix, delta=0.01) - self.assertAlmostEqual(exp_data.analysis_results("A_ZX").value.value, e_zx, delta=0.01) + self.assertAlmostEqual( + exp_data.analysis_results("A_IX").value.nominal_value, e_ix, delta=0.01 + ) + self.assertAlmostEqual( + exp_data.analysis_results("A_ZX").value.nominal_value, e_zx, delta=0.01 + ) @data( [0.02, -0.01], @@ -227,15 +232,16 @@ def test_y_error_amplification(self, e_zy, e_iy): exp.set_experiment_options(heat_gate=gate) exp.set_transpile_options(basis_gates=["x", "sx", "rz", "unitary"]) - exp_data = exp.run().block_for_results() + exp_data = exp.run() + self.assertExperimentDone(exp_data) # The factor 0.7 is estimated from numerical analysis, which comes from ZX commutator term. # Note that this number may depend on magnitude of coefficients. self.assertAlmostEqual( - exp_data.analysis_results("A_IY").value.value, 0.7 * e_iy, delta=0.01 + exp_data.analysis_results("A_IY").value.nominal_value, 0.7 * e_iy, delta=0.01 ) self.assertAlmostEqual( - exp_data.analysis_results("A_ZY").value.value, 0.7 * e_zy, delta=0.01 + exp_data.analysis_results("A_ZY").value.nominal_value, 0.7 * e_zy, delta=0.01 ) @data( @@ -255,15 +261,16 @@ def test_z_error_amplification(self, e_zz, e_iz): exp.set_experiment_options(heat_gate=gate) exp.set_transpile_options(basis_gates=["x", "sx", "rz", "unitary"]) - exp_data = exp.run().block_for_results() + exp_data = exp.run() + self.assertExperimentDone(exp_data) # The factor 0.7 is estimated from numerical analysis, which comes from ZX commutator term. # Note that this number may depend on magnitude of coefficients. self.assertAlmostEqual( - exp_data.analysis_results("A_IZ").value.value, 0.7 * e_iz, delta=0.01 + exp_data.analysis_results("A_IZ").value.nominal_value, 0.7 * e_iz, delta=0.01 ) self.assertAlmostEqual( - exp_data.analysis_results("A_ZZ").value.value, 0.7 * e_zz, delta=0.01 + exp_data.analysis_results("A_ZZ").value.nominal_value, 0.7 * e_zz, delta=0.01 ) @data(123, 456) @@ -307,13 +314,14 @@ def test_pseudo_calibration(self, seed): exp_z.set_transpile_options(basis_gates=["x", "sx", "rz", "unitary"]) exp = BatchExperiment([exp_x, exp_y, exp_z], backend=self.backend) - exp_data = exp.run().block_for_results() + exp_data = exp.run() + self.assertExperimentDone(exp_data) for n, tp in enumerate(["x", "y", "z"]): a_zp = exp_data.child_data(n).analysis_results(f"A_Z{tp.upper()}") a_ip = exp_data.child_data(n).analysis_results(f"A_I{tp.upper()}") - errors_dict[f"e_z{tp}"] -= a_zp.value.value - errors_dict[f"e_i{tp}"] -= a_ip.value.value + errors_dict[f"e_z{tp}"] -= a_zp.value.nominal_value + errors_dict[f"e_i{tp}"] -= a_ip.value.nominal_value for v in errors_dict.values(): self.assertAlmostEqual(v, 0.0, delta=0.005) From fec3997212754d94a251cce167e7a8ba96c2ee0b Mon Sep 17 00:00:00 2001 From: knzwnao Date: Thu, 10 Feb 2022 15:42:20 +0900 Subject: [PATCH 45/46] lint --- qiskit_experiments/library/hamiltonian/heat_zx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_experiments/library/hamiltonian/heat_zx.py b/qiskit_experiments/library/hamiltonian/heat_zx.py index eb8c6a62ee..a96409133f 100644 --- a/qiskit_experiments/library/hamiltonian/heat_zx.py +++ b/qiskit_experiments/library/hamiltonian/heat_zx.py @@ -102,7 +102,7 @@ class ZXHeat(composite.BatchExperiment): which may be fit by :math:`P(N) = \cos(N (d\theta_j + \pi) + \phi_{\rm offset})`, where :math:`d\theta_j = \Delta_{ZX}\pm \Delta_{IX}`. By combining error amplification fit parameters :math:`d\theta_j` for - different control qubit states :math:`j`, we can differentiate the local (IX) from the + different control qubit states :math:`j`, we can differentiate the local (IX) from the non-local (ZX) dynamics. In this pulse sequence, the pi-pulse echo is applied to the target qubit From 1e21e36b5dd3b30a44d5198c6f0a26877802401e Mon Sep 17 00:00:00 2001 From: knzwnao Date: Thu, 10 Feb 2022 17:22:01 +0900 Subject: [PATCH 46/46] fix bug due to #662 --- qiskit_experiments/library/hamiltonian/heat_analysis.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/qiskit_experiments/library/hamiltonian/heat_analysis.py b/qiskit_experiments/library/hamiltonian/heat_analysis.py index 8cafcc0592..08dde8097b 100644 --- a/qiskit_experiments/library/hamiltonian/heat_analysis.py +++ b/qiskit_experiments/library/hamiltonian/heat_analysis.py @@ -25,11 +25,17 @@ AnalysisResultData, Options, ) +from qiskit_experiments.data_processing import DataProcessor, Probability class HeatElementAnalysis(ErrorAmplificationAnalysis): """An analysis class for HEAT experiment to define the fixed parameters. + # section: note + + This analysis assumes the experiment measures only single qubit + regardless of the number of physical qubits used in the experiment. + # section: overview This is standard error amplification analysis. @@ -47,6 +53,7 @@ def _default_options(cls) -> Options: options.angle_per_gate = np.pi options.phase_offset = np.pi / 2 options.amp = 1.0 + options.data_processor = DataProcessor(input_key="counts", data_actions=[Probability("1")]) return options