Skip to main content

cqlib_core/circuit/
classical.rs

1// This code is part of Cqlib.
2//
3// (C) Copyright China Telecom Quantum Group 2026
4//
5// This code is licensed under the Apache License, Version 2.0. You may
6// obtain a copy of this license in the LICENSE.txt file in the root directory
7// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8//
9// Any modifications or derivative works of this code must retain this
10// copyright notice, and modified files need to carry a notice indicating
11// that they have been altered from the originals.
12
13//! Runtime classical storage for circuits.
14//!
15//! This module defines the first layer of the dynamic control-flow model:
16//! typed classical values and storage locations that exist only while a
17//! circuit executes. A [`ClassicalValue`] is an immutable runtime result,
18//! similar to an SSA value. A [`ClassicalVar`] is a circuit-local handle to a
19//! mutable runtime storage location. Control-flow expressions may read both,
20//! while store operations may only write variables.
21
22use std::fmt;
23use std::num::{NonZeroU32, NonZeroU64};
24use std::sync::atomic::{AtomicU64, Ordering};
25
26use crate::circuit::bit::Qubit;
27use crate::circuit::classical_expr::ClassicalExpr;
28use crate::device::Outcome;
29use crate::qis::error::QisError;
30use smallvec::SmallVec;
31
32static NEXT_CIRCUIT_ID: AtomicU64 = AtomicU64::new(1);
33
34/// Process-local identity for classical handles owned by a circuit.
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
36pub struct CircuitId(NonZeroU64);
37
38impl fmt::Display for CircuitId {
39    /// Formats the process-local identity as `CircuitId(<id>)`.
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        write!(f, "CircuitId({})", self.0)
42    }
43}
44
45impl CircuitId {
46    /// Allocates a new process-local circuit identity.
47    pub fn new() -> Self {
48        let id = NEXT_CIRCUIT_ID
49            .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
50                current.checked_add(1).filter(|next| *next != u64::MAX)
51            })
52            .unwrap_or_else(|_| panic!("circuit identity space exhausted"));
53        Self(NonZeroU64::new(id).expect("circuit identity counter must remain non-zero"))
54    }
55}
56
57impl Default for CircuitId {
58    fn default() -> Self {
59        Self::new()
60    }
61}
62
63/// Static type of a runtime classical variable.
64///
65/// `Bit` and `BitVec` are the direct targets of measurement operations.
66/// `Bool` is kept distinct from `Bit` so control-flow predicates can require
67/// explicit boolean expressions. `UInt` represents an unsigned integer with a
68/// fixed bit width.
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
70pub enum ClassicalType {
71    /// A single measured or assigned bit.
72    Bit,
73    /// A logical boolean value.
74    Bool,
75    /// An unsigned integer with the given non-zero bit width.
76    UInt(NonZeroU32),
77    /// An ordered bit-vector with the given non-zero bit width.
78    BitVec(NonZeroU32),
79}
80
81impl ClassicalType {
82    /// Creates an unsigned integer type with `width` bits.
83    ///
84    /// Returns `None` when `width` is zero.
85    pub fn uint(width: u32) -> Option<Self> {
86        NonZeroU32::new(width).map(Self::UInt)
87    }
88
89    /// Creates a bit-vector type with `width` bits.
90    ///
91    /// Returns `None` when `width` is zero.
92    pub fn bit_vec(width: u32) -> Option<Self> {
93        NonZeroU32::new(width).map(Self::BitVec)
94    }
95
96    /// Returns the number of bits used to represent values of this type.
97    pub fn width(self) -> u32 {
98        match self {
99            Self::Bit | Self::Bool => 1,
100            Self::UInt(width) | Self::BitVec(width) => width.get(),
101        }
102    }
103
104    /// Returns the zero literal for this type.
105    ///
106    /// Width-bearing literals currently support widths up to 128 bits.
107    ///
108    /// # Errors
109    ///
110    /// Returns [`CircuitError`](crate::circuit::CircuitError) when this is a
111    /// `UInt` or `BitVec` wider than the 128-bit literal representation.
112    pub fn zero_literal(self) -> Result<ClassicalExpr, crate::circuit::CircuitError> {
113        match self {
114            Self::Bool => Ok(ClassicalExpr::bool_literal(false)),
115            Self::Bit => Ok(ClassicalExpr::bit_literal(false)),
116            Self::UInt(width) => ClassicalExpr::uint_literal(width.get(), 0),
117            Self::BitVec(width) => ClassicalExpr::bit_vec_literal(width.get(), 0),
118        }
119    }
120
121    /// Returns the one literal for this type.
122    ///
123    /// Width-bearing literals currently support widths up to 128 bits.
124    ///
125    /// # Errors
126    ///
127    /// Returns [`CircuitError`](crate::circuit::CircuitError) when this is a
128    /// `UInt` or `BitVec` wider than the 128-bit literal representation.
129    pub fn one_literal(self) -> Result<ClassicalExpr, crate::circuit::CircuitError> {
130        match self {
131            Self::Bool => Ok(ClassicalExpr::bool_literal(true)),
132            Self::Bit => Ok(ClassicalExpr::bit_literal(true)),
133            Self::UInt(width) => ClassicalExpr::uint_literal(width.get(), 1),
134            Self::BitVec(width) => ClassicalExpr::bit_vec_literal(width.get(), 1),
135        }
136    }
137
138    /// Returns the number of measured bits accepted by this type.
139    ///
140    /// Only `Bit` and `BitVec` are valid direct measurement targets. `Bool`
141    /// and `UInt` require explicit expression-level conversion.
142    pub fn measurement_width(self) -> Option<u32> {
143        match self {
144            Self::Bit => Some(1),
145            Self::BitVec(width) => Some(width.get()),
146            Self::Bool | Self::UInt(_) => None,
147        }
148    }
149}
150
151/// Circuit-local handle to a mutable runtime classical storage location.
152///
153/// The handle carries its static type so expression and operation builders can
154/// validate uses without maintaining parallel typed ID families. The `id` is
155/// meaningful only inside the circuit that allocated it.
156#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
157pub struct ClassicalVar {
158    circuit_id: CircuitId,
159    index: u32,
160    ty: ClassicalType,
161}
162
163impl ClassicalVar {
164    /// Creates a new classical variable handle.
165    ///
166    /// Prefer [`Circuit::var`](super::Circuit::var) for normal circuit
167    /// construction so the handle is registered in the circuit's type table.
168    pub fn new(circuit_id: CircuitId, index: u32, ty: ClassicalType) -> Self {
169        Self {
170            circuit_id,
171            index,
172            ty,
173        }
174    }
175
176    /// Returns the circuit-local variable identifier.
177    pub fn id(self) -> u32 {
178        self.index
179    }
180
181    /// Returns the circuit-local variable table index.
182    pub fn index(self) -> u32 {
183        self.index
184    }
185
186    /// Returns the identity of the circuit that owns this variable.
187    pub fn circuit_id(self) -> CircuitId {
188        self.circuit_id
189    }
190
191    /// Returns the static type of this variable.
192    pub fn ty(self) -> ClassicalType {
193        self.ty
194    }
195
196    /// Creates an expression that reads this variable's current runtime value.
197    pub fn expr(self) -> ClassicalExpr {
198        ClassicalExpr::var(self)
199    }
200}
201
202/// Circuit-local handle to an immutable runtime classical value.
203///
204/// Values are produced by operations such as measurement. Unlike
205/// [`ClassicalVar`], a value is never overwritten. If a runtime result must be
206/// preserved as loop-carried mutable state, store it into a [`ClassicalVar`].
207#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
208pub struct ClassicalValue {
209    circuit_id: CircuitId,
210    index: u32,
211    ty: ClassicalType,
212}
213
214impl ClassicalValue {
215    /// Creates a new classical value handle.
216    ///
217    /// Prefer circuit measurement builders for normal circuit construction so
218    /// the value has a dominating producer in the circuit IR.
219    pub fn new(circuit_id: CircuitId, index: u32, ty: ClassicalType) -> Self {
220        Self {
221            circuit_id,
222            index,
223            ty,
224        }
225    }
226
227    /// Returns the circuit-local value table index.
228    pub fn index(self) -> u32 {
229        self.index
230    }
231
232    /// Returns the identity of the circuit that owns this value.
233    pub fn circuit_id(self) -> CircuitId {
234        self.circuit_id
235    }
236
237    /// Returns the static type of this value.
238    pub fn ty(self) -> ClassicalType {
239        self.ty
240    }
241
242    /// Creates an expression that reads this immutable runtime value.
243    pub fn expr(self) -> ClassicalExpr {
244        ClassicalExpr::value(self)
245    }
246}
247
248/// Self-contained handle returned by circuit measurement builders.
249///
250/// [`ClassicalValue`] is the IR value used by expressions and control-flow.
251/// `Measurement` adds the measured qubits and their bit order, so state-level
252/// sampling APIs can consume a measurement without searching the circuit IR.
253///
254/// Bit order follows the `measure_bits` input order: `qubits()[0]` is bit index
255/// `0`, the least-significant bit in packed results.
256#[derive(Debug, Clone, PartialEq, Eq)]
257pub struct Measurement {
258    value: ClassicalValue,
259    qubits: SmallVec<[Qubit; 3]>,
260}
261
262impl Measurement {
263    /// Creates a measurement handle from its IR value and measured qubits.
264    pub fn new(value: ClassicalValue, qubits: SmallVec<[Qubit; 3]>) -> Self {
265        Self { value, qubits }
266    }
267
268    /// Returns the immutable circuit value produced by this measurement.
269    pub fn value(&self) -> ClassicalValue {
270        self.value
271    }
272
273    /// Creates an expression that reads this measurement's immutable value.
274    pub fn expr(&self) -> ClassicalExpr {
275        self.value.expr()
276    }
277
278    /// Returns the measured qubits in result bit order.
279    pub fn qubits(&self) -> &[Qubit] {
280        &self.qubits
281    }
282
283    /// Returns the number of measured bits.
284    pub fn width(&self) -> usize {
285        self.qubits.len()
286    }
287
288    /// Returns the static type of the measurement result.
289    pub fn ty(&self) -> ClassicalType {
290        self.value.ty()
291    }
292
293    /// Checks that all measured qubits are valid for a state with `num_qubits`.
294    ///
295    /// Returns [`QisError::IndexOutOfBounds`] for the first qubit index outside
296    /// `0..num_qubits`.
297    pub fn check_qubits(&self, num_qubits: usize) -> Result<(), QisError> {
298        for qubit in self.qubits() {
299            let index = qubit.index();
300            if index >= num_qubits {
301                return Err(QisError::IndexOutOfBounds {
302                    index,
303                    max: num_qubits.saturating_sub(1),
304                });
305            }
306        }
307        Ok(())
308    }
309
310    /// Projects a full-register outcome onto this measurement's qubit order.
311    ///
312    /// If `qubits()[i]` is one in `full`, bit `i` is set in the returned
313    /// [`Outcome`].
314    pub fn project(&self, full: &Outcome) -> Outcome {
315        Outcome::from_indices(
316            self.width(),
317            self.qubits()
318                .iter()
319                .enumerate()
320                .filter_map(|(bit, qubit)| full.is_one(qubit.index()).then_some(bit)),
321        )
322    }
323
324    /// Projects a computational-basis index onto this measurement's qubit order.
325    ///
326    /// If bit `qubits()[i]` is one in `basis`, bit `i` is set in the returned
327    /// [`Outcome`].
328    pub fn project_basis(&self, basis: usize) -> Outcome {
329        Outcome::from_indices(
330            self.width(),
331            self.qubits()
332                .iter()
333                .enumerate()
334                .filter_map(|(bit, qubit)| (((basis >> qubit.index()) & 1) == 1).then_some(bit)),
335        )
336    }
337}
338
339#[cfg(test)]
340mod tests {
341    use super::{CircuitId, ClassicalType, ClassicalValue, ClassicalVar, Measurement};
342    use crate::circuit::Qubit;
343    use crate::device::Outcome;
344    use crate::qis::QisError;
345    use smallvec::smallvec;
346
347    #[test]
348    fn type_widths_are_reported() {
349        assert_eq!(ClassicalType::Bit.width(), 1);
350        assert_eq!(ClassicalType::Bool.width(), 1);
351        assert_eq!(ClassicalType::uint(7).unwrap().width(), 7);
352        assert_eq!(ClassicalType::bit_vec(3).unwrap().width(), 3);
353    }
354
355    #[test]
356    fn zero_width_integer_and_bit_vector_are_rejected() {
357        assert_eq!(ClassicalType::uint(0), None);
358        assert_eq!(ClassicalType::bit_vec(0), None);
359    }
360
361    #[test]
362    fn measurement_width_accepts_only_bits_and_bit_vectors() {
363        assert_eq!(ClassicalType::Bit.measurement_width(), Some(1));
364        assert_eq!(
365            ClassicalType::bit_vec(5).unwrap().measurement_width(),
366            Some(5)
367        );
368        assert_eq!(ClassicalType::Bool.measurement_width(), None);
369        assert_eq!(ClassicalType::uint(5).unwrap().measurement_width(), None);
370    }
371
372    #[test]
373    fn variables_expose_id_and_type() {
374        let var = ClassicalVar::new(CircuitId::new(), 12, ClassicalType::bit_vec(4).unwrap());
375
376        assert_eq!(var.id(), 12);
377        assert_eq!(var.ty(), ClassicalType::bit_vec(4).unwrap());
378    }
379
380    #[test]
381    fn variable_identity_includes_id_and_type() {
382        let circuit_id = CircuitId::new();
383        let bit = ClassicalVar::new(circuit_id, 1, ClassicalType::Bit);
384        let same_bit = ClassicalVar::new(circuit_id, 1, ClassicalType::Bit);
385        let bool_var = ClassicalVar::new(circuit_id, 1, ClassicalType::Bool);
386        let other_bit = ClassicalVar::new(circuit_id, 2, ClassicalType::Bit);
387
388        assert_eq!(bit, same_bit);
389        assert_ne!(bit, bool_var);
390        assert_ne!(bit, other_bit);
391    }
392
393    #[test]
394    fn measurement_exposes_value_and_qubit_order() {
395        let value = ClassicalValue::new(CircuitId::new(), 3, ClassicalType::bit_vec(2).unwrap());
396        let measurement = Measurement::new(value, smallvec![Qubit::new(1), Qubit::new(0)]);
397
398        assert_eq!(measurement.value(), value);
399        assert_eq!(measurement.ty(), ClassicalType::bit_vec(2).unwrap());
400        assert_eq!(measurement.width(), 2);
401        assert_eq!(measurement.qubits(), &[Qubit::new(1), Qubit::new(0)]);
402        assert_eq!(measurement.expr().ty(), ClassicalType::bit_vec(2).unwrap());
403    }
404
405    #[test]
406    fn measurement_projects_outcomes_in_measurement_order() {
407        let value = ClassicalValue::new(CircuitId::new(), 3, ClassicalType::bit_vec(3).unwrap());
408        let measurement = Measurement::new(
409            value,
410            smallvec![Qubit::new(2), Qubit::new(0), Qubit::new(1)],
411        );
412        let full = Outcome::from_indices(3, [0, 2]);
413
414        let projected = measurement.project(&full);
415        assert_eq!(projected.to_bitstring(3), "011");
416        assert_eq!(measurement.project_basis(0b101).to_bitstring(3), "011");
417    }
418
419    #[test]
420    fn measurement_checks_qubit_bounds() {
421        let value = ClassicalValue::new(CircuitId::new(), 3, ClassicalType::bit_vec(2).unwrap());
422        let measurement = Measurement::new(value, smallvec![Qubit::new(0), Qubit::new(2)]);
423
424        assert!(measurement.check_qubits(3).is_ok());
425        assert!(matches!(
426            measurement.check_qubits(2),
427            Err(QisError::IndexOutOfBounds { index: 2, max: 1 })
428        ));
429    }
430}