diff --git a/cqlib/pulse/__init__.py b/cqlib/pulse/__init__.py deleted file mode 100644 index d634487aa7260abefd941b444bd29e57c3d9d0c5..0000000000000000000000000000000000000000 --- a/cqlib/pulse/__init__.py +++ /dev/null @@ -1,34 +0,0 @@ -# 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. - -""" -Pulse module based on the pulse instruction in the QCIS instruction. -""" - -from .coupler_qubit import CouplerQubit -from .waveform import Waveform, WaveformType, CosineWaveform, \ - FlattopWaveform, SlepianWaveform, NumericWaveform -from .g import G -from .pz import PZ -from .pz0 import PZ0 -from .pxy import PXY -from .pulse_circuit import PulseCircuit - -__all__ = [ - 'CouplerQubit', - 'Waveform', 'WaveformType', - 'CosineWaveform', 'FlattopWaveform', - 'SlepianWaveform', 'NumericWaveform', - 'G', 'PXY', 'PZ', 'PZ0', - 'PulseCircuit', -] diff --git a/cqlib/pulse/base_pulse.py b/cqlib/pulse/base_pulse.py deleted file mode 100644 index dda4e2e7993dd3a72770d7b6607e3f64b90992bf..0000000000000000000000000000000000000000 --- a/cqlib/pulse/base_pulse.py +++ /dev/null @@ -1,55 +0,0 @@ -# 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. - -""" -Base Class for QCIS Pulse instruction -""" - -from abc import ABC, abstractmethod - -from cqlib.circuits.instruction import Instruction - -from .waveform import Waveform - - -class BasePulse(Instruction, ABC): - """ - Abstract base class for Quantum Control Instruction System (QCIS) pulse commands. - """ - - def __init__( - self, - name: str, - waveform: Waveform = None, - label: str = None - ): - """Initialize base pulse instance - - Args: - name (str): Pulse type identifier (e.g. PZ/PZ0/PXY/G) - waveform (Waveform): Waveform parameter container storing physical values. - label (str): Optional operational label for experimental tracking - """ - self._waveform = waveform - if waveform: - params = waveform.data - else: - params = [] - super().__init__(name=name, num_qubits=1, params=params, label=label) - self.validate() - - @abstractmethod - def validate(self): - """ - Abstract template method for parameter validation - """ diff --git a/cqlib/pulse/coupler_qubit.py b/cqlib/pulse/coupler_qubit.py deleted file mode 100644 index cc8e6852b83cabad7d2d2590ff7754b8c9e1483f..0000000000000000000000000000000000000000 --- a/cqlib/pulse/coupler_qubit.py +++ /dev/null @@ -1,41 +0,0 @@ -# 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. - -"""Coupler Qubit""" - -from __future__ import annotations - -import weakref - -from cqlib.circuits.bit import Bit - - -class CouplerQubit(Bit): - """A class representing a coupled qubit in a quantum system.""" - - _cache = weakref.WeakValueDictionary[int, 'CouplerQubit']() - - def __new__(cls, index: int) -> CouplerQubit: - if index < 0: - raise ValueError("CouplerQubit index must be non-negative.") - inst = cls._cache.get(index) - if inst is None: - inst = super().__new__(cls) - inst._index = index - inst._initialized = True - inst._hash = None - cls._cache[index] = inst - return inst - - def __str__(self): - return f'G{self.index}' diff --git a/cqlib/pulse/g.py b/cqlib/pulse/g.py deleted file mode 100644 index f0cb626fc0323585ff49b7b34a15ad2308721e92..0000000000000000000000000000000000000000 --- a/cqlib/pulse/g.py +++ /dev/null @@ -1,52 +0,0 @@ -# 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. - -""" G: Adjust and control the coupling function of the coupling qubit. """ -from cqlib.exceptions import CqlibError -from .base_pulse import BasePulse - - -class G(BasePulse): - """ - Coupling strength controller for coupling qubits via sequential DC(Direct Current) pulses. - Applies tunable interaction between adjacent qubits by setting duration (DAC cycles) - and coupling amplitude (Hz). Exclusively operates on coupling qubits. - - Parameters: - - length: Coupling activation duration in DAC sampling cycles (1 cycle=0.5ns) - - coupling_strength: Interaction magnitude in Hertz (Hz) - - Example: - `G G107 100 -3E6` sets 3MHz coupling on G107 for 50ns (100 cycles). - """ - - def __init__(self, length: int, coupling_strength: int, label: str = None): - self.length = length - self.coupling_strength = coupling_strength - super().__init__('G', waveform=None, label=label) - self.params = [length, coupling_strength] - - def validate(self): - """ - Validate pulse parameters for G-type coupling operations. - - Ensures: - - Duration (length) is within valid range [0, 1e5] DAC cycles - - Coupling strength meets implementation constraints - """ - if self.length < 0 or self.length > 1e5: - raise CqlibError(f"Invalid duration {self.length}: " - f"Must be 0-100,000 DAC cycles (1 cycle=0.5ns)") - - def __str__(self): - return f'{self.__class__.__name__}({self.length},{self.coupling_strength})' diff --git a/cqlib/pulse/pulse_circuit.py b/cqlib/pulse/pulse_circuit.py deleted file mode 100644 index 8aec60b5c0857ca2e38b46ba504e4d5fd1d1f84e..0000000000000000000000000000000000000000 --- a/cqlib/pulse/pulse_circuit.py +++ /dev/null @@ -1,341 +0,0 @@ -# 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. - -""" -A specialized quantum circuit class designed for pulse-level control -of qubits and coupler qubits. -""" -import re -from collections.abc import Sequence - -from cqlib.circuits.circuit import Circuit, Qubits -from cqlib.circuits.instruction import Instruction -from cqlib.circuits.instruction_data import InstructionData -from cqlib.circuits.parameter import Parameter -from cqlib.circuits.qubit import Qubit -from cqlib.exceptions import CqlibError - -from .coupler_qubit import CouplerQubit -from .g import G -from .pxy import PXY -from .pz import PZ -from .pz0 import PZ0 -from .waveform import Waveform -from .base_pulse import BasePulse - -CouplerQubits = int | CouplerQubit | Sequence[int | CouplerQubit] - -PULSE_GATES = ['PZ', 'PZ0', 'PXY', 'G'] - - -class PulseCircuit(Circuit): - """ - Circuit with pulse support. - """ - - def __init__( - self, - qubits: Qubits, - coupler_qubits: CouplerQubits, - parameters: Sequence[Parameter | str] | None = None - ) -> None: - """ - Initialize PulseCircuit. - - Args: - qubits(Qubits): Specification for standard qubits. - coupler_qubits(CouplerQubits): Specification for coupler qubits. - parameters(Sequence[Parameter | str] | None): Circuit parameters. - """ - super().__init__(qubits, parameters) - self._coupler_qubits = self._initialize_coupler_qubits(coupler_qubits) - - @property - def coupler_qubits(self) -> list[CouplerQubit]: - """ - A list of initialized coupler qubit objects in the circuit. - """ - return list(self._coupler_qubits.values()) - - def append_pulse(self, instruction: Instruction, qubit: CouplerQubit | Qubit): - """ - Appends a pulse instruction to the circuit. - - Args: - instruction (Instruction): Pulse instruction to add. - qubit (Qubit | CouplerQubit): Target qubit or coupler qubit. - """ - if isinstance(qubit, CouplerQubit) and str(qubit) not in self._coupler_qubits: - raise CqlibError(f"Coupler qubit {qubit} is not registered in the circuit. " - f"Available coupler qubits: {self.coupler_qubits}") - if isinstance(qubit, Qubit) and str(qubit) not in self._qubits: - raise CqlibError(f"Qubit {qubit} is not registered in the circuit. " - f"Available qubits: {self.qubits}") - self._circuit_data.append(InstructionData(instruction=instruction, qubits=[qubit])) - - def g(self, couple_qubit: CouplerQubit | int, length: int, coupling_strength: int): - """ - Applies a G-pulse (coupling pulse) to a coupler qubit. - - Args: - couple_qubit(CouplerQubit | int): Target coupler qubit or its index. - length(int): Duration of the pulse in time steps. - coupling_strength(int): Strength of the coupling interaction. - """ - if isinstance(couple_qubit, int): - couple_qubit = CouplerQubit(couple_qubit) - if not isinstance(couple_qubit, CouplerQubit): - raise CqlibError(f"Invalid type for coupler qubit. Expected CouplerQubit or int, " - f"got {type(couple_qubit).__name__} instead.") - self.append_pulse(G(length, coupling_strength), couple_qubit) - - def pxy(self, qubit: Qubit | int, waveform: Waveform): - """ - Applies a PXY-pulse (XY control pulse) to a standard qubit. - - Args: - qubit(Qubit | int): Target qubit or its index. - waveform(Waveform): Waveform object defining the pulse shape. - """ - if isinstance(qubit, int): - qubit = Qubit(qubit) - if not isinstance(qubit, Qubit): - raise CqlibError(f"Invalid type for qubit. Expected Qubit or int, " - f"got {type(qubit).__name__} instead.") - self.append_pulse(PXY(waveform=waveform), qubit) - - def pz(self, qubit: CouplerQubit | Qubit, waveform: Waveform): - """ - Applies a PZ-pulse (Z control pulse) to a qubit or coupler qubit. - - Args: - qubit(CouplerQubit | Qubit): Target CouplerQubit or Qubit. - waveform (Waveform): Waveform object defining the pulse shape. - """ - self.append_pulse(PZ(waveform=waveform), qubit) - - def pz0(self, qubit: CouplerQubit | Qubit, waveform: Waveform): - """ - Applies a PZ0-pulse (parallel) to a qubit or coupler qubit. - - Args: - qubit(CouplerQubit | Qubit): Target CouplerQubit or Qubit. - waveform (Waveform): Waveform object defining the pulse shape. - """ - self.append_pulse(PZ0(waveform=waveform), qubit) - - def add_coupler_qubits(self, couple_qubits: CouplerQubits): - """ - Adds a coupler qubit(or list) to the circuit, ensuring it does not already exist. - - Args: - couple_qubits (CouplerQubits): The coupler qubits to add, specified as an - integer index or a Qubit object. - """ - if not isinstance(couple_qubits, Sequence): - couple_qubits = [couple_qubits] - - for qubit in couple_qubits: - if isinstance(qubit, int): - qubit = CouplerQubit(qubit) - elif not isinstance(qubit, CouplerQubit): - raise TypeError(f"{qubit} must be an int or CouplerQubit instance.") - if str(qubit) in self._qubits: - raise ValueError(f"CouplerQubit {qubit} already exists in the circuit.") - self._coupler_qubits[str(qubit)] = qubit - - @staticmethod - def _initialize_coupler_qubits(coupler_qubits: CouplerQubits) -> dict[str, CouplerQubit]: - """ - Helper function to initialize coupler_qubits. - - Args: - coupler_qubits (CouplerQubits): Input coupler qubits specification. - - Returns: - dict[int, CouplerQubit]: Dictionary of CouplerQubit objects. - """ - if isinstance(coupler_qubits, int): - if coupler_qubits < 0: - raise ValueError("Number of coupler qubits must be non-negative.") - return {str(Qubit(i)): CouplerQubit(i) for i in range(coupler_qubits)} - if isinstance(coupler_qubits, CouplerQubit): - return {str(coupler_qubits): coupler_qubits} - if not isinstance(coupler_qubits, (list, tuple)): - raise ValueError("Invalid coupler_qubits input. Expected int, CouplerQubit," - " or list/tuple of these.") - - qs = {} - for qubit in coupler_qubits: - if isinstance(qubit, int): - qubit = CouplerQubit(qubit) - elif not isinstance(qubit, CouplerQubit): - raise TypeError("CouplerQubit must be an int or CouplerQubit instance.") - if qubit.index in qs: - raise ValueError(f"Duplicate qubit detected: {qubit}") - qs[str(qubit)] = qubit - return qs - - def to_qasm2(self) -> str: - """ - Generate a QASM 2.0 string representation of the circuit. - - Note: - Circuits containing coupler qubits cannot be exported to QASM 2.0 format, - as the standard does not support coupler qubit operations. - - Returns: - str: QASM 2.0 compliant code representing the circuit - - Raises: - CqlibError: If the circuit contains coupler qubits, as they are not supported - in QASM 2.0. - """ - if self._coupler_qubits: - raise CqlibError(f"QASM 2.0 export not supported for circuits with coupler qubits. " - f"Found coupler(s): {self.coupler_qubits}") - return super().to_qasm2() - - @classmethod - def load(cls, qcis: str) -> 'PulseCircuit': - """ - Loads quantum circuit with pulse-level instructions from QCIS string. - - Extends base Circuit functionality with support for: - - Coupler qubits (G-prefixed identifiers) - - Pulse waveforms (PXY, PZ, PZ0) - - Hybrid circuits mixing gates and pulses - - Args: - qcis: String containing hybrid quantum instructions. - - Returns: - PulseCircuit: Circuit with pulse capabilities initialized. - """ - circuit = cls(qubits=[], coupler_qubits=[]) - pattern = re.compile(r'^([A-Z][A-Z0-9]*)\s+((?:[QG][0-9]+\s*)+)' - r'((?:[+-]?(?:\d*\.\d+|\d+)(?:[Ee][+-]?\d+)?\s*)*)$') - for line in qcis.split('\n'): - line = re.sub(r'(#|//).*', '', line).strip() - if not line: - continue - match = pattern.match(line) - if not match: - raise ValueError(f'Invalid instruction format: {line}') - gate, qubits_str, params_str = match.groups() - qubits, coupler_qubits = cls._parse_pulse_qubits_str(circuit, qubits_str) - params = [float(p) for p in params_str.split()] if params_str else [] - if gate in PULSE_GATES: - cls._process_pulse_instruction(circuit, gate, qubits, coupler_qubits, params) - else: - cls._process_instruction(circuit, gate, qubits, params) - - return circuit - - def _parse_pulse_qubits_str( - self, - qubits_str: str - ) -> tuple[list[Qubit], list[CouplerQubit]]: - """ - Parses and categorizes qubit identifiers from QCIS instruction string. - - Processes space-separated qubit tokens distinguishing between: - - Standard qubits (Q-prefixed) - - Coupler qubits (G-prefixed) - - Args: - qubits_str: Raw qubit specification from QCIS instruction line. - - Returns: - tuple: Contains two lists respectively holding: - [0] Parsed standard Qubit objects - [1] Parsed CouplerQubit objects - """ - qubits, coupler_qubits = [], [] - for q_str in qubits_str.split(): - if q_str.startswith('Q'): - qubit = self._qubits.setdefault(q_str, Qubit(int(q_str[1:]))) - qubits.append(qubit) - elif q_str.startswith('G'): - coupler_qubit = self._coupler_qubits.setdefault(q_str, CouplerQubit(int(q_str[1:]))) - coupler_qubits.append(coupler_qubit) - else: - raise ValueError(f"Invalid qubit format: {q_str}") - return qubits, coupler_qubits - - def _process_pulse_instruction( - self, - gate: str, - qubits: list[Qubit], - coupler_qubits: list[CouplerQubit], - params: list[float | int] - ): - """ - Processes pulse-specific quantum instructions from parsed components. - - Handles three categories of pulse operations: - 1. G-type coupling pulses - 2. PXY parametric XY control pulses - 3. General pulse operations (PZ/PZ0 and dynamically resolved instructions) - - Args: - gate(str): Uppercase gate identifier (e.g., 'G', 'PXY', 'PZ') - qubits( list[Qubit]): Standard qubit targets parsed from instruction - coupler_qubits(list[CouplerQubit]): Coupler qubit targets parsed from instruction - params(list[float | int]): Numerical parameters following the qubit specification - """ - if gate == 'G': - if len(params) != 2: - raise CqlibError("G gate requires exactly 2 parameters (length, coupling_strength)") - length, coupling_strength = params - if length != int(length): - raise CqlibError( - f"G pulse length parameter must be integer value, " - f"received {type(length).__name__} {length}" - ) - if coupling_strength != int(coupling_strength): - raise CqlibError( - f"G pulse coupling_strength parameter requires integer value, " - f"got {type(coupling_strength).__name__} {coupling_strength}" - ) - self._circuit_data.append( - InstructionData(instruction=G(int(length), int(coupling_strength)), - qubits=coupler_qubits) - ) - return - # Create waveform for pulse operations - waveform = Waveform.load(params, gate) - if gate == 'PXY': - if not qubits: - raise CqlibError("PXY pulse requires valid qubit targets") - self._circuit_data.append( - InstructionData(instruction=PXY(waveform), qubits=qubits) - ) - return - # For PZ/PZ0, validate mutual exclusivity of qubit types - if bool(qubits) == bool(coupler_qubits): - raise CqlibError("Must provide exactly one of qubits or coupler_qubits for PZ/PZ0") - qubits = qubits if len(qubits) > 0 else coupler_qubits - - # Dynamic gate resolution - try: - # pylint: disable=import-outside-toplevel, cyclic-import - from cqlib import pulse - instruction_class = getattr(pulse, gate) - except AttributeError as ex: - raise CqlibError(f"Unknown pulse instruction: {gate}") from ex - if not issubclass(instruction_class, BasePulse): - raise CqlibError(f"{gate} is not a valid pulse instruction") - self._circuit_data.append( - InstructionData(instruction=instruction_class(waveform=waveform), qubits=qubits) - ) diff --git a/cqlib/pulse/pxy.py b/cqlib/pulse/pxy.py deleted file mode 100644 index e0944a09accd37cf94d893bd35e4c01314990da8..0000000000000000000000000000000000000000 --- a/cqlib/pulse/pxy.py +++ /dev/null @@ -1,48 +0,0 @@ -# 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. - -""" -Sequential AC(Alternating Current) pulse controller for data qubit XY channels. -""" -from cqlib.exceptions import CqlibError -from .base_pulse import BasePulse -from .waveform import Waveform, NumericWaveform - - -class PXY(BasePulse): - """ - Pulse controller for data qubit XY channels. - - For PXY pulses, the numerical list of data pulses is in the form of - [i0, i1, ..., in, q0, q1, ..., qn]. The first half describes the - pulse values of I channel, and the second half describes the - pulse values of Q channel. The length of its list is >=6 and - must be even. - """ - - def __init__(self, waveform: Waveform, label: str = None): - """ - Initialize XY channel pulse with waveform parameters - """ - super().__init__('PXY', waveform=waveform, label=label) - - def validate(self): - """ - Verify PXY-specific waveform constraints. - """ - waveform = self._waveform - if waveform.phase is None or waveform.drag_alpha is None: - raise CqlibError('PXY must have phase and drag_alpha params') - if isinstance(waveform, NumericWaveform) \ - and (len(waveform.data_list) < 6 or len(waveform.data_list) % 2 == 1): - raise CqlibError('The length of its list must >=6 and must be even.') diff --git a/cqlib/pulse/pz.py b/cqlib/pulse/pz.py deleted file mode 100644 index d2e277d2be7a1e9ea6675a43b2ad204d8db698b3..0000000000000000000000000000000000000000 --- a/cqlib/pulse/pz.py +++ /dev/null @@ -1,32 +0,0 @@ -# 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. - -""" Sequential DC pulse controller for data/coupling qubit Z channels. """ -from cqlib.exceptions import CqlibError -from .base_pulse import BasePulse -from .waveform import Waveform - - -class PZ(BasePulse): - """ - Sequential DC pulse controller for data/coupling qubit Z channels. - """ - - def __init__(self, waveform: Waveform, label: str = None): - """Initialize Z-axis DC pulse with Stark shift parameters""" - super().__init__('PZ', waveform=waveform, label=label) - - def validate(self): - p = self._waveform - if p.phase is not None or p.drag_alpha is not None: - raise CqlibError('PZ must not have phase and drag_alpha params') diff --git a/cqlib/pulse/pz0.py b/cqlib/pulse/pz0.py deleted file mode 100644 index 20a8bd73e564643dba142e1a1bbf1653b883dbe9..0000000000000000000000000000000000000000 --- a/cqlib/pulse/pz0.py +++ /dev/null @@ -1,43 +0,0 @@ -# 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. - - -"""Parallel DC pulse controller for data/coupling qubit Z channels.""" -from cqlib.exceptions import CqlibError -from .base_pulse import BasePulse -from .waveform import Waveform - - -class PZ0(BasePulse): - """ - Parallel DC pulse controller for data/coupling qubit Z channels. - - Enables synchronized multi-channel DC pulse generation for simultaneous - frequency adjustments. - """ - - def __init__(self, waveform: Waveform, label: str = None): - """ - Initialize parallel Z-axis DC pulse controller. - - Args: - waveform (Waveform): Waveform. - label (str, optional): Operational identifier for batch processing - """ - super().__init__('PZ0', waveform, label=label) - - def validate(self): - """ Verify parallel DC pulse constraints. """ - p = self._waveform - if p.phase is not None or p.drag_alpha is not None: - raise CqlibError('PZ0 must not have phase and drag_alpha params') diff --git a/cqlib/pulse/waveform.py b/cqlib/pulse/waveform.py deleted file mode 100644 index c766d365fe4a0098a852a5c029f7a93cdf38bfa5..0000000000000000000000000000000000000000 --- a/cqlib/pulse/waveform.py +++ /dev/null @@ -1,272 +0,0 @@ -# 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. - -""" -Waveform - -This module defines waveform type enumerations and parameter classes -for generating quantum pulse sequences. It provides a standardized -interface to configure and validate waveform parameters across different -waveform types. -""" -from __future__ import annotations -from dataclasses import dataclass, field -from enum import IntEnum, unique -from math import pi - -from cqlib.exceptions import CqlibError - - -@unique -class WaveformType(IntEnum): - """ - Enumeration of supported waveform types for quantum pulse sequences. - - Members: - COSINE (0): Cosine-shaped waveform. - FLATTOP (1): Flat-top waveform with customizable edge length. - SLEPIAN (2): Slepian tapering waveform defined by time-bandwidth - product parameters. - NUMERIC (3): Arbitrary numeric waveform specified via sample array. - """ - COSINE = 0 - FLATTOP = 1 - SLEPIAN = 2 - NUMERIC = 3 - - -@dataclass(kw_only=True) -class Waveform: - """ - Base dataclass for waveform parameter validation and string representation. - - Attributes: - waveform (WaveformType): Enumerated waveform type. - length (int): Total waveform duration (1-1e5). - amplitude (float): Normalized signal amplitude (0.0-1.0). - phase (float, optional): Phase shift for PXY modulations. - drag_alpha (float, optional): Drag coefficient for PXY modulations. - """ - waveform: WaveformType - length: int - amplitude: float - phase: float = None # PXY only - drag_alpha: float = None # PXY only - - @staticmethod - def create( - w_type: WaveformType, - length: int, - amplitude: float, - phase: float = None, - drag_alpha: float = None, - **kwargs - ) -> 'Waveform': - """ - Factory method to create waveform parameter objects based on the specified type. - - Instantiates a concrete waveform parameter class according to the given waveform type, - validates parameters, and returns the initialized object. - - Args: - w_type(WaveformType): Enumerated waveform type identifier. - length(int): Total duration of the waveform in samples (1-1e5). - amplitude: Normalized signal amplitude (0.0-1.0). - phase (float, optional): Phase shift for PXY. Must be in (-π, π] range. - drag_alpha (float, optional): DRAG correction coefficient for PXY. Must be positive. - **kwargs: Type-specific parameters for advanced waveform configurations: - - FLATTOP: edge (int) - - SLEPIAN: thf, thi, lam2, lam3 (float) - - NUMERIC: samples (list[float]) - """ - param_classes = { - WaveformType.COSINE: CosineWaveform, - WaveformType.FLATTOP: FlattopWaveform, - WaveformType.SLEPIAN: SlepianWaveform, - WaveformType.NUMERIC: NumericWaveform - } - - if w_type not in param_classes: - raise ValueError(f"Unsupported waveform type: {w_type}") - - return param_classes[w_type]( - waveform=w_type, - length=length, - amplitude=amplitude, - phase=phase, - drag_alpha=drag_alpha, - **kwargs - ) - - def validate(self): - """Performs basic parameter validation.""" - if not isinstance(self.length, int) or self.length <= 0 or self.length > 1e5: - raise CqlibError(f"Invalid length: {self.length}. Must be in [0, 1e5]") - if not 0.0 <= self.amplitude <= 1.0: - raise CqlibError(f"Invalid amplitude: {self.amplitude}. Must be in [0, 1]") - if self.phase is not None and (self.phase <= -pi or self.phase >= pi): - raise CqlibError("The phase value must be within (-pi, pi].") - if self.drag_alpha is not None and self.drag_alpha < 0: - raise CqlibError("The drag_alpha value must be positive float.") - - def __str__(self): - return ' '.join(map(str, self.data)) - - @property - def data(self) -> list[float]: - """Data list""" - ps = [self.waveform.value, self.length, self.amplitude] - if self.phase is not None and self.drag_alpha is not None: - ps.extend([self.phase, self.drag_alpha]) - return ps - - @classmethod - def load(cls, waveform: str | list[float | int], gate: str) -> Waveform: - """ - Constructs Waveform from serialized data. - - Args: - waveform (str | list[float | int]): Input data as either: - - Space-separated string of parameters - - List of numeric values - gate (str): Target gate operation (PXY/PZ/etc.) determining - parameter interpretation - - Returns: - Waveform: Instantiated waveform object - """ - if isinstance(waveform, str): - waveform = map(float, waveform.split()) - ps = waveform - try: - waveform_type = WaveformType(ps[0]) - except ValueError as e: - raise CqlibError(f"Invalid waveform type value: {ps[0]}") from e - length, amplitude = ps[1:3] - ps = ps[3:] - if gate == 'PXY': - phase, drag_alpha = ps[:2] - ps = ps[2:] - else: - phase, drag_alpha = None, None - kwargs = { - 'w_type': waveform_type, - 'length': int(length), - 'amplitude': amplitude, - 'phase': phase, - 'drag_alpha': drag_alpha - } - - if waveform_type == WaveformType.FLATTOP: - edge = ps[0] - if edge != int(edge): - raise CqlibError('') - kwargs['edge'] = int(edge) - else: - ps = [int(p) if p == int(p) else p for p in ps] - if waveform_type == WaveformType.SLEPIAN: - kwargs.update({ - 'thf': ps[0], - 'thi': ps[1], - 'lam2': ps[2], - 'lam3': ps[3], - }) - elif waveform_type == WaveformType.NUMERIC: - kwargs['data_list'] = ps - return Waveform.create(**kwargs) - - -@dataclass -class CosineWaveform(Waveform): - """Configuration parameters for cosine-shaped waveforms.""" - waveform: WaveformType = field(default=WaveformType.COSINE) - - def __post_init__(self): - self.validate() - - -@dataclass -class FlattopWaveform(Waveform): - """ - Specialized parameters for flat-top waveforms with edge control. - - Additional Attributes: - edge (int): Length of rising/falling edges (must be < total length/2). - """ - waveform: WaveformType = field(default=WaveformType.FLATTOP) - edge: int = None - - def __post_init__(self): - self.validate() - if self.edge < 0 or not isinstance(self.edge, int): - raise CqlibError(f"Flattop waveform edge parameter must be a positive integer, " - f"got {type(self.edge).__name__} {self.edge}") - if 2 * self.edge >= self.length: - raise CqlibError(f"Edge ({self.edge}) too large for length {self.length}") - - @property - def data(self) -> list[float]: - ps = super().data - ps.append(self.edge) - return ps - - -@dataclass -class SlepianWaveform(Waveform): - """ - Specialized parameters for slepian waveforms - - Additional Attributes: - - thf: - - thi: - - lam2: - - lam3: - """ - waveform: WaveformType = field(default=WaveformType.SLEPIAN) - thf: float = None - thi: float = None - lam2: float = None - lam3: float = None - - @property - def data(self) -> list[float]: - ps = super().data - ps.extend([self.thf, self.thi, self.lam2, self.lam3]) - return ps - - -@dataclass -class NumericWaveform(Waveform): - """ - Specialized parameters for numeric waveforms. - - Additional Attributes: - - params: List of floats, Values must be in [0.0, 1.0], - Minimum length of 3 samples; - """ - waveform: WaveformType = field(default=WaveformType.NUMERIC) - data_list: list[float] = None - - def __post_init__(self): - super().validate() - if any(not (0.0 <= x <= 1.0) for x in self.data_list): - raise CqlibError("All data values must be in [0,1]") - if len(self.data_list) < 3: - raise CqlibError("The numerical list of data pulses is in the form" - " of [d0, d1, ..., dn], and the list length must be >= 3") - - @property - def data(self) -> list[float]: - ps = super().data - ps.extend(self.data_list) - return ps diff --git a/tests/pulse/__init__.py b/tests/pulse/__init__.py deleted file mode 100644 index c32c814b80e89ddb81b1dac29fc75336112c257c..0000000000000000000000000000000000000000 --- a/tests/pulse/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -# 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. diff --git a/tests/pulse/test_instruction.py b/tests/pulse/test_instruction.py deleted file mode 100644 index a9ce7bec63d70a882286055d15d05d4129458b8a..0000000000000000000000000000000000000000 --- a/tests/pulse/test_instruction.py +++ /dev/null @@ -1,128 +0,0 @@ -# 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. - -""" Tests for pulse instruction """ - -import pytest - -from cqlib.exceptions import CqlibError -from cqlib.pulse import WaveformType, Waveform, PZ, PXY, PZ0, G - -# pylint: disable=missing-function-docstring - -class TestPXY: - """Test cases for the PXY pulse class.""" - - def test_cosine(self): - waveform = Waveform.create(WaveformType.COSINE, length=10, amplitude=0.1) - with pytest.raises(CqlibError) as exec_info: - PXY(waveform=waveform) - assert "PXY must have phase and drag_alpha params" in exec_info.value.args[0] - waveform = Waveform.create(WaveformType.COSINE, length=10, amplitude=0.1, - phase=2, drag_alpha=0.2) - pxy = PXY(waveform=waveform) - assert str(pxy) == 'PXY(0,10,0.1,2,0.2)' - - def test_flattop(self): - waveform = Waveform.create(WaveformType.FLATTOP, length=10, amplitude=0.1, edge=1, - phase=2, drag_alpha=0.2) - pxy = PXY(waveform=waveform) - assert str(pxy) == 'PXY(1,10,0.1,2,0.2,1)' - - def test_slepian(self): - waveform = Waveform.create(WaveformType.SLEPIAN, length=10, amplitude=0.1, phase=2, - drag_alpha=0.2, thf=0.1, thi=0.2, lam2=0.3, lam3=1) - pxy = PXY(waveform=waveform) - assert str(pxy) == 'PXY(2,10,0.1,2,0.2,0.1,0.2,0.3,1)' - - def test_numeric(self): - waveform = Waveform.create(WaveformType.NUMERIC, length=10, amplitude=0.1, phase=2, - drag_alpha=0.2, data_list=[0.1, 0.2, 0.3]) - with pytest.raises(CqlibError) as exec_info: - PXY(waveform=waveform) - assert 'The length of its list must >=6 and must be even.' in exec_info.value.args[0] - waveform = Waveform.create(WaveformType.NUMERIC, length=10, amplitude=0.1, phase=2, - drag_alpha=0.2, data_list=[0.1, 0.2, 0.3, 0.11, 0.22, 0.33]) - pxy = PXY(waveform=waveform) - assert str(pxy) == 'PXY(3,10,0.1,2,0.2,0.1,0.2,0.3,0.11,0.22,0.33)' - - -class TestPZ: - """Test cases for the PZ pulse class.""" - - def test_cosine(self): - waveform = Waveform.create(WaveformType.COSINE, length=10, amplitude=0.1) - pz = PZ(waveform=waveform) - assert str(pz) == 'PZ(0,10,0.1)' - - def test_flattop(self): - waveform = Waveform.create(WaveformType.FLATTOP, length=10, amplitude=0.1, edge=1) - pz = PZ(waveform=waveform) - assert str(pz) == 'PZ(1,10,0.1,1)' - - def test_slepian(self): - waveform = Waveform.create(WaveformType.SLEPIAN, length=10, amplitude=0.1, - thf=0.1, thi=0.2, lam2=0.3, lam3=1) - pz = PZ(waveform=waveform) - assert str(pz) == 'PZ(2,10,0.1,0.1,0.2,0.3,1)' - - def test_numeric(self): - waveform = Waveform.create(WaveformType.NUMERIC, length=10, amplitude=0.1, - data_list=[0.1, 0.2, 0.3]) - pz = PZ(waveform=waveform) - assert str(pz) == 'PZ(3,10,0.1,0.1,0.2,0.3)' - - with pytest.raises(CqlibError) as exec_info: - Waveform.create(WaveformType.NUMERIC, length=10, amplitude=0.1, data_list=[0.1, 0.2]) - assert ('The numerical list of data pulses is in the form of [d0, d1, ..., dn]' - ', and the list length must be >= 3') in exec_info.value.args[0] - - -class TestPZ0: - """Test cases for the PZ0 pulse class.""" - - def test_cosine(self): - waveform = Waveform.create(WaveformType.COSINE, length=10, amplitude=0.1) - pz0 = PZ0(waveform=waveform) - assert str(pz0) == 'PZ0(0,10,0.1)' - - def test_flattop(self): - waveform = Waveform.create(WaveformType.FLATTOP, length=10, amplitude=0.1, edge=1) - pz = PZ0(waveform=waveform) - assert str(pz) == 'PZ0(1,10,0.1,1)' - - def test_slepian(self): - waveform = Waveform.create(WaveformType.SLEPIAN, length=10, amplitude=0.1, - thf=0.1, thi=0.2, lam2=0.3, lam3=1) - pz0 = PZ0(waveform=waveform) - assert str(pz0) == 'PZ0(2,10,0.1,0.1,0.2,0.3,1)' - - def test_numeric(self): - waveform = Waveform.create(WaveformType.NUMERIC, length=10, amplitude=0.1, - data_list=[0.1, 0.2, 0.3]) - pz0 = PZ0(waveform=waveform) - assert str(pz0) == 'PZ0(3,10,0.1,0.1,0.2,0.3)' - - -class TestG: - """Test cases for the G (global coupling) pulse class.""" - - def test_g(self): - g = G(length=100, coupling_strength=-3) - assert str(g) == 'G(100,-3)' - - def test_g1(self): - with pytest.raises(TypeError) as exec_info: - # pylint: disable=unexpected-keyword-arg,no-value-for-parameter - G(waveform=100) - assert "got an unexpected keyword argument 'waveform'" in exec_info.value.args[0] diff --git a/tests/pulse/test_pulse_circuit.py b/tests/pulse/test_pulse_circuit.py deleted file mode 100644 index ea0d4e4de175c64aab680b03b21df635ca5a0c59..0000000000000000000000000000000000000000 --- a/tests/pulse/test_pulse_circuit.py +++ /dev/null @@ -1,58 +0,0 @@ -# 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. - -""" testcase for pulse circuit """ - -from cqlib.circuits.qubit import Qubit -from cqlib.pulse import PulseCircuit, CouplerQubit, Waveform, WaveformType - - -def test_coupler_qubit(): - """ test coupler qubits""" - cq = CouplerQubit(1) - assert cq.index == 1 - assert str(cq) == 'G1' - - -def test_pulse_circuit(): - """ test base pulse circuit """ - pc = PulseCircuit(qubits=[0], coupler_qubits=[0]) - assert pc.qubits == [Qubit(0)] - assert pc.coupler_qubits == [CouplerQubit(0)] - - -def test_base_qcis(): - """ test base qcis """ - pc = PulseCircuit(qubits=[0], coupler_qubits=[0]) - pc.h(0) - pc.measure(0) - assert pc.qcis == 'H Q0\nM Q0' - - pc = PulseCircuit(qubits=[0], coupler_qubits=[0]) - waveform = Waveform.create(WaveformType.COSINE, length=100, amplitude=0.2, - phase=1.1, drag_alpha=0.1) - pc.pxy(0, waveform) - pc.pz(CouplerQubit(0), Waveform.create(WaveformType.COSINE, length=100, amplitude=0.2)) - pc.measure(0) - assert pc.qcis == 'PXY Q0 0 100 0.2 1.1 0.1\nPZ G0 0 100 0.2\nM Q0' - - -def test_load_pulse(): - """ test load pulse qcis """ - qcis = """PXY Q0 0 100 0.2 1.1 0.1 -PZ G0 0 100 0.2 -PZ0 Q1 1 100 0.2 10 -G G0 10 1000 -M Q0""" - qc = PulseCircuit.load(qcis) - assert qc.as_str() == qcis diff --git a/tests/pulse/test_waveform.py b/tests/pulse/test_waveform.py deleted file mode 100644 index ab4df6fd6d52068e894e7d6eab11b0fe411d12c3..0000000000000000000000000000000000000000 --- a/tests/pulse/test_waveform.py +++ /dev/null @@ -1,77 +0,0 @@ -# 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 waveform""" - -# pylint: disable=no-value-for-parameter - -from cqlib.pulse import Waveform, WaveformType, CosineWaveform, FlattopWaveform, \ - SlepianWaveform, NumericWaveform - - -def test_cosine(): - """test cosine waveform""" - length = 10 - amplitude = 1 - cos = Waveform.create(WaveformType.COSINE, length=length, amplitude=amplitude) - assert cos.data == [WaveformType.COSINE, length, amplitude] - assert str(cos) == f'{WaveformType.COSINE.value} {length} {amplitude}' - - cos = CosineWaveform(length=length, amplitude=amplitude) - assert str(cos) == f'{WaveformType.COSINE.value} {length} {amplitude}' - - -def test_flattop(): - """test flattop waveform""" - length = 10 - amplitude = 1 - edge = 1 - waveform = Waveform.create(WaveformType.FLATTOP, length=length, amplitude=amplitude, edge=edge) - assert waveform.data == [WaveformType.FLATTOP, length, amplitude, edge] - assert str(waveform) == f'{WaveformType.FLATTOP.value} {length} {amplitude} {edge}' - waveform = FlattopWaveform(length=length, amplitude=amplitude, edge=edge) - assert str(waveform) == f'{WaveformType.FLATTOP.value} {length} {amplitude} {edge}' - - -def test_slepian(): - """test slepian waveform""" - length = 10 - amplitude = 1 - thf, thi, lam2, lam3 = 0.1, 0.2, 0.3, 0.4 - waveform = Waveform.create(WaveformType.SLEPIAN, length=length, amplitude=amplitude, - thf=thf, thi=thi, lam2=lam2, lam3=lam3) - assert waveform.data == [WaveformType.SLEPIAN, length, amplitude, thf, thi, lam2, lam3] - assert str(waveform) == (f'{WaveformType.SLEPIAN.value} {length} ' - f'{amplitude} {thf} {thi} {lam2} {lam3}') - - waveform = SlepianWaveform(length=length, amplitude=amplitude, - thf=thf, thi=thi, lam2=lam2, lam3=lam3) - assert str(waveform) == (f'{WaveformType.SLEPIAN.value} {length} ' - f'{amplitude} {thf} {thi} {lam2} {lam3}') - - -def test_numeric(): - """test numeric waveform""" - length = 10 - amplitude = 1 - params = [0.1, 0.2, 0.3, 0.4] - waveform = Waveform.create(WaveformType.NUMERIC, length=length, amplitude=amplitude, - data_list=params) - d = [WaveformType.NUMERIC.value, length, amplitude] - d.extend(params) - assert waveform.data == d - assert str(waveform) == (f'{WaveformType.NUMERIC.value} {length} ' - f'{amplitude} {" ".join(map(str, params))}') - waveform = NumericWaveform(length=length, amplitude=amplitude, data_list=params) - assert str(waveform) == (f'{WaveformType.NUMERIC.value} {length} ' - f'{amplitude} {" ".join(map(str, params))}')