Skip to main content

cqlib_core/compile/transform/
rebuild.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//! Shared infrastructure for classical-safe circuit rebuilds.
14//!
15//! # The problem
16//!
17//! Compile passes that rebuild a [`Circuit`] must not create an empty target
18//! circuit with pre-copied classical tables and then append operations
19//! incrementally. That pattern breaks three invariants of the runtime classical
20//! IR:
21//!
22//! 1. Every entry in the `classical_values` table must be defined by a
23//!    `MeasureBit` / `MeasureBits` operation in the circuit operation stream.
24//! 2. Every [`ClassicalVar`] and [`ClassicalValue`] handle must belong to the
25//!    circuit that contains it (`circuit_id` must match).
26//! 3. Definition and control-flow bodies carry their own classical state with
27//!    instance boundaries — expanding the same definition twice must produce
28//!    two independent sets of handles.
29//!
30//! The most common symptom of a violation is
31//! `CircuitError::UndefinedClassicalValue`: the value table declares a value
32//! but no defining measurement exists in the operation stream.
33//!
34//! # The fix
35//!
36//! All rebuild passes follow the same pattern:
37//!
38//! 1. Accept storage-level [`Operation`]s from the source circuit.
39//! 2. Produce a flat list of [`ValueOperation`]s, remapping every classical
40//!    handle through a [`ClassicalRemap`].
41//! 3. Call [`Circuit::from_operations`] exactly once via
42//!    [`CircuitRebuildContext::finish`], which owns parameter interning and
43//!    validation.
44//!
45//! # Quick start
46//!
47//! ```ignore
48//! use crate::compile::transform::rebuild::CircuitRebuildContext;
49//!
50//! let rebuild = CircuitRebuildContext::new(&source);
51//! let root_remap = rebuild.root_classical();
52//! let mut operations = Vec::new();
53//!
54//! for op in source.operations() {
55//!     if needs_transform(op) {
56//!         // produce new ValueOperations with handles from root_remap
57//!     } else {
58//!         operations.push(rebuild.remap_preserved_operation(
59//!             &source, op, root_remap,
60//!         )?);
61//!     }
62//! }
63//!
64//! let circuit = rebuild.finish(source.qubits(), operations, source.global_phase())?;
65//! ```
66
67use crate::circuit::{
68    Circuit, CircuitId, CircuitParam, ClassicalControlOp, ClassicalDataOp, ClassicalExpr,
69    ClassicalType, ClassicalValue, ClassicalVar, Instruction, Operation, Parameter, ParameterValue,
70    Qubit, ValueClassicalControlOp, ValueControlBody, ValueInstruction, ValueOperation,
71    ValueSwitchCase,
72};
73use crate::compile::CompilerError;
74use smallvec::SmallVec;
75use std::collections::HashMap;
76
77/// Mapping from source-circuit runtime classical handles to rebuilt handles.
78///
79/// Values of this type are produced by [`CircuitRebuildContext`]. The mapping
80/// internals are intentionally private so callers cannot create partial remaps
81/// that violate the rebuilt circuit's classical ownership invariants.
82///
83/// # Standalone use
84///
85/// A `ClassicalRemap` is self-contained once obtained from
86/// [`CircuitRebuildContext::root_classical`] or
87/// [`CircuitRebuildContext::allocate_classical_instance`]. All remap methods
88/// on this type work without holding a reference to the rebuild context, so
89/// callers can pass the remap into helper functions freely.
90#[derive(Debug, Clone)]
91pub struct ClassicalRemap {
92    vars: HashMap<ClassicalVar, ClassicalVar>,
93    values: HashMap<ClassicalValue, ClassicalValue>,
94}
95
96impl ClassicalRemap {
97    /// Returns the rebuilt handle for `var`, if this remap covers it.
98    pub fn map_var(&self, var: ClassicalVar) -> Option<ClassicalVar> {
99        self.vars.get(&var).copied()
100    }
101
102    /// Returns the rebuilt handle for `value`, if this remap covers it.
103    pub fn map_value(&self, value: ClassicalValue) -> Option<ClassicalValue> {
104        self.values.get(&value).copied()
105    }
106
107    /// Remaps a classical variable, failing if this remap does not cover it.
108    pub fn remap_var(&self, var: ClassicalVar) -> Result<ClassicalVar, CompilerError> {
109        self.map_var(var).ok_or_else(|| {
110            CompilerError::InvariantViolation(format!(
111                "missing classical variable remap for id {}",
112                var.id()
113            ))
114        })
115    }
116
117    /// Remaps a classical value, failing if this remap does not cover it.
118    pub fn remap_value(&self, value: ClassicalValue) -> Result<ClassicalValue, CompilerError> {
119        self.map_value(value).ok_or_else(|| {
120            CompilerError::InvariantViolation(format!(
121                "missing classical value remap for id {}",
122                value.index()
123            ))
124        })
125    }
126
127    /// Remaps runtime classical variable and value reads inside an expression.
128    ///
129    /// Every [`ClassicalVar`] and [`ClassicalValue`] read by `expr` must be
130    /// covered by this remap, otherwise an error is returned.
131    pub fn remap_expr(&self, expr: &ClassicalExpr) -> Result<ClassicalExpr, CompilerError> {
132        expr.remap_classical_ids(&self.vars, &self.values)
133            .map_err(CompilerError::Circuit)
134    }
135
136    /// Remaps classical handles inside a runtime classical data operation.
137    ///
138    /// Covers [`ClassicalDataOp::Store`] (target variable and value
139    /// expression), [`ClassicalDataOp::MeasureBit`], and
140    /// [`ClassicalDataOp::MeasureBits`] (result value).
141    pub fn remap_data_op(&self, op: &ClassicalDataOp) -> Result<ClassicalDataOp, CompilerError> {
142        Ok(match op {
143            ClassicalDataOp::Store { target, value } => ClassicalDataOp::Store {
144                target: self.remap_var(*target)?,
145                value: self.remap_expr(value)?,
146            },
147            ClassicalDataOp::MeasureBit { result } => ClassicalDataOp::MeasureBit {
148                result: self.remap_value(*result)?,
149            },
150            ClassicalDataOp::MeasureBits { result } => ClassicalDataOp::MeasureBits {
151                result: self.remap_value(*result)?,
152            },
153        })
154    }
155}
156
157/// Context for rebuilding a circuit through value-level operations.
158///
159/// The context owns the new circuit identity and the accumulated classical
160/// variable / value tables that will be supplied to
161/// [`Circuit::from_operations`] in [`finish`](Self::finish).
162///
163/// # Lifecycle
164///
165/// ```ignore
166/// let rebuild = CircuitRebuildContext::new(&source);
167/// let root_remap = rebuild.root_classical();
168///
169/// // For definition expansion, allocate per-instance remaps:
170/// let instance_remap = rebuild.allocate_classical_instance(&definition);
171///
172/// // Produce ValueOperations, then commit:
173/// let circuit = rebuild.finish(qubits, operations, global_phase)?;
174/// ```
175///
176/// # Design
177///
178/// `CircuitRebuildContext` delegates per-handle and per-expression remapping
179/// to [`ClassicalRemap`] so that callers can pass the remap into helpers
180/// without threading the full context. The context itself handles higher-level
181/// concerns: accumulating classical tables across `allocate_classical_instance`
182/// calls, resolving source parameters, and constructing the final `Circuit`.
183#[derive(Debug, Clone)]
184pub struct CircuitRebuildContext {
185    target_circuit_id: CircuitId,
186    classical_vars: Vec<ClassicalType>,
187    classical_values: Vec<ClassicalType>,
188    root_classical: ClassicalRemap,
189}
190
191impl CircuitRebuildContext {
192    /// Creates a rebuild context for a new circuit with the same root-level
193    /// classical tables as `source`.
194    ///
195    /// The root [`ClassicalRemap`] maps every classical handle in `source` to
196    /// a corresponding handle with the new `circuit_id`. Access it via
197    /// [`root_classical`](Self::root_classical).
198    pub fn new(source: &Circuit) -> Self {
199        let target_circuit_id = CircuitId::new();
200        let classical_vars = source.classical_vars().to_vec();
201        let classical_values = source.classical_values().to_vec();
202
203        let vars = classical_vars
204            .iter()
205            .copied()
206            .enumerate()
207            .map(|(index, ty)| {
208                (
209                    ClassicalVar::new(source.id(), index as u32, ty),
210                    ClassicalVar::new(target_circuit_id, index as u32, ty),
211                )
212            })
213            .collect();
214        let values = classical_values
215            .iter()
216            .copied()
217            .enumerate()
218            .map(|(index, ty)| {
219                (
220                    ClassicalValue::new(source.id(), index as u32, ty),
221                    ClassicalValue::new(target_circuit_id, index as u32, ty),
222                )
223            })
224            .collect();
225
226        Self {
227            target_circuit_id,
228            classical_vars,
229            classical_values,
230            root_classical: ClassicalRemap { vars, values },
231        }
232    }
233
234    /// Returns the remap for root-level classical handles from the source
235    /// circuit.
236    ///
237    /// Use this remap when preserving operations from the top-level operation
238    /// stream. For definition bodies or other nested circuits, use
239    /// [`allocate_classical_instance`](Self::allocate_classical_instance)
240    /// instead.
241    pub fn root_classical(&self) -> &ClassicalRemap {
242        &self.root_classical
243    }
244
245    /// Allocates a fresh copy of `circuit`'s classical tables in the rebuilt
246    /// circuit and returns the corresponding handle remap.
247    ///
248    /// This is intended for definition expansion and other transforms that may
249    /// instantiate the same source circuit more than once. Each call appends
250    /// new entries to the accumulated classical tables and returns a
251    /// [`ClassicalRemap`] with distinct rebuilt handles, so two expansions of
252    /// the same definition never share classical state.
253    pub fn allocate_classical_instance(&mut self, circuit: &Circuit) -> ClassicalRemap {
254        let var_base = self.classical_vars.len();
255        let mut vars = HashMap::with_capacity(circuit.classical_vars().len());
256        for (index, ty) in circuit.classical_vars().iter().copied().enumerate() {
257            self.classical_vars.push(ty);
258            vars.insert(
259                ClassicalVar::new(circuit.id(), index as u32, ty),
260                ClassicalVar::new(self.target_circuit_id, (var_base + index) as u32, ty),
261            );
262        }
263
264        let value_base = self.classical_values.len();
265        let mut values = HashMap::with_capacity(circuit.classical_values().len());
266        for (index, ty) in circuit.classical_values().iter().copied().enumerate() {
267            self.classical_values.push(ty);
268            values.insert(
269                ClassicalValue::new(circuit.id(), index as u32, ty),
270                ClassicalValue::new(self.target_circuit_id, (value_base + index) as u32, ty),
271            );
272        }
273
274        ClassicalRemap { vars, values }
275    }
276
277    /// Builds and validates the final [`Circuit`] from rebuilt value-level
278    /// operations.
279    ///
280    /// This is the only entry point for constructing the output circuit. It
281    /// calls [`Circuit::from_operations`] which infers the classical circuit
282    /// id, interns parameters, and runs full validation. Returns an error if
283    /// any classical handle in `operations` is inconsistent with the
284    /// accumulated classical tables.
285    pub fn finish(
286        self,
287        qubits: Vec<Qubit>,
288        operations: Vec<ValueOperation>,
289        global_phase: Parameter,
290    ) -> Result<Circuit, CompilerError> {
291        let mut circuit = Circuit::from_operations(
292            qubits,
293            operations,
294            Some(self.classical_vars),
295            Some(self.classical_values),
296        )?;
297        circuit.set_global_phase(global_phase);
298        Ok(circuit)
299    }
300
301    /// Resolves a storage-level parameter from `source` into a value-level
302    /// parameter suitable for a rebuilt operation.
303    ///
304    /// [`CircuitParam::Index`] is resolved against `source`'s parameter table.
305    /// Resolved parameters are converted to fixed values when constant evaluation succeeds.
306    pub fn resolve_source_param(
307        source: &Circuit,
308        param: &CircuitParam,
309    ) -> Result<ParameterValue, CompilerError> {
310        Ok(ParameterValue::from(source.resolve_parameter(param)?))
311    }
312
313    /// Resolves storage-level parameters from `source` into value-level
314    /// parameters suitable for a rebuilt operation.
315    pub fn resolve_source_params(
316        source: &Circuit,
317        params: &[CircuitParam],
318    ) -> Result<SmallVec<[ParameterValue; 1]>, CompilerError> {
319        params
320            .iter()
321            .map(|param| Self::resolve_source_param(source, param))
322            .collect()
323    }
324
325    /// Converts an unchanged source operation into a rebuilt value-level
326    /// operation, resolving parameters and remapping runtime classical handles.
327    ///
328    /// This is the primary entry point for preserved (non-transformed)
329    /// operations. It calls [`remap_instruction`](Self::remap_instruction)
330    /// internally, so classical-control operations are recursively remapped.
331    /// Passes that apply their own transform to control-flow bodies should
332    /// use the lower-level remap methods instead.
333    pub fn remap_preserved_operation(
334        &self,
335        source: &Circuit,
336        operation: &Operation,
337        classical_remap: &ClassicalRemap,
338    ) -> Result<ValueOperation, CompilerError> {
339        Ok(ValueOperation {
340            instruction: self.remap_instruction(source, &operation.instruction, classical_remap)?,
341            qubits: operation.qubits.clone(),
342            params: Self::resolve_source_params(source, &operation.params)?,
343            label: operation.label.clone(),
344        })
345    }
346
347    /// Converts an instruction into its value-level rebuilt form.
348    ///
349    /// Classical data operations are remapped via
350    /// [`ClassicalRemap::remap_data_op`]. Classical control is recursively
351    /// remapped via [`remap_control_flow`](Self::remap_control_flow). Other
352    /// instruction kinds are cloned unchanged.
353    ///
354    /// Passes that apply their own transform to control-flow bodies should use
355    /// [`remap_non_control_instruction`](Self::remap_non_control_instruction)
356    /// instead, which rejects classical control to prevent accidental
357    /// double-processing.
358    pub fn remap_instruction(
359        &self,
360        source: &Circuit,
361        instruction: &Instruction,
362        classical_remap: &ClassicalRemap,
363    ) -> Result<ValueInstruction, CompilerError> {
364        match instruction {
365            Instruction::ClassicalControl(control) => Ok(ValueInstruction::ClassicalControl(
366                self.remap_control_flow(source, control, classical_remap)?,
367            )),
368            Instruction::ClassicalData(op) => Ok(ValueInstruction::from_instruction(
369                Instruction::ClassicalData(classical_remap.remap_data_op(op)?),
370            )),
371            _ => Ok(ValueInstruction::from_instruction(instruction.clone())),
372        }
373    }
374
375    /// Converts a non-control instruction into its value-level rebuilt form.
376    ///
377    /// This helper rejects structured classical control so transforms that
378    /// rewrite control-flow bodies themselves cannot accidentally preserve an
379    /// unprocessed nested control operation. Use
380    /// [`remap_instruction`](Self::remap_instruction) when the transform does
381    /// not recurse into control flow.
382    pub fn remap_non_control_instruction(
383        &self,
384        instruction: &Instruction,
385        classical_remap: &ClassicalRemap,
386    ) -> Result<ValueInstruction, CompilerError> {
387        match instruction {
388            Instruction::ClassicalControl(_) => Err(CompilerError::InvariantViolation(
389                "classical control must be rebuilt by the transform".to_string(),
390            )),
391            Instruction::ClassicalData(op) => Ok(ValueInstruction::from_instruction(
392                Instruction::ClassicalData(classical_remap.remap_data_op(op)?),
393            )),
394            _ => Ok(ValueInstruction::from_instruction(instruction.clone())),
395        }
396    }
397
398    /// Recursively remaps a classical control operation and its bodies.
399    ///
400    /// Conditions, loop variables, and switch targets are remapped through
401    /// `classical_remap`. Every operation inside each control-flow body is
402    /// preserved and remapped via
403    /// [`remap_preserved_sequence`](Self::remap_preserved_sequence).
404    ///
405    /// Passes that apply their own transform inside control-flow bodies (such
406    /// as the knowledge rewriter or unitary decomposer with
407    /// `recurse_control_flow`) should call [`remap_expr`](ClassicalRemap::remap_expr)
408    /// and [`remap_var`](ClassicalRemap::remap_var) on the condition or loop
409    /// variable, then process the bodies through their own transform pipeline
410    /// instead of calling this method.
411    pub fn remap_control_flow(
412        &self,
413        source: &Circuit,
414        control: &ClassicalControlOp,
415        classical_remap: &ClassicalRemap,
416    ) -> Result<ValueClassicalControlOp, CompilerError> {
417        Ok(match control {
418            ClassicalControlOp::If(op) => ValueClassicalControlOp::If {
419                condition: classical_remap.remap_expr(op.condition())?,
420                then_body: ValueControlBody::new(self.remap_preserved_sequence(
421                    source,
422                    op.then_body().operations(),
423                    classical_remap,
424                )?),
425                else_body: op
426                    .else_body()
427                    .map(|body| {
428                        self.remap_preserved_sequence(source, body.operations(), classical_remap)
429                            .map(ValueControlBody::new)
430                    })
431                    .transpose()?,
432            },
433            ClassicalControlOp::While(op) => ValueClassicalControlOp::While {
434                condition: classical_remap.remap_expr(op.condition())?,
435                body: ValueControlBody::new(self.remap_preserved_sequence(
436                    source,
437                    op.body().operations(),
438                    classical_remap,
439                )?),
440            },
441            ClassicalControlOp::For(op) => ValueClassicalControlOp::For {
442                var: classical_remap.remap_var(op.var())?,
443                start: classical_remap.remap_expr(op.start())?,
444                stop: classical_remap.remap_expr(op.stop())?,
445                step: classical_remap.remap_expr(op.step())?,
446                body: ValueControlBody::new(self.remap_preserved_sequence(
447                    source,
448                    op.body().operations(),
449                    classical_remap,
450                )?),
451            },
452            ClassicalControlOp::Switch(op) => ValueClassicalControlOp::Switch {
453                target: classical_remap.remap_expr(op.target())?,
454                cases: op
455                    .cases()
456                    .iter()
457                    .map(|case| {
458                        Ok(ValueSwitchCase::new(
459                            case.value(),
460                            ValueControlBody::new(self.remap_preserved_sequence(
461                                source,
462                                case.body().operations(),
463                                classical_remap,
464                            )?),
465                        ))
466                    })
467                    .collect::<Result<Vec<_>, CompilerError>>()?,
468                default: op
469                    .default()
470                    .map(|body| {
471                        self.remap_preserved_sequence(source, body.operations(), classical_remap)
472                            .map(ValueControlBody::new)
473                    })
474                    .transpose()?,
475            },
476            ClassicalControlOp::Break => ValueClassicalControlOp::Break,
477            ClassicalControlOp::Continue => ValueClassicalControlOp::Continue,
478        })
479    }
480
481    /// Remaps a sequence of preserved operations.
482    ///
483    /// Each operation is individually remapped via
484    /// [`remap_preserved_operation`](Self::remap_preserved_operation). This is
485    /// a convenience for remapping control-flow bodies and other operation
486    /// slices in bulk.
487    pub fn remap_preserved_sequence(
488        &self,
489        source: &Circuit,
490        operations: &[Operation],
491        classical_remap: &ClassicalRemap,
492    ) -> Result<Vec<ValueOperation>, CompilerError> {
493        operations
494            .iter()
495            .map(|operation| self.remap_preserved_operation(source, operation, classical_remap))
496            .collect()
497    }
498}
499
500#[cfg(test)]
501mod tests {
502    use super::*;
503    use crate::circuit::StandardGate;
504
505    #[test]
506    fn source_resolution_folds_constants_but_parameter_value_preserves_representation() {
507        use crate::circuit::{CircuitError, Parameter};
508
509        let mut source = Circuit::new(1);
510        let (index, _) = source.add_parameter(Parameter::pi());
511        let param = CircuitParam::Index(index as u32);
512        assert!(matches!(source.parameter_value(&param).unwrap(),
513            ParameterValue::Param(value) if value == Parameter::pi()));
514        assert!(
515            matches!(CircuitRebuildContext::resolve_source_param(&source, &param).unwrap(),
516            ParameterValue::Fixed(value) if value == std::f64::consts::PI)
517        );
518        let (index, _) = source.add_parameter(Parameter::symbol("theta"));
519        assert!(
520            matches!(CircuitRebuildContext::resolve_source_param(&source, &CircuitParam::Index(index as u32)).unwrap(),
521            ParameterValue::Param(value) if value == Parameter::symbol("theta"))
522        );
523        for value in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
524            let param = CircuitParam::Fixed(value);
525            assert!(
526                matches!(source.parameter_value(&param).unwrap(), ParameterValue::Fixed(stored) if stored.to_bits() == value.to_bits())
527            );
528            assert!(matches!(
529                CircuitRebuildContext::resolve_source_param(&source, &param),
530                Err(CompilerError::Circuit(CircuitError::InvalidParameterValue(
531                    0,
532                    _
533                )))
534            ));
535        }
536    }
537
538    #[test]
539    fn preserved_rebuild_remaps_runtime_classical_handles_recursively() {
540        let mut source = Circuit::new(3);
541        let flag = source.var(ClassicalType::Bool);
542        let counter = source.var(ClassicalType::uint(2).unwrap());
543        let selector = source.var(ClassicalType::uint(2).unwrap());
544        let bits_out = source.var(ClassicalType::bit_vec(2).unwrap());
545        let measured = source.measure(Qubit::new(0)).unwrap();
546        let measured_bits = source.measure_bits([Qubit::new(1), Qubit::new(2)]).unwrap();
547        source
548            .store(flag, ClassicalExpr::bit_to_bool(measured.expr()).unwrap())
549            .unwrap();
550        source
551            .if_(flag.expr(), |body| body.x(Qubit::new(0)))
552            .unwrap();
553        source
554            .for_uint(
555                counter,
556                selector.expr(),
557                ClassicalExpr::uint_literal(2, 3).unwrap(),
558                ClassicalExpr::uint_literal(2, 1).unwrap(),
559                |body, loop_value| {
560                    body.store(bits_out, measured_bits.expr())?;
561                    body.switch(loop_value, |cases| {
562                        cases.value(1, |case_body| case_body.h(Qubit::new(1)))?;
563                        cases.default(|case_body| case_body.z(Qubit::new(2)))?;
564                        Ok(())
565                    })
566                },
567            )
568            .unwrap();
569
570        let rebuild = CircuitRebuildContext::new(&source);
571        assert!(rebuild.root_classical().map_var(flag).is_some());
572        assert!(
573            rebuild
574                .root_classical()
575                .map_value(measured.value())
576                .is_some()
577        );
578        let operations = source
579            .operations()
580            .iter()
581            .map(|operation| {
582                rebuild.remap_preserved_operation(&source, operation, rebuild.root_classical())
583            })
584            .collect::<Result<Vec<_>, _>>()
585            .unwrap();
586        let rebuilt = rebuild
587            .finish(source.qubits(), operations, source.global_phase())
588            .unwrap();
589
590        assert_ne!(source.id(), rebuilt.id());
591        assert_eq!(rebuilt.classical_vars(), source.classical_vars());
592        assert_eq!(rebuilt.classical_values(), source.classical_values());
593        rebuilt.validate().unwrap();
594        for operation in rebuilt.operations() {
595            assert_operation_handles_belong_to(operation, rebuilt.id());
596        }
597    }
598
599    #[test]
600    fn allocate_classical_instance_produces_distinct_handles_for_same_source() {
601        let mut definition = Circuit::new(1);
602        let flag = definition.var(ClassicalType::Bool);
603        definition.measure(Qubit::new(0)).unwrap();
604        definition
605            .if_(flag.expr(), |body| body.x(Qubit::new(0)))
606            .unwrap();
607
608        let source = definition.clone(); // treat definition as a standalone circuit
609        let mut rebuild = CircuitRebuildContext::new(&source);
610
611        let first = rebuild.allocate_classical_instance(&definition);
612        let second = rebuild.allocate_classical_instance(&definition);
613
614        // The two instances must have distinct handles for the same source var
615        let source_var = ClassicalVar::new(definition.id(), 0, ClassicalType::Bool);
616        let first_mapped = first.map_var(source_var).unwrap();
617        let second_mapped = second.map_var(source_var).unwrap();
618        assert_ne!(
619            first_mapped, second_mapped,
620            "two allocations of the same definition must produce independent handles"
621        );
622
623        // The source value (from measure) must also differ
624        let source_value = ClassicalValue::new(definition.id(), 0, ClassicalType::Bit);
625        let first_val = first.map_value(source_value).unwrap();
626        let second_val = second.map_value(source_value).unwrap();
627        assert_ne!(first_val, second_val);
628    }
629
630    #[test]
631    fn remap_data_op_is_callable_on_standalone_remap() {
632        let mut source = Circuit::new(1);
633        let old_var = source.var(ClassicalType::Bool);
634        let measured = source.measure(Qubit::new(0)).unwrap();
635        let old_value = measured.value();
636        let rebuild = CircuitRebuildContext::new(&source);
637        let remap = rebuild.root_classical();
638        let new_var = remap.map_var(old_var).unwrap();
639        let new_value = remap.map_value(old_value).unwrap();
640
641        let op = ClassicalDataOp::MeasureBit { result: old_value };
642        let remapped = remap.remap_data_op(&op).unwrap();
643        let ClassicalDataOp::MeasureBit { result } = remapped else {
644            panic!("expected MeasureBit");
645        };
646        assert_eq!(result, new_value);
647
648        let op = ClassicalDataOp::Store {
649            target: old_var,
650            value: old_value.expr(),
651        };
652        let remapped = remap.remap_data_op(&op).unwrap();
653        match remapped {
654            ClassicalDataOp::Store { target, value } => {
655                assert_eq!(target, new_var);
656                assert_eq!(value, new_value.expr());
657            }
658            _ => panic!("expected Store"),
659        }
660    }
661
662    fn assert_operation_handles_belong_to(operation: &Operation, circuit_id: CircuitId) {
663        match &operation.instruction {
664            Instruction::ClassicalData(op) => {
665                assert_classical_data_handles_belong_to(op, circuit_id)
666            }
667            Instruction::ClassicalControl(op) => assert_control_handles_belong_to(op, circuit_id),
668            Instruction::Standard(StandardGate::X | StandardGate::H | StandardGate::Z) => {}
669            _ => {}
670        }
671    }
672
673    fn assert_classical_data_handles_belong_to(op: &ClassicalDataOp, circuit_id: CircuitId) {
674        match op {
675            ClassicalDataOp::Store { target, value } => {
676                assert_eq!(target.circuit_id(), circuit_id);
677                assert_expr_handles_belong_to(value, circuit_id);
678            }
679            ClassicalDataOp::MeasureBit { result } | ClassicalDataOp::MeasureBits { result } => {
680                assert_eq!(result.circuit_id(), circuit_id);
681            }
682        }
683    }
684
685    fn assert_control_handles_belong_to(op: &ClassicalControlOp, circuit_id: CircuitId) {
686        match op {
687            ClassicalControlOp::If(op) => {
688                assert_expr_handles_belong_to(op.condition(), circuit_id);
689                assert_body_handles_belong_to(op.then_body().operations(), circuit_id);
690                if let Some(body) = op.else_body() {
691                    assert_body_handles_belong_to(body.operations(), circuit_id);
692                }
693            }
694            ClassicalControlOp::While(op) => {
695                assert_expr_handles_belong_to(op.condition(), circuit_id);
696                assert_body_handles_belong_to(op.body().operations(), circuit_id);
697            }
698            ClassicalControlOp::For(op) => {
699                assert_eq!(op.var().circuit_id(), circuit_id);
700                assert_expr_handles_belong_to(op.start(), circuit_id);
701                assert_expr_handles_belong_to(op.stop(), circuit_id);
702                assert_expr_handles_belong_to(op.step(), circuit_id);
703                assert_body_handles_belong_to(op.body().operations(), circuit_id);
704            }
705            ClassicalControlOp::Switch(op) => {
706                assert_expr_handles_belong_to(op.target(), circuit_id);
707                for case in op.cases() {
708                    assert_body_handles_belong_to(case.body().operations(), circuit_id);
709                }
710                if let Some(body) = op.default() {
711                    assert_body_handles_belong_to(body.operations(), circuit_id);
712                }
713            }
714            ClassicalControlOp::Break | ClassicalControlOp::Continue => {}
715        }
716    }
717
718    fn assert_body_handles_belong_to(operations: &[Operation], circuit_id: CircuitId) {
719        for operation in operations {
720            assert_operation_handles_belong_to(operation, circuit_id);
721        }
722    }
723
724    fn assert_expr_handles_belong_to(expr: &ClassicalExpr, circuit_id: CircuitId) {
725        for var in expr.vars() {
726            assert_eq!(var.circuit_id(), circuit_id);
727        }
728        for value in expr.values() {
729            assert_eq!(value.circuit_id(), circuit_id);
730        }
731    }
732}