Skip to main content

cqlib_core/circuit/classical_expr/
simplify.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//! Structural simplification for classical expressions.
14//!
15//! This module provides bottom-up algebraic simplification of [`ClassicalExpr`]
16//! ASTs. Simplification eliminates literal-driven redundancies and structural
17//! identities without evaluating runtime variable or value reads.
18//!
19//! # Rules
20//!
21//! - **Double negation**: `not(not(x)) → x`
22//! - **Identity elements**: `and(x, true) → x`, `or(x, false) → x`,
23//!   `xor(x, false) → x` (and symmetric forms)
24//! - **Idempotence**: `and(x, x) → x`, `or(x, x) → x`
25//! - **Self-inverse**: `xor(x, x) → false`
26//! - **Complement**: `and(x, not(x)) → false`, `or(x, not(x)) → true`,
27//!   `xor(x, not(x)) → true`
28//! - **Comparison reflexivity**: `eq(x, x) → true`, `ne(x, x) → false`,
29//!   `lt(x, x) → false`, `gt(x, x) → false`, `le(x, x) → true`,
30//!   `ge(x, x) → true`
31//! - **Select folding**: `select(true, a, b) → a`,
32//!   `select(false, a, b) → b`
33//! - **Cast folding**: `bit_to_bool(BitLiteral(v)) → BoolLiteral(v)`,
34//!   `bit_vec_to_uint(BitVecLiteral{w, v}) → UIntLiteral(w, v)`
35//!
36//! # Non-goals
37//!
38//! - Value-dependent constant folding (e.g. `and(x, false) → false` removes
39//!   runtime variable reads and belongs to a higher-level optimization pass)
40//! - Commutation normalization (e.g. `and(a, b) → and(b, a)`)
41//! - Bit-width-aware extract/concat inversion (deferred)
42
43use crate::circuit::classical::ClassicalType;
44use crate::circuit::classical_expr::expr::{
45    ClassicalBinaryOp, ClassicalCast, ClassicalCompareOp, ClassicalExpr, ClassicalExprKind,
46    ClassicalUnaryOp,
47};
48
49/// Returns a structurally simplified copy of `expr`.
50///
51/// The result has the same static type as the input and reads a subset of the
52/// input's runtime classical variables and values. Simplification is
53/// idempotent: applying it twice yields the same expression.
54///
55/// Rules that require constructing a `UInt` or `BitVec` zero/one literal are
56/// skipped when the type is wider than the current 128-bit literal
57/// representation. In that case the original, type-correct expression shape
58/// is preserved.
59pub fn simplify(expr: &ClassicalExpr) -> ClassicalExpr {
60    simplify_node(expr)
61}
62
63fn simplify_node(expr: &ClassicalExpr) -> ClassicalExpr {
64    match expr.kind() {
65        ClassicalExprKind::Var(_)
66        | ClassicalExprKind::Value(_)
67        | ClassicalExprKind::BoolLiteral(_)
68        | ClassicalExprKind::BitLiteral(_)
69        | ClassicalExprKind::UIntLiteral { .. }
70        | ClassicalExprKind::BitVecLiteral { .. } => expr.clone(),
71
72        ClassicalExprKind::Unary { op, expr: inner } => {
73            let inner = simplify_node(inner);
74            simplify_unary(*op, &inner).unwrap_or_else(|| {
75                ClassicalExpr::from_kind(
76                    inner.ty(),
77                    ClassicalExprKind::Unary {
78                        op: *op,
79                        expr: inner,
80                    },
81                )
82            })
83        }
84
85        ClassicalExprKind::Binary { op, lhs, rhs } => {
86            let lhs = simplify_node(lhs);
87            let rhs = simplify_node(rhs);
88            simplify_binary(*op, &lhs, &rhs).unwrap_or_else(|| {
89                let ty = lhs.ty();
90                ClassicalExpr::from_kind(ty, ClassicalExprKind::Binary { op: *op, lhs, rhs })
91            })
92        }
93
94        ClassicalExprKind::Compare { op, lhs, rhs } => {
95            let lhs = simplify_node(lhs);
96            let rhs = simplify_node(rhs);
97            simplify_compare(*op, &lhs, &rhs).unwrap_or_else(|| {
98                ClassicalExpr::from_kind(
99                    ClassicalType::Bool,
100                    ClassicalExprKind::Compare { op: *op, lhs, rhs },
101                )
102            })
103        }
104
105        ClassicalExprKind::Cast { cast, expr: inner } => {
106            let inner = simplify_node(inner);
107            simplify_cast(*cast, &inner).unwrap_or_else(|| {
108                let ty = match cast {
109                    ClassicalCast::BitToBool => ClassicalType::Bool,
110                    ClassicalCast::BitVecToUInt => {
111                        if let ClassicalType::BitVec(width) = inner.ty() {
112                            ClassicalType::UInt(width)
113                        } else {
114                            // Unreachable: cast constructors validate types
115                            ClassicalType::Bool
116                        }
117                    }
118                };
119                ClassicalExpr::from_kind(
120                    ty,
121                    ClassicalExprKind::Cast {
122                        cast: *cast,
123                        expr: inner,
124                    },
125                )
126            })
127        }
128
129        ClassicalExprKind::Select {
130            condition,
131            then_expr,
132            else_expr,
133        } => {
134            let condition = simplify_node(condition);
135            // Only fold when the condition resolves to a literal.
136            if let Some(result) = simplify_select(&condition, then_expr, else_expr) {
137                return result;
138            }
139            let then_expr = simplify_node(then_expr);
140            let else_expr = simplify_node(else_expr);
141            ClassicalExpr::from_kind(
142                then_expr.ty(),
143                ClassicalExprKind::Select {
144                    condition,
145                    then_expr,
146                    else_expr,
147                },
148            )
149        }
150
151        ClassicalExprKind::ExtractBit { value, index } => {
152            let value = simplify_node(value);
153            ClassicalExpr::from_kind(
154                ClassicalType::Bit,
155                ClassicalExprKind::ExtractBit {
156                    value,
157                    index: *index,
158                },
159            )
160        }
161
162        ClassicalExprKind::ExtractBits {
163            value,
164            offset,
165            width,
166        } => {
167            let value = simplify_node(value);
168            ClassicalExpr::from_kind(
169                ClassicalType::BitVec(*width),
170                ClassicalExprKind::ExtractBits {
171                    value,
172                    offset: *offset,
173                    width: *width,
174                },
175            )
176        }
177
178        ClassicalExprKind::Concat { parts } => {
179            let mut changed = false;
180            let simplified: Vec<ClassicalExpr> = parts
181                .iter()
182                .map(|part| {
183                    let s = simplify_node(part);
184                    if !changed && &s != part {
185                        changed = true;
186                    }
187                    s
188                })
189                .collect();
190            if !changed {
191                return expr.clone();
192            }
193            let total_width = simplified
194                .iter()
195                .fold(0u32, |acc, part| acc.saturating_add(part.ty().width()));
196            let Some(width) = std::num::NonZeroU32::new(total_width) else {
197                // Unreachable: concat requires at least one part
198                return expr.clone();
199            };
200            ClassicalExpr::from_kind(
201                ClassicalType::BitVec(width),
202                ClassicalExprKind::Concat {
203                    parts: simplified.into_boxed_slice(),
204                },
205            )
206        }
207
208        ClassicalExprKind::PackBits { bits } => {
209            let mut changed = false;
210            let simplified: Vec<ClassicalExpr> = bits
211                .iter()
212                .map(|bit| {
213                    let s = simplify_node(bit);
214                    if !changed && &s != bit {
215                        changed = true;
216                    }
217                    s
218                })
219                .collect();
220            if !changed {
221                return expr.clone();
222            }
223            let Some(width) = std::num::NonZeroU32::new(simplified.len() as u32) else {
224                return expr.clone();
225            };
226            ClassicalExpr::from_kind(
227                ClassicalType::BitVec(width),
228                ClassicalExprKind::PackBits {
229                    bits: simplified.into_boxed_slice(),
230                },
231            )
232        }
233    }
234}
235
236/// R1: `not(not(x))` → `x`
237fn simplify_unary(op: ClassicalUnaryOp, child: &ClassicalExpr) -> Option<ClassicalExpr> {
238    if op != ClassicalUnaryOp::Not {
239        return None;
240    }
241    match child.kind() {
242        ClassicalExprKind::Unary {
243            op: ClassicalUnaryOp::Not,
244            expr: grandchild,
245        } => Some(grandchild.clone()),
246        _ => None,
247    }
248}
249
250/// R2-R13: identity elements, idempotence, self-inverse, complement
251fn simplify_binary(
252    op: ClassicalBinaryOp,
253    lhs: &ClassicalExpr,
254    rhs: &ClassicalExpr,
255) -> Option<ClassicalExpr> {
256    let ty = lhs.ty();
257
258    match op {
259        ClassicalBinaryOp::And => {
260            if rhs.is_bool_true() || rhs.is_bit_true() {
261                return Some(lhs.clone());
262            }
263            if lhs.is_bool_true() || lhs.is_bit_true() {
264                return Some(rhs.clone());
265            }
266        }
267        ClassicalBinaryOp::Or => {
268            if rhs.is_bool_false() || rhs.is_bit_false() {
269                return Some(lhs.clone());
270            }
271            if lhs.is_bool_false() || lhs.is_bit_false() {
272                return Some(rhs.clone());
273            }
274        }
275        ClassicalBinaryOp::Xor => {
276            if rhs.is_bool_false() || rhs.is_bit_false() {
277                return Some(lhs.clone());
278            }
279            if lhs.is_bool_false() || lhs.is_bit_false() {
280                return Some(rhs.clone());
281            }
282        }
283    }
284
285    if lhs == rhs {
286        match op {
287            ClassicalBinaryOp::And | ClassicalBinaryOp::Or => {
288                return Some(lhs.clone());
289            }
290            ClassicalBinaryOp::Xor => {
291                return ty.zero_literal().ok();
292            }
293        }
294    }
295
296    // ── complement: and(x, not(x)) → false, or(x, not(x)) → true,
297    //     xor(x, not(x)) → true. Check both lhs/rhs directions. ────
298    for (a, b) in [(lhs, rhs), (rhs, lhs)] {
299        if let ClassicalExprKind::Unary {
300            op: ClassicalUnaryOp::Not,
301            expr: x,
302        } = b.kind()
303            && a == x
304        {
305            return match op {
306                ClassicalBinaryOp::And => ty.zero_literal(),
307                ClassicalBinaryOp::Or | ClassicalBinaryOp::Xor => ty.one_literal(),
308            }
309            .ok();
310        }
311    }
312
313    None
314}
315
316/// R14-R19: comparison reflexivity
317fn simplify_compare(
318    op: ClassicalCompareOp,
319    lhs: &ClassicalExpr,
320    rhs: &ClassicalExpr,
321) -> Option<ClassicalExpr> {
322    if lhs != rhs {
323        return None;
324    }
325    match op {
326        ClassicalCompareOp::Eq | ClassicalCompareOp::Le | ClassicalCompareOp::Ge => {
327            Some(ClassicalExpr::bool_literal(true))
328        }
329        ClassicalCompareOp::Ne | ClassicalCompareOp::Lt | ClassicalCompareOp::Gt => {
330            Some(ClassicalExpr::bool_literal(false))
331        }
332    }
333}
334
335/// R20-R21: `select(true, a, b) → a`, `select(false, a, b) → b`
336fn simplify_select(
337    condition: &ClassicalExpr,
338    then_expr: &ClassicalExpr,
339    else_expr: &ClassicalExpr,
340) -> Option<ClassicalExpr> {
341    match condition.kind() {
342        ClassicalExprKind::BoolLiteral(true) => Some(simplify_node(then_expr)),
343        ClassicalExprKind::BoolLiteral(false) => Some(simplify_node(else_expr)),
344        _ => None,
345    }
346}
347
348/// R22-R23: cast-of-literal folding
349fn simplify_cast(cast: ClassicalCast, child: &ClassicalExpr) -> Option<ClassicalExpr> {
350    match (cast, child.kind()) {
351        (ClassicalCast::BitToBool, ClassicalExprKind::BitLiteral(v)) => {
352            Some(ClassicalExpr::bool_literal(*v))
353        }
354        (ClassicalCast::BitVecToUInt, ClassicalExprKind::BitVecLiteral { width, value }) => Some(
355            ClassicalExpr::uint_literal(width.get(), *value)
356                .expect("bit-vec literal value must be valid for uint of same width"),
357        ),
358        _ => None,
359    }
360}
361
362#[cfg(test)]
363#[path = "simplify_test.rs"]
364mod simplify_test;