Skip to main content

Module classical_expr

Module classical_expr 

Source
Expand description

Typed, side-effect-free runtime classical expressions.

This module defines a strongly-typed expression AST for classical computation that lives alongside quantum operations in a dynamic circuit. Expressions read runtime ClassicalVar and ClassicalValue handles, but they never measure qubits, write classical storage, or transfer control. Control-flow statements (IfOp, WhileOp, ForOp, SwitchOp) consume these expressions to make branching decisions.

§Type system

Every expression has a static ClassicalType:

TypeWidthDescription
Bool1Logical boolean
Bit1Single measured/assigned bit
UInt(w)wUnsigned integer (non-zero width)
BitVec(w)wOrdered bit-vector (non-zero width)

Bit and Bool are intentionally distinct: control-flow conditions require Bool, so a measured Bit must be explicitly converted via ClassicalExpr::to_bool. BitVec interprets index 0 as the least-significant bit; ClassicalExpr::to_uint provides a little-endian conversion to UInt.

§Building expressions

Expressions are constructed from literals, runtime classical handles, and typed builders on ClassicalExpr. ClassicalVar and ClassicalValue handles can be used directly as expression sources; direct operator use is equivalent to converting the handle with .expr() first.

§Leaf nodes

ConstructorProducesDescription
var(v)type of vReads the current runtime value of a mutable ClassicalVar
value(v)type of vReads an immutable ClassicalValue produced by measurement
bool_literal(b)BoolCompile-time boolean constant
bit_literal(b)BitCompile-time bit constant (false = 0, true = 1)
uint_literal(w, v)UInt(w)Compile-time unsigned integer (width ≤ 128, value fits in width)
bit_vec_literal(w, v)BitVec(w)Compile-time bit-vector, same constraints as uint_literal
use cqlib_core::circuit::{ClassicalExpr, ClassicalType, ClassicalVar};

let flag = ClassicalVar::new(Default::default(), 0, ClassicalType::Bool);
let expr = flag.expr();                        // Bool
let lit  = ClassicalExpr::bool_literal(true);  // Bool
let num  = ClassicalExpr::uint_literal(8, 42).unwrap(); // UInt(8)

§Unary operations

OperationSignatureProduces
!exprBool → Bool, Bit → Bitsame type as input
try_not(expr)Bool → Bool, Bit → Bitsame type as input

Negation on Bool is logical negation; on Bit it is bitwise inversion. Use try_not when invalid input should return CircuitError instead of panicking through the operator trait.

let cond = !flag; // Bool

§Binary operations

OperationSignatureProduces
lhs & rhs / try_and(lhs, rhs)Bool×Bool → Bool, Bit×Bit → Bitsame type
lhs | rhs / try_or(lhs, rhs)samesame type
lhs ^ rhs / try_xor(lhs, rhs)samesame type

Both operands must have the same type. Bool and Bit cannot be mixed.

let a = ClassicalVar::new(cid, 0, ClassicalType::Bool);
let b = ClassicalVar::new(cid, 1, ClassicalType::Bool);
let both = a & b; // Bool

§Comparison operations

ConstructorSignatureProduces
eq(lhs, rhs)any type × same typeBool
ne(lhs, rhs)any type × same typeBool
lt(lhs, rhs)UInt(w)×UInt(w)Bool
le(lhs, rhs)UInt(w)×UInt(w)Bool
gt(lhs, rhs)UInt(w)×UInt(w)Bool
ge(lhs, rhs)UInt(w)×UInt(w)Bool

Equality and inequality work on all types (including BitVec). Ordered comparisons (lt, le, gt, ge) are restricted to UInt.

let x = ClassicalExpr::var(ClassicalVar::new(cid, 0, ClassicalType::uint(8).unwrap()));
let y = ClassicalExpr::var(ClassicalVar::new(cid, 1, ClassicalType::uint(8).unwrap()));
let is_less = ClassicalExpr::lt(x, y).unwrap(); // Bool

§Type casts

ConstructorSignatureProduces
expr.to_bool() / bit_to_bool(expr)Bit → BoolBool
expr.to_uint() / bit_vec_to_uint(expr)BitVec(w) → UInt(w)UInt(w)

Casts are explicit — there is no implicit promotion. to_uint interprets bit index 0 as the least-significant bit.

let measured = ClassicalExpr::value(ClassicalValue::new(cid, 0, ClassicalType::Bit));
let condition = measured.to_bool().unwrap(); // Bool

§Conditional selection

ConstructorSignatureProduces
select(cond, then, else)Bool × T × T → Tsame type as branches

Both branches must have the same type. The condition must be Bool.

let cond = ClassicalExpr::bool_literal(true);
let a = ClassicalExpr::uint_literal(8, 10).unwrap();
let b = ClassicalExpr::uint_literal(8, 20).unwrap();
let chosen = ClassicalExpr::select(cond, a, b).unwrap(); // UInt(8)

§Bit extraction and manipulation

ConstructorSignatureProduces
extract_bit(val, idx)UInt(w)×u32 → Bit, BitVec(w)×u32 → BitBit
extract_bits(val, off, w)UInt×u32×u32 → BitVec(w), BitVec×u32×u32 → BitVec(w)BitVec(w)
concat(parts)[Bit or BitVec] → BitVec(total_width)BitVec
pack_bits(bits)[Bit] → BitVec(n)BitVec(n)

Index 0 is the least-significant bit. concat places the first part in the least-significant output bits. pack_bits places the first bit at index 0.

let lsb = ClassicalExpr::extract_bit(bv.clone(), 0).unwrap();    // Bit — bit 0
let hi  = ClassicalExpr::extract_bits(bv, 4, 4).unwrap();       // BitVec(4) — bits [4..8)

let a = ClassicalExpr::bit_literal(true);
let b = ClassicalExpr::bit_literal(false);
let packed = ClassicalExpr::pack_bits([a, b]).unwrap();          // BitVec(2), a at bit 0

§Expression introspection

MethodReturns
ty()ClassicalType — the static type of this expression
kind()&ClassicalExprKind — the AST node variant
vars()BTreeSet<ClassicalVar> — all mutable variables read
values()BTreeSet<ClassicalValue> — all immutable values read
remap_classical_ids(var_map, val_map)ClassicalExpr — clone with remapped handles
simplified()ClassicalExpr — structurally simplified copy
is_bool_true() / is_bool_false()bool — literal predicate
is_bit_true() / is_bit_false()bool — literal predicate

§Simplification

simplify() performs bottom-up algebraic simplification without evaluating runtime variable or value reads. It is idempotent: applying it twice yields the same expression.

§Rules (23 total)

Double negation:

PatternResult
not(not(x))x

Identity elements:

PatternResult
and(x, true) / and(true, x)x
or(x, false) / or(false, x)x
xor(x, false) / xor(false, x)x

Idempotence and self-inverse:

PatternResult
and(x, x)x
or(x, x)x
xor(x, x)false

Complement:

PatternResult
and(x, not(x))false
or(x, not(x))true
xor(x, not(x))true

Comparison reflexivity:

PatternResult
eq(x, x) / le(x, x) / ge(x, x)true
ne(x, x) / lt(x, x) / gt(x, x)false

Select and cast folding:

PatternResult
select(true, a, b)a
select(false, a, b)b
bit_to_bool(bit_literal(v))bool_literal(v)
bit_vec_to_uint(bit_vec_literal{w, v})uint_literal(w, v)
use cqlib_core::circuit::ClassicalExpr;

// not(not(a)) → a
let a = ClassicalExpr::bool_literal(true);
let expr = ClassicalExpr::try_not(ClassicalExpr::try_not(a.clone()).unwrap()).unwrap();
assert_eq!(expr.simplified(), a);

// and(b, true) → b
let b = ClassicalExpr::bool_literal(false);
let expr = ClassicalExpr::try_and(b.clone(), ClassicalExpr::bool_literal(true)).unwrap();
assert_eq!(expr.simplified(), b);

// eq(x, x) → true
let x = ClassicalExpr::uint_literal(8, 42).unwrap();
let expr = ClassicalExpr::eq(x.clone(), x).unwrap();
assert_eq!(expr.simplified(), ClassicalExpr::bool_literal(true));

§Non-goals

Simplification deliberately does not perform:

  • Value-dependent constant folding — and(x, false) → false would eliminate a runtime variable read and belongs to a higher-level optimization pass.
  • Commutation normalization — and(a, b) is not reordered to and(b, a).
  • Bit-width-aware extract/concat inversion — extract_bits(concat(a, b), ...) is not reduced.

§Integration with control flow

The typical flow from measurement to control:

measure q[0] → value: Bit
value.to_bool() → condition: Bool
if condition { ... }
use cqlib_core::circuit::{Circuit, Qubit};

fn build() -> Result<Circuit, Box<dyn std::error::Error>> {
    let mut circuit = Circuit::new(2);
    let q0 = Qubit::new(0);
    let q1 = Qubit::new(1);

    let measured = circuit.measure(q0)?;
    let condition = measured.expr().to_bool()?;
    circuit.if_(condition, |body| {
        body.x(q1)?;
        Ok(())
    })?;

    Ok(circuit)
}

For loop-carried state, allocate a ClassicalVar and store into it:

use cqlib_core::circuit::{Circuit, ClassicalExpr, ClassicalType, Qubit};

fn build() -> Result<Circuit, Box<dyn std::error::Error>> {
    let mut circuit = Circuit::new(1);
    let q0 = Qubit::new(0);

    let running = circuit.var(ClassicalType::Bool);
    circuit.store(running, ClassicalExpr::bool_literal(true))?;

    circuit.while_(running.expr(), |body| {
        let measured = body.measure(q0)?;
        body.store(running, measured.expr().to_bool()?)?;
        Ok(())
    })?;

    Ok(circuit)
}

Re-exports§

pub use expr::ClassicalBinaryOp;
pub use expr::ClassicalCast;
pub use expr::ClassicalCompareOp;
pub use expr::ClassicalExpr;
pub use expr::ClassicalExprKind;
pub use expr::ClassicalExprNode;
pub use expr::ClassicalUnaryOp;
pub use simplify::simplify;

Modules§

expr
Typed, side-effect-free runtime classical expressions.
simplify
Structural simplification for classical expressions.