Skip to main content

cqlib_core/qis/state/
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 state produced while simulating a circuit.
14//!
15//! The circuit IR stores classical handles, not concrete values. During
16//! simulation this module fills those handles with runtime data:
17//!
18//! - [`ClassicalValue`] handles address immutable operation results such as
19//!   measurements by circuit identity and table index.
20//! - [`ClassicalVar`] handles address mutable storage written by `store` using
21//!   the same circuit-local ownership model.
22//!
23//! `BitVec` values use [`Outcome`] so their bit order matches the rest of the
24//! device/result layer: bit index `0` is stored as the least-significant bit and
25//! string formatting prints the most-significant bit on the left.
26
27use crate::circuit::{
28    Circuit, CircuitError, CircuitId, ClassicalBinaryOp, ClassicalCast, ClassicalCompareOp,
29    ClassicalExpr, ClassicalExprKind, ClassicalType, ClassicalUnaryOp, ClassicalValue,
30    ClassicalVar,
31};
32use crate::device::Outcome;
33use crate::qis::QisError;
34
35/// A typed runtime classical value.
36#[derive(Debug, Clone, PartialEq, Eq, Hash)]
37pub enum RuntimeValue {
38    Bit(bool),
39    Bool(bool),
40    UInt { width: u32, value: u128 },
41    BitVec { width: u32, bits: Outcome },
42}
43
44impl RuntimeValue {
45    /// Returns the static circuit type represented by this runtime value.
46    pub fn ty(&self) -> ClassicalType {
47        match self {
48            Self::Bit(_) => ClassicalType::Bit,
49            Self::Bool(_) => ClassicalType::Bool,
50            Self::UInt { width, .. } => ClassicalType::uint(*width).unwrap(),
51            Self::BitVec { width, .. } => ClassicalType::bit_vec(*width).unwrap(),
52        }
53    }
54
55    /// Returns a bit-string for `Bit` and `BitVec` values using MSB-left display order.
56    pub fn to_bitstring(&self) -> Option<String> {
57        match self {
58            Self::Bit(value) => Some(if *value { "1" } else { "0" }.to_string()),
59            Self::BitVec { width, bits } => Some(bits.to_bitstring(*width as usize)),
60            Self::Bool(_) | Self::UInt { .. } => None,
61        }
62    }
63
64    /// Builds a `BitVec` from little-endian bits.
65    ///
66    /// `bits[0]` becomes bit index 0, the least-significant bit. This is the
67    /// same convention used by `measure_bits`, `pack_bits`, and `concat`.
68    pub(crate) fn bit_vec_from_lsb(bits: &[bool]) -> Self {
69        Self::BitVec {
70            width: bits.len() as u32,
71            bits: Outcome::from_lsb_bits(bits),
72        }
73    }
74
75    fn bit_at(&self, index: u32) -> Result<bool, QisError> {
76        match self {
77            Self::UInt { width, value } if index < *width && index < 128 => {
78                Ok((value >> index) & 1 == 1)
79            }
80            Self::BitVec { width, bits } if index < *width => Ok(bits.is_one(index as usize)),
81            _ => Err(qis_unsupported(
82                "cannot read the requested bit from runtime value",
83            )),
84        }
85    }
86
87    fn bits_lsb(&self) -> Result<Vec<bool>, QisError> {
88        match self {
89            Self::Bit(value) => Ok(vec![*value]),
90            Self::BitVec { width, bits } => Ok((0..*width)
91                .map(|index| bits.is_one(index as usize))
92                .collect()),
93            Self::Bool(_) | Self::UInt { .. } => Err(qis_unsupported(
94                "runtime value cannot be interpreted as BitVec bits",
95            )),
96        }
97    }
98
99    fn as_u128(&self) -> Result<u128, QisError> {
100        match self {
101            Self::UInt { value, .. } => Ok(*value),
102            Self::BitVec { width, bits } if *width <= 128 => Ok((0..*width)
103                .fold(0u128, |value, index| {
104                    value | ((bits.is_one(index as usize) as u128) << index)
105                })),
106            Self::BitVec { .. } => Err(qis_unsupported(
107                "BitVec values wider than 128 bits cannot be cast to UInt",
108            )),
109            Self::Bit(_) | Self::Bool(_) => Err(qis_unsupported(
110                "runtime value cannot be interpreted as an unsigned integer",
111            )),
112        }
113    }
114}
115
116/// Runtime storage for immutable circuit values and mutable classical variables.
117#[derive(Debug, Clone)]
118pub struct ClassicalState {
119    circuit_id: CircuitId,
120    value_types: Vec<ClassicalType>,
121    values: Vec<Option<RuntimeValue>>,
122    var_types: Vec<ClassicalType>,
123    vars: Vec<Option<RuntimeValue>>,
124}
125
126impl ClassicalState {
127    pub(crate) fn for_circuit(circuit: &Circuit) -> Self {
128        Self {
129            circuit_id: circuit.id(),
130            value_types: circuit.classical_values().to_vec(),
131            values: vec![None; circuit.classical_values().len()],
132            var_types: circuit.classical_vars().to_vec(),
133            vars: vec![None; circuit.classical_vars().len()],
134        }
135    }
136
137    /// Returns the runtime result produced for an immutable circuit value.
138    ///
139    /// Returns `None` when the handle belongs to another circuit, has a
140    /// mismatched type, is outside the value table, or has not been produced.
141    pub fn value(&self, value: ClassicalValue) -> Option<&RuntimeValue> {
142        if value.circuit_id() != self.circuit_id {
143            return None;
144        }
145        self.value_types
146            .get(value.index() as usize)
147            .filter(|ty| **ty == value.ty())?;
148        self.values.get(value.index() as usize)?.as_ref()
149    }
150
151    /// Returns the current runtime value of a mutable classical variable.
152    ///
153    /// Returns `None` when the handle belongs to another circuit, has a
154    /// mismatched type, is outside the variable table, or has not been stored.
155    pub fn var(&self, var: ClassicalVar) -> Option<&RuntimeValue> {
156        if var.circuit_id() != self.circuit_id {
157            return None;
158        }
159        self.var_types
160            .get(var.index() as usize)
161            .filter(|ty| **ty == var.ty())?;
162        self.vars.get(var.index() as usize)?.as_ref()
163    }
164
165    pub(crate) fn set_value(
166        &mut self,
167        target: ClassicalValue,
168        value: RuntimeValue,
169    ) -> Result<(), QisError> {
170        if target.circuit_id() != self.circuit_id {
171            return Err(CircuitError::ForeignClassicalHandle {
172                kind: "classical value",
173                index: target.index(),
174            }
175            .into());
176        }
177        let expected = self
178            .value_types
179            .get(target.index() as usize)
180            .copied()
181            .ok_or_else(|| qis_unsupported("measurement result is not owned by this circuit"))?;
182        if expected != target.ty() || expected != value.ty() {
183            return Err(qis_unsupported(
184                "measurement result type does not match circuit IR",
185            ));
186        }
187        self.values[target.index() as usize] = Some(value);
188        Ok(())
189    }
190
191    pub(crate) fn store(
192        &mut self,
193        target: ClassicalVar,
194        expr: &ClassicalExpr,
195    ) -> Result<(), QisError> {
196        if target.circuit_id() != self.circuit_id {
197            return Err(CircuitError::ForeignClassicalHandle {
198                kind: "classical variable",
199                index: target.index(),
200            }
201            .into());
202        }
203        let expected = self
204            .var_types
205            .get(target.index() as usize)
206            .copied()
207            .ok_or_else(|| qis_unsupported("store target is not owned by this circuit"))?;
208        let value = self.evaluate(expr)?;
209        if expected != target.ty() || expected != value.ty() {
210            return Err(qis_unsupported(
211                "store value type does not match its target",
212            ));
213        }
214        self.vars[target.index() as usize] = Some(value);
215        Ok(())
216    }
217
218    /// Re-associates results produced from a transformed circuit with the
219    /// source circuit whose handles are exposed to the caller.
220    pub(crate) fn rebind_to_circuit(&mut self, circuit: &Circuit) -> Result<(), QisError> {
221        if self.value_types.as_slice() != circuit.classical_values()
222            || self.var_types.as_slice() != circuit.classical_vars()
223        {
224            return Err(CircuitError::InvalidOperation(
225                "cannot rebind classical state to a circuit with different classical tables"
226                    .to_string(),
227            )
228            .into());
229        }
230        self.circuit_id = circuit.id();
231        Ok(())
232    }
233
234    /// Evaluates a side-effect-free classical expression against current runtime state.
235    ///
236    /// This intentionally does not execute control flow. It is used by
237    /// `ClassicalDataOp::Store` after measurements have populated `values`.
238    pub(crate) fn evaluate(&self, expr: &ClassicalExpr) -> Result<RuntimeValue, QisError> {
239        match expr.kind() {
240            ClassicalExprKind::Var(var) => self
241                .var(*var)
242                .cloned()
243                .ok_or_else(|| qis_unsupported("classical variable is read before being stored")),
244            ClassicalExprKind::Value(value) => self
245                .value(*value)
246                .cloned()
247                .ok_or_else(|| qis_unsupported("classical value is read before being produced")),
248            ClassicalExprKind::BoolLiteral(value) => Ok(RuntimeValue::Bool(*value)),
249            ClassicalExprKind::BitLiteral(value) => Ok(RuntimeValue::Bit(*value)),
250            ClassicalExprKind::UIntLiteral { width, value } => Ok(RuntimeValue::UInt {
251                width: width.get(),
252                value: *value,
253            }),
254            ClassicalExprKind::BitVecLiteral { width, value } => Ok(RuntimeValue::BitVec {
255                width: width.get(),
256                bits: Outcome::from_lsb_bits(
257                    &(0..width.get())
258                        .map(|index| (value >> index) & 1 == 1)
259                        .collect::<Vec<_>>(),
260                ),
261            }),
262            ClassicalExprKind::Unary { op, expr } => {
263                let value = self.evaluate(expr)?;
264                match (op, value) {
265                    (ClassicalUnaryOp::Not, RuntimeValue::Bit(value)) => {
266                        Ok(RuntimeValue::Bit(!value))
267                    }
268                    (ClassicalUnaryOp::Not, RuntimeValue::Bool(value)) => {
269                        Ok(RuntimeValue::Bool(!value))
270                    }
271                    _ => Err(qis_unsupported("invalid unary classical expression")),
272                }
273            }
274            ClassicalExprKind::Binary { op, lhs, rhs } => {
275                let lhs = self.evaluate(lhs)?;
276                let rhs = self.evaluate(rhs)?;
277                evaluate_binary(*op, lhs, rhs)
278            }
279            ClassicalExprKind::Compare { op, lhs, rhs } => {
280                let lhs = self.evaluate(lhs)?;
281                let rhs = self.evaluate(rhs)?;
282                evaluate_compare(*op, lhs, rhs)
283            }
284            ClassicalExprKind::Cast { cast, expr } => {
285                let value = self.evaluate(expr)?;
286                match (cast, value) {
287                    (ClassicalCast::BitToBool, RuntimeValue::Bit(value)) => {
288                        Ok(RuntimeValue::Bool(value))
289                    }
290                    (ClassicalCast::BitVecToUInt, RuntimeValue::BitVec { width, bits }) => {
291                        Ok(RuntimeValue::UInt {
292                            width,
293                            value: RuntimeValue::BitVec { width, bits }.as_u128()?,
294                        })
295                    }
296                    _ => Err(qis_unsupported("invalid classical cast")),
297                }
298            }
299            ClassicalExprKind::Select {
300                condition,
301                then_expr,
302                else_expr,
303            } => match self.evaluate(condition)? {
304                RuntimeValue::Bool(true) => self.evaluate(then_expr),
305                RuntimeValue::Bool(false) => self.evaluate(else_expr),
306                _ => Err(qis_unsupported("select condition is not a Bool")),
307            },
308            ClassicalExprKind::ExtractBit { value, index } => {
309                Ok(RuntimeValue::Bit(self.evaluate(value)?.bit_at(*index)?))
310            }
311            ClassicalExprKind::ExtractBits {
312                value,
313                offset,
314                width,
315            } => {
316                let value = self.evaluate(value)?;
317                let bits = (0..width.get())
318                    .map(|index| value.bit_at(offset + index))
319                    .collect::<Result<Vec<_>, _>>()?;
320                Ok(RuntimeValue::bit_vec_from_lsb(&bits))
321            }
322            ClassicalExprKind::Concat { parts } => {
323                let mut bits = Vec::new();
324                for part in parts.iter() {
325                    bits.extend(self.evaluate(part)?.bits_lsb()?);
326                }
327                Ok(RuntimeValue::bit_vec_from_lsb(&bits))
328            }
329            ClassicalExprKind::PackBits { bits } => {
330                let bits = bits
331                    .iter()
332                    .map(|bit| match self.evaluate(bit)? {
333                        RuntimeValue::Bit(value) => Ok(value),
334                        _ => Err(qis_unsupported("pack_bits input is not a Bit")),
335                    })
336                    .collect::<Result<Vec<_>, _>>()?;
337                Ok(RuntimeValue::bit_vec_from_lsb(&bits))
338            }
339        }
340    }
341}
342
343fn evaluate_binary(
344    op: ClassicalBinaryOp,
345    lhs: RuntimeValue,
346    rhs: RuntimeValue,
347) -> Result<RuntimeValue, QisError> {
348    let apply = |lhs: bool, rhs: bool| match op {
349        ClassicalBinaryOp::And => lhs & rhs,
350        ClassicalBinaryOp::Or => lhs | rhs,
351        ClassicalBinaryOp::Xor => lhs ^ rhs,
352    };
353    match (lhs, rhs) {
354        (RuntimeValue::Bit(lhs), RuntimeValue::Bit(rhs)) => Ok(RuntimeValue::Bit(apply(lhs, rhs))),
355        (RuntimeValue::Bool(lhs), RuntimeValue::Bool(rhs)) => {
356            Ok(RuntimeValue::Bool(apply(lhs, rhs)))
357        }
358        _ => Err(qis_unsupported("invalid binary classical expression")),
359    }
360}
361
362fn evaluate_compare(
363    op: ClassicalCompareOp,
364    lhs: RuntimeValue,
365    rhs: RuntimeValue,
366) -> Result<RuntimeValue, QisError> {
367    let result = match op {
368        ClassicalCompareOp::Eq => lhs == rhs,
369        ClassicalCompareOp::Ne => lhs != rhs,
370        ClassicalCompareOp::Lt
371        | ClassicalCompareOp::Le
372        | ClassicalCompareOp::Gt
373        | ClassicalCompareOp::Ge => match (lhs, rhs) {
374            (
375                RuntimeValue::UInt {
376                    width: lhs_width,
377                    value: lhs,
378                },
379                RuntimeValue::UInt {
380                    width: rhs_width,
381                    value: rhs,
382                },
383            ) if lhs_width == rhs_width => match op {
384                ClassicalCompareOp::Lt => lhs < rhs,
385                ClassicalCompareOp::Le => lhs <= rhs,
386                ClassicalCompareOp::Gt => lhs > rhs,
387                ClassicalCompareOp::Ge => lhs >= rhs,
388                ClassicalCompareOp::Eq | ClassicalCompareOp::Ne => unreachable!(),
389            },
390            _ => {
391                return Err(qis_unsupported(
392                    "ordered comparison operands are not matching UInts",
393                ));
394            }
395        },
396    };
397    Ok(RuntimeValue::Bool(result))
398}
399
400fn qis_unsupported(message: impl Into<String>) -> QisError {
401    QisError::UnsupportedOperation(message.into())
402}
403
404#[cfg(test)]
405mod tests {
406    use super::{ClassicalState, RuntimeValue};
407    use crate::circuit::{Circuit, CircuitError, ClassicalExpr, ClassicalType, Qubit};
408    use crate::qis::QisError;
409
410    #[test]
411    fn evaluates_literals_and_bit_vector_operations() {
412        let circuit = Circuit::new(0);
413        let state = ClassicalState::for_circuit(&circuit);
414        let packed = ClassicalExpr::pack_bits([
415            ClassicalExpr::bit_literal(true),
416            ClassicalExpr::bit_literal(false),
417            ClassicalExpr::bit_literal(true),
418        ])
419        .unwrap();
420
421        let value = state.evaluate(&packed).unwrap();
422        assert_eq!(value.to_bitstring().as_deref(), Some("101"));
423    }
424
425    #[test]
426    fn stores_and_reads_a_variable() {
427        let mut circuit = Circuit::new(0);
428        let var = circuit.var(ClassicalType::Bit);
429        let mut state = ClassicalState::for_circuit(&circuit);
430
431        state.store(var, &ClassicalExpr::bit_literal(true)).unwrap();
432
433        assert_eq!(state.var(var), Some(&RuntimeValue::Bit(true)));
434    }
435
436    #[test]
437    fn rejects_handles_owned_by_another_circuit() {
438        let mut owner = Circuit::new(1);
439        let owner_var = owner.var(ClassicalType::Bit);
440        let owner_value = owner.measure(Qubit::new(0)).unwrap().value();
441        let mut state = ClassicalState::for_circuit(&owner);
442        state
443            .set_value(owner_value, RuntimeValue::Bit(true))
444            .unwrap();
445        state
446            .store(owner_var, &ClassicalExpr::bit_literal(true))
447            .unwrap();
448
449        let mut other = Circuit::new(1);
450        let other_var = other.var(ClassicalType::Bit);
451        let other_value = other.measure(Qubit::new(0)).unwrap().value();
452
453        assert_eq!(state.value(other_value), None);
454        assert_eq!(state.var(other_var), None);
455        assert!(matches!(
456            state.set_value(other_value, RuntimeValue::Bit(false)),
457            Err(QisError::CircuitError(
458                CircuitError::ForeignClassicalHandle {
459                    kind: "classical value",
460                    ..
461                }
462            ))
463        ));
464        assert!(matches!(
465            state.store(other_var, &ClassicalExpr::bit_literal(false)),
466            Err(QisError::CircuitError(
467                CircuitError::ForeignClassicalHandle {
468                    kind: "classical variable",
469                    ..
470                }
471            ))
472        ));
473    }
474
475    #[test]
476    fn runtime_value_compares_by_value_and_hashes_consistently() {
477        use std::collections::hash_map::DefaultHasher;
478        use std::hash::{Hash, Hasher};
479
480        let hash_of = |value: &RuntimeValue| {
481            let mut hasher = DefaultHasher::new();
482            value.hash(&mut hasher);
483            hasher.finish()
484        };
485
486        assert_eq!(
487            RuntimeValue::UInt {
488                width: 8,
489                value: 42
490            },
491            RuntimeValue::UInt {
492                width: 8,
493                value: 42
494            }
495        );
496        assert_ne!(
497            RuntimeValue::UInt {
498                width: 8,
499                value: 42
500            },
501            RuntimeValue::UInt {
502                width: 8,
503                value: 43
504            }
505        );
506        assert_ne!(RuntimeValue::Bit(true), RuntimeValue::Bool(true),);
507
508        let bit_vec = RuntimeValue::BitVec {
509            width: 3,
510            bits: crate::device::Outcome::from_bitstring("101").unwrap(),
511        };
512        let bit_vec_again = RuntimeValue::BitVec {
513            width: 3,
514            bits: crate::device::Outcome::from_bitstring("101").unwrap(),
515        };
516        assert_eq!(bit_vec, bit_vec_again);
517        assert_eq!(hash_of(&bit_vec), hash_of(&bit_vec_again));
518        assert_eq!(
519            hash_of(&RuntimeValue::UInt {
520                width: 8,
521                value: 42
522            }),
523            hash_of(&RuntimeValue::UInt {
524                width: 8,
525                value: 42
526            })
527        );
528    }
529}