diff --git a/cqlib/quantum_platform/guodun.py b/cqlib/quantum_platform/guodun.py index 20e18431133bbf1c4d60f06221ca3f3d69f8a32c..58fd486516b75ee5a96212464d0803311b6171cd 100644 --- a/cqlib/quantum_platform/guodun.py +++ b/cqlib/quantum_platform/guodun.py @@ -40,7 +40,7 @@ class GuoDunPlatform(BasePlatform): # query exp path QUERY_EXP_PATH = '/experiment/sdk/experiment/result/find' # download config path - DOWNLOAD_CONFIG_PATH = '/experiment/sdk/experiment/download/config/' + DOWNLOAD_CONFIG_PATH = '/experiment/sdk/experiment/download/config' # qcis check regular path QCIS_CHECK_REGULAR_PATH = '/experiment/sdk/experiment/qcis/rule/verify' # get exp circuit path diff --git a/cqlib/utils/tomography.py b/cqlib/utils/tomography.py new file mode 100644 index 0000000000000000000000000000000000000000..3b8eda65f03fe020307358426a0fa851715d578a --- /dev/null +++ b/cqlib/utils/tomography.py @@ -0,0 +1,164 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE 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. + +"""tomography measurement""" +from enum import Enum +from itertools import product +from cqlib import Circuit, BasePlatform, Qubit +from cqlib.circuits.gates import I, X, X2P, Y2P, X2M, Y2M +from cqlib.exceptions import CqlibInputParaError + + +class InitialGateList(Enum): + """Enum of possible initial gate sequences for process tomography.""" + I_I = (I, I) + SINGLE_X = (X,) + I_X2P = (I, X2P) + I_Y2P = (I, Y2P) + I_X2M = (I, X2M) + I_Y2M = (I, Y2M) + + +class MeasureGateList(Enum): + """Enum of possible initial gate sequences for process tomography and state tomography""" + I_I = (I, I) + SINGLE_X = (X,) + X2P_I = (X2P, I) + Y2P_I = (Y2P, I) + X2M_I = (X2M, I) + Y2M_I = (Y2M, I) + + +class Tomography: + """Quantum State Tomography and Process Tomography quantum circuit Generator""" + + def __init__(self, platform: BasePlatform, circuit: Circuit | str): + """ + Initializes a tomography class. + Args: + platform: cloud platform entity class + circuit: The circuit for tomography + """ + if not platform.machine_name: + raise CqlibInputParaError("The machine_name attribute in the" + " parameter platform cannot be None." + "Please enter the machine_name parameters.") + + self.qubit_i_lengths = self._process_parameter(platform) + self.circuit, self.return_str = self._process_input_circuit(circuit) + + @staticmethod + def _process_parameter(platform): + """Extracts qubit-specific I-gate length from platform configuration""" + try: + config = platform.download_config()['gatePulseParameter']["single_parameter"] + except (KeyError, TypeError) as exc: + raise CqlibInputParaError(f"This quantum computer(machine_name:{platform.machine_name})" + f" does not have the configuration required" + f" for tomography.") from exc + + return {qubit: params['length'] for qubit, params in config.items()} + + @staticmethod + def _process_input_circuit(quantum_circuit: Circuit | str): + """Uniformly handle the input circuit, return the circuit object and the output type flag""" + if isinstance(quantum_circuit, str): + return Circuit.load(quantum_circuit), True + return quantum_circuit, False + + def _append_gate_sequence( + self, + circuit: Circuit, + qubits: list[Qubit], + gate_sequence: list[Enum], + ): + """Appends a sequence of gates to specified qubits in a circuit. + Handles special duration requirements for identity gates (I).""" + for qubit, gate_enum in zip(qubits, gate_sequence): + for gate in gate_enum.value: + if gate == I: + length = self.qubit_i_lengths[f'Q{qubit.index}'] + circuit.append(gate(length), [qubit]) + else: + circuit.append(gate(), [qubit]) + + @staticmethod + def _create_m_b_circuit(qubits: list[Qubit]) -> tuple[Circuit, Circuit]: + """Create reusable measure and barrier circuit templates""" + b_circuit = Circuit(qubits) + m_circuit = Circuit(qubits) + for qubit in qubits: + b_circuit.barrier(qubit) + m_circuit.measure(qubit) + return b_circuit, m_circuit + + def state_tomography(self, measure_gates: list[MeasureGateList]) -> list[Circuit | str]: + """ + Generates state tomography circuits for the target quantum state. + Constructs measurement circuits in all combinations of the specified + measurement bases. + + Args: + measure_gates: List of allowed measurement basis enums + + Returns: + list: Generated state tomography circuits (Circuit objects or QCIS strings) + """ + qubits = self.circuit.qubits + b_circuit, m_circuit = self._create_m_b_circuit(qubits) + basis_combinations = list(product(measure_gates, repeat=len(qubits))) + circuits = [] + for combination in basis_combinations: + temp_circuit = b_circuit.copy() + temp_circuit.compose(self.circuit, inplace=True) + temp_circuit.compose(b_circuit, inplace=True) + self._append_gate_sequence(temp_circuit, qubits, combination) + temp_circuit.compose(b_circuit, inplace=True) + if self.return_str: + circuits.append(temp_circuit.compose(m_circuit, inplace=True).qcis) + else: + circuits.append(temp_circuit.compose(m_circuit, inplace=True)) + return circuits + + def process_tomography(self, + init_gates: list[InitialGateList], + measure_gates: list[MeasureGateList]) -> list[Circuit | str]: + """ + Generates process tomography circuits for the target quantum operation. + + Args: + measure_gates: List of allowed measurement basis enums + init_gates: List of allowed initialization basis enums + + Returns: + list: Generated tomography circuits (Circuit objects or QCIS strings) + """ + qubits = self.circuit.qubits + b_circuit, m_circuit = self._create_m_b_circuit(qubits) + measure_combinations = list(product(measure_gates, repeat=len(qubits))) + init_combinations = list(product(init_gates, repeat=len(qubits))) + init_measure_combinations = list(product(init_combinations, measure_combinations)) + circuits = [] + for init_measure_combination in init_measure_combinations: + temp_circuit = b_circuit.copy() + self._append_gate_sequence(temp_circuit, qubits, init_measure_combination[0]) + temp_circuit.compose(b_circuit, inplace=True) + temp_circuit.compose(self.circuit, inplace=True) + temp_circuit.compose(b_circuit, inplace=True) + self._append_gate_sequence(temp_circuit, qubits, init_measure_combination[1]) + temp_circuit.compose(b_circuit, inplace=True) + if self.return_str: + circuits.append(temp_circuit.compose(m_circuit).qcis) + else: + circuits.append(temp_circuit.compose(m_circuit)) + return circuits diff --git a/tests/utils/test_tomography.py b/tests/utils/test_tomography.py new file mode 100644 index 0000000000000000000000000000000000000000..2aef4918538e7334ad04a6b9913ecadf55b4872c --- /dev/null +++ b/tests/utils/test_tomography.py @@ -0,0 +1,140 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE 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 tomography module +""" +import os +import pytest + +from cqlib import GuoDunPlatform, Circuit +from cqlib.utils.tomography import Tomography, MeasureGateList, InitialGateList + +quantum_machine = os.getenv('QUANTUM_MACHINE') + +# pylint: disable=redefined-outer-name +@pytest.fixture +def platform(): + """ + Fixture to create an instance of the GuoDunPlatform class + """ + return GuoDunPlatform(login_key="", auto_login=False, machine_name=quantum_machine) + + +class TestStateTomography: + """test state tomography""" + + def test_state_tomography_qcis_str(self, platform): + """ test the state tomography of the QCIS string""" + circuit = """ + X Q1 + H Q1 + X2P Q1 + """ + tomography = Tomography(platform, circuit) + circuits = tomography.state_tomography([MeasureGateList.I_I]) + print(f"state tomography circuits(qcis): {circuits}") + qcis = ('B Q1\nX Q1\nH Q1\nX2P Q1\nB Q1\nI Q1 -1.4159265358979276\n' + 'I Q1 -1.4159265358979276\nB Q1\nM Q1') + assert circuits[0] == qcis + + + def test_state_tomography_circuit_class(self, platform): + """ test the state tomography of the Circuit class""" + circuit = Circuit([1]) + circuit.x(1) + circuit.h(1) + circuit.x2p(1) + tomography = Tomography(platform,circuit) + circuits = tomography.state_tomography([MeasureGateList.I_I]) + print(f"state tomography circuits(circuit): {[circuit.qcis for circuit in circuits]}") + qcis = ('B Q1\nX Q1\nH Q1\nX2P Q1\nB Q1\nI Q1 -1.4159265358979276\n' + 'I Q1 -1.4159265358979276\nB Q1\nM Q1') + assert circuits[0].qcis == qcis + + def test_state_tomography_circuit_all_m_gate(self, platform): + """test uses all available measurement gates to generate the process tomography circuit.""" + circuit = Circuit([1, 2]) + circuit.x(1) + circuit.h(1) + circuit.x2p(1) + circuit.x(2) + tomography = Tomography(platform, circuit) + circuits = tomography.state_tomography([MeasureGateList.I_I, MeasureGateList.SINGLE_X, + MeasureGateList.X2P_I, MeasureGateList.Y2P_I, + MeasureGateList.X2M_I, MeasureGateList.Y2M_I]) + print(f"state tomography circuits(all_m_gate): {[circuit.qcis for circuit in circuits]}") + assert circuits + + +class TestProcessTomography: + """test process tomography""" + + def test_process_tomography_qcis_str(self, platform): + """ test the process tomography of the QCIS string""" + circuit = """ + X Q1 + H Q1 + X2P Q1 + X2P Q2 + """ + tomography = Tomography(platform=platform, circuit=circuit) + circuits = tomography.process_tomography(measure_gates=[MeasureGateList.I_I], + init_gates=[InitialGateList.I_X2M]) + print(f"process tomography circuits(qcis): {circuits}") + qcis = ('B Q1\nB Q2\nI Q1 -1.4159265358979276\nX2M Q1\n' + 'I Q2 -1.4159265358979276\nX2M Q2\nB Q1\n' + 'B Q2\nX Q1\nH Q1\nX2P Q1\nX2P Q2\nB Q1\nB Q2\n' + 'I Q1 -1.4159265358979276\nI Q1 -1.4159265358979276\n' + 'I Q2 -1.4159265358979276\nI Q2 -1.4159265358979276\n' + 'B Q1\nB Q2\nM Q1\nM Q2') + assert circuits[0] == qcis + + + def test_process_tomography_circuit_class(self, platform): + """ test the process tomography of the Circuit class""" + circuit = Circuit([1]) + circuit.x(1) + circuit.h(1) + circuit.x2p(1) + tomography = Tomography(platform=platform, circuit=circuit) + circuits = tomography.process_tomography([InitialGateList.I_I], + [MeasureGateList.X2M_I]) + print(f"process tomography circuits(circuit): {[circuit.qcis for circuit in circuits]}") + qcis = ('B Q1\nI Q1 -1.4159265358979276\nI Q1 -1.4159265358979276\n' + 'B Q1\nX Q1\nH Q1\nX2P Q1\nB Q1\nX2M Q1\n' + 'I Q1 -1.4159265358979276\nB Q1\nM Q1') + assert circuits[0].qcis == qcis + + def test_process_tomography_circuit_all_m_gate(self, platform): + """ test uses all available measurement and + initialization gates to generate the process tomography circuit.""" + circuit = Circuit([1, 2]) + circuit.x(1) + circuit.h(1) + circuit.x2p(1) + circuit.x(2) + tomography = Tomography(platform=platform, circuit=circuit) + circuits = tomography.process_tomography([InitialGateList.I_I, + InitialGateList.SINGLE_X, + InitialGateList.I_X2P, + InitialGateList.I_X2M, + InitialGateList.I_Y2M, + InitialGateList.I_Y2P], + [MeasureGateList.I_I, + MeasureGateList.SINGLE_X, + MeasureGateList.X2P_I, + MeasureGateList.Y2P_I, + MeasureGateList.X2M_I, + MeasureGateList.Y2M_I]) + print(f"process tomography circuits(all_m_gate): {[circuit.qcis for circuit in circuits]}") + assert circuits