Skip to main content

cqlib_core/circuit/classical_expr/
mod.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//! Typed, side-effect-free runtime classical expressions.
14//!
15//! This module defines a strongly-typed expression AST for classical
16//! computation that lives alongside quantum operations in a dynamic circuit.
17//! Expressions read runtime [`ClassicalVar`] and [`ClassicalValue`] handles, but
18//! they never measure qubits, write classical storage, or transfer control.
19//! Control-flow statements ([`IfOp`], [`WhileOp`], [`ForOp`], [`SwitchOp`])
20//! consume these expressions to make branching decisions.
21//!
22//! [`ClassicalVar`]: super::ClassicalVar
23//! [`ClassicalValue`]: super::ClassicalValue
24//! [`IfOp`]: super::control_flow::IfOp
25//! [`WhileOp`]: super::control_flow::WhileOp
26//! [`ForOp`]: super::control_flow::ForOp
27//! [`SwitchOp`]: super::control_flow::SwitchOp
28//!
29//! # Type system
30//!
31//! Every expression has a static [`ClassicalType`]:
32//!
33//! | Type | Width | Description |
34//! |------|-------|-------------|
35//! | `Bool` | 1 | Logical boolean |
36//! | `Bit` | 1 | Single measured/assigned bit |
37//! | `UInt(w)` | w | Unsigned integer (non-zero width) |
38//! | `BitVec(w)` | w | Ordered bit-vector (non-zero width) |
39//!
40//! `Bit` and `Bool` are intentionally distinct: control-flow conditions
41//! require `Bool`, so a measured `Bit` must be explicitly converted via
42//! [`ClassicalExpr::to_bool`]. `BitVec` interprets index `0` as the
43//! least-significant bit; [`ClassicalExpr::to_uint`] provides a little-endian
44//! conversion to `UInt`.
45//!
46//! [`ClassicalType`]: super::ClassicalType
47//!
48//! # Building expressions
49//!
50//! Expressions are constructed from literals, runtime classical handles, and
51//! typed builders on [`ClassicalExpr`]. [`ClassicalVar`] and [`ClassicalValue`]
52//! handles can be used directly as expression sources; direct operator use is
53//! equivalent to converting the handle with `.expr()` first.
54//!
55//! ## Leaf nodes
56//!
57//! | Constructor | Produces | Description |
58//! |-------------|----------|-------------|
59//! | `var(v)` | type of `v` | Reads the current runtime value of a mutable [`ClassicalVar`] |
60//! | `value(v)` | type of `v` | Reads an immutable [`ClassicalValue`] produced by measurement |
61//! | `bool_literal(b)` | `Bool` | Compile-time boolean constant |
62//! | `bit_literal(b)` | `Bit` | Compile-time bit constant (`false` = 0, `true` = 1) |
63//! | `uint_literal(w, v)` | `UInt(w)` | Compile-time unsigned integer (width ≤ 128, value fits in width) |
64//! | `bit_vec_literal(w, v)` | `BitVec(w)` | Compile-time bit-vector, same constraints as `uint_literal` |
65//!
66//! ```rust
67//! use cqlib_core::circuit::{ClassicalExpr, ClassicalType, ClassicalVar};
68//!
69//! let flag = ClassicalVar::new(Default::default(), 0, ClassicalType::Bool);
70//! let expr = flag.expr();                        // Bool
71//! let lit  = ClassicalExpr::bool_literal(true);  // Bool
72//! let num  = ClassicalExpr::uint_literal(8, 42).unwrap(); // UInt(8)
73//! ```
74//!
75//! ## Unary operations
76//!
77//! | Operation | Signature | Produces |
78//! |-----------|-----------|----------|
79//! | `!expr` | `Bool → Bool`, `Bit → Bit` | same type as input |
80//! | `try_not(expr)` | `Bool → Bool`, `Bit → Bit` | same type as input |
81//!
82//! Negation on `Bool` is logical negation; on `Bit` it is bitwise inversion.
83//! Use `try_not` when invalid input should return
84//! [`CircuitError`](crate::circuit::CircuitError) instead of
85//! panicking through the operator trait.
86//!
87//! ```rust
88//! # use cqlib_core::circuit::{ClassicalType, ClassicalVar};
89//! # let flag = ClassicalVar::new(Default::default(), 0, ClassicalType::Bool);
90//! let cond = !flag; // Bool
91//! ```
92//!
93//! ## Binary operations
94//!
95//! | Operation | Signature | Produces |
96//! |-----------|-----------|----------|
97//! | `lhs & rhs` / `try_and(lhs, rhs)` | `Bool×Bool → Bool`, `Bit×Bit → Bit` | same type |
98//! | `lhs \| rhs` / `try_or(lhs, rhs)` | same | same type |
99//! | `lhs ^ rhs` / `try_xor(lhs, rhs)` | same | same type |
100//!
101//! Both operands must have the **same** type. `Bool` and `Bit` cannot be mixed.
102//!
103//! ```rust
104//! # use cqlib_core::circuit::{ClassicalType, ClassicalVar, CircuitId};
105//! # let cid = CircuitId::new();
106//! let a = ClassicalVar::new(cid, 0, ClassicalType::Bool);
107//! let b = ClassicalVar::new(cid, 1, ClassicalType::Bool);
108//! let both = a & b; // Bool
109//! ```
110//!
111//! ## Comparison operations
112//!
113//! | Constructor | Signature | Produces |
114//! |-------------|-----------|----------|
115//! | `eq(lhs, rhs)` | any type × same type | `Bool` |
116//! | `ne(lhs, rhs)` | any type × same type | `Bool` |
117//! | `lt(lhs, rhs)` | `UInt(w)×UInt(w)` | `Bool` |
118//! | `le(lhs, rhs)` | `UInt(w)×UInt(w)` | `Bool` |
119//! | `gt(lhs, rhs)` | `UInt(w)×UInt(w)` | `Bool` |
120//! | `ge(lhs, rhs)` | `UInt(w)×UInt(w)` | `Bool` |
121//!
122//! Equality and inequality work on **all** types (including `BitVec`).
123//! Ordered comparisons (`lt`, `le`, `gt`, `ge`) are restricted to `UInt`.
124//!
125//! ```rust
126//! # use cqlib_core::circuit::{ClassicalExpr, ClassicalType, ClassicalVar, CircuitId};
127//! # let cid = CircuitId::new();
128//! let x = ClassicalExpr::var(ClassicalVar::new(cid, 0, ClassicalType::uint(8).unwrap()));
129//! let y = ClassicalExpr::var(ClassicalVar::new(cid, 1, ClassicalType::uint(8).unwrap()));
130//! let is_less = ClassicalExpr::lt(x, y).unwrap(); // Bool
131//! ```
132//!
133//! ## Type casts
134//!
135//! | Constructor | Signature | Produces |
136//! |-------------|-----------|----------|
137//! | `expr.to_bool()` / `bit_to_bool(expr)` | `Bit → Bool` | `Bool` |
138//! | `expr.to_uint()` / `bit_vec_to_uint(expr)` | `BitVec(w) → UInt(w)` | `UInt(w)` |
139//!
140//! Casts are **explicit** — there is no implicit promotion. `to_uint`
141//! interprets bit index `0` as the least-significant bit.
142//!
143//! ```rust
144//! # use cqlib_core::circuit::{ClassicalExpr, ClassicalType, ClassicalValue, CircuitId};
145//! # let cid = CircuitId::new();
146//! let measured = ClassicalExpr::value(ClassicalValue::new(cid, 0, ClassicalType::Bit));
147//! let condition = measured.to_bool().unwrap(); // Bool
148//! ```
149//!
150//! ## Conditional selection
151//!
152//! | Constructor | Signature | Produces |
153//! |-------------|-----------|----------|
154//! | `select(cond, then, else)` | `Bool × T × T → T` | same type as branches |
155//!
156//! Both branches must have the same type. The condition must be `Bool`.
157//!
158//! ```rust
159//! # use cqlib_core::circuit::{ClassicalExpr, ClassicalType, ClassicalVar, CircuitId};
160//! # let cid = CircuitId::new();
161//! let cond = ClassicalExpr::bool_literal(true);
162//! let a = ClassicalExpr::uint_literal(8, 10).unwrap();
163//! let b = ClassicalExpr::uint_literal(8, 20).unwrap();
164//! let chosen = ClassicalExpr::select(cond, a, b).unwrap(); // UInt(8)
165//! ```
166//!
167//! ## Bit extraction and manipulation
168//!
169//! | Constructor | Signature | Produces |
170//! |-------------|-----------|----------|
171//! | `extract_bit(val, idx)` | `UInt(w)×u32 → Bit`, `BitVec(w)×u32 → Bit` | `Bit` |
172//! | `extract_bits(val, off, w)` | `UInt×u32×u32 → BitVec(w)`, `BitVec×u32×u32 → BitVec(w)` | `BitVec(w)` |
173//! | `concat(parts)` | `[Bit or BitVec] → BitVec(total_width)` | `BitVec` |
174//! | `pack_bits(bits)` | `[Bit] → BitVec(n)` | `BitVec(n)` |
175//!
176//! Index `0` is the least-significant bit. `concat` places the first part in
177//! the least-significant output bits. `pack_bits` places the first bit at
178//! index `0`.
179//!
180//! ```rust
181//! # use cqlib_core::circuit::{ClassicalExpr, ClassicalType, ClassicalVar, CircuitId};
182//! # let cid = CircuitId::new();
183//! # let bv = ClassicalExpr::var(ClassicalVar::new(cid, 0, ClassicalType::bit_vec(8).unwrap()));
184//! let lsb = ClassicalExpr::extract_bit(bv.clone(), 0).unwrap();    // Bit — bit 0
185//! let hi  = ClassicalExpr::extract_bits(bv, 4, 4).unwrap();       // BitVec(4) — bits [4..8)
186//!
187//! let a = ClassicalExpr::bit_literal(true);
188//! let b = ClassicalExpr::bit_literal(false);
189//! let packed = ClassicalExpr::pack_bits([a, b]).unwrap();          // BitVec(2), a at bit 0
190//! ```
191//!
192//! ## Expression introspection
193//!
194//! | Method | Returns |
195//! |--------|---------|
196//! | `ty()` | `ClassicalType` — the static type of this expression |
197//! | `kind()` | `&ClassicalExprKind` — the AST node variant |
198//! | `vars()` | `BTreeSet<ClassicalVar>` — all mutable variables read |
199//! | `values()` | `BTreeSet<ClassicalValue>` — all immutable values read |
200//! | `remap_classical_ids(var_map, val_map)` | `ClassicalExpr` — clone with remapped handles |
201//! | `simplified()` | `ClassicalExpr` — structurally simplified copy |
202//! | `is_bool_true()` / `is_bool_false()` | `bool` — literal predicate |
203//! | `is_bit_true()` / `is_bit_false()` | `bool` — literal predicate |
204//!
205//! # Simplification
206//!
207//! [`simplify()`] performs bottom-up algebraic simplification without evaluating
208//! runtime variable or value reads. It is **idempotent**: applying it twice
209//! yields the same expression.
210//!
211//! ## Rules (23 total)
212//!
213//! **Double negation:**
214//!
215//! | Pattern | Result |
216//! |---------|--------|
217//! | `not(not(x))` | `x` |
218//!
219//! **Identity elements:**
220//!
221//! | Pattern | Result |
222//! |---------|--------|
223//! | `and(x, true)` / `and(true, x)` | `x` |
224//! | `or(x, false)` / `or(false, x)` | `x` |
225//! | `xor(x, false)` / `xor(false, x)` | `x` |
226//!
227//! **Idempotence and self-inverse:**
228//!
229//! | Pattern | Result |
230//! |---------|--------|
231//! | `and(x, x)` | `x` |
232//! | `or(x, x)` | `x` |
233//! | `xor(x, x)` | `false` |
234//!
235//! **Complement:**
236//!
237//! | Pattern | Result |
238//! |---------|--------|
239//! | `and(x, not(x))` | `false` |
240//! | `or(x, not(x))` | `true` |
241//! | `xor(x, not(x))` | `true` |
242//!
243//! **Comparison reflexivity:**
244//!
245//! | Pattern | Result |
246//! |---------|--------|
247//! | `eq(x, x)` / `le(x, x)` / `ge(x, x)` | `true` |
248//! | `ne(x, x)` / `lt(x, x)` / `gt(x, x)` | `false` |
249//!
250//! **Select and cast folding:**
251//!
252//! | Pattern | Result |
253//! |---------|--------|
254//! | `select(true, a, b)` | `a` |
255//! | `select(false, a, b)` | `b` |
256//! | `bit_to_bool(bit_literal(v))` | `bool_literal(v)` |
257//! | `bit_vec_to_uint(bit_vec_literal{w, v})` | `uint_literal(w, v)` |
258//!
259//! ```rust
260//! use cqlib_core::circuit::ClassicalExpr;
261//!
262//! // not(not(a)) → a
263//! let a = ClassicalExpr::bool_literal(true);
264//! let expr = ClassicalExpr::try_not(ClassicalExpr::try_not(a.clone()).unwrap()).unwrap();
265//! assert_eq!(expr.simplified(), a);
266//!
267//! // and(b, true) → b
268//! let b = ClassicalExpr::bool_literal(false);
269//! let expr = ClassicalExpr::try_and(b.clone(), ClassicalExpr::bool_literal(true)).unwrap();
270//! assert_eq!(expr.simplified(), b);
271//!
272//! // eq(x, x) → true
273//! let x = ClassicalExpr::uint_literal(8, 42).unwrap();
274//! let expr = ClassicalExpr::eq(x.clone(), x).unwrap();
275//! assert_eq!(expr.simplified(), ClassicalExpr::bool_literal(true));
276//! ```
277//!
278//! ## Non-goals
279//!
280//! Simplification deliberately does **not** perform:
281//!
282//! - **Value-dependent constant folding** — `and(x, false) → false` would
283//!   eliminate a runtime variable read and belongs to a higher-level
284//!   optimization pass.
285//! - **Commutation normalization** — `and(a, b)` is not reordered to
286//!   `and(b, a)`.
287//! - **Bit-width-aware extract/concat inversion** — `extract_bits(concat(a, b), ...)`
288//!   is not reduced.
289//!
290//! # Integration with control flow
291//!
292//! The typical flow from measurement to control:
293//!
294//! ```text
295//! measure q[0] → value: Bit
296//! value.to_bool() → condition: Bool
297//! if condition { ... }
298//! ```
299//!
300//! ```rust
301//! use cqlib_core::circuit::{Circuit, Qubit};
302//!
303//! fn build() -> Result<Circuit, Box<dyn std::error::Error>> {
304//!     let mut circuit = Circuit::new(2);
305//!     let q0 = Qubit::new(0);
306//!     let q1 = Qubit::new(1);
307//!
308//!     let measured = circuit.measure(q0)?;
309//!     let condition = measured.expr().to_bool()?;
310//!     circuit.if_(condition, |body| {
311//!         body.x(q1)?;
312//!         Ok(())
313//!     })?;
314//!
315//!     Ok(circuit)
316//! }
317//! ```
318//!
319//! For loop-carried state, allocate a [`ClassicalVar`] and `store` into it:
320//!
321//! ```rust
322//! use cqlib_core::circuit::{Circuit, ClassicalExpr, ClassicalType, Qubit};
323//!
324//! fn build() -> Result<Circuit, Box<dyn std::error::Error>> {
325//!     let mut circuit = Circuit::new(1);
326//!     let q0 = Qubit::new(0);
327//!
328//!     let running = circuit.var(ClassicalType::Bool);
329//!     circuit.store(running, ClassicalExpr::bool_literal(true))?;
330//!
331//!     circuit.while_(running.expr(), |body| {
332//!         let measured = body.measure(q0)?;
333//!         body.store(running, measured.expr().to_bool()?)?;
334//!         Ok(())
335//!     })?;
336//!
337//!     Ok(circuit)
338//! }
339//! ```
340
341pub mod expr;
342pub mod simplify;
343
344pub use expr::{
345    ClassicalBinaryOp, ClassicalCast, ClassicalCompareOp, ClassicalExpr, ClassicalExprKind,
346    ClassicalExprNode, ClassicalUnaryOp,
347};
348pub use simplify::simplify;