From d8449fa7c218305e604cce7de6c2b91fb9de6aec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=81=A5=E5=81=A5?= Date: Wed, 30 Sep 2026 16:12:27 +0800 Subject: [PATCH 1/4] feat(core): add payload accessors to Instruction and ValueInstruction Pair every is_*() predicate with an Option-returning payload accessor, so callers can dispatch on the predicate and read the payload without pattern matching or parsing instruction names: - Instruction: mc_gate(), unitary_gate(), circuit_gate(), classical_data() and classical_control() return references, directive() returns the Copy payload by value - ValueInstruction: mc_gate(), unitary_gate(), circuit_gate() and classical_data() delegate to the wrapped Instruction, and classical_control() returns the value-level control-flow payload --- .../src/circuit/gate/instruction.rs | 48 ++++++++++ .../src/circuit/gate/instruction_test.rs | 82 ++++++++++++++++ .../src/circuit/value_instruction.rs | 95 +++++++++++++++++-- 3 files changed, 219 insertions(+), 6 deletions(-) diff --git a/crates/cqlib-core/src/circuit/gate/instruction.rs b/crates/cqlib-core/src/circuit/gate/instruction.rs index 4c33fbaa..31b9513c 100644 --- a/crates/cqlib-core/src/circuit/gate/instruction.rs +++ b/crates/cqlib-core/src/circuit/gate/instruction.rs @@ -156,6 +156,54 @@ impl Instruction { } } + /// Returns the multi-controlled gate when this is an `McGate` instruction. + pub fn mc_gate(&self) -> Option<&MCGate> { + match self { + Self::McGate(gate) => Some(gate), + _ => None, + } + } + + /// Returns the user-defined unitary gate when this is a `UnitaryGate` instruction. + pub fn unitary_gate(&self) -> Option<&UnitaryGate> { + match self { + Self::UnitaryGate(gate) => Some(gate), + _ => None, + } + } + + /// Returns the circuit-backed gate when this is a `CircuitGate` instruction. + pub fn circuit_gate(&self) -> Option<&CircuitGate> { + match self { + Self::CircuitGate(gate) => Some(gate), + _ => None, + } + } + + /// Returns the directive when this is a `Directive` instruction. + pub fn directive(&self) -> Option { + match self { + Self::Directive(directive) => Some(*directive), + _ => None, + } + } + + /// Returns the classical data operation when this is a `ClassicalData` instruction. + pub fn classical_data(&self) -> Option<&ClassicalDataOp> { + match self { + Self::ClassicalData(operation) => Some(operation), + _ => None, + } + } + + /// Returns the classical control operation when this is a `ClassicalControl` instruction. + pub fn classical_control(&self) -> Option<&ClassicalControlOp> { + match self { + Self::ClassicalControl(operation) => Some(operation), + _ => None, + } + } + /// Returns true when this instruction directly or recursively contains a measurement. pub fn has_measurement(&self) -> bool { match self { diff --git a/crates/cqlib-core/src/circuit/gate/instruction_test.rs b/crates/cqlib-core/src/circuit/gate/instruction_test.rs index 7e8a418a..33f33779 100644 --- a/crates/cqlib-core/src/circuit/gate/instruction_test.rs +++ b/crates/cqlib-core/src/circuit/gate/instruction_test.rs @@ -255,3 +255,85 @@ fn classical_control_op_converts_to_instruction() { Instruction::ClassicalControl(ClassicalControlOp::Break) )); } + +#[test] +fn payload_accessors_match_predicates_for_every_variant() { + let circuit_id = CircuitId::new(); + let measured = ClassicalValue::new(circuit_id, 0, ClassicalType::Bit); + let circuit_gate = CircuitGate::new("composite", FrozenCircuit::new(Circuit::new(1))).unwrap(); + + let cases = [ + Instruction::Standard(StandardGate::H), + Instruction::McGate(Box::new(MCGate::new(2, StandardGate::X))), + Instruction::UnitaryGate(Box::new(UnitaryGate::new("oracle", 1, 0))), + Instruction::CircuitGate(Box::new(circuit_gate)), + Instruction::Directive(Directive::Barrier), + Instruction::ClassicalData(ClassicalDataOp::MeasureBit { result: measured }), + Instruction::ClassicalControl(ClassicalControlOp::Break), + Instruction::Delay, + ]; + + for instruction in cases { + assert_eq!( + instruction.standard_gate().is_some(), + instruction.is_standard() + ); + assert_eq!(instruction.mc_gate().is_some(), instruction.is_mcgate()); + assert_eq!( + instruction.unitary_gate().is_some(), + instruction.is_unitary() + ); + assert_eq!( + instruction.circuit_gate().is_some(), + instruction.is_circuit_gate() + ); + assert_eq!( + instruction.directive().is_some(), + instruction.is_directive() + ); + assert_eq!( + instruction.classical_data().is_some(), + instruction.is_classical_data() + ); + assert_eq!( + instruction.classical_control().is_some(), + instruction.is_classical_control() + ); + } +} + +#[test] +fn payload_accessors_return_the_stored_payload() { + let circuit_id = CircuitId::new(); + let measured = ClassicalValue::new(circuit_id, 0, ClassicalType::Bit); + let target = ClassicalVar::new(circuit_id, 0, ClassicalType::Bit); + + let mc = Instruction::McGate(Box::new(MCGate::new(2, StandardGate::RZ))); + let mc_gate = mc.mc_gate().unwrap(); + assert_eq!(mc_gate.num_ctrl_qubits(), 2); + assert_eq!(mc_gate.base_gate(), &StandardGate::RZ); + + let unitary = Instruction::UnitaryGate(Box::new(UnitaryGate::new("oracle", 2, 1))); + let unitary_gate = unitary.unitary_gate().unwrap(); + assert_eq!(unitary_gate.label(), "oracle"); + assert_eq!(unitary_gate.num_qubits(), 2); + + let circuit_gate = CircuitGate::new("composite", FrozenCircuit::new(Circuit::new(1))).unwrap(); + let circuit = Instruction::CircuitGate(Box::new(circuit_gate)); + assert_eq!(circuit.circuit_gate().unwrap().name(), "composite"); + + let directive = Instruction::Directive(Directive::Measure); + assert_eq!(directive.directive(), Some(Directive::Measure)); + + let store = Instruction::ClassicalData(ClassicalDataOp::Store { + target, + value: ClassicalExpr::value(measured), + }); + assert_eq!(store.classical_data().unwrap().target(), Some(target)); + + let control = Instruction::ClassicalControl(ClassicalControlOp::Continue); + assert!(matches!( + control.classical_control(), + Some(ClassicalControlOp::Continue) + )); +} diff --git a/crates/cqlib-core/src/circuit/value_instruction.rs b/crates/cqlib-core/src/circuit/value_instruction.rs index e57d5b17..5cb8fd9c 100644 --- a/crates/cqlib-core/src/circuit/value_instruction.rs +++ b/crates/cqlib-core/src/circuit/value_instruction.rs @@ -47,9 +47,11 @@ use crate::circuit::classical::{ClassicalValue, ClassicalVar}; use crate::circuit::classical_expr::ClassicalExpr; use crate::circuit::control_flow::{ControlBody, validate_for_types, validate_switch}; use crate::circuit::error::CircuitError; +use crate::circuit::gate::circuit_gate::CircuitGate; use crate::circuit::gate::directive::Directive; use crate::circuit::gate::instruction::Instruction; use crate::circuit::gate::standard_gate::StandardGate; +use crate::circuit::gate::{ClassicalDataOp, MCGate, UnitaryGate}; use crate::circuit::operation::{Operation, ValueOperation}; use alloc::collections::BTreeSet; use std::fmt; @@ -551,17 +553,40 @@ impl ValueInstruction { /// Returns the standard gate when this is a standard-gate instruction. pub fn standard_gate(&self) -> Option { - match self { - Self::Instruction(Instruction::Standard(gate)) => Some(*gate), - _ => None, - } + self.as_instruction().and_then(Instruction::standard_gate) } /// Returns the directive when this is a directive instruction. pub fn directive(&self) -> Option { + self.as_instruction().and_then(Instruction::directive) + } + + /// Returns the multi-controlled gate when this is an mc-gate instruction. + pub fn mc_gate(&self) -> Option<&MCGate> { + self.as_instruction().and_then(Instruction::mc_gate) + } + + /// Returns the user-defined unitary gate when this is a unitary instruction. + pub fn unitary_gate(&self) -> Option<&UnitaryGate> { + self.as_instruction().and_then(Instruction::unitary_gate) + } + + /// Returns the circuit-backed gate when this is a circuit-gate instruction. + pub fn circuit_gate(&self) -> Option<&CircuitGate> { + self.as_instruction().and_then(Instruction::circuit_gate) + } + + /// Returns the classical data operation when this is a classical-data instruction. + pub fn classical_data(&self) -> Option<&ClassicalDataOp> { + self.as_instruction().and_then(Instruction::classical_data) + } + + /// Returns the value-level control-flow operation when this is a classical + /// control-flow instruction. + pub fn classical_control(&self) -> Option<&ValueClassicalControlOp> { match self { - Self::Instruction(Instruction::Directive(directive)) => Some(*directive), - _ => None, + Self::Instruction(_) => None, + Self::ClassicalControl(op) => Some(op), } } @@ -789,6 +814,64 @@ mod tests { assert!(delay.is_delay()); } + #[test] + fn value_instruction_delegates_payload_accessors() { + let mc = ValueInstruction::from_instruction(Instruction::McGate(Box::new(MCGate::new( + 2, + StandardGate::X, + )))); + assert!(mc.is_mcgate()); + let mc_gate = mc.mc_gate().unwrap(); + assert_eq!(mc_gate.num_ctrl_qubits(), 2); + assert_eq!(mc_gate.base_gate(), &StandardGate::X); + assert!(mc.unitary_gate().is_none()); + assert!(mc.circuit_gate().is_none()); + assert!(mc.classical_data().is_none()); + + let unitary = ValueInstruction::from_instruction(Instruction::UnitaryGate(Box::new( + UnitaryGate::new("oracle", 1, 0), + ))); + assert!(unitary.is_unitary()); + assert_eq!( + unitary.unitary_gate().map(|gate| gate.label()), + Some("oracle") + ); + assert!(unitary.mc_gate().is_none()); + + let circuit_gate = CircuitGate::new( + "composite", + crate::circuit::gate::circuit_gate::FrozenCircuit::new(crate::circuit::Circuit::new(1)), + ) + .unwrap(); + let circuit = + ValueInstruction::from_instruction(Instruction::CircuitGate(Box::new(circuit_gate))); + assert_eq!( + circuit.circuit_gate().map(|gate| gate.name()), + Some("composite") + ); + + let value = + ClassicalValue::new(crate::circuit::CircuitId::default(), 0, ClassicalType::Bit); + let data = ValueInstruction::from_instruction(Instruction::ClassicalData( + ClassicalDataOp::MeasureBit { result: value }, + )); + assert_eq!( + data.classical_data().and_then(|op| op.result()), + Some(value) + ); + + let control = ValueInstruction::from(ValueClassicalControlOp::Break); + assert!(control.mc_gate().is_none()); + assert!(control.unitary_gate().is_none()); + assert!(control.circuit_gate().is_none()); + assert!(control.classical_data().is_none()); + assert_eq!( + control.classical_control(), + Some(&ValueClassicalControlOp::Break) + ); + assert!(data.classical_control().is_none()); + } + #[test] fn value_classical_control_reads_and_writes() { let var = ClassicalVar::new( From 459da0af99b8d6ddf9f6881db3d28bad52a0aa4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=81=A5=E5=81=A5?= Date: Wed, 30 Sep 2026 16:13:13 +0800 Subject: [PATCH 2/4] feat(binding-python): expose instruction payload accessors and ClassicalDataOp - Add mc_gate / unitary_gate / circuit_gate / classical_data getters to Instruction and ValueInstruction, cloning the payload by value; gate parameters belong to the operation layer (ValueOperation.params) - Register the new ClassicalDataOp class wrapping core classical data ops, with kind / target / value / result getters, its runtime module and type stubs --- .../binding-python/cqlib/circuit/__init__.py | 2 + .../binding-python/cqlib/circuit/__init__.pyi | 3 + .../cqlib/circuit/classical_data.py | 19 ++ .../cqlib/circuit/classical_data.pyi | 46 ++++ .../cqlib/circuit/operation.pyi | 56 +++++ .../src/circuit/classical_data.rs | 85 ++++++++ .../binding-python/src/circuit/instruction.rs | 188 +++++++++++++++- crates/binding-python/src/circuit/mod.rs | 3 + .../circuit/test_instruction_accessors.py | 200 ++++++++++++++++++ 9 files changed, 592 insertions(+), 10 deletions(-) create mode 100644 crates/binding-python/cqlib/circuit/classical_data.py create mode 100644 crates/binding-python/cqlib/circuit/classical_data.pyi create mode 100644 crates/binding-python/src/circuit/classical_data.rs create mode 100644 crates/binding-python/tests/circuit/test_instruction_accessors.py diff --git a/crates/binding-python/cqlib/circuit/__init__.py b/crates/binding-python/cqlib/circuit/__init__.py index 9c07cc62..b75bda5d 100644 --- a/crates/binding-python/cqlib/circuit/__init__.py +++ b/crates/binding-python/cqlib/circuit/__init__.py @@ -49,6 +49,7 @@ ClassicalValue = _circuit_module.ClassicalValue Measurement = _circuit_module.Measurement ClassicalExpr = _circuit_module.ClassicalExpr +ClassicalDataOp = _circuit_module.ClassicalDataOp ValueControlBody = _circuit_module.ValueControlBody ValueSwitchCase = _circuit_module.ValueSwitchCase ClassicalControlOp = _circuit_module.ClassicalControlOp @@ -66,6 +67,7 @@ "CircuitId", "CircuitError", "ClassicalControlOp", + "ClassicalDataOp", "ClassicalExpr", "ClassicalType", "ClassicalValue", diff --git a/crates/binding-python/cqlib/circuit/__init__.pyi b/crates/binding-python/cqlib/circuit/__init__.pyi index 137a02ca..5eb8918a 100644 --- a/crates/binding-python/cqlib/circuit/__init__.pyi +++ b/crates/binding-python/cqlib/circuit/__init__.pyi @@ -57,6 +57,7 @@ Submodule Purpose Key types ``.gates`` Gate definitions and factory constants :class:`StandardGate` ``.parameter`` Symbolic/numeric parameter expressions :class:`Parameter` ``.classical`` Runtime classical types and storage handles :class:`ClassicalType` +``.classical_data`` Runtime classical data operations :class:`ClassicalDataOp` ``.classical_expr`` Typed classical expression AST :class:`ClassicalExpr` ``.control_flow`` Classical control-flow operations :class:`ClassicalControlOp` ``.operation`` Instruction and operation types :class:`ValueOperation` @@ -84,6 +85,7 @@ from .classical import ( ClassicalVar as ClassicalVar, Measurement as Measurement, ) +from .classical_data import ClassicalDataOp as ClassicalDataOp from .classical_expr import ClassicalExpr as ClassicalExpr from .control_flow import ( ClassicalControlOp as ClassicalControlOp, @@ -121,6 +123,7 @@ __all__ = [ "CircuitId", "CircuitError", "ClassicalControlOp", + "ClassicalDataOp", "ClassicalExpr", "ClassicalType", "ClassicalValue", diff --git a/crates/binding-python/cqlib/circuit/classical_data.py b/crates/binding-python/cqlib/circuit/classical_data.py new file mode 100644 index 00000000..64c27fde --- /dev/null +++ b/crates/binding-python/cqlib/circuit/classical_data.py @@ -0,0 +1,19 @@ +# This code is part of Cqlib. +# +# (C) Copyright China Telecom Quantum Group 2026 +# +# 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. + +"""Runtime exports for classical data operations.""" + +from .._native import circuit as _circuit_module + +ClassicalDataOp = _circuit_module.ClassicalDataOp + +__all__ = ["ClassicalDataOp"] diff --git a/crates/binding-python/cqlib/circuit/classical_data.pyi b/crates/binding-python/cqlib/circuit/classical_data.pyi new file mode 100644 index 00000000..1c389662 --- /dev/null +++ b/crates/binding-python/cqlib/circuit/classical_data.pyi @@ -0,0 +1,46 @@ +# This code is part of Cqlib. +# +# (C) Copyright China Telecom Quantum Group 2026 +# +# 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. + +"""Runtime classical data operations. + +:class:`ClassicalDataOp` models side-effecting classical updates in a +circuit schedule: stores into mutable classical variables and measurements +producing immutable classical values. Instances are obtained from +:attr:`Instruction.classical_data` or :attr:`ValueInstruction.classical_data`; +they are not constructed directly. +""" + +from .classical import ClassicalValue, ClassicalVar +from .classical_expr import ClassicalExpr + +class ClassicalDataOp: + """Runtime classical data operation (store or measurement).""" + @property + def kind(self) -> str: + """One of ``"store"``, ``"measure_bit"``, or ``"measure_bits"``.""" + ... + @property + def target(self) -> ClassicalVar | None: + """The store target variable if this is a store operation.""" + ... + @property + def value(self) -> ClassicalExpr | None: + """The stored expression if this is a store operation.""" + ... + @property + def result(self) -> ClassicalValue | None: + """The immutable value receiving the result if this is a measurement.""" + ... + def __eq__(self, other: object) -> bool: ... + def __copy__(self) -> ClassicalDataOp: ... + def __deepcopy__(self, memo: dict) -> ClassicalDataOp: ... + def __repr__(self) -> str: ... diff --git a/crates/binding-python/cqlib/circuit/operation.pyi b/crates/binding-python/cqlib/circuit/operation.pyi index d422a7a6..0e541f29 100644 --- a/crates/binding-python/cqlib/circuit/operation.pyi +++ b/crates/binding-python/cqlib/circuit/operation.pyi @@ -21,6 +21,7 @@ qubits and parameters to form a complete circuit operation. import numpy as np from numpy.typing import NDArray from .bit import Qubit +from .classical_data import ClassicalDataOp from .control_flow import ClassicalControlOp from .gates import CircuitGate, Directive, MCGate, StandardGate, UnitaryGate from .parameter import Parameter @@ -99,6 +100,34 @@ class Instruction: def directive(self) -> Directive | None: """The :class:`Directive` if this is a directive instruction.""" ... + @property + def mc_gate(self) -> MCGate | None: + """The :class:`MCGate` if this is an mc-gate instruction. + + The returned gate carries no bound parameters: parameters belong to + the operation (``Operation.params`` / ``ValueOperation.params``), not + to the instruction. + """ + ... + @property + def unitary_gate(self) -> UnitaryGate | None: + """The :class:`UnitaryGate` if this is a unitary instruction. + + Parameters of a parametric unitary belong to the operation, not to + the instruction. + """ + ... + @property + def circuit_gate(self) -> CircuitGate | None: + """The :class:`CircuitGate` if this is a circuit-gate instruction. + + Parameters belong to the operation, not to the instruction. + """ + ... + @property + def classical_data(self) -> ClassicalDataOp | None: + """The :class:`ClassicalDataOp` if this is a classical-data instruction.""" + ... def __str__(self) -> str: ... def __copy__(self) -> Instruction: ... def __deepcopy__(self, memo: dict) -> Instruction: ... @@ -153,6 +182,33 @@ class ValueInstruction: """The :class:`Directive` if this is a directive instruction.""" ... @property + def mc_gate(self) -> MCGate | None: + """The :class:`MCGate` if this is an mc-gate instruction. + + The returned gate carries no bound parameters: parameters belong to + the operation (``ValueOperation.params``), not to the instruction. + """ + ... + @property + def unitary_gate(self) -> UnitaryGate | None: + """The :class:`UnitaryGate` if this is a unitary instruction. + + Parameters of a parametric unitary belong to the operation, not to + the instruction. + """ + ... + @property + def circuit_gate(self) -> CircuitGate | None: + """The :class:`CircuitGate` if this is a circuit-gate instruction. + + Parameters belong to the operation, not to the instruction. + """ + ... + @property + def classical_data(self) -> ClassicalDataOp | None: + """The :class:`ClassicalDataOp` if this is a classical-data instruction.""" + ... + @property def instruction(self) -> Instruction | None: """The inner :class:`Instruction` when this is a plain instruction.""" ... diff --git a/crates/binding-python/src/circuit/classical_data.rs b/crates/binding-python/src/circuit/classical_data.rs new file mode 100644 index 00000000..c16d7350 --- /dev/null +++ b/crates/binding-python/src/circuit/classical_data.rs @@ -0,0 +1,85 @@ +// This code is part of Cqlib. +// +// (C) Copyright China Telecom Quantum Group 2026 +// +// 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. + +//! Python wrapper for runtime classical data operations. + +use crate::circuit::{PyClassicalExpr, PyClassicalValue, PyClassicalVar}; +use cqlib_core::circuit::gate::ClassicalDataOp; +use pyo3::prelude::*; + +/// Runtime classical data operation: a store into a mutable classical +/// variable, or a measurement producing an immutable classical value. +/// +/// Instances are obtained from `Instruction.classical_data` / +/// `ValueInstruction.classical_data`; they are not constructed directly. +#[pyclass(name = "ClassicalDataOp", module = "cqlib.circuit", from_py_object)] +#[derive(Debug, Clone)] +pub struct PyClassicalDataOp { + pub(crate) inner: ClassicalDataOp, +} + +impl From for PyClassicalDataOp { + fn from(inner: ClassicalDataOp) -> Self { + Self { inner } + } +} + +#[pymethods] +impl PyClassicalDataOp { + /// Returns the operation kind: `'store'`, `'measure_bit'`, or + /// `'measure_bits'`. + #[getter] + pub fn kind(&self) -> &'static str { + self.inner.name() + } + + /// Returns the store target variable if this is a store operation, + /// None otherwise. + #[getter] + pub fn target(&self) -> Option { + self.inner.target().map(PyClassicalVar::from) + } + + /// Returns the stored expression if this is a store operation, + /// None otherwise. + #[getter] + pub fn value(&self) -> Option { + self.inner.value().cloned().map(PyClassicalExpr::from) + } + + /// Returns the immutable value receiving the result if this is a + /// measurement operation, None otherwise. + #[getter] + pub fn result(&self) -> Option { + self.inner.result().map(PyClassicalValue::from) + } + + fn __repr__(&self) -> String { + format!("ClassicalDataOp({})", self.inner.name()) + } + + fn __eq__(&self, other: &Bound<'_, PyAny>) -> PyResult { + if !other.is_instance_of::() { + return Ok(false); + } + let other = other.extract::()?; + Ok(self.inner == other.inner) + } + + fn __copy__(&self) -> Self { + self.clone() + } + + fn __deepcopy__(&self, _memo: &Bound<'_, PyAny>) -> Self { + self.clone() + } +} diff --git a/crates/binding-python/src/circuit/instruction.rs b/crates/binding-python/src/circuit/instruction.rs index 6aeed35a..08cbeb17 100644 --- a/crates/binding-python/src/circuit/instruction.rs +++ b/crates/binding-python/src/circuit/instruction.rs @@ -19,7 +19,8 @@ use crate::circuit::error::CircuitError as PyCircuitError; use crate::circuit::gate::standard::standard_gate_from_name; use crate::circuit::{ - PyCircuitGate, PyClassicalControlOp, PyDirective, PyMcGate, PyStandardGate, PyUnitaryGate, + PyCircuitGate, PyClassicalControlOp, PyClassicalDataOp, PyDirective, PyMcGate, PyStandardGate, + PyUnitaryGate, }; use crate::utils::python_string_literal; use cqlib_core::circuit::{Instruction, ValueInstruction}; @@ -180,10 +181,50 @@ impl PyInstruction { #[getter] fn directive(&self) -> Option { - match &self.inner { - Instruction::Directive(directive) => Some(PyDirective::from(*directive)), - _ => None, - } + self.inner.directive().map(PyDirective::from) + } + + /// Returns the multi-controlled gate if this is an mc-gate instruction, + /// None otherwise. + /// + /// The returned gate carries no bound parameters: parameters belong to the + /// operation (`Operation.params` / `ValueOperation.params`), not to the + /// instruction. + #[getter] + fn mc_gate(&self) -> Option { + self.inner.mc_gate().cloned().map(|gate| PyMcGate { + inner: gate, + params: vec![], + }) + } + + /// Returns the unitary gate if this is a unitary instruction, None otherwise. + /// + /// Parameters of a parametric unitary belong to the operation + /// (`Operation.params` / `ValueOperation.params`), not to the instruction. + #[getter] + fn unitary_gate(&self) -> Option { + self.inner.unitary_gate().cloned().map(PyUnitaryGate::from) + } + + /// Returns the circuit-backed gate if this is a circuit-gate instruction, + /// None otherwise. + /// + /// Parameters belong to the operation (`Operation.params` / + /// `ValueOperation.params`), not to the instruction. + #[getter] + fn circuit_gate(&self) -> Option { + self.inner.circuit_gate().cloned().map(PyCircuitGate::from) + } + + /// Returns the classical data operation if this is a classical-data + /// instruction, None otherwise. + #[getter] + fn classical_data(&self) -> Option { + self.inner + .classical_data() + .cloned() + .map(PyClassicalDataOp::from) } fn __str__(&self) -> String { @@ -327,6 +368,48 @@ impl PyValueInstruction { self.inner.directive().map(PyDirective::from) } + /// Returns the multi-controlled gate if this is an mc-gate instruction, + /// None otherwise. + /// + /// The returned gate carries no bound parameters: parameters belong to the + /// operation (`ValueOperation.params`), not to the instruction. + #[getter] + fn mc_gate(&self) -> Option { + self.inner.mc_gate().cloned().map(|gate| PyMcGate { + inner: gate, + params: vec![], + }) + } + + /// Returns the unitary gate if this is a unitary instruction, None otherwise. + /// + /// Parameters of a parametric unitary belong to the operation + /// (`ValueOperation.params`), not to the instruction. + #[getter] + fn unitary_gate(&self) -> Option { + self.inner.unitary_gate().cloned().map(PyUnitaryGate::from) + } + + /// Returns the circuit-backed gate if this is a circuit-gate instruction, + /// None otherwise. + /// + /// Parameters belong to the operation (`ValueOperation.params`), not to + /// the instruction. + #[getter] + fn circuit_gate(&self) -> Option { + self.inner.circuit_gate().cloned().map(PyCircuitGate::from) + } + + /// Returns the classical data operation if this is a classical-data + /// instruction, None otherwise. + #[getter] + fn classical_data(&self) -> Option { + self.inner + .classical_data() + .cloned() + .map(PyClassicalDataOp::from) + } + #[getter] fn instruction(&self) -> Option { self.inner @@ -337,10 +420,10 @@ impl PyValueInstruction { #[getter] fn classical_control(&self) -> Option { - match &self.inner { - ValueInstruction::ClassicalControl(control) => Some(control.clone().into()), - ValueInstruction::Instruction(_) => None, - } + self.inner + .classical_control() + .cloned() + .map(PyClassicalControlOp::from) } fn __str__(&self) -> String { @@ -371,11 +454,96 @@ impl PyValueInstruction { #[cfg(test)] mod tests { use super::*; - use cqlib_core::circuit::{Parameter, StandardGate}; + use cqlib_core::circuit::gate::{ + CircuitGate, ClassicalDataOp, FrozenCircuit, MCGate, UnitaryGate, + }; + use cqlib_core::circuit::{ + Circuit, CircuitId, ClassicalType, ClassicalValue, Parameter, StandardGate, + }; #[test] fn storage_instruction_rejects_bound_gate_parameters() { let gate = PyStandardGate::from(StandardGate::RX, vec![Parameter::symbol("theta")]); assert!(PyInstruction::from_standard_gate(gate).is_err()); } + + #[test] + fn storage_instruction_payload_accessors_return_cloned_gates() { + let mc = PyInstruction::from(Instruction::McGate(Box::new(MCGate::new( + 2, + StandardGate::X, + )))); + let mc_gate = mc.mc_gate().expect("mc gate"); + assert_eq!(mc_gate.inner.num_ctrl_qubits(), 2); + assert_eq!(*mc_gate.inner.base_gate(), StandardGate::X); + assert!(mc_gate.params.is_empty()); + assert!(mc.unitary_gate().is_none()); + assert!(mc.circuit_gate().is_none()); + assert!(mc.classical_data().is_none()); + + let unitary = PyInstruction::from(Instruction::UnitaryGate(Box::new(UnitaryGate::new( + "oracle", 2, 1, + )))); + let unitary_gate = unitary.unitary_gate().expect("unitary gate"); + assert_eq!(unitary_gate.label(), "oracle"); + assert_eq!(unitary_gate.num_qubits(), 2); + assert!(unitary.mc_gate().is_none()); + + let circuit_gate = + CircuitGate::new("composite", FrozenCircuit::new(Circuit::new(1))).unwrap(); + let circuit = PyInstruction::from(Instruction::CircuitGate(Box::new(circuit_gate))); + assert!(circuit.circuit_gate().is_some()); + assert!(circuit.mc_gate().is_none()); + + let standard = PyInstruction::from(Instruction::Standard(StandardGate::H)); + assert!(standard.mc_gate().is_none()); + assert!(standard.unitary_gate().is_none()); + assert!(standard.circuit_gate().is_none()); + assert!(standard.classical_data().is_none()); + } + + #[test] + fn storage_instruction_classical_data_accessor_returns_operation() { + let result = ClassicalValue::new(CircuitId::new(), 0, ClassicalType::Bit); + let instruction = + PyInstruction::from(Instruction::ClassicalData(ClassicalDataOp::MeasureBit { + result, + })); + + let data = instruction.classical_data().expect("classical data"); + assert_eq!(data.kind(), "measure_bit"); + assert_eq!(data.result().map(|value| value.inner), Some(result)); + assert!(data.target().is_none()); + assert!(data.value().is_none()); + } + + #[test] + fn value_instruction_payload_accessors_delegate() { + let mc = PyValueInstruction::from(ValueInstruction::from_instruction(Instruction::McGate( + Box::new(MCGate::new(3, StandardGate::Z)), + ))); + let mc_gate = mc.mc_gate().expect("mc gate"); + assert_eq!(mc_gate.inner.num_ctrl_qubits(), 3); + assert!(mc_gate.params.is_empty()); + assert!(mc.unitary_gate().is_none()); + assert!(mc.circuit_gate().is_none()); + assert!(mc.classical_data().is_none()); + + let unitary = PyValueInstruction::from(ValueInstruction::from_instruction( + Instruction::UnitaryGate(Box::new(UnitaryGate::new("oracle", 1, 0))), + )); + assert_eq!( + unitary.unitary_gate().map(|gate| gate.label()), + Some("oracle".to_string()) + ); + + let control = + PyValueInstruction::from_classical_control(crate::circuit::PyClassicalControlOp { + inner: cqlib_core::circuit::ValueClassicalControlOp::Break, + }); + assert!(control.mc_gate().is_none()); + assert!(control.unitary_gate().is_none()); + assert!(control.circuit_gate().is_none()); + assert!(control.classical_data().is_none()); + } } diff --git a/crates/binding-python/src/circuit/mod.rs b/crates/binding-python/src/circuit/mod.rs index ab68fa4c..6ddc3555 100644 --- a/crates/binding-python/src/circuit/mod.rs +++ b/crates/binding-python/src/circuit/mod.rs @@ -21,6 +21,7 @@ pub mod bit; pub mod circuit_impl; pub mod circuit_to_matrix; pub mod classical; +pub mod classical_data; pub mod classical_expr; pub mod control_flow; pub mod dag; @@ -42,6 +43,7 @@ pub use circuit_to_matrix::py_circuit_to_matrix; pub use classical::{ PyCircuitId, PyClassicalType, PyClassicalValue, PyClassicalVar, PyMeasurement, }; +pub use classical_data::PyClassicalDataOp; pub use classical_expr::PyClassicalExpr; pub use control_flow::{ PyClassicalControlOp, PySwitchBuilder, PyValueControlBody, PyValueSwitchCase, @@ -73,6 +75,7 @@ pub(crate) fn register_circuit_module(parent: &Bound<'_, PyModule>) -> PyResult< m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; diff --git a/crates/binding-python/tests/circuit/test_instruction_accessors.py b/crates/binding-python/tests/circuit/test_instruction_accessors.py new file mode 100644 index 00000000..b2da7dfd --- /dev/null +++ b/crates/binding-python/tests/circuit/test_instruction_accessors.py @@ -0,0 +1,200 @@ +# This code is part of Cqlib. +# +# (C) Copyright China Telecom Quantum Group 2026 +# +# 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. + +""" +Tests for Instruction / ValueInstruction payload accessors. + +Test coverage: +- Predicate/accessor symmetry for every instruction variant +- MCGate / UnitaryGate / CircuitGate payload contents +- ClassicalDataOp payload accessors (kind, target, value, result) +- Parameter ownership: instruction-level gates carry no bound parameters +- Non-matching variants return None +""" + +import pytest + +from cqlib.circuit import ( + Circuit, + ClassicalDataOp, + ClassicalControlOp, + Instruction, + Parameter, + Qubit, + ValueInstruction, + ValueOperation, +) +from cqlib.circuit.gates import Directive, MCGate, RX, StandardGate, UnitaryGate + +# instruction_type -> (predicate property, payload accessor property) +VARIANTS = { + "standard": ("is_standard", "standard_gate"), + "mcgate": ("is_mcgate", "mc_gate"), + "unitary": ("is_unitary", "unitary_gate"), + "circuit": ("is_circuit_gate", "circuit_gate"), + "directive": ("is_directive", "directive"), + "classical_data": ("is_classical_data", "classical_data"), +} + + +def _circuit_gate(): + sub = Circuit(1) + sub.h(0) + return sub.to_gate("h_gate") + + +def _instructions(): + """One Instruction per directly constructible variant.""" + return [ + ("standard", Instruction.from_standard_gate(StandardGate.H)), + ("mcgate", Instruction.from_mc_gate(MCGate(2, StandardGate.X))), + ("unitary", Instruction.from_unitary_gate(UnitaryGate("oracle", 1))), + ("circuit", Instruction.from_circuit_gate(_circuit_gate())), + ("directive", Instruction.from_directive(Directive.barrier())), + ] + + +class TestInstructionAccessorSymmetry: + """Every predicate must pair with a payload accessor that agrees with it.""" + + @pytest.mark.parametrize(("kind", "instruction"), _instructions()) + def test_accessors_agree_with_predicates(self, kind, instruction) -> None: + assert instruction.instruction_type == kind + for other_kind, (predicate, accessor) in VARIANTS.items(): + matching = other_kind == kind + assert getattr(instruction, predicate) is matching + assert (getattr(instruction, accessor) is not None) is matching + + def test_delay_has_no_payload(self) -> None: + instruction = Instruction.delay() + assert instruction.is_delay + for predicate, accessor in VARIANTS.values(): + assert getattr(instruction, predicate) is False + assert getattr(instruction, accessor) is None + + +class TestValueInstructionAccessorSymmetry: + @pytest.mark.parametrize(("kind", "instruction"), _instructions()) + def test_accessors_agree_with_predicates(self, kind, instruction) -> None: + value_instruction = ValueInstruction.from_instruction(instruction) + assert value_instruction.instruction_type == kind + for other_kind, (predicate, accessor) in VARIANTS.items(): + matching = other_kind == kind + assert getattr(value_instruction, predicate) is matching + assert (getattr(value_instruction, accessor) is not None) is matching + + def test_classical_control_has_no_gate_payload(self) -> None: + value_instruction = ValueInstruction.from_classical_control( + ClassicalControlOp.break_() + ) + assert value_instruction.is_classical_control + assert value_instruction.classical_control is not None + for predicate, accessor in VARIANTS.values(): + assert getattr(value_instruction, predicate) is False + assert getattr(value_instruction, accessor) is None + + +class TestMcGatePayload: + def test_payload_contents(self) -> None: + instruction = Instruction.from_mc_gate(MCGate(2, StandardGate.RZ)) + gate = instruction.mc_gate + assert gate is not None + assert gate.num_ctrl_qubits == 2 + assert gate.base_gate == StandardGate.RZ + assert gate.num_qubits == 3 + + def test_payload_carries_no_bound_parameters(self) -> None: + """Parameters belong to the operation, not to the instruction.""" + theta = Parameter("theta") + operation = ValueOperation.from_mc_gate( + MCGate(1, RX(theta)), [Qubit(0), Qubit(1)] + ) + instruction = operation.instruction + assert instruction.is_mcgate + gate = instruction.mc_gate + assert gate is not None + assert gate.params == [] + assert operation.params == [theta] + + +class TestUnitaryGatePayload: + def test_payload_contents(self) -> None: + instruction = Instruction.from_unitary_gate(UnitaryGate("oracle", 2)) + gate = instruction.unitary_gate + assert gate is not None + assert gate.label == "oracle" + assert gate.num_qubits == 2 + + +class TestCircuitGatePayload: + def test_payload_contents(self) -> None: + instruction = Instruction.from_circuit_gate(_circuit_gate()) + gate = instruction.circuit_gate + assert gate is not None + assert gate.name == "h_gate" + assert gate.num_qubits == 1 + + +class TestDirectivePayload: + def test_payload_contents(self) -> None: + instruction = Instruction.from_directive(Directive.measure()) + directive = instruction.directive + assert directive is not None + assert str(directive) == "measure" + + +class TestClassicalDataPayload: + def test_value_instruction_payload(self) -> None: + circuit = Circuit(1) + circuit.measure(0) + instruction = circuit.operations[0].instruction + assert instruction.is_classical_data + data = instruction.classical_data + assert isinstance(data, ClassicalDataOp) + assert data.kind == "measure_bit" + assert data.result is not None + assert data.target is None + assert data.value is None + + def test_storage_instruction_payload(self) -> None: + circuit = Circuit(2) + circuit.measure_bits([0, 1]) + value_instruction = circuit.operations[0].instruction + instruction = value_instruction.instruction + assert instruction is not None + assert instruction.is_classical_data + data = instruction.classical_data + assert data is not None + assert data.kind == "measure_bits" + assert data.result is not None + + def test_store_payload(self) -> None: + circuit = Circuit(1) + measurement = circuit.measure(0) + target = circuit.var(measurement.ty) + circuit.store(target, measurement.expr()) + instruction = circuit.operations[1].instruction + assert instruction.is_classical_data + data = instruction.classical_data + assert data is not None + assert data.kind == "store" + assert data.target is not None + assert data.value is not None + assert data.result is None + + +class TestClassicalDataSubmodule: + def test_runtime_submodule_matches_stub(self) -> None: + import cqlib.circuit.classical_data as submodule + + assert submodule.ClassicalDataOp is ClassicalDataOp + assert submodule.__all__ == ["ClassicalDataOp"] From 9e7cee259faa198e620cd845a2e6b4ad867a3063 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=81=A5=E5=81=A5?= Date: Wed, 30 Sep 2026 16:14:02 +0800 Subject: [PATCH 3/4] docs: document instruction payload accessors and ClassicalDataOp - List the new Instruction / ValueInstruction payload accessors in the Python and Rust API references (EN + CN) - Add a ClassicalDataOp section to the classical data / control flow pages and to the circuit overview tables --- docs/api/cn/python/0_circuit/0_overview.md | 1 + .../0_circuit/4_operation_instruction.md | 10 +++++++ .../0_circuit/9_classical_control_flow.md | 29 ++++++++++++++++++- .../rust/0_circuit/4_operation_instruction.md | 25 +++++++++++++--- docs/api/en/python/0_circuit/0_overview.md | 1 + .../0_circuit/4_operation_instruction.md | 10 +++++++ .../0_circuit/9_classical_control_flow.md | 27 +++++++++++++++++ .../rust/0_circuit/4_operation_instruction.md | 23 +++++++++++++-- 8 files changed, 118 insertions(+), 8 deletions(-) diff --git a/docs/api/cn/python/0_circuit/0_overview.md b/docs/api/cn/python/0_circuit/0_overview.md index b7caf528..c4e513f7 100644 --- a/docs/api/cn/python/0_circuit/0_overview.md +++ b/docs/api/cn/python/0_circuit/0_overview.md @@ -111,6 +111,7 @@ print(bound.used_symbols) # [] | `ClassicalVar` | [Classical / Control Flow](9_classical_control_flow.md) | 可变经典存储句柄,由 `Circuit.var()` 创建。 | | `ClassicalValue` | [Classical / Control Flow](9_classical_control_flow.md) | 不可变经典值,通常由测量产生。 | | `Measurement` | [Classical / Control Flow](9_classical_control_flow.md) | 测量回执,包含测量值和被测量的量子比特顺序。 | +| `ClassicalDataOp` | [Classical / Control Flow](9_classical_control_flow.md) | 经典数据操作的指令形式(变量写入或测量结果写入),通过 `Instruction.classical_data` 获得。 | | `ClassicalExpr` | [Classical / Control Flow](9_classical_control_flow.md) | 类型化经典表达式 AST,用于条件、比较、位抽取和表达式组合。 | | `ClassicalControlOp` | [Classical / Control Flow](9_classical_control_flow.md) | 结构化控制流 IR,包含 `if`、`while`、`for`、`switch`、`break` 和 `continue`。 | | `ValueControlBody` | [Classical / Control Flow](9_classical_control_flow.md) | 构造层控制流体,包含若干 `ValueOperation`。 | diff --git a/docs/api/cn/python/0_circuit/4_operation_instruction.md b/docs/api/cn/python/0_circuit/4_operation_instruction.md index 868dd572..f1dd7e98 100644 --- a/docs/api/cn/python/0_circuit/4_operation_instruction.md +++ b/docs/api/cn/python/0_circuit/4_operation_instruction.md @@ -71,7 +71,13 @@ delay_inst = Instruction.delay() | `is_classical_data` | `bool` | 是否为经典数据相关指令。 | | `is_delay` | `bool` | 是否为延迟指令。 | | `standard_gate` | `StandardGate / None` | 当指令为标准门时,返回内部标准门对象。 | +| `mc_gate` | `MCGate / None` | 当指令为多控制门时,返回内部 `MCGate` 对象。 | +| `unitary_gate` | `UnitaryGate / None` | 当指令为用户自定义酉门时,返回内部 `UnitaryGate` 对象。 | +| `circuit_gate` | `CircuitGate / None` | 当指令为子线路复合门时,返回内部 `CircuitGate` 对象。 | | `directive` | `Directive / None` | 当指令为 `Directive` 时,返回内部 directive 对象。 | +| `classical_data` | `ClassicalDataOp / None` | 当指令为经典数据操作时,返回内部 `ClassicalDataOp` 对象。 | + +这些 payload 访问器返回的门对象不携带绑定参数:参数属于 operation 层(见 `ValueOperation.params`),返回门的参数列表恒为空。注意 `Instruction` 不提供 `classical_control` payload 访问器:存储层控制流操作内联了线路内部句柄,其内容只能在构造层通过 `ValueInstruction.classical_control` 读取。 --- @@ -98,7 +104,11 @@ delay_inst = Instruction.delay() | `instruction_type` | `str` | 指令类别,取值与 `Instruction.instruction_type` 一致;包裹控制流时为 `"classical_control"`。 | | `is_standard` / `is_mcgate` / `is_unitary` / `is_circuit_gate` / `is_directive` / `is_classical_data` / `is_delay` | `bool` | 与 `Instruction` 同名的类别判定属性;包裹控制流时均为 `False`。 | | `standard_gate` | `StandardGate / None` | 当包裹的是标准门指令时,返回内部标准门对象。 | +| `mc_gate` | `MCGate / None` | 当包裹的是多控制门指令时,返回内部 `MCGate` 对象。 | +| `unitary_gate` | `UnitaryGate / None` | 当包裹的是用户自定义酉门指令时,返回内部 `UnitaryGate` 对象。 | +| `circuit_gate` | `CircuitGate / None` | 当包裹的是子线路复合门指令时,返回内部 `CircuitGate` 对象。 | | `directive` | `Directive / None` | 当包裹的是 `Directive` 指令时,返回内部 directive 对象。 | +| `classical_data` | `ClassicalDataOp / None` | 当包裹的是经典数据指令时,返回内部 `ClassicalDataOp` 对象。 | --- diff --git a/docs/api/cn/python/0_circuit/9_classical_control_flow.md b/docs/api/cn/python/0_circuit/9_classical_control_flow.md index 41926948..3fabfe58 100644 --- a/docs/api/cn/python/0_circuit/9_classical_control_flow.md +++ b/docs/api/cn/python/0_circuit/9_classical_control_flow.md @@ -5,6 +5,7 @@ - `cqlib.circuit.ClassicalVar` - `cqlib.circuit.ClassicalValue` - `cqlib.circuit.Measurement` +- `cqlib.circuit.ClassicalDataOp` - `cqlib.circuit.ClassicalExpr` - `cqlib.circuit.ClassicalControlOp` - `cqlib.circuit.ValueControlBody` @@ -15,6 +16,7 @@ from cqlib.circuit import ( CircuitId, ClassicalType, ClassicalExpr, + ClassicalDataOp, ClassicalControlOp, ValueControlBody, ValueSwitchCase, @@ -35,6 +37,7 @@ from cqlib.circuit import ( | `ClassicalVar` | 可变经典变量句柄,由 `Circuit.var()` 创建。 | | `ClassicalValue` | 不可变经典值句柄,通常由测量产生。 | | `Measurement` | 测量回执,记录测量结果句柄和被测量的量子比特顺序。 | +| `ClassicalDataOp` | 经典数据操作的指令形式(变量写入或测量结果写入),通过 `Instruction.classical_data` 获得。 | | `ClassicalExpr` | 无副作用的经典表达式,可表示字面量、变量读取、比较和逻辑组合。 | | `ClassicalControlOp` | 低层控制流对象,用于表示 `if`、`while`、`for`、`switch`、`break` 和 `continue`。 | | `ValueControlBody` | 控制流分支体或循环体中的操作序列。 | @@ -238,6 +241,30 @@ condition = single.expr().to_bool() --- +## `ClassicalDataOp` + +`ClassicalDataOp` 是经典数据操作的指令形式:写入可变经典变量,或产生不可变经典值的测量。它不直接构造,实例通过 `Instruction` 或 `ValueInstruction` 的 `classical_data` 属性获得。 + +| 属性 | 类型 | 说明 | +|---|---|---| +| `kind` | `str` | 操作类别:`"store"`、`"measure_bit"` 或 `"measure_bits"`。 | +| `target` | `ClassicalVar / None` | 当 `kind` 为 `"store"` 时,返回写入的目标变量。 | +| `value` | `ClassicalExpr / None` | 当 `kind` 为 `"store"` 时,返回写入的表达式。 | +| `result` | `ClassicalValue / None` | 当 `kind` 为 `"measure_bit"` 或 `"measure_bits"` 时,返回接收结果的不可变值。 | + +```python +from cqlib import Circuit + +circuit = Circuit(2) +circuit.measure_bits([0, 1]) + +data = circuit.operations[0].instruction.classical_data +print(data.kind) # measure_bits +print(data.result) # 测量产生的 ClassicalValue +``` + +--- + ## `ClassicalExpr` `ClassicalExpr` 它用于描述经典计算关系,如读取变量、读取测量值、构造字面量、执行逻辑运算、比较表达式、条件选择、位抽取和位拼接。 @@ -618,4 +645,4 @@ circuit = Circuit(1) circuit.if_(ClassicalExpr.bool_literal(True), lambda body: body.compose(branch)) ``` -这种方式可以将“量子门级验证”和“控制流结构构造”分开处理,使测试和调试过程更加清晰。 \ No newline at end of file +这种方式可以将“量子门级验证”和“控制流结构构造”分开处理,使测试和调试过程更加清晰。 diff --git a/docs/api/cn/rust/0_circuit/4_operation_instruction.md b/docs/api/cn/rust/0_circuit/4_operation_instruction.md index 33a86171..7143049f 100644 --- a/docs/api/cn/rust/0_circuit/4_operation_instruction.md +++ b/docs/api/cn/rust/0_circuit/4_operation_instruction.md @@ -98,11 +98,25 @@ pub enum Instruction { | `is_delay()` | 是否为延迟指令。 | | `is_quantum_gate()` | 是否为酉量子门,即前四类之一。 | -### 3. 结构相等 +### 3. Payload 访问器 + +每个类别谓词都配有一个 payload 访问器,返回包裹的定义对象,调用方可以先用 `is_*()` 分派、再读取内容,无需手动模式匹配。变体不匹配时访问器一律返回 `None`。 + +| 方法 | 返回 | 说明 | +| --- | --- | --- | +| `standard_gate()` | `Option` | 当指令为标准门时,返回标准门定义。 | +| `mc_gate()` | `Option<&MCGate>` | 当指令为多控制门时,返回多控制门定义。 | +| `unitary_gate()` | `Option<&UnitaryGate>` | 当指令为自定义酉门时,返回酉门定义。 | +| `circuit_gate()` | `Option<&CircuitGate>` | 当指令为复合门时,返回复合门定义。 | +| `directive()` | `Option` | 当指令为非酉指令时,返回对应 `Directive`。 | +| `classical_data()` | `Option<&ClassicalDataOp>` | 当指令为经典数据操作时,返回经典数据操作。 | +| `classical_control()` | `Option<&ClassicalControlOp>` | 当指令为结构化控制流时,返回存储层控制流操作。 | + +### 4. 结构相等 `Instruction` 实现了 `PartialEq`。两个指令相等当且仅当变体与内部定义在结构上一致,比较过程不涉及量子比特绑定与参数取值。 -### 4. 常用方法 +### 5. 常用方法 | 方法 | 说明 | | --- | --- | @@ -113,7 +127,7 @@ pub enum Instruction { | `inverse(params)` | 尝试返回反指令及反参数;不可逆指令返回 `None`。 | | `control(num_new_ctrls)` | 尝试将当前指令提升为受控指令。 | -### 5. `From` 转换 +### 6. `From` 转换 `Instruction` 支持从多种底层类型转换而来,便于在构造 IR 或编译 pass 中快速创建指令。 @@ -277,7 +291,10 @@ pub enum ValueInstruction { | `is_standard()` / `is_mcgate()` / `is_unitary()` / `is_circuit_gate()` | 判断是否分别为标准门、多控制门、自定义酉门、复合门指令。 | | `is_directive()` / `is_classical_data()` / `is_delay()` | 判断是否分别为非酉指令、经典数据操作、延迟指令。 | | `standard_gate()` | 当指令为标准门时返回对应 `StandardGate`。 | +| `mc_gate()` / `unitary_gate()` / `circuit_gate()` | 当指令属于对应类别时,返回对应门定义的引用。 | | `directive()` | 当指令为非酉指令时返回对应 `Directive`。 | +| `classical_data()` | 当指令为经典数据操作时,返回对应 `ClassicalDataOp` 的引用。 | +| `classical_control()` | 当指令为构造层控制流时,返回对应 `ValueClassicalControlOp` 的引用。 | | `as_instruction()` | 读取内部 `Instruction` 引用。 | | `into_instruction()` | 消耗对象并取出内部 `Instruction`。 | @@ -419,4 +436,4 @@ op.label = Some("calibrated-rz".into()); circuit.validate()?; ``` -这样可以尽早发现 IR 构造问题,避免错误延迟到矩阵转换、设备映射或真实后端执行阶段。 \ No newline at end of file +这样可以尽早发现 IR 构造问题,避免错误延迟到矩阵转换、设备映射或真实后端执行阶段。 diff --git a/docs/api/en/python/0_circuit/0_overview.md b/docs/api/en/python/0_circuit/0_overview.md index dec0bf09..0571912e 100644 --- a/docs/api/en/python/0_circuit/0_overview.md +++ b/docs/api/en/python/0_circuit/0_overview.md @@ -111,6 +111,7 @@ This page gives a brief description of terms commonly used in the `cqlib.circuit | `ClassicalVar` | [Classical / Control Flow](9_classical_control_flow.md) | A mutable classical storage handle, created by `Circuit.var()`. | | `ClassicalValue` | [Classical / Control Flow](9_classical_control_flow.md) | An immutable classical value, usually produced by measurement. | | `Measurement` | [Classical / Control Flow](9_classical_control_flow.md) | A measurement receipt, containing the measured values and the order of the measured qubits. | +| `ClassicalDataOp` | [Classical / Control Flow](9_classical_control_flow.md) | The instruction form of a classical data operation (a store or a measurement result write), obtained from `Instruction.classical_data`. | | `ClassicalExpr` | [Classical / Control Flow](9_classical_control_flow.md) | A typed classical expression AST, used for conditions, comparisons, bit extraction and expression composition. | | `ClassicalControlOp` | [Classical / Control Flow](9_classical_control_flow.md) | The structured control flow IR, including `if`, `while`, `for`, `switch`, `break` and `continue`. | | `ValueControlBody` | [Classical / Control Flow](9_classical_control_flow.md) | A construction-layer control flow body, containing several `ValueOperation`s. | diff --git a/docs/api/en/python/0_circuit/4_operation_instruction.md b/docs/api/en/python/0_circuit/4_operation_instruction.md index 59ba67c5..566b516f 100644 --- a/docs/api/en/python/0_circuit/4_operation_instruction.md +++ b/docs/api/en/python/0_circuit/4_operation_instruction.md @@ -71,7 +71,13 @@ delay_inst = Instruction.delay() | `is_classical_data` | `bool` | Whether it is an instruction related to classical data. | | `is_delay` | `bool` | Whether it is a delay instruction. | | `standard_gate` | `StandardGate / None` | When the instruction is a standard gate, return the internal standard gate object. | +| `mc_gate` | `MCGate / None` | When the instruction is a multi-controlled gate, return the internal `MCGate` object. | +| `unitary_gate` | `UnitaryGate / None` | When the instruction is a user-defined unitary gate, return the internal `UnitaryGate` object. | +| `circuit_gate` | `CircuitGate / None` | When the instruction is a sub-circuit composite gate, return the internal `CircuitGate` object. | | `directive` | `Directive / None` | When the instruction is a `Directive`, return the internal directive object. | +| `classical_data` | `ClassicalDataOp / None` | When the instruction is a classical data operation, return the internal `ClassicalDataOp` object. | + +Gate objects returned by these payload accessors carry no bound parameters: parameters belong to the operation layer (see `ValueOperation.params`), so the returned gates always have an empty parameter list. Note that `Instruction` provides no `classical_control` payload accessor: the storage-layer control flow operation inlines circuit-internal handles, and its content can only be read at the construction layer through `ValueInstruction.classical_control`. --- @@ -98,7 +104,11 @@ delay_inst = Instruction.delay() | `instruction_type` | `str` | The instruction category, with the same values as `Instruction.instruction_type`; it is `"classical_control"` when control flow is wrapped. | | `is_standard` / `is_mcgate` / `is_unitary` / `is_circuit_gate` / `is_directive` / `is_classical_data` / `is_delay` | `bool` | Category check attributes with the same names as on `Instruction`; all are `False` when control flow is wrapped. | | `standard_gate` | `StandardGate / None` | When a standard gate instruction is wrapped, return the internal standard gate object. | +| `mc_gate` | `MCGate / None` | When a multi-controlled gate instruction is wrapped, return the internal `MCGate` object. | +| `unitary_gate` | `UnitaryGate / None` | When a user-defined unitary gate instruction is wrapped, return the internal `UnitaryGate` object. | +| `circuit_gate` | `CircuitGate / None` | When a sub-circuit composite gate instruction is wrapped, return the internal `CircuitGate` object. | | `directive` | `Directive / None` | When a `Directive` instruction is wrapped, return the internal directive object. | +| `classical_data` | `ClassicalDataOp / None` | When a classical data instruction is wrapped, return the internal `ClassicalDataOp` object. | --- diff --git a/docs/api/en/python/0_circuit/9_classical_control_flow.md b/docs/api/en/python/0_circuit/9_classical_control_flow.md index 2795589d..e5766ec1 100644 --- a/docs/api/en/python/0_circuit/9_classical_control_flow.md +++ b/docs/api/en/python/0_circuit/9_classical_control_flow.md @@ -5,6 +5,7 @@ - `cqlib.circuit.ClassicalVar` - `cqlib.circuit.ClassicalValue` - `cqlib.circuit.Measurement` +- `cqlib.circuit.ClassicalDataOp` - `cqlib.circuit.ClassicalExpr` - `cqlib.circuit.ClassicalControlOp` - `cqlib.circuit.ValueControlBody` @@ -15,6 +16,7 @@ from cqlib.circuit import ( CircuitId, ClassicalType, ClassicalExpr, + ClassicalDataOp, ClassicalControlOp, ValueControlBody, ValueSwitchCase, @@ -35,6 +37,7 @@ Classical data and control flow objects fall into the following categories: | `ClassicalVar` | A mutable classical variable handle, created by `Circuit.var()`. | | `ClassicalValue` | An immutable classical value handle, usually produced by measurement. | | `Measurement` | A measurement receipt, recording the measurement result handle and the order of the measured qubits. | +| `ClassicalDataOp` | The instruction form of a classical data operation (a store or a measurement result write), obtained from `Instruction.classical_data`. | | `ClassicalExpr` | A side-effect-free classical expression, which can represent literals, variable reads, comparisons and logical composition. | | `ClassicalControlOp` | A low-level control flow object, used to represent `if`, `while`, `for`, `switch`, `break` and `continue`. | | `ValueControlBody` | The operation sequence in a control flow branch body or loop body. | @@ -238,6 +241,30 @@ condition = single.expr().to_bool() --- +## `ClassicalDataOp` + +`ClassicalDataOp` is the instruction form of a classical data operation: a store into a mutable classical variable, or a measurement producing an immutable classical value. It is not constructed directly; instances are obtained from the `classical_data` attribute of `Instruction` or `ValueInstruction`. + +| Attribute | Type | Description | +|---|---|---| +| `kind` | `str` | The operation kind: `"store"`, `"measure_bit"` or `"measure_bits"`. | +| `target` | `ClassicalVar / None` | The store target variable when `kind` is `"store"`. | +| `value` | `ClassicalExpr / None` | The stored expression when `kind` is `"store"`. | +| `result` | `ClassicalValue / None` | The immutable value receiving the result when `kind` is `"measure_bit"` or `"measure_bits"`. | + +```python +from cqlib import Circuit + +circuit = Circuit(2) +circuit.measure_bits([0, 1]) + +data = circuit.operations[0].instruction.classical_data +print(data.kind) # measure_bits +print(data.result) # the produced ClassicalValue +``` + +--- + ## `ClassicalExpr` `ClassicalExpr` describes classical computation relations, such as reading variables, reading measurement values, constructing literals, performing logical operations, comparing expressions, conditional selection, bit extraction and bit concatenation. diff --git a/docs/api/en/rust/0_circuit/4_operation_instruction.md b/docs/api/en/rust/0_circuit/4_operation_instruction.md index b76256c2..2a8df7a4 100644 --- a/docs/api/en/rust/0_circuit/4_operation_instruction.md +++ b/docs/api/en/rust/0_circuit/4_operation_instruction.md @@ -98,11 +98,25 @@ Category checks use a set of constant predicates, all evaluated at compile time: | `is_delay()` | Whether the instruction is a delay instruction. | | `is_quantum_gate()` | Whether the instruction is a unitary quantum gate, that is, one of the first four categories. | -### 3. Structural equality +### 3. Payload accessors + +Each category predicate is paired with a payload accessor that returns the wrapped definition, so callers can dispatch on `is_*()` and then read the payload without pattern matching. Every accessor returns `None` when the variant does not match. + +| Method | Returns | Description | +| --- | --- | --- | +| `standard_gate()` | `Option` | The standard gate definition when the instruction is a standard gate. | +| `mc_gate()` | `Option<&MCGate>` | The multi-controlled gate definition when the instruction is an mc-gate instruction. | +| `unitary_gate()` | `Option<&UnitaryGate>` | The user-defined unitary gate definition when the instruction is a unitary instruction. | +| `circuit_gate()` | `Option<&CircuitGate>` | The composite gate definition when the instruction is a circuit-gate instruction. | +| `directive()` | `Option` | The directive when the instruction is a non-unitary instruction. | +| `classical_data()` | `Option<&ClassicalDataOp>` | The classical data operation when the instruction is a classical data operation. | +| `classical_control()` | `Option<&ClassicalControlOp>` | The storage-layer control flow operation when the instruction is structured control flow. | + +### 4. Structural equality `Instruction` implements `PartialEq`. Two instructions are equal if and only if their variants and internal definitions agree structurally; the comparison does not involve qubit binding or parameter values. -### 4. Common methods +### 5. Common methods | Method | Description | | --- | --- | @@ -113,7 +127,7 @@ Category checks use a set of constant predicates, all evaluated at compile time: | `inverse(params)` | Try to return the inverse instruction and inverse parameters; non-invertible instructions return `None`. | | `control(num_new_ctrls)` | Try to promote the current instruction to a controlled instruction. | -### 5. `From` conversions +### 6. `From` conversions `Instruction` supports conversion from several underlying types, which makes it convenient to create instructions quickly when building IR or in compiler passes. @@ -277,7 +291,10 @@ The common methods are as follows: | `is_standard()` / `is_mcgate()` / `is_unitary()` / `is_circuit_gate()` | Determine whether the instruction is a standard gate, a multi-controlled gate, a custom unitary gate or a composite gate respectively. | | `is_directive()` / `is_classical_data()` / `is_delay()` | Determine whether the instruction is a non-unitary instruction, a classical data operation or a delay instruction respectively. | | `standard_gate()` | Return the corresponding `StandardGate` when the instruction is a standard gate. | +| `mc_gate()` / `unitary_gate()` / `circuit_gate()` | Return a reference to the corresponding gate definition when the instruction is of the matching category. | | `directive()` | Return the corresponding `Directive` when the instruction is a non-unitary instruction. | +| `classical_data()` | Return a reference to the `ClassicalDataOp` when the instruction is a classical data operation. | +| `classical_control()` | Return a reference to the `ValueClassicalControlOp` when the instruction is construction-layer control flow. | | `as_instruction()` | Read a reference to the inner `Instruction`. | | `into_instruction()` | Consume the object and take out the inner `Instruction`. | From 72cedd3ca53c5243158b9a09fff2d6adcc4b40e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=81=A5=E5=81=A5?= Date: Wed, 30 Sep 2026 16:37:41 +0800 Subject: [PATCH 4/4] chore(release): bump versions to 0.1.0-beta.3 / 2.0.0-beta3 --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- crates/binding-c/Cargo.toml | 2 +- crates/binding-python/Cargo.toml | 4 ++-- crates/cqlib-core/README.md | 4 ++-- crates/cqlib/Cargo.toml | 2 +- docs/releasing.md | 6 +++--- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a6eac5bc..a3f3c1e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -165,7 +165,7 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "binding-c" -version = "0.1.0-beta.2" +version = "0.1.0-beta.3" dependencies = [ "cbindgen", "cqlib-core", @@ -177,7 +177,7 @@ dependencies = [ [[package]] name = "binding-python" -version = "2.0.0-beta2" +version = "2.0.0-beta3" dependencies = [ "chrono", "cqlib-core", @@ -464,14 +464,14 @@ dependencies = [ [[package]] name = "cqlib" -version = "0.1.0-beta.2" +version = "0.1.0-beta.3" dependencies = [ "cqlib-core", ] [[package]] name = "cqlib-core" -version = "0.1.0-beta.2" +version = "0.1.0-beta.3" dependencies = [ "approx", "bitvec", diff --git a/Cargo.toml b/Cargo.toml index de2a69a9..d12b31b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ members = [ resolver = "2" [workspace.package] -version = "0.1.0-beta.2" +version = "0.1.0-beta.3" edition = "2024" rust-version = "1.89" license = "Apache-2.0" diff --git a/crates/binding-c/Cargo.toml b/crates/binding-c/Cargo.toml index d7b538f2..cc9dea41 100644 --- a/crates/binding-c/Cargo.toml +++ b/crates/binding-c/Cargo.toml @@ -9,7 +9,7 @@ license.workspace = true crate-type = ["rlib", "staticlib", "cdylib"] [dependencies] -cqlib-core = { path = "../cqlib-core", version = "0.1.0-beta.2" } +cqlib-core = { path = "../cqlib-core", version = "0.1.0-beta.3" } libc = "0.2" ndarray.workspace = true num-complex.workspace = true diff --git a/crates/binding-python/Cargo.toml b/crates/binding-python/Cargo.toml index 2ac30fc5..7ed2767a 100644 --- a/crates/binding-python/Cargo.toml +++ b/crates/binding-python/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "binding-python" -version = "2.0.0-beta2" +version = "2.0.0-beta3" edition.workspace = true rust-version.workspace = true license-file.workspace = true @@ -14,7 +14,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] pyo3 = { version = "0.29", features = ["abi3-py310", "num-complex", "chrono"] } chrono = "0.4" -cqlib-core = { path = "../cqlib-core", version = '0.1.0-beta.2' } +cqlib-core = { path = "../cqlib-core", version = '0.1.0-beta.3' } num-complex.workspace = true numpy.workspace = true rustworkx-core.workspace = true diff --git a/crates/cqlib-core/README.md b/crates/cqlib-core/README.md index f6bf3f7c..e9e5d17a 100644 --- a/crates/cqlib-core/README.md +++ b/crates/cqlib-core/README.md @@ -14,13 +14,13 @@ Add the crate to your project: ```toml [dependencies] -cqlib-core = "0.1.0-beta.2" +cqlib-core = "0.1.0-beta.3" ``` Or use Cargo: ```shell -cargo add cqlib-core@0.1.0-beta.2 +cargo add cqlib-core@0.1.0-beta.3 ``` ## Quick start diff --git a/crates/cqlib/Cargo.toml b/crates/cqlib/Cargo.toml index 735273f3..8cd25336 100644 --- a/crates/cqlib/Cargo.toml +++ b/crates/cqlib/Cargo.toml @@ -26,4 +26,4 @@ categories = [ [dependencies] -cqlib-core = { path = "../cqlib-core", version = "0.1.0-beta.2" } +cqlib-core = { path = "../cqlib-core", version = "0.1.0-beta.3" } diff --git a/docs/releasing.md b/docs/releasing.md index 8cbcd033..f7b9b3d7 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -40,9 +40,9 @@ Tags use the Python version to identify a release batch: - Stable releases: `vX.Y.Z`. - Prereleases: `vX.Y.Z-alpha.N`, `vX.Y.Z-beta.N`, or `vX.Y.Z-rc.N`, with N starting at 1. -- Current tag: `v2.0.0-beta.2`. -- Current Python source version: `2.0.0-beta2`, normalized to `2.0.0b2` for PEP 440 and wheel filenames. -- Current Rust/C version: `0.1.0-beta.2`; both inherit the workspace version. +- Current tag: `v2.0.0-beta.3`. +- Current Python source version: `2.0.0-beta3`, normalized to `2.0.0b3` for PEP 440 and wheel filenames. +- Current Rust/C version: `0.1.0-beta.3`; both inherit the workspace version. Python and Rust/C versions evolve independently and do not need matching numbers. The Cargo.toml files at a given tag record the version mapping for that