Skip to main content

cqlib_core/circuit/
circuit_impl.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//! # Quantum Circuit Module
14//!
15//! This module defines the [`Circuit`] struct, which is the primary container for quantum programs
16//! in the `Cqlib` ecosystem. It acts as an intermediate representation (IR) that captures the sequence
17//! of quantum operations, qubit management, and symbolic parameters.
18//!
19//! ## Core Features
20//!
21//! - **Instruction Scheduling**: Stores a sequence of operations ([`Operation`]) including gates, measurements, and barriers.
22//! - **Qubit Management**: Efficiently handles qubit allocation using topological ordering.
23//! - **Parametric Circuits**: Native support for variational quantum algorithms (VQA) via symbolic parameters.
24//!   Parameters are "interned" to minimize memory usage and accelerate bulk evaluation.
25//! - **Extensibility**: Supports standard gates, custom unitary matrices, and arbitrary control structures.
26//!
27//! ## Example
28//!
29//! ```rust
30//! use cqlib_core::circuit::circuit_impl::Circuit;
31//! use cqlib_core::circuit::Qubit;
32//!
33//! // Create a circuit with 2 qubits
34//! let mut circuit = Circuit::new(2);
35//!
36//! let q0 = Qubit::new(0);
37//! let q1 = Qubit::new(1);
38//!
39//! // Apply Hadamard gate to q0
40//! circuit.h(q0);
41//!
42//! // Apply Controlled-NOT gate (q0 controls q1)
43//! circuit.cx(q0, q1);
44//!
45//! // Measure q0 and keep its immutable runtime result
46//! let measured = circuit.measure(q0).unwrap();
47//! assert_eq!(measured.ty().width(), 1);
48//! ```
49
50use crate::circuit::bit::Qubit;
51use crate::circuit::circuit_classical::ControlScopeKind;
52use crate::circuit::circuit_param::{CircuitParam, ParameterValue};
53use crate::circuit::classical::CircuitId;
54use crate::circuit::classical_expr::{ClassicalExpr, ClassicalExprKind};
55use crate::circuit::error::CircuitError;
56use crate::circuit::gate::circuit_gate::{CircuitGate, FrozenCircuit};
57use crate::circuit::gate::instruction::Instruction;
58use crate::circuit::gate::{Directive, StandardGate, UnitaryGate};
59use crate::circuit::operation::{Operation, ValueOperation};
60use crate::circuit::parameter::Parameter;
61use crate::circuit::value_instruction::{
62    ValueControlBody, ValueInstruction, storage_operation_to_value,
63};
64use crate::circuit::{
65    ClassicalControlOp, ClassicalType, ClassicalValue, ClassicalVar, ControlBody, ForOp, IfOp,
66    SwitchCase, SwitchOp, ValueClassicalControlOp, WhileOp,
67};
68use crate::circuit::{ClassicalDataOp, circuit_to_matrix};
69use indexmap::IndexSet;
70use ndarray::Array2;
71use num_complex::Complex64;
72use smallvec::{SmallVec, smallvec};
73use std::collections::{HashMap, HashSet};
74use std::sync::Arc;
75
76/// A quantum circuit representation serving as the core IR for quantum programs.
77///
78/// The `Circuit` struct is designed to be a high-performance, memory-efficient container for quantum
79/// operations. It supports both static circuits (fixed angles) and parameterized circuits (symbolic angles),
80/// making it suitable for a wide range of applications from error correction to variational quantum algorithms.
81///
82/// # Internal Architecture
83///
84/// - **Qubit Storage**: Uses `IndexSet<Qubit>` to maintain deterministic ordering of qubits while allowing $O(1)$ lookups.
85/// - **Parameter Interning**: Symbolic parameters are stored in a centralized `IndexSet`. Instructions reference these parameters
86///   by index rather than owning them. This "interning" strategy significantly reduces memory footprint for deep parameterized
87///   circuits and enables vectorized parameter updates.
88#[derive(Debug)]
89pub struct Circuit {
90    pub(super) circuit_id: CircuitId,
91    /// The set of quantum bits (qubits) managed by this circuit.
92    ///
93    /// # Implementation Note
94    /// Used `IndexSet` to maintain the strict insertion order of qubits (which defines the logical
95    /// qubit indices 0, 1, 2...) while allowing $O(1)$ membership testing (`contains`).
96    pub(super) qubits: IndexSet<Qubit>,
97    /// A registry of all unique symbolic variables (e.g., "theta", "phi") used within the circuit.
98    /// This field serves as a cache to quickly identify which free parameters need to be bound
99    /// before simulation, avoiding the need to traverse the entire instruction list.
100    pub(super) symbols: IndexSet<String>,
101    /// The centralized storage for symbolic parameters.
102    ///
103    /// This table implements the **Interning** pattern. Instructions in the `data` vector do not
104    /// own their `Parameter` objects; instead, they store lightweight indices pointing to this set.
105    /// This design allows for:
106    /// 1. **Deduplication**: Identical expressions are stored only once.
107    /// 2. **Batch Evaluation**: All parameters can be resolved to `f64` values in a single linear pass.
108    pub(super) parameters: IndexSet<Parameter>,
109    /// The ordered sequence of operations (quantum gates, measurements, etc.) in the circuit.
110    ///
111    /// This vector represents the circuit schedule.
112    pub(super) data: Arc<Vec<Operation>>,
113    /// Static types of circuit-local runtime classical variables.
114    ///
115    /// A [`ClassicalVar`] ID is an index into this table. Keeping ownership in
116    /// the circuit prevents expressions from silently referencing variables
117    /// allocated by another circuit.
118    pub(super) classical_vars: Vec<ClassicalType>,
119    /// Static types of immutable circuit-local runtime classical values.
120    ///
121    /// A [`ClassicalValue`] ID is an index into this table. This verifies that
122    /// measurement-produced values consumed by expressions belong to this circuit.
123    pub(super) classical_values: Vec<ClassicalType>,
124    /// Active structured-control scopes while closure bodies are being built.
125    ///
126    /// This transient builder stack validates placement of `break` and `continue`.
127    pub(super) control_scope_stack: Vec<ControlScopeKind>,
128    ///  The global phase of the circuit, representing a scalar factor $e^{i\theta}$.
129    ///
130    /// While the global phase is unobservable in isolated systems, it is critical for:
131    /// - **Controlled Operations**: When this circuit is controlled by another qubit.
132    /// - **Sub-circuit Composition**: Correctly merging phases when combining circuits.
133    pub(super) global_phase: CircuitParam,
134}
135
136impl Clone for Circuit {
137    fn clone(&self) -> Self {
138        let circuit_id = CircuitId::new();
139        if self.classical_vars.is_empty() && self.classical_values.is_empty() {
140            return Self {
141                circuit_id,
142                qubits: self.qubits.clone(),
143                symbols: self.symbols.clone(),
144                parameters: self.parameters.clone(),
145                data: self.data.clone(),
146                classical_vars: Vec::new(),
147                classical_values: Vec::new(),
148                control_scope_stack: self.control_scope_stack.clone(),
149                global_phase: self.global_phase.clone(),
150            };
151        }
152        let var_map = self
153            .classical_vars
154            .iter()
155            .enumerate()
156            .map(|(index, ty)| {
157                (
158                    ClassicalVar::new(self.circuit_id, index as u32, *ty),
159                    ClassicalVar::new(circuit_id, index as u32, *ty),
160                )
161            })
162            .collect::<HashMap<_, _>>();
163        let value_map = self
164            .classical_values
165            .iter()
166            .enumerate()
167            .map(|(index, ty)| {
168                (
169                    ClassicalValue::new(self.circuit_id, index as u32, *ty),
170                    ClassicalValue::new(circuit_id, index as u32, *ty),
171                )
172            })
173            .collect::<HashMap<_, _>>();
174        let data = self
175            .data
176            .iter()
177            .map(|operation| {
178                Self::remap_operation(
179                    operation,
180                    &|qubit| Ok(qubit),
181                    &|param| Ok(param.clone()),
182                    &var_map,
183                    &value_map,
184                )
185            })
186            .collect::<Result<Vec<_>, _>>()
187            .expect("a valid circuit must be cloneable with complete handle mappings");
188
189        Self {
190            circuit_id,
191            qubits: self.qubits.clone(),
192            symbols: self.symbols.clone(),
193            parameters: self.parameters.clone(),
194            data: Arc::new(data),
195            classical_vars: self.classical_vars.clone(),
196            classical_values: self.classical_values.clone(),
197            control_scope_stack: self.control_scope_stack.clone(),
198            global_phase: self.global_phase.clone(),
199        }
200    }
201}
202
203/// Compares exact compiler-IR structure while ignoring process-local circuit identity.
204impl PartialEq for Circuit {
205    fn eq(&self, other: &Self) -> bool {
206        if self.qubits != other.qubits
207            || self.symbols != other.symbols
208            || self.parameters.len() != other.parameters.len()
209            || !self
210                .parameters
211                .iter()
212                .all(|parameter| other.parameters.contains(parameter))
213            || self.classical_vars != other.classical_vars
214            || self.classical_values != other.classical_values
215            || self.control_scope_stack != other.control_scope_stack
216            || self.data.len() != other.data.len()
217        {
218            return false;
219        }
220        circuit_params_equal(
221            &self.global_phase,
222            &other.global_phase,
223            &self.parameters,
224            &other.parameters,
225        ) && operations_structurally_equal(
226            self.data.as_slice(),
227            other.data.as_slice(),
228            &self.parameters,
229            &other.parameters,
230        )
231    }
232}
233
234impl Circuit {
235    /// Compares operation semantics without allocating remapped operation trees.
236    pub(crate) fn operations_structurally_equal(&self, other: &Self) -> bool {
237        operations_structurally_equal(
238            self.data.as_slice(),
239            other.data.as_slice(),
240            &self.parameters,
241            &other.parameters,
242        )
243    }
244}
245
246fn circuit_params_equal(
247    left: &CircuitParam,
248    right: &CircuitParam,
249    left_parameters: &IndexSet<Parameter>,
250    right_parameters: &IndexSet<Parameter>,
251) -> bool {
252    match (left, right) {
253        (CircuitParam::Fixed(left), CircuitParam::Fixed(right)) => left == right,
254        (CircuitParam::Index(left), CircuitParam::Index(right)) => left_parameters
255            .get_index(*left as usize)
256            .zip(right_parameters.get_index(*right as usize))
257            .is_some_and(|(left, right)| left == right),
258        (CircuitParam::Fixed(value), CircuitParam::Index(index)) => right_parameters
259            .get_index(*index as usize)
260            .is_some_and(|right| Parameter::from(*value) == *right),
261        (CircuitParam::Index(index), CircuitParam::Fixed(value)) => left_parameters
262            .get_index(*index as usize)
263            .is_some_and(|left| *left == Parameter::from(*value)),
264    }
265}
266
267fn classical_var_equal(left: ClassicalVar, right: ClassicalVar) -> bool {
268    left.index() == right.index() && left.ty() == right.ty()
269}
270
271fn classical_value_equal(left: ClassicalValue, right: ClassicalValue) -> bool {
272    left.index() == right.index() && left.ty() == right.ty()
273}
274
275fn classical_expr_equal(left: &ClassicalExpr, right: &ClassicalExpr) -> bool {
276    if left.ty() != right.ty() {
277        return false;
278    }
279    match (left.kind(), right.kind()) {
280        (ClassicalExprKind::Var(left), ClassicalExprKind::Var(right)) => {
281            classical_var_equal(*left, *right)
282        }
283        (ClassicalExprKind::Value(left), ClassicalExprKind::Value(right)) => {
284            classical_value_equal(*left, *right)
285        }
286        (
287            ClassicalExprKind::Unary {
288                op: left_op,
289                expr: left,
290            },
291            ClassicalExprKind::Unary {
292                op: right_op,
293                expr: right,
294            },
295        ) => left_op == right_op && classical_expr_equal(left, right),
296        (
297            ClassicalExprKind::Binary {
298                op: left_op,
299                lhs: left_lhs,
300                rhs: left_rhs,
301            },
302            ClassicalExprKind::Binary {
303                op: right_op,
304                lhs: right_lhs,
305                rhs: right_rhs,
306            },
307        ) => {
308            left_op == right_op
309                && classical_expr_equal(left_lhs, right_lhs)
310                && classical_expr_equal(left_rhs, right_rhs)
311        }
312        (
313            ClassicalExprKind::Compare {
314                op: left_op,
315                lhs: left_lhs,
316                rhs: left_rhs,
317            },
318            ClassicalExprKind::Compare {
319                op: right_op,
320                lhs: right_lhs,
321                rhs: right_rhs,
322            },
323        ) => {
324            left_op == right_op
325                && classical_expr_equal(left_lhs, right_lhs)
326                && classical_expr_equal(left_rhs, right_rhs)
327        }
328        (
329            ClassicalExprKind::Cast {
330                cast: left_cast,
331                expr: left,
332            },
333            ClassicalExprKind::Cast {
334                cast: right_cast,
335                expr: right,
336            },
337        ) => left_cast == right_cast && classical_expr_equal(left, right),
338        (
339            ClassicalExprKind::Select {
340                condition: left_condition,
341                then_expr: left_then,
342                else_expr: left_else,
343            },
344            ClassicalExprKind::Select {
345                condition: right_condition,
346                then_expr: right_then,
347                else_expr: right_else,
348            },
349        ) => {
350            classical_expr_equal(left_condition, right_condition)
351                && classical_expr_equal(left_then, right_then)
352                && classical_expr_equal(left_else, right_else)
353        }
354        (
355            ClassicalExprKind::ExtractBit {
356                value: left,
357                index: left_index,
358            },
359            ClassicalExprKind::ExtractBit {
360                value: right,
361                index: right_index,
362            },
363        ) => left_index == right_index && classical_expr_equal(left, right),
364        (
365            ClassicalExprKind::ExtractBits {
366                value: left,
367                offset: left_offset,
368                width: left_width,
369            },
370            ClassicalExprKind::ExtractBits {
371                value: right,
372                offset: right_offset,
373                width: right_width,
374            },
375        ) => {
376            left_offset == right_offset
377                && left_width == right_width
378                && classical_expr_equal(left, right)
379        }
380        (ClassicalExprKind::Concat { parts: left }, ClassicalExprKind::Concat { parts: right })
381        | (
382            ClassicalExprKind::PackBits { bits: left },
383            ClassicalExprKind::PackBits { bits: right },
384        ) => {
385            left.len() == right.len()
386                && left
387                    .iter()
388                    .zip(right.iter())
389                    .all(|(left, right)| classical_expr_equal(left, right))
390        }
391        (left, right) => left == right,
392    }
393}
394
395fn classical_data_equal(left: &ClassicalDataOp, right: &ClassicalDataOp) -> bool {
396    match (left, right) {
397        (
398            ClassicalDataOp::Store {
399                target: left_target,
400                value: left_value,
401            },
402            ClassicalDataOp::Store {
403                target: right_target,
404                value: right_value,
405            },
406        ) => {
407            classical_var_equal(*left_target, *right_target)
408                && classical_expr_equal(left_value, right_value)
409        }
410        (
411            ClassicalDataOp::MeasureBit { result: left },
412            ClassicalDataOp::MeasureBit { result: right },
413        )
414        | (
415            ClassicalDataOp::MeasureBits { result: left },
416            ClassicalDataOp::MeasureBits { result: right },
417        ) => classical_value_equal(*left, *right),
418        _ => false,
419    }
420}
421
422fn operation_instruction_equal(
423    left: &Instruction,
424    right: &Instruction,
425    left_parameters: &IndexSet<Parameter>,
426    right_parameters: &IndexSet<Parameter>,
427) -> bool {
428    match (left, right) {
429        (Instruction::ClassicalData(left), Instruction::ClassicalData(right)) => {
430            classical_data_equal(left, right)
431        }
432        (Instruction::ClassicalControl(left), Instruction::ClassicalControl(right)) => {
433            classical_control_equal(left, right, left_parameters, right_parameters)
434        }
435        _ => left == right,
436    }
437}
438
439fn control_body_equal(
440    left: &ControlBody,
441    right: &ControlBody,
442    left_parameters: &IndexSet<Parameter>,
443    right_parameters: &IndexSet<Parameter>,
444) -> bool {
445    operations_structurally_equal(
446        left.operations(),
447        right.operations(),
448        left_parameters,
449        right_parameters,
450    )
451}
452
453fn classical_control_equal(
454    left: &ClassicalControlOp,
455    right: &ClassicalControlOp,
456    left_parameters: &IndexSet<Parameter>,
457    right_parameters: &IndexSet<Parameter>,
458) -> bool {
459    match (left, right) {
460        (ClassicalControlOp::If(left), ClassicalControlOp::If(right)) => {
461            classical_expr_equal(left.condition(), right.condition())
462                && control_body_equal(
463                    left.then_body(),
464                    right.then_body(),
465                    left_parameters,
466                    right_parameters,
467                )
468                && match (left.else_body(), right.else_body()) {
469                    (Some(left), Some(right)) => {
470                        control_body_equal(left, right, left_parameters, right_parameters)
471                    }
472                    (None, None) => true,
473                    _ => false,
474                }
475        }
476        (ClassicalControlOp::While(left), ClassicalControlOp::While(right)) => {
477            classical_expr_equal(left.condition(), right.condition())
478                && control_body_equal(left.body(), right.body(), left_parameters, right_parameters)
479        }
480        (ClassicalControlOp::For(left), ClassicalControlOp::For(right)) => {
481            classical_var_equal(left.var(), right.var())
482                && classical_expr_equal(left.start(), right.start())
483                && classical_expr_equal(left.stop(), right.stop())
484                && classical_expr_equal(left.step(), right.step())
485                && control_body_equal(left.body(), right.body(), left_parameters, right_parameters)
486        }
487        (ClassicalControlOp::Switch(left), ClassicalControlOp::Switch(right)) => {
488            classical_expr_equal(left.target(), right.target())
489                && left.cases().len() == right.cases().len()
490                && left.cases().iter().zip(right.cases()).all(|(left, right)| {
491                    left.value() == right.value()
492                        && control_body_equal(
493                            left.body(),
494                            right.body(),
495                            left_parameters,
496                            right_parameters,
497                        )
498                })
499                && match (left.default(), right.default()) {
500                    (Some(left), Some(right)) => {
501                        control_body_equal(left, right, left_parameters, right_parameters)
502                    }
503                    (None, None) => true,
504                    _ => false,
505                }
506        }
507        (ClassicalControlOp::Break, ClassicalControlOp::Break)
508        | (ClassicalControlOp::Continue, ClassicalControlOp::Continue) => true,
509        _ => false,
510    }
511}
512
513fn operations_structurally_equal(
514    left: &[Operation],
515    right: &[Operation],
516    left_parameters: &IndexSet<Parameter>,
517    right_parameters: &IndexSet<Parameter>,
518) -> bool {
519    left.len() == right.len()
520        && left.iter().zip(right).all(|(left, right)| {
521            left.qubits == right.qubits
522                && left.label == right.label
523                && left.params.len() == right.params.len()
524                && left.params.iter().zip(&right.params).all(|(left, right)| {
525                    circuit_params_equal(left, right, left_parameters, right_parameters)
526                })
527                && operation_instruction_equal(
528                    &left.instruction,
529                    &right.instruction,
530                    left_parameters,
531                    right_parameters,
532                )
533        })
534}
535
536impl From<usize> for Circuit {
537    fn from(num_qubits: usize) -> Self {
538        Circuit::new(num_qubits)
539    }
540}
541
542impl Circuit {
543    /// Creates a new, empty quantum circuit with a specified number of qubits.
544    ///
545    /// The qubits will be automatically indexed from `0` to `num_qubits - 1`.
546    ///
547    /// # Arguments
548    ///
549    /// * `num_qubits` - The number of qubits to initialize in the circuit.
550    ///
551    /// # Example
552    ///
553    /// ```rust
554    /// use cqlib_core::circuit::circuit_impl::Circuit;
555    ///
556    /// let circuit = Circuit::new(5);
557    /// assert_eq!(circuit.num_qubits(), 5);
558    /// ```
559    pub fn new(num_qubits: usize) -> Self {
560        let qubits = (0..num_qubits).map(|i| Qubit::new(i as u32)).collect();
561
562        Self {
563            circuit_id: CircuitId::new(),
564            qubits,
565            data: Arc::new(Vec::new()),
566            classical_vars: vec![],
567            classical_values: vec![],
568            control_scope_stack: vec![],
569            symbols: IndexSet::default(),
570            parameters: IndexSet::default(),
571            global_phase: CircuitParam::Fixed(0.0),
572        }
573    }
574
575    /// Creates a circuit from a specific list of qubits.
576    ///
577    /// This is useful when you want to define a sub-circuit or use non-contiguous qubit indices.
578    ///
579    /// # Arguments
580    ///
581    /// * `qubits` - A vector of `Qubit` identifiers.
582    ///
583    /// # Errors
584    ///
585    /// Returns [`CircuitError::DuplicateQubits`] if the input vector contains duplicate qubits.
586    pub fn from_qubits(qubits: Vec<Qubit>) -> Result<Circuit, CircuitError> {
587        if !Self::check_qubits_unique(&qubits) {
588            return Err(CircuitError::DuplicateQubits);
589        }
590
591        Ok(Self {
592            circuit_id: CircuitId::new(),
593            symbols: IndexSet::new(),
594            qubits: qubits.into_iter().collect(),
595            data: Arc::new(Vec::new()),
596            classical_vars: vec![],
597            classical_values: vec![],
598            control_scope_stack: vec![],
599            parameters: IndexSet::default(),
600            global_phase: CircuitParam::Fixed(0.0),
601        })
602    }
603
604    /// Creates a circuit from qubits, value-level operations, and optional runtime classical tables.
605    ///
606    /// Operations are appended in order through value-level lowering, so qubit
607    /// membership is validated and every [`ParameterValue`] is interned into
608    /// this circuit's parameter table. `classical_vars` and
609    /// `classical_values` are installed before appending operations, allowing
610    /// expression-based control flow and classical data operations to reference
611    /// existing circuit-local handles.
612    ///
613    /// # Errors
614    ///
615    /// Returns [`CircuitError::DuplicateQubits`] if `qubits` contains
616    /// duplicates. Also returns any error from [`Circuit::append`], including
617    /// [`CircuitError::QubitNotFound`] when an operation references a qubit
618    /// outside the circuit.
619    pub fn from_operations(
620        qubits: Vec<Qubit>,
621        operations: impl IntoIterator<Item = ValueOperation>,
622        classical_vars: Option<Vec<ClassicalType>>,
623        classical_values: Option<Vec<ClassicalType>>,
624    ) -> Result<Self, CircuitError> {
625        let operations = operations.into_iter().collect::<Vec<_>>();
626        let mut circuit = Self::from_qubits(qubits)?;
627        Arc::make_mut(&mut circuit.data).reserve(operations.len());
628        if let Some(circuit_id) = infer_classical_circuit_id(&operations)? {
629            circuit.circuit_id = circuit_id;
630        }
631        circuit.classical_vars = classical_vars.unwrap_or_default();
632        circuit.classical_values = classical_values.unwrap_or_default();
633        for operation in operations {
634            circuit.append_value_operation_deferred_validation(operation)?;
635        }
636        circuit.validate_operation_parameters(circuit.operations())?;
637        circuit.validate()?;
638        Ok(circuit)
639    }
640
641    /// Lowers and appends a self-contained value-level operation.
642    ///
643    /// # Errors
644    ///
645    /// Returns errors from value-instruction lowering or [`Circuit::append`].
646    pub fn append_value_operation(
647        &mut self,
648        operation: ValueOperation,
649    ) -> Result<(), CircuitError> {
650        self.append_value_operation_with_validation(operation, true)
651    }
652
653    fn append_value_operation_deferred_validation(
654        &mut self,
655        operation: ValueOperation,
656    ) -> Result<(), CircuitError> {
657        self.append_value_operation_with_validation(operation, false)
658    }
659
660    fn append_value_operation_with_validation(
661        &mut self,
662        operation: ValueOperation,
663        validate_builder_state: bool,
664    ) -> Result<(), CircuitError> {
665        let ValueOperation {
666            instruction,
667            qubits,
668            params,
669            label,
670        } = operation;
671        let instruction = self.lower_instruction(instruction)?;
672        self.append_owned_with_validation(
673            instruction,
674            qubits,
675            params,
676            label,
677            validate_builder_state,
678        )
679    }
680
681    /// Adds new qubits to the existing circuit.
682    ///
683    /// # Arguments
684    ///
685    /// * `new_qubits` - A vector of new `Qubit` identifiers to add.
686    ///
687    /// # Errors
688    ///
689    /// Returns [`CircuitError::DuplicateQubits`] if any of the new qubits already exist in the circuit
690    /// or if `new_qubits` contains duplicates.
691    pub fn add_qubits(&mut self, new_qubits: Vec<Qubit>) -> Result<(), CircuitError> {
692        let mut seen_new = HashSet::with_capacity(new_qubits.len());
693
694        for q in &new_qubits {
695            if self.qubits.contains(q) {
696                return Err(CircuitError::DuplicateQubits);
697            }
698
699            if !seen_new.insert(*q) {
700                return Err(CircuitError::DuplicateQubits);
701            }
702        }
703        self.qubits.extend(new_qubits);
704        Ok(())
705    }
706
707    /// Interns a parameter and records its symbols.
708    ///
709    /// Returns the stable table index and whether the parameter was newly inserted.
710    pub fn add_parameter(&mut self, param: Parameter) -> (usize, bool) {
711        let (index, is_new) = self.parameters.insert_full(param.clone());
712        if is_new {
713            for sym in param.get_symbols() {
714                self.symbols.insert(sym);
715            }
716        }
717        (index, is_new)
718    }
719
720    /// Resolves an operation parameter reference into a high-level parameter.
721    ///
722    /// `CircuitParam::Fixed` values are converted directly into numeric
723    /// [`Parameter`] values after validating that the stored float is finite.
724    /// `CircuitParam::Index` values are resolved through this circuit's
725    /// parameter table.
726    ///
727    /// # Errors
728    ///
729    /// Returns [`CircuitError::InvalidParameterValue`] for non-finite fixed
730    /// values and [`CircuitError::InvalidParameterIndex`] for missing parameter
731    /// table entries.
732    pub fn resolve_parameter(&self, param: &CircuitParam) -> Result<Parameter, CircuitError> {
733        match param {
734            CircuitParam::Fixed(value) => {
735                if !value.is_finite() {
736                    return Err(CircuitError::InvalidParameterValue(0, *value));
737                }
738                Ok(Parameter::from(*value))
739            }
740            CircuitParam::Index(index) => self
741                .parameters
742                .get_index(*index as usize)
743                .cloned()
744                .ok_or(CircuitError::InvalidParameterIndex(*index)),
745        }
746    }
747
748    /// Resolves an operation parameter reference into an appendable value.
749    ///
750    /// This is useful when rebuilding operations from an existing circuit:
751    /// fixed values remain fixed, while indexed symbolic parameters are looked
752    /// up in the circuit parameter table and returned as [`ParameterValue::Param`].
753    ///
754    /// # Errors
755    ///
756    /// Returns [`CircuitError::InvalidParameterIndex`] when an indexed parameter
757    /// does not exist in this circuit's parameter table.
758    pub fn parameter_value(&self, param: &CircuitParam) -> Result<ParameterValue, CircuitError> {
759        match param {
760            CircuitParam::Fixed(value) => Ok(ParameterValue::Fixed(*value)),
761            CircuitParam::Index(index) => self
762                .parameters
763                .get_index(*index as usize)
764                .cloned()
765                .map(ParameterValue::Param)
766                .ok_or(CircuitError::InvalidParameterIndex(*index)),
767        }
768    }
769
770    /// Maps a high-level parameter into the circuit's operation parameter form.
771    ///
772    /// This is not the same as [`Circuit::add_parameter`]. `add_parameter`
773    /// always inserts a [`Parameter`] into the circuit parameter table and
774    /// returns the raw table index. `map_param` first canonicalizes the
775    /// parameter and then chooses how operations should store it:
776    ///
777    /// - constant parameters are simplified, evaluated, finite-checked,
778    ///   normalized from `-0.0` to `0.0`, and returned as
779    ///   [`CircuitParam::Fixed`] without being inserted into the parameter
780    ///   table;
781    /// - symbolic parameters are simplified, interned in the parameter table,
782    ///   and returned as [`CircuitParam::Index`]. The circuit symbol table is
783    ///   updated when a new symbolic parameter is inserted.
784    ///
785    /// # Errors
786    ///
787    /// Returns [`CircuitError::InvalidParameter`] if parameter simplification
788    /// or constant evaluation fails.
789    pub fn map_param(&mut self, param: Parameter) -> Result<CircuitParam, CircuitError> {
790        let param = param.canonicalized()?;
791        if param.get_symbols().is_empty() {
792            let value = param.evaluate(&None)?;
793            let value = if value == 0.0 { 0.0 } else { value };
794            Ok(CircuitParam::Fixed(value))
795        } else {
796            let (index, _) = self.add_parameter(param);
797            Ok(CircuitParam::Index(index as u32))
798        }
799    }
800
801    /// Returns the number of qubits in the circuit.
802    ///
803    /// Alias for `num_qubits()`.
804    pub fn width(&self) -> usize {
805        self.qubits.len()
806    }
807
808    /// Returns the number of qubits in the circuit.
809    pub fn num_qubits(&self) -> usize {
810        self.qubits.len()
811    }
812
813    /// Returns the parameters of the circuit.
814    pub fn parameters(&self) -> &IndexSet<Parameter> {
815        &self.parameters
816    }
817
818    /// Returns all symbolic variable names present in the parameter registry.
819    ///
820    /// The registry is stable and is not compacted when operations stop using a
821    /// parameter, so this set may include symbols that are no longer referenced
822    /// by the executable IR. Use [`Circuit::used_symbols`] when only live
823    /// dependencies are needed.
824    pub fn symbols(&self) -> &IndexSet<String> {
825        &self.symbols
826    }
827
828    /// Returns symbolic variable names referenced by the executable IR.
829    ///
830    /// This includes the global phase and operation parameters in top-level and
831    /// nested structured control-flow bodies. The result preserves the stable
832    /// insertion order of [`Circuit::symbols`] while filtering out interned but
833    /// unreferenced symbols.
834    pub fn used_symbols(&self) -> IndexSet<String> {
835        let referenced_indices = self.referenced_parameter_indices();
836        let mut referenced_symbols = HashSet::new();
837        for index in referenced_indices {
838            if let Some(parameter) = self.parameters.get_index(index as usize) {
839                referenced_symbols.extend(parameter.get_symbols());
840            }
841        }
842
843        self.symbols
844            .iter()
845            .filter(|symbol| referenced_symbols.contains(symbol.as_str()))
846            .cloned()
847            .collect()
848    }
849
850    /// Returns whether `symbol` is referenced by the circuit's executable IR.
851    ///
852    /// This inspects the global phase and operation parameters, including
853    /// parameters in nested structured control-flow bodies. It intentionally
854    /// does not use the circuit's symbol registry because that registry may
855    /// retain symbols that are interned but no longer referenced.
856    pub fn uses_symbol(&self, symbol: &str) -> bool {
857        self.referenced_parameter_indices()
858            .into_iter()
859            .any(|index| {
860                self.parameters
861                    .get_index(index as usize)
862                    .is_some_and(|parameter| parameter.get_symbols().contains(symbol))
863            })
864    }
865
866    fn referenced_parameter_indices(&self) -> HashSet<u32> {
867        let mut indices = HashSet::new();
868        if let CircuitParam::Index(index) = self.global_phase {
869            indices.insert(index);
870        }
871        Self::collect_operation_parameter_indices(self.data.as_slice(), &mut indices);
872        indices
873    }
874
875    fn collect_operation_parameter_indices(operations: &[Operation], indices: &mut HashSet<u32>) {
876        for operation in operations {
877            indices.extend(operation.params.iter().filter_map(|param| match param {
878                CircuitParam::Index(index) => Some(*index),
879                CircuitParam::Fixed(_) => None,
880            }));
881
882            if let Instruction::ClassicalControl(control) = &operation.instruction {
883                match control {
884                    ClassicalControlOp::If(op) => {
885                        Self::collect_operation_parameter_indices(
886                            op.then_body().operations(),
887                            indices,
888                        );
889                        if let Some(body) = op.else_body() {
890                            Self::collect_operation_parameter_indices(body.operations(), indices);
891                        }
892                    }
893                    ClassicalControlOp::While(op) => {
894                        Self::collect_operation_parameter_indices(op.body().operations(), indices);
895                    }
896                    ClassicalControlOp::For(op) => {
897                        Self::collect_operation_parameter_indices(op.body().operations(), indices);
898                    }
899                    ClassicalControlOp::Switch(op) => {
900                        for case in op.cases() {
901                            Self::collect_operation_parameter_indices(
902                                case.body().operations(),
903                                indices,
904                            );
905                        }
906                        if let Some(body) = op.default() {
907                            Self::collect_operation_parameter_indices(body.operations(), indices);
908                        }
909                    }
910                    ClassicalControlOp::Break | ClassicalControlOp::Continue => {}
911                }
912            }
913        }
914    }
915
916    /// Returns a vector of all qubits in the circuit, preserving their insertion order.
917    pub fn qubits(&self) -> Vec<Qubit> {
918        self.qubits.iter().cloned().collect()
919    }
920
921    /// Returns whether `qubit` belongs to this circuit without materializing
922    /// the ordered qubit list.
923    pub(crate) fn contains_qubit(&self, qubit: &Qubit) -> bool {
924        self.qubits.contains(qubit)
925    }
926
927    /// Returns whether two circuits have the same ordered qubit domain without
928    /// allocating either public qubit vector.
929    pub(crate) fn has_same_qubits(&self, other: &Self) -> bool {
930        self.qubits == other.qubits
931    }
932
933    /// Returns the global phase of the circuit as a `Parameter`.
934    pub fn global_phase(&self) -> Parameter {
935        match self.global_phase {
936            CircuitParam::Index(index) => self.parameters[index as usize].clone(),
937            CircuitParam::Fixed(value) => Parameter::from(value),
938        }
939    }
940
941    /// Returns the compact storage representation of the global phase.
942    pub fn global_phase_param(&self) -> &CircuitParam {
943        &self.global_phase
944    }
945
946    /// Sets the global phase of the circuit.
947    pub fn set_global_phase(&mut self, phase: Parameter) {
948        // Try to simplify/evaluate to keep it clean
949        if let Ok(val) = phase.evaluate(&None) {
950            self.global_phase = CircuitParam::Fixed(val);
951        } else {
952            let (index, is_new) = self.parameters.insert_full(phase.clone());
953            if is_new {
954                for sym in phase.get_symbols() {
955                    self.symbols.insert(sym);
956                }
957            }
958            self.global_phase = CircuitParam::Index(index as u32);
959        }
960    }
961
962    /// Returns storage-IR operations in execution order.
963    pub fn operations(&self) -> &[Operation] {
964        self.data.as_slice()
965    }
966
967    /// Removes a top-level operation from the circuit schedule.
968    ///
969    /// This deletes exactly one operation from the circuit's top-level
970    /// operation list. Structured control-flow operations and circuit-backed
971    /// gates are removed as whole operations; this API does not delete
972    /// individual operations inside a [`ControlBody`] or nested circuit.
973    ///
974    /// If the removed operation defines measurement-backed classical values,
975    /// those values are removed from the circuit's immutable classical value
976    /// table and remaining value references are compacted. Deletion fails
977    /// atomically when any remaining operation still reads a removed value.
978    ///
979    /// # Errors
980    ///
981    /// Returns the same errors as [`Self::remove_operations`] for a single index.
982    pub fn remove_operation(&mut self, index: usize) -> Result<Operation, CircuitError> {
983        let mut removed = self.remove_operations([index])?;
984        Ok(removed.remove(0))
985    }
986
987    /// Removes multiple top-level operations from the circuit schedule.
988    ///
989    /// `indices` are interpreted against the original top-level operation
990    /// list before deletion. Duplicate indices are ignored, and removed
991    /// operations are returned in ascending original-index order. Structured
992    /// control-flow operations and circuit-backed gates are removed as whole
993    /// operations; this API does not delete individual operations inside a
994    /// [`ControlBody`] or nested circuit.
995    ///
996    /// If the removed operations define measurement-backed classical values,
997    /// those values are removed from the circuit's immutable classical value
998    /// table and remaining value references are compacted. Deletion fails
999    /// atomically when any remaining operation still reads a removed value.
1000    ///
1001    /// # Errors
1002    ///
1003    /// Returns [`CircuitError::OperationIndexOutOfBounds`] when any index is
1004    /// not a valid operation index. Returns
1005    /// [`CircuitError::ClassicalValueStillInUse`] when a measurement result
1006    /// defined by the removed operations is still read by an operation that is
1007    /// not also being removed. Also returns any validation or remapping error
1008    /// raised by the candidate circuit after deletion.
1009    pub fn remove_operations<I>(&mut self, indices: I) -> Result<Vec<Operation>, CircuitError>
1010    where
1011        I: IntoIterator<Item = usize>,
1012    {
1013        let mut indices = indices.into_iter().collect::<Vec<_>>();
1014        indices.sort_unstable();
1015        indices.dedup();
1016
1017        if indices.is_empty() {
1018            return Ok(Vec::new());
1019        }
1020
1021        if let Some(index) = indices
1022            .iter()
1023            .copied()
1024            .find(|&index| index >= self.data.len())
1025        {
1026            return Err(CircuitError::OperationIndexOutOfBounds {
1027                index,
1028                len: self.data.len(),
1029            });
1030        }
1031
1032        let mut removed = Vec::with_capacity(indices.len());
1033        let mut removed_values = Vec::new();
1034        for &index in &indices {
1035            let operation = self.data[index].clone();
1036            Self::collect_defined_classical_values(&operation, &mut removed_values);
1037            removed.push(operation);
1038        }
1039        removed_values.sort_unstable();
1040        removed_values.dedup();
1041
1042        let remove_set = indices.iter().copied().collect::<HashSet<_>>();
1043
1044        for (operation_index, operation) in self.data.iter().enumerate() {
1045            if remove_set.contains(&operation_index) {
1046                continue;
1047            }
1048            for value in &removed_values {
1049                if operation.instruction.reads_value(*value) {
1050                    return Err(CircuitError::ClassicalValueStillInUse {
1051                        index: value.index(),
1052                        context: format!("operation {operation_index}"),
1053                    });
1054                }
1055            }
1056        }
1057
1058        let mut candidate_classical_values = self.classical_values.clone();
1059        let candidate_data = if removed_values.is_empty() {
1060            let mut candidate_data = Vec::with_capacity(self.data.len() - indices.len());
1061            for (operation_index, operation) in self.data.iter().cloned().enumerate() {
1062                if !remove_set.contains(&operation_index) {
1063                    candidate_data.push(operation);
1064                }
1065            }
1066            candidate_data
1067        } else {
1068            let (compacted_values, value_map) = Self::build_value_compaction_map(
1069                self.circuit_id,
1070                &candidate_classical_values,
1071                &removed_values,
1072            );
1073            candidate_classical_values = compacted_values;
1074
1075            let qubit_mapping = self
1076                .qubits
1077                .iter()
1078                .copied()
1079                .map(|qubit| (qubit, qubit))
1080                .collect::<HashMap<_, _>>();
1081            let param_index_map = (0..self.parameters.len())
1082                .map(|index| CircuitParam::Index(index as u32))
1083                .collect::<Vec<_>>();
1084            let var_map = self
1085                .classical_vars
1086                .iter()
1087                .copied()
1088                .enumerate()
1089                .map(|(var_index, ty)| {
1090                    let var = ClassicalVar::new(self.circuit_id, var_index as u32, ty);
1091                    (var, var)
1092                })
1093                .collect::<HashMap<_, _>>();
1094
1095            self.data
1096                .iter()
1097                .enumerate()
1098                .filter(|(operation_index, _)| !remove_set.contains(operation_index))
1099                .map(|(_, operation)| {
1100                    Self::remap_compose_operation(
1101                        operation,
1102                        &qubit_mapping,
1103                        &param_index_map,
1104                        &var_map,
1105                        &value_map,
1106                    )
1107                })
1108                .collect::<Result<Vec<_>, _>>()?
1109        };
1110
1111        let candidate = Self {
1112            circuit_id: self.circuit_id,
1113            qubits: self.qubits.clone(),
1114            symbols: self.symbols.clone(),
1115            parameters: self.parameters.clone(),
1116            data: Arc::new(candidate_data),
1117            classical_vars: self.classical_vars.clone(),
1118            classical_values: candidate_classical_values,
1119            control_scope_stack: self.control_scope_stack.clone(),
1120            global_phase: self.global_phase.clone(),
1121        };
1122        candidate.validate()?;
1123
1124        self.data = candidate.data;
1125        self.classical_values = candidate.classical_values;
1126
1127        Ok(removed)
1128    }
1129
1130    fn collect_defined_classical_values(operation: &Operation, out: &mut Vec<ClassicalValue>) {
1131        let collect_from_body = |body: &ControlBody, out: &mut Vec<ClassicalValue>| {
1132            for operation in body.operations() {
1133                Circuit::collect_defined_classical_values(operation, out);
1134            }
1135        };
1136
1137        match &operation.instruction {
1138            Instruction::ClassicalData(op) => {
1139                if let Some(result) = op.result() {
1140                    out.push(result);
1141                }
1142            }
1143            Instruction::ClassicalControl(op) => match op {
1144                ClassicalControlOp::If(op) => {
1145                    collect_from_body(op.then_body(), out);
1146                    if let Some(body) = op.else_body() {
1147                        collect_from_body(body, out);
1148                    }
1149                }
1150                ClassicalControlOp::While(op) => collect_from_body(op.body(), out),
1151                ClassicalControlOp::For(op) => collect_from_body(op.body(), out),
1152                ClassicalControlOp::Switch(op) => {
1153                    for case in op.cases() {
1154                        collect_from_body(case.body(), out);
1155                    }
1156                    if let Some(body) = op.default() {
1157                        collect_from_body(body, out);
1158                    }
1159                }
1160                ClassicalControlOp::Break | ClassicalControlOp::Continue => {}
1161            },
1162            _ => {}
1163        }
1164    }
1165
1166    fn build_value_compaction_map(
1167        circuit_id: CircuitId,
1168        values: &[ClassicalType],
1169        removed: &[ClassicalValue],
1170    ) -> (Vec<ClassicalType>, HashMap<ClassicalValue, ClassicalValue>) {
1171        let removed_indices = removed
1172            .iter()
1173            .map(|value| value.index())
1174            .collect::<HashSet<_>>();
1175        let mut compacted = Vec::with_capacity(values.len().saturating_sub(removed_indices.len()));
1176        let mut value_map =
1177            HashMap::with_capacity(values.len().saturating_sub(removed_indices.len()));
1178
1179        for (old_index, ty) in values.iter().copied().enumerate() {
1180            if removed_indices.contains(&(old_index as u32)) {
1181                continue;
1182            }
1183            let new_index = compacted.len() as u32;
1184            compacted.push(ty);
1185            value_map.insert(
1186                ClassicalValue::new(circuit_id, old_index as u32, ty),
1187                ClassicalValue::new(circuit_id, new_index, ty),
1188            );
1189        }
1190
1191        (compacted, value_map)
1192    }
1193
1194    /// Returns the circuit depth (longest ASAP schedule path over qubit wires).
1195    ///
1196    /// Every instruction node contributes one layer on the qubits it touches;
1197    /// barriers synchronize their listed qubits (an empty-qubit barrier is
1198    /// global, synchronizing every circuit qubit). `CircuitGate` is opaque
1199    /// (depth 1); call [`decompose`](Self::decompose) first to recurse into
1200    /// nested sub-circuits.
1201    ///
1202    /// With `recurse = false` (default), an error is returned if the circuit
1203    /// contains any classical control-flow operation (`if`/`while`/`for`/
1204    /// `switch`/`break`/`continue`). With `recurse = true`, control flow is
1205    /// unfolded into an estimated depth: `if`/`switch` take the max branch,
1206    /// `while` counts the body once, `for` with a statically-known
1207    /// unsigned-literal range is fully unrolled (else counted once).
1208    ///
1209    /// # Errors
1210    ///
1211    /// Returns [`CircuitError::ControlFlowPresent`] when `recurse = false` and
1212    /// the circuit contains control flow.
1213    pub fn depth(&self, recurse: bool) -> Result<usize, CircuitError> {
1214        crate::circuit::depth::circuit_depth(
1215            self.qubits.iter().copied(),
1216            self.data.as_slice(),
1217            recurse,
1218        )
1219    }
1220
1221    /// Returns the static types of runtime classical variables owned by this circuit.
1222    pub fn classical_vars(&self) -> &[ClassicalType] {
1223        &self.classical_vars
1224    }
1225
1226    /// Returns this circuit's process-local identity for constructing explicit
1227    /// classical IR handles.
1228    pub fn id(&self) -> CircuitId {
1229        self.circuit_id
1230    }
1231
1232    /// Returns the static types of immutable runtime classical values owned by this circuit.
1233    pub fn classical_values(&self) -> &[ClassicalType] {
1234        &self.classical_values
1235    }
1236
1237    /// Appends a generic instruction to the circuit.
1238    ///
1239    /// This is the low-level method used by all specific gate methods (e.g., `h`, `cx`).
1240    /// It handles arity validation, parameter interning, and qubit validation.
1241    ///
1242    /// # Arguments
1243    ///
1244    /// * `instruction` - The instruction to append (Standard, Extended, or Directive).
1245    /// * `qubits` - The qubits this instruction acts upon.
1246    /// * `params` - The parameters for the instruction (if any).
1247    /// * `label` - An optional label for the operation.
1248    ///
1249    /// # Errors
1250    ///
1251    /// Returns [`CircuitError::QubitCountMismatch`] or
1252    /// [`CircuitError::ParameterCountMismatch`] when a fixed-arity instruction
1253    /// receives the wrong number of operands. Returns [`CircuitError::DuplicateQubits`]
1254    /// when the same qubit is supplied more than once and
1255    /// [`CircuitError::QubitNotFound`] if a qubit is not present in the circuit.
1256    /// Non-finite fixed parameters produce [`CircuitError::InvalidParameterValue`].
1257    /// Classical data and control-flow instructions may additionally fail their
1258    /// ownership, type, scope, or structural validation.
1259    pub fn append<Q, P>(
1260        &mut self,
1261        instruction: Instruction,
1262        qubits: Q,
1263        params: P,
1264        label: Option<&str>,
1265    ) -> Result<(), CircuitError>
1266    where
1267        Q: IntoIterator,
1268        Q::Item: Into<Qubit>,
1269        P: IntoIterator<Item = ParameterValue>,
1270    {
1271        self.append_with_validation(instruction, qubits, params, label, true)
1272    }
1273
1274    fn append_with_validation<Q, P>(
1275        &mut self,
1276        instruction: Instruction,
1277        qubits: Q,
1278        params: P,
1279        label: Option<&str>,
1280        validate_builder_state: bool,
1281    ) -> Result<(), CircuitError>
1282    where
1283        Q: IntoIterator,
1284        Q::Item: Into<Qubit>,
1285        P: IntoIterator<Item = ParameterValue>,
1286    {
1287        self.append_owned_with_validation(
1288            instruction,
1289            qubits,
1290            params,
1291            label.map(Into::into),
1292            validate_builder_state,
1293        )
1294    }
1295
1296    fn append_owned_with_validation<Q, P>(
1297        &mut self,
1298        instruction: Instruction,
1299        qubits: Q,
1300        params: P,
1301        label: Option<Box<str>>,
1302        validate_builder_state: bool,
1303    ) -> Result<(), CircuitError>
1304    where
1305        Q: IntoIterator,
1306        Q::Item: Into<Qubit>,
1307        P: IntoIterator<Item = ParameterValue>,
1308    {
1309        let validate_classical_now = validate_builder_state
1310            && matches!(
1311                instruction,
1312                Instruction::ClassicalData(_) | Instruction::ClassicalControl(_)
1313            );
1314        let checkpoint = validate_classical_now.then(|| self.checkpoint());
1315
1316        if let Instruction::ClassicalControl(op) = &instruction {
1317            self.validate_control_op(op)?;
1318        }
1319
1320        let qubits_sv: SmallVec<[Qubit; 3]> = qubits.into_iter().map(|q| q.into()).collect();
1321        let params_sv: SmallVec<[ParameterValue; 1]> = params.into_iter().collect();
1322
1323        if let Some((expected_qubits, expected_params)) = instruction.gate_arity() {
1324            if qubits_sv.len() != expected_qubits {
1325                return Err(CircuitError::QubitCountMismatch {
1326                    expected: expected_qubits,
1327                    actual: qubits_sv.len(),
1328                });
1329            }
1330            if params_sv.len() != expected_params {
1331                return Err(CircuitError::ParameterCountMismatch {
1332                    expected: expected_params,
1333                    actual: params_sv.len(),
1334                });
1335            }
1336        }
1337
1338        const INLINE_DUPLICATE_CHECK_LIMIT: usize = 8;
1339        if qubits_sv.len() <= INLINE_DUPLICATE_CHECK_LIMIT {
1340            if qubits_sv
1341                .iter()
1342                .enumerate()
1343                .any(|(index, qubit)| qubits_sv[..index].contains(qubit))
1344            {
1345                return Err(CircuitError::DuplicateQubits);
1346            }
1347        } else {
1348            let mut seen = HashSet::with_capacity(qubits_sv.len());
1349            for &qubit in &qubits_sv {
1350                if !seen.insert(qubit) {
1351                    return Err(CircuitError::DuplicateQubits);
1352                }
1353            }
1354        }
1355
1356        for qubit in &qubits_sv {
1357            if !self.qubits.contains(qubit) {
1358                return Err(CircuitError::QubitNotFound(qubit.id()));
1359            }
1360        }
1361        if let Instruction::ClassicalData(op) = &instruction {
1362            self.validate_classical_data_op(op, qubits_sv.len())?;
1363        }
1364
1365        let mut circuit_params = smallvec![];
1366        for (param_index, p) in params_sv.into_iter().enumerate() {
1367            match p {
1368                ParameterValue::Param(param) => {
1369                    let (index, is_new) = self.parameters.insert_full(param.clone());
1370                    if is_new {
1371                        for sym in param.get_symbols() {
1372                            self.symbols.insert(sym);
1373                        }
1374                    }
1375                    circuit_params.push(CircuitParam::Index(index as u32));
1376                }
1377                ParameterValue::Fixed(value) => {
1378                    if !value.is_finite() {
1379                        return Err(CircuitError::InvalidParameterValue(param_index, value));
1380                    }
1381                    circuit_params.push(CircuitParam::Fixed(value));
1382                }
1383            }
1384        }
1385
1386        Arc::make_mut(&mut self.data).push(Operation {
1387            instruction,
1388            qubits: qubits_sv,
1389            params: circuit_params,
1390            label,
1391        });
1392
1393        if validate_classical_now && let Err(error) = self.validate_builder_state() {
1394            self.rollback_to(checkpoint.expect("classical append must define a checkpoint"));
1395            return Err(error);
1396        }
1397
1398        Ok(())
1399    }
1400
1401    /// Appends a Hadamard (H) gate.
1402    ///
1403    /// The H gate creates a superposition state: $H|0\rangle = \frac{|0\rangle + |1\rangle}{\sqrt{2}}$.
1404    pub fn h(&mut self, qubit: Qubit) -> Result<(), CircuitError> {
1405        self.append(
1406            Instruction::Standard(StandardGate::H),
1407            [qubit],
1408            std::iter::empty(),
1409            None,
1410        )
1411    }
1412
1413    // --- Pauli Gates ---
1414
1415    /// Appends an Identity (I) gate.
1416    ///
1417    /// This is a no-op gate, often used for alignment or waiting.
1418    pub fn i(&mut self, qubit: Qubit) -> Result<(), CircuitError> {
1419        self.append(
1420            Instruction::Standard(StandardGate::I),
1421            [qubit],
1422            std::iter::empty(),
1423            None,
1424        )
1425    }
1426
1427    /// Appends a Pauli-X (NOT) gate.
1428    ///
1429    /// Performs a bit flip: $X|0\rangle = |1\rangle, X|1\rangle = |0\rangle$.
1430    pub fn x(&mut self, qubit: Qubit) -> Result<(), CircuitError> {
1431        self.append(
1432            Instruction::Standard(StandardGate::X),
1433            [qubit],
1434            std::iter::empty(),
1435            None,
1436        )
1437    }
1438
1439    /// Appends a Pauli-Y gate.
1440    ///
1441    /// Performs a bit and phase flip: $Y|0\rangle = i|1\rangle, Y|1\rangle = -i|0\rangle$.
1442    pub fn y(&mut self, qubit: Qubit) -> Result<(), CircuitError> {
1443        self.append(
1444            Instruction::Standard(StandardGate::Y),
1445            [qubit],
1446            std::iter::empty(),
1447            None,
1448        )
1449    }
1450
1451    /// Appends a Pauli-Z gate.
1452    ///
1453    /// Performs a phase flip: $Z|0\rangle = |0\rangle, Z|1\rangle = -|1\rangle$.
1454    pub fn z(&mut self, qubit: Qubit) -> Result<(), CircuitError> {
1455        self.append(
1456            Instruction::Standard(StandardGate::Z),
1457            [qubit],
1458            std::iter::empty(),
1459            None,
1460        )
1461    }
1462
1463    /// Appends a $\sqrt{X}$ (SX) gate.
1464    ///
1465    /// A 90-degree rotation around the X-axis. $SX^2 = X$.
1466    pub fn x2p(&mut self, qubit: Qubit) -> Result<(), CircuitError> {
1467        self.append(
1468            Instruction::Standard(StandardGate::X2P),
1469            [qubit],
1470            std::iter::empty(),
1471            None,
1472        )
1473    }
1474
1475    /// Appends a $\sqrt{X}^\dagger$ (SXdg) gate.
1476    ///
1477    /// The inverse of the SX gate.
1478    pub fn x2m(&mut self, qubit: Qubit) -> Result<(), CircuitError> {
1479        self.append(
1480            Instruction::Standard(StandardGate::X2M),
1481            [qubit],
1482            std::iter::empty(),
1483            None,
1484        )
1485    }
1486
1487    /// Appends a $\sqrt{Y}$ gate.
1488    pub fn y2p(&mut self, qubit: Qubit) -> Result<(), CircuitError> {
1489        self.append(
1490            Instruction::Standard(StandardGate::Y2P),
1491            [qubit],
1492            std::iter::empty(),
1493            None,
1494        )
1495    }
1496
1497    /// Appends a $\sqrt{Y}^\dagger$ gate.
1498    pub fn y2m(&mut self, qubit: Qubit) -> Result<(), CircuitError> {
1499        self.append(
1500            Instruction::Standard(StandardGate::Y2M),
1501            [qubit],
1502            std::iter::empty(),
1503            None,
1504        )
1505    }
1506
1507    /// Appends an XY gate.
1508    ///
1509    /// Applies a pi rotation about the axis at angle `theta` in the single-qubit XY plane.
1510    pub fn xy(
1511        &mut self,
1512        qubit: Qubit,
1513        theta: impl Into<ParameterValue>,
1514    ) -> Result<(), CircuitError> {
1515        let params: SmallVec<[ParameterValue; 1]> = smallvec![theta.into()];
1516        self.append(
1517            Instruction::Standard(StandardGate::XY),
1518            [qubit],
1519            params,
1520            None,
1521        )
1522    }
1523
1524    /// Appends a positive half-pi rotation in the single-qubit XY plane.
1525    pub fn xy2p(
1526        &mut self,
1527        qubit: Qubit,
1528        theta: impl Into<ParameterValue>,
1529    ) -> Result<(), CircuitError> {
1530        let params: SmallVec<[ParameterValue; 1]> = smallvec![theta.into()];
1531        self.append(
1532            Instruction::Standard(StandardGate::XY2P),
1533            [qubit],
1534            params,
1535            None,
1536        )
1537    }
1538
1539    /// Appends a negative half-pi rotation in the single-qubit XY plane.
1540    pub fn xy2m(
1541        &mut self,
1542        qubit: Qubit,
1543        theta: impl Into<ParameterValue>,
1544    ) -> Result<(), CircuitError> {
1545        let params: SmallVec<[ParameterValue; 1]> = smallvec![theta.into()];
1546        self.append(
1547            Instruction::Standard(StandardGate::XY2M),
1548            [qubit],
1549            params,
1550            None,
1551        )
1552    }
1553
1554    // --- Clifford & Phase Gates ---
1555
1556    /// Appends an S (Phase) gate.
1557    ///
1558    /// Applies a phase of $i$ to the $|1\rangle$ state ($Z^{1/2}$).
1559    pub fn s(&mut self, qubit: Qubit) -> Result<(), CircuitError> {
1560        self.append(
1561            Instruction::Standard(StandardGate::S),
1562            [qubit],
1563            std::iter::empty(),
1564            None,
1565        )
1566    }
1567
1568    /// Appends an S-dagger ($S^\dagger$) gate.
1569    ///
1570    /// Applies a phase of $-i$ to the $|1\rangle$ state.
1571    pub fn sdg(&mut self, qubit: Qubit) -> Result<(), CircuitError> {
1572        self.append(
1573            Instruction::Standard(StandardGate::SDG),
1574            [qubit],
1575            std::iter::empty(),
1576            None,
1577        )
1578    }
1579
1580    /// Appends a T gate.
1581    ///
1582    /// Applies a phase of $e^{i\pi/4}$ to the $|1\rangle$ state ($Z^{1/4}$).
1583    pub fn t(&mut self, qubit: Qubit) -> Result<(), CircuitError> {
1584        self.append(
1585            Instruction::Standard(StandardGate::T),
1586            [qubit],
1587            std::iter::empty(),
1588            None,
1589        )
1590    }
1591
1592    /// Appends a T-dagger ($T^\dagger$) gate.
1593    pub fn tdg(&mut self, qubit: Qubit) -> Result<(), CircuitError> {
1594        self.append(
1595            Instruction::Standard(StandardGate::TDG),
1596            [qubit],
1597            std::iter::empty(),
1598            None,
1599        )
1600    }
1601
1602    // --- Parametric Rotations ---
1603
1604    /// Appends a rotation around the X-axis by angle `theta`.
1605    ///
1606    /// $RX(\theta) = e^{-i\theta X/2}$
1607    pub fn rx(
1608        &mut self,
1609        qubit: Qubit,
1610        theta: impl Into<ParameterValue>,
1611    ) -> Result<(), CircuitError> {
1612        let params: SmallVec<[ParameterValue; 1]> = smallvec![theta.into()];
1613        self.append(
1614            Instruction::Standard(StandardGate::RX),
1615            [qubit],
1616            params,
1617            None,
1618        )
1619    }
1620
1621    /// Appends a rotation around the Y-axis by angle `theta`.
1622    ///
1623    /// $RY(\theta) = e^{-i\theta Y/2}$
1624    pub fn ry(
1625        &mut self,
1626        qubit: Qubit,
1627        theta: impl Into<ParameterValue>,
1628    ) -> Result<(), CircuitError> {
1629        let params: SmallVec<[ParameterValue; 1]> = smallvec![theta.into()];
1630
1631        self.append(
1632            Instruction::Standard(StandardGate::RY),
1633            [qubit],
1634            params,
1635            None,
1636        )
1637    }
1638
1639    /// Appends a rotation around the Z-axis by angle `theta`.
1640    ///
1641    /// $RZ(\theta) = e^{-i\theta Z/2}$
1642    pub fn rz(
1643        &mut self,
1644        qubit: Qubit,
1645        theta: impl Into<ParameterValue>,
1646    ) -> Result<(), CircuitError> {
1647        let params: SmallVec<[ParameterValue; 1]> = smallvec![theta.into()];
1648
1649        self.append(
1650            Instruction::Standard(StandardGate::RZ),
1651            [qubit],
1652            params,
1653            None,
1654        )
1655    }
1656
1657    /// Appends a Phase gate (P gate).
1658    ///
1659    /// Applies a phase of $e^{i\lambda}$ to the $|1\rangle$ state.
1660    /// Equivalent to $RZ(\lambda)$ up to a global phase.
1661    pub fn phase(
1662        &mut self,
1663        qubit: Qubit,
1664        lambda: impl Into<ParameterValue>,
1665    ) -> Result<(), CircuitError> {
1666        let params: SmallVec<[ParameterValue; 1]> = smallvec![lambda.into()];
1667
1668        self.append(
1669            Instruction::Standard(StandardGate::Phase),
1670            [qubit],
1671            params,
1672            None,
1673        )
1674    }
1675
1676    /// Appends a generic single-qubit rotation gate $U(\theta, \phi, \lambda)$.
1677    ///
1678    /// This is the most general single-qubit unitary gate.
1679    /// $$
1680    /// U(\theta, \phi, \lambda) = \begin{pmatrix}
1681    /// \cos(\theta/2) & -e^{i\lambda}\sin(\theta/2) \\
1682    /// e^{i\phi}\sin(\theta/2) & e^{i(\phi+\lambda)}\cos(\theta/2)
1683    /// \end{pmatrix}
1684    /// $$
1685    pub fn u(
1686        &mut self,
1687        qubit: Qubit,
1688        theta: impl Into<ParameterValue>,
1689        phi: impl Into<ParameterValue>,
1690        lambda: impl Into<ParameterValue>,
1691    ) -> Result<(), CircuitError> {
1692        let params: SmallVec<[ParameterValue; 1]> =
1693            smallvec![theta.into(), phi.into(), lambda.into()];
1694
1695        self.append(
1696            Instruction::Standard(StandardGate::U),
1697            [qubit],
1698            params,
1699            None,
1700        )
1701    }
1702
1703    /// Appends a Controlled-NOT (CX or CNOT) gate.
1704    ///
1705    /// Flips the `target` qubit if and only if the `control` qubit is $|1\rangle$.
1706    pub fn cx(&mut self, control: Qubit, target: Qubit) -> Result<(), CircuitError> {
1707        self.append(
1708            Instruction::Standard(StandardGate::CX),
1709            [control, target],
1710            std::iter::empty(),
1711            None,
1712        )
1713    }
1714
1715    /// Appends a Controlled-Y (CY) gate.
1716    pub fn cy(&mut self, control: Qubit, target: Qubit) -> Result<(), CircuitError> {
1717        self.append(
1718            Instruction::Standard(StandardGate::CY),
1719            [control, target],
1720            std::iter::empty(),
1721            None,
1722        )
1723    }
1724
1725    /// Appends a Controlled-Z (CZ) gate.
1726    ///
1727    /// Adds a phase of -1 only if both qubits are $|1\rangle$.
1728    pub fn cz(&mut self, control: Qubit, target: Qubit) -> Result<(), CircuitError> {
1729        self.append(
1730            Instruction::Standard(StandardGate::CZ),
1731            [control, target],
1732            std::iter::empty(),
1733            None,
1734        )
1735    }
1736
1737    /// Appends a SWAP gate.
1738    ///
1739    /// Exchange the states of two qubits.
1740    pub fn swap(&mut self, a: Qubit, b: Qubit) -> Result<(), CircuitError> {
1741        self.append(
1742            Instruction::Standard(StandardGate::SWAP),
1743            [a, b],
1744            std::iter::empty(),
1745            None,
1746        )
1747    }
1748
1749    /// Appends an Ising XX coupling gate ($R_{XX}(\theta)$).
1750    ///
1751    /// $R_{XX}(\theta) = e^{-i\theta X \otimes X / 2}$
1752    pub fn rxx(
1753        &mut self,
1754        a: Qubit,
1755        b: Qubit,
1756        theta: impl Into<ParameterValue>,
1757    ) -> Result<(), CircuitError> {
1758        let params: SmallVec<[ParameterValue; 1]> = smallvec![theta.into()];
1759
1760        self.append(
1761            Instruction::Standard(StandardGate::RXX),
1762            [a, b],
1763            params,
1764            None,
1765        )
1766    }
1767
1768    /// Appends an Ising YY coupling gate ($R_{YY}(\theta)$).
1769    ///
1770    /// $R_{YY}(\theta) = e^{-i\theta Y \otimes Y / 2}$
1771    pub fn ryy(
1772        &mut self,
1773        a: Qubit,
1774        b: Qubit,
1775        theta: impl Into<ParameterValue>,
1776    ) -> Result<(), CircuitError> {
1777        let params: SmallVec<[ParameterValue; 1]> = smallvec![theta.into()];
1778
1779        self.append(
1780            Instruction::Standard(StandardGate::RYY),
1781            [a, b],
1782            params,
1783            None,
1784        )
1785    }
1786
1787    /// Appends an Ising ZZ coupling gate ($R_{ZZ}(\theta)$).
1788    ///
1789    /// $R_{ZZ}(\theta) = e^{-i\theta Z \otimes Z / 2}$
1790    pub fn rzz(
1791        &mut self,
1792        a: Qubit,
1793        b: Qubit,
1794        theta: impl Into<ParameterValue>,
1795    ) -> Result<(), CircuitError> {
1796        let params: SmallVec<[ParameterValue; 1]> = smallvec![theta.into()];
1797
1798        self.append(
1799            Instruction::Standard(StandardGate::RZZ),
1800            [a, b],
1801            params,
1802            None,
1803        )
1804    }
1805
1806    /// Appends an Ising ZX coupling gate ($R_{ZX}(\theta)$).
1807    ///
1808    /// $R_{ZX}(\theta) = e^{-i\theta Z \otimes X / 2}$
1809    pub fn rzx(
1810        &mut self,
1811        a: Qubit,
1812        b: Qubit,
1813        theta: impl Into<ParameterValue>,
1814    ) -> Result<(), CircuitError> {
1815        let params: SmallVec<[ParameterValue; 1]> = smallvec![theta.into()];
1816
1817        self.append(
1818            Instruction::Standard(StandardGate::RZX),
1819            [a, b],
1820            params,
1821            None,
1822        )
1823    }
1824
1825    /// Appends a Controlled-RX gate (CRX).
1826    ///
1827    /// Performs an X-rotation on the target if the control is $|1\rangle$.
1828    pub fn crx(
1829        &mut self,
1830        control: Qubit,
1831        target: Qubit,
1832        theta: impl Into<ParameterValue>,
1833    ) -> Result<(), CircuitError> {
1834        let params: SmallVec<[ParameterValue; 1]> = smallvec![theta.into()];
1835
1836        self.append(
1837            Instruction::Standard(StandardGate::CRX),
1838            [control, target],
1839            params,
1840            None,
1841        )
1842    }
1843
1844    /// Appends a Controlled-RY gate (CRY).
1845    pub fn cry(
1846        &mut self,
1847        control: Qubit,
1848        target: Qubit,
1849        theta: impl Into<ParameterValue>,
1850    ) -> Result<(), CircuitError> {
1851        let params: SmallVec<[ParameterValue; 1]> = smallvec![theta.into()];
1852
1853        self.append(
1854            Instruction::Standard(StandardGate::CRY),
1855            [control, target],
1856            params,
1857            None,
1858        )
1859    }
1860
1861    /// Appends a Controlled-RZ gate (CRZ).
1862    pub fn crz(
1863        &mut self,
1864        control: Qubit,
1865        target: Qubit,
1866        theta: impl Into<ParameterValue>,
1867    ) -> Result<(), CircuitError> {
1868        let params: SmallVec<[ParameterValue; 1]> = smallvec![theta.into()];
1869
1870        self.append(
1871            Instruction::Standard(StandardGate::CRZ),
1872            [control, target],
1873            params,
1874            None,
1875        )
1876    }
1877
1878    /// Appends a Toffoli gate (CCX).
1879    ///
1880    /// A 3-qubit gate where the target flips if and only if both controls are $|1\rangle$.
1881    pub fn ccx(
1882        &mut self,
1883        control1: Qubit,
1884        control2: Qubit,
1885        target: Qubit,
1886    ) -> Result<(), CircuitError> {
1887        self.append(
1888            Instruction::Standard(StandardGate::CCX),
1889            [control1, control2, target],
1890            std::iter::empty(),
1891            None,
1892        )
1893    }
1894
1895    // --- Advanced / Other Gates ---
1896
1897    /// Appends a Fermionic Simulation gate (fSim).
1898    ///
1899    /// Useful in quantum chemistry simulations.
1900    ///
1901    /// $$
1902    /// \text{fSim}(\theta, \phi) = \begin{pmatrix}
1903    /// 1 & 0 & 0 & 0 \\
1904    /// 0 & \cos\theta & -i\sin\theta & 0 \\
1905    /// 0 & -i\sin\theta & \cos\theta & 0 \\
1906    /// 0 & 0 & 0 & e^{-i\phi}
1907    /// \end{pmatrix}
1908    /// $$
1909    pub fn fsim(
1910        &mut self,
1911        a: Qubit,
1912        b: Qubit,
1913        theta: impl Into<ParameterValue>,
1914        phi: impl Into<ParameterValue>,
1915    ) -> Result<(), CircuitError> {
1916        let params: SmallVec<[ParameterValue; 1]> = smallvec![theta.into(), phi.into()];
1917
1918        self.append(
1919            Instruction::Standard(StandardGate::FSIM),
1920            [a, b],
1921            params,
1922            None,
1923        )
1924    }
1925
1926    /// Appends a rotation in the XY plane.
1927    pub fn rxy(
1928        &mut self,
1929        qubit: Qubit,
1930        theta: impl Into<ParameterValue>,
1931        phi: impl Into<ParameterValue>,
1932    ) -> Result<(), CircuitError> {
1933        let params: SmallVec<[ParameterValue; 2]> = smallvec![theta.into(), phi.into()];
1934
1935        self.append(
1936            Instruction::Standard(StandardGate::RXY),
1937            [qubit],
1938            params,
1939            None,
1940        )
1941    }
1942
1943    /// Inserts a Barrier.
1944    ///
1945    /// A barrier forbids the compiler from optimizing or reordering across this line. It has no
1946    /// physical effect on the quantum state, but is crucial for debugging and manual optimization
1947    /// control.
1948    ///
1949    /// A non-empty `qubits` list creates a barrier over exactly those qubits. An empty list
1950    /// creates a global barrier over every qubit in this circuit.
1951    pub fn barrier(&mut self, qubits: Vec<Qubit>) -> Result<(), CircuitError> {
1952        self.append(
1953            Instruction::Directive(Directive::Barrier),
1954            qubits,
1955            std::iter::empty(),
1956            None,
1957        )
1958    }
1959
1960    /// Resets a qubit to the $|0\rangle$ state.
1961    ///
1962    /// This is a non-unitary operation.
1963    pub fn reset(&mut self, qubit: Qubit) -> Result<(), CircuitError> {
1964        self.append(
1965            Instruction::Directive(Directive::Reset),
1966            [qubit],
1967            std::iter::empty(),
1968            None,
1969        )
1970    }
1971
1972    /// Applies a multi-controlled version of a standard gate.
1973    ///
1974    /// This method automatically handles gate promotion. For example, applying `X` with 1 control
1975    /// becomes `CX`, and with 2 controls becomes `CCX`. For higher numbers of controls, it creates
1976    /// an [`Instruction::McGate`].
1977    ///
1978    /// # Arguments
1979    ///
1980    /// * `gate` - The base standard gate to apply (e.g., `X`, `Y`, `RX`).
1981    /// * `controls` - A list of control qubits.
1982    /// * `targets` - A list of target qubits.
1983    /// * `params` - Parameters for the base gate.
1984    ///
1985    /// # Example
1986    ///
1987    /// ```rust
1988    /// use cqlib_core::circuit::circuit_impl::Circuit;
1989    /// use cqlib_core::circuit::Qubit;
1990    /// use cqlib_core::circuit::gate::StandardGate;
1991    ///
1992    /// let mut circuit = Circuit::new(4);
1993    /// let q0 = Qubit::new(0);
1994    /// let q1 = Qubit::new(1);
1995    /// let q2 = Qubit::new(2);
1996    ///
1997    /// // Equivalent to CCX(q0, q1, q2)
1998    /// circuit.multi_control(StandardGate::X, [q0, q1], vec![q2], []).unwrap();
1999    /// ```
2000    pub fn multi_control<I, C, T, P>(
2001        &mut self,
2002        instruction: I,
2003        controls: C,
2004        targets: T,
2005        params: P,
2006    ) -> Result<(), CircuitError>
2007    where
2008        I: Into<Instruction>,
2009        C: IntoIterator,
2010        C::Item: Into<Qubit>,
2011        T: IntoIterator,
2012        T::Item: Into<Qubit>,
2013        P: IntoIterator<Item = ParameterValue>,
2014    {
2015        let controls_sv: SmallVec<[Qubit; 3]> = controls.into_iter().map(|q| q.into()).collect();
2016        let targets_sv: SmallVec<[Qubit; 1]> = targets.into_iter().map(|q| q.into()).collect();
2017        let num_controls = controls_sv.len();
2018
2019        let inst: Instruction = instruction.into();
2020
2021        let controlled_inst = inst
2022            .control(num_controls)
2023            .ok_or_else(|| CircuitError::InvalidControlOperation(inst.to_string()))?;
2024
2025        let mut all_qubits = controls_sv;
2026        all_qubits.extend(targets_sv);
2027        self.append(controlled_inst, all_qubits, params, None)
2028    }
2029
2030    /// Appends a custom unitary gate to the circuit.
2031    ///
2032    /// This allows inserting user-defined gates defined by a specific matrix.
2033    ///
2034    /// # Arguments
2035    /// * `definition` - The definition of the custom gate (matrix, label, etc.).
2036    /// * `qubits` - The list of qubits to apply the gate to.
2037    ///
2038    /// # Example
2039    /// ```rust
2040    /// use ndarray::Array2;
2041    /// use num_complex::Complex64;
2042    /// use cqlib_core::circuit::circuit_impl::Circuit;
2043    /// use cqlib_core::circuit::gate::UnitaryGate;
2044    /// use cqlib_core::circuit::Qubit;
2045    ///
2046    /// // Define a custom gate (e.g., Identity)
2047    /// let mat = Array2::eye(2).mapv(|x| Complex64::new(x, 0.0));
2048    /// let u_gate = UnitaryGate::new("MyGate", 1, 0)
2049    ///      .with_matrix(mat)
2050    ///      .unwrap();
2051    ///
2052    /// let mut circuit = Circuit::new(4);
2053    /// let q0 = Qubit::new(0);
2054    ///
2055    /// // Apply the custom gate
2056    /// circuit.unitary(u_gate, vec![q0]).unwrap();
2057    /// ```
2058    pub fn unitary(&mut self, gate: UnitaryGate, qubits: Vec<Qubit>) -> Result<(), CircuitError> {
2059        self.unitary_with_params(gate, qubits, std::iter::empty())
2060    }
2061
2062    /// Appends a parameterized custom unitary gate to the circuit.
2063    pub fn unitary_with_params<P>(
2064        &mut self,
2065        gate: UnitaryGate,
2066        qubits: Vec<Qubit>,
2067        params: P,
2068    ) -> Result<(), CircuitError>
2069    where
2070        P: IntoIterator<Item = ParameterValue>,
2071    {
2072        let qubits_sv: SmallVec<[Qubit; 3]> = qubits.into();
2073        let params_vec: Vec<ParameterValue> = params.into_iter().collect();
2074
2075        // Check if qubit count matches definition.num_qubits
2076        if qubits_sv.len() != gate.num_qubits() as usize {
2077            return Err(CircuitError::QubitCountMismatch {
2078                expected: gate.num_qubits() as usize,
2079                actual: qubits_sv.len(),
2080            });
2081        }
2082        if params_vec.len() != gate.num_params() as usize {
2083            return Err(CircuitError::ParameterCountMismatch {
2084                expected: gate.num_params() as usize,
2085                actual: params_vec.len(),
2086            });
2087        }
2088
2089        self.append(
2090            Instruction::UnitaryGate(Box::new(gate)),
2091            qubits_sv,
2092            params_vec,
2093            None,
2094        )
2095    }
2096
2097    /// Appends a Delay instruction to the circuit.
2098    ///
2099    /// This instruction represents an idle period on a specific qubit, often used for
2100    /// dynamical decoupling or timing control in pulse-level scheduling.
2101    ///
2102    /// # Arguments
2103    ///
2104    /// * `qubit` - The qubit to apply the delay to.
2105    /// * `delay` - The duration of the delay. The unit depends on the target backend (e.g., seconds, samples, or dt).
2106    pub fn delay(
2107        &mut self,
2108        qubit: impl Into<Qubit>,
2109        delay: ParameterValue,
2110    ) -> Result<(), CircuitError> {
2111        self.append(Instruction::Delay, vec![qubit], vec![delay], None)
2112    }
2113
2114    /// Appends a pre-compiled `CircuitGate` to this circuit.
2115    ///
2116    /// This allows nesting circuits within circuits.
2117    ///
2118    /// # Arguments
2119    ///
2120    /// * `gate` - The `CircuitGate` instance to append.
2121    /// * `qubits` - The qubits in this circuit that the sub-circuit acts upon.
2122    /// * `params` - The parameter values to bind to the sub-circuit's parameters.
2123    pub fn circuit_gate(
2124        &mut self,
2125        gate: CircuitGate,
2126        qubits: Vec<Qubit>,
2127        params: impl IntoIterator<Item = ParameterValue>,
2128    ) -> Result<(), CircuitError> {
2129        self.append(
2130            Instruction::CircuitGate(Box::new(gate)),
2131            qubits,
2132            params,
2133            None,
2134        )
2135    }
2136
2137    /// Creates the inverse (adjoint) of the circuit.
2138    ///
2139    /// The inverse circuit represents the unitary $U^\dagger$ such that $U^\dagger U = I$.
2140    ///
2141    /// # Errors
2142    ///
2143    /// Returns [`CircuitError::IrreversibleOperation`] if the circuit contains non-unitary
2144    /// operations (e.g., `Measure`, `Reset`) or gates that cannot be symbolically inverted.
2145    pub fn inverse(&self) -> Result<Circuit, CircuitError> {
2146        let mut new_circuit = Circuit::from_qubits(self.qubits())?;
2147        new_circuit.classical_vars = self.classical_vars.clone();
2148        new_circuit.classical_values = self.classical_values.clone();
2149        Arc::make_mut(&mut new_circuit.data).reserve(self.data.len());
2150        // 1. Invert Global Phase
2151        let current_phase_param = self.global_phase();
2152        // New phase = -1.0 * old_phase
2153        let new_phase_param = Parameter::from(-1.0) * current_phase_param;
2154
2155        // Try to simplify/evaluate to keep it clean (e.g. Fixed(-0.5))
2156        if let Ok(val) = new_phase_param.evaluate(&None) {
2157            new_circuit.global_phase = CircuitParam::Fixed(val);
2158        } else {
2159            let (index, is_new) = new_circuit.parameters.insert_full(new_phase_param.clone());
2160            if is_new {
2161                for sym in new_phase_param.get_symbols() {
2162                    new_circuit.symbols.insert(sym);
2163                }
2164            }
2165            new_circuit.global_phase = CircuitParam::Index(index as u32);
2166        }
2167
2168        // 2. Iterate backwards
2169        for op in self.data.iter().rev() {
2170            // Special handling for Directives
2171            match &op.instruction {
2172                Instruction::Directive(directive) => match directive {
2173                    Directive::Barrier => {
2174                        new_circuit.append(
2175                            Instruction::Directive(Directive::Barrier),
2176                            op.qubits.clone(),
2177                            std::iter::empty(),
2178                            op.label.as_deref(),
2179                        )?;
2180                        continue;
2181                    }
2182                    _ => return Err(CircuitError::IrreversibleOperation),
2183                },
2184                Instruction::ClassicalData(_) | Instruction::ClassicalControl(_) => {
2185                    // Control flow operations cannot be statically inverted
2186                    return Err(CircuitError::IrreversibleOperation);
2187                }
2188                _ => {
2189                    // Resolve parameters
2190                    let params: SmallVec<[Parameter; 3]> = op
2191                        .params
2192                        .iter()
2193                        .map(|p| match p {
2194                            CircuitParam::Fixed(val) => Parameter::from(*val),
2195                            CircuitParam::Index(idx) => self.parameters[*idx as usize].clone(),
2196                        })
2197                        .collect();
2198
2199                    // Invert instruction
2200                    if let Some((inv_inst, inv_params)) = op.instruction.inverse(&params) {
2201                        // Convert back to CircuitParam/ParameterValue
2202                        let param_values: SmallVec<[ParameterValue; 3]> =
2203                            inv_params.into_iter().map(ParameterValue::from).collect();
2204
2205                        new_circuit.append(
2206                            inv_inst,
2207                            op.qubits.clone(),
2208                            param_values,
2209                            op.label.as_deref(),
2210                        )?;
2211                    } else {
2212                        return Err(CircuitError::IrreversibleOperation);
2213                    }
2214                }
2215            }
2216        }
2217
2218        Ok(new_circuit)
2219    }
2220
2221    /// Converts the circuit into a `CircuitGate` instruction.
2222    ///
2223    /// This method "freezes" the current circuit and wraps it into an instruction that can be
2224    /// appended to another circuit. Its positional parameter signature is
2225    /// inferred from symbols currently referenced by the executable IR, in
2226    /// stable registry order.
2227    ///
2228    /// # Arguments
2229    ///
2230    /// * `name` - A name for the new gate.
2231    pub fn to_gate(self, name: impl Into<String>) -> Result<Instruction, CircuitError> {
2232        let frozen = FrozenCircuit::new(self);
2233        let gate = CircuitGate::new(name, frozen)?;
2234        Ok(Instruction::CircuitGate(Box::new(gate)))
2235    }
2236
2237    fn check_qubits_unique(qubits: &[Qubit]) -> bool {
2238        let mut seen = HashSet::with_capacity(qubits.len());
2239        for q in qubits {
2240            if !seen.insert(q) {
2241                return false;
2242            }
2243        }
2244        true
2245    }
2246
2247    /// Decomposes the circuit by resolving sub-circuit gates into their fundamental operations.
2248    ///
2249    /// This method recursively unpacks any [`Instruction::CircuitGate`] (hierarchical instructions)
2250    /// found in the circuit. It handles:
2251    ///
2252    /// 1. **Parameter Substitution**: Parameters in the sub-circuit are replaced by the arguments
2253    ///    passed from the parent circuit.
2254    ///    - Example: If sub-circuit has `Rx(theta+1)` and is called with `theta = beta`,
2255    ///      the result is `Rx(beta+1)`.
2256    /// 2. **Qubit Mapping**: Virtual qubits in the sub-circuit definition are mapped to the
2257    ///    physical qubits in the parent circuit.
2258    /// 3. **Global Phase**: Each sub-circuit's phase is substituted using its call arguments
2259    ///    and accumulated into the flattened circuit, including empty definitions.
2260    ///
2261    /// # Returns
2262    ///
2263    /// - `Ok(Circuit)`: A new flattened `Circuit` containing only base instructions (Standard, Unitary, Directives).
2264    /// - `Err(CircuitError)`: If a parameter cannot be resolved during decomposition.
2265    ///
2266    /// # Errors
2267    ///
2268    /// Returns an error if a fully resolved parameter or accumulated global phase is invalid,
2269    /// or if the circuit contains unsupported classical control. Unbound symbolic parameters
2270    /// and phases are preserved.
2271    pub fn decompose(&self) -> Result<Circuit, CircuitError> {
2272        let mut new_circuit = Circuit::from_qubits(self.qubits()).unwrap();
2273        new_circuit.classical_vars = self.classical_vars.clone();
2274        new_circuit.classical_values = self.classical_values.clone();
2275        // Preserve the order of symbols from the original circuit.
2276        new_circuit.symbols = self.symbols.clone();
2277
2278        // Copy global phase
2279        match &self.global_phase {
2280            CircuitParam::Fixed(f) => new_circuit.global_phase = CircuitParam::Fixed(*f),
2281            CircuitParam::Index(i) => {
2282                let p = self.parameters[*i as usize].clone();
2283                let (idx, is_new) = new_circuit.parameters.insert_full(p.clone());
2284                if is_new {
2285                    for sym in p.get_symbols() {
2286                        new_circuit.symbols.insert(sym);
2287                    }
2288                }
2289                new_circuit.global_phase = CircuitParam::Index(idx as u32);
2290            }
2291        }
2292
2293        let initial_qubit_map: HashMap<Qubit, Qubit> =
2294            self.qubits.iter().map(|q| (*q, *q)).collect();
2295        let initial_param_map: HashMap<String, Parameter> = HashMap::new();
2296        let var_map = self
2297            .classical_vars
2298            .iter()
2299            .enumerate()
2300            .map(|(index, ty)| {
2301                (
2302                    ClassicalVar::new(self.circuit_id, index as u32, *ty),
2303                    ClassicalVar::new(new_circuit.circuit_id, index as u32, *ty),
2304                )
2305            })
2306            .collect();
2307        let value_map = self
2308            .classical_values
2309            .iter()
2310            .enumerate()
2311            .map(|(index, ty)| {
2312                (
2313                    ClassicalValue::new(self.circuit_id, index as u32, *ty),
2314                    ClassicalValue::new(new_circuit.circuit_id, index as u32, *ty),
2315                )
2316            })
2317            .collect();
2318
2319        for op in self.data.iter() {
2320            Self::decompose_recursive(
2321                op,
2322                self,
2323                &initial_qubit_map,
2324                &initial_param_map,
2325                &var_map,
2326                &value_map,
2327                &mut new_circuit,
2328            )?;
2329        }
2330
2331        Ok(new_circuit)
2332    }
2333
2334    fn decompose_recursive(
2335        op: &Operation,
2336        context_circuit: &Circuit,
2337        qubit_map: &HashMap<Qubit, Qubit>,
2338        param_map: &HashMap<String, Parameter>,
2339        var_map: &HashMap<ClassicalVar, ClassicalVar>,
2340        value_map: &HashMap<ClassicalValue, ClassicalValue>,
2341        target_circuit: &mut Circuit,
2342    ) -> Result<(), CircuitError> {
2343        match &op.instruction {
2344            Instruction::CircuitGate(cg) => {
2345                // 1. Resolve Parameters in current context
2346                let mut resolved_params = Vec::with_capacity(op.params.len());
2347                for p in &op.params {
2348                    let mut param = match p {
2349                        CircuitParam::Fixed(v) => Parameter::from(*v),
2350                        CircuitParam::Index(idx) => {
2351                            context_circuit.parameters[*idx as usize].clone()
2352                        }
2353                    };
2354
2355                    // Apply substitution from the *parent* scope (if we are deep in recursion)
2356                    // We need simultaneous substitution here too
2357                    param = Self::apply_param_map(param, param_map);
2358                    if param.get_symbols().is_empty() {
2359                        param.evaluate(&None)?;
2360                    }
2361                    resolved_params.push(param);
2362                }
2363
2364                // 2. Build maps for the next level
2365                // Param Map: Inner Symbol -> Resolved Value
2366                let mut next_param_map = HashMap::new();
2367                for (i, sym) in cg.signature_params().iter().enumerate() {
2368                    if i < resolved_params.len() {
2369                        next_param_map.insert(sym.clone(), resolved_params[i].clone());
2370                    }
2371                }
2372
2373                // Phase is circuit metadata, so it must be accumulated even when
2374                // the definition has no operations. Each gate application adds it once.
2375                if cg.circuit.circuit.global_phase_param() != &CircuitParam::Fixed(0.0) {
2376                    let phase =
2377                        Self::apply_param_map(cg.circuit.circuit.global_phase(), &next_param_map);
2378                    if phase.get_symbols().is_empty() {
2379                        phase.evaluate(&None)?;
2380                    }
2381                    let phase = target_circuit.global_phase() + phase;
2382                    if phase.get_symbols().is_empty() {
2383                        phase.evaluate(&None)?;
2384                    }
2385                    target_circuit.set_global_phase(phase);
2386                }
2387
2388                // Qubit Map: Inner Qubit -> Outer Qubit
2389                // op.qubits are the qubits in 'context_circuit' that the gate acts on.
2390                // We need to map them through 'qubit_map' to get 'target_circuit' qubits.
2391                let mut next_qubit_map = HashMap::new();
2392                for (i, inner_q) in cg.circuit.circuit.qubits().iter().enumerate() {
2393                    if i < op.qubits.len() {
2394                        let local_q = op.qubits[i];
2395                        let global_q = qubit_map.get(&local_q).unwrap_or(&local_q);
2396                        next_qubit_map.insert(*inner_q, *global_q);
2397                    }
2398                }
2399
2400                // 3. Recurse
2401                for sub_op in cg.circuit.circuit.data.iter() {
2402                    Self::decompose_recursive(
2403                        sub_op,
2404                        &cg.circuit.circuit,
2405                        &next_qubit_map,
2406                        &next_param_map,
2407                        var_map,
2408                        value_map,
2409                        target_circuit,
2410                    )?;
2411                }
2412                Ok(())
2413            }
2414            Instruction::ClassicalControl(_) => Err(CircuitError::InvalidOperation(
2415                "decomposing circuits with classical control is not yet supported".to_string(),
2416            )),
2417            _ => {
2418                // Base case: Standard/Unitary/Directive
2419                // Map Qubits
2420                let mapped_qubits: SmallVec<[Qubit; 3]> = op
2421                    .qubits
2422                    .iter()
2423                    .map(|q| *qubit_map.get(q).unwrap_or(q))
2424                    .collect();
2425
2426                // Map Parameters
2427                let mut mapped_params: SmallVec<[ParameterValue; 3]> = smallvec![];
2428                for p in &op.params {
2429                    let mut param = match p {
2430                        CircuitParam::Fixed(v) => Parameter::from(*v),
2431                        CircuitParam::Index(idx) => {
2432                            context_circuit.parameters[*idx as usize].clone()
2433                        }
2434                    };
2435
2436                    param = Self::apply_param_map(param, param_map);
2437                    if param.get_symbols().is_empty() {
2438                        param.evaluate(&None)?;
2439                    }
2440
2441                    mapped_params.push(ParameterValue::from(param));
2442                }
2443
2444                let instruction = match &op.instruction {
2445                    Instruction::ClassicalData(op) => Instruction::ClassicalData(
2446                        Self::remap_classical_data_op(op, var_map, value_map)?,
2447                    ),
2448                    _ => op.instruction.clone(),
2449                };
2450
2451                target_circuit.append(
2452                    instruction,
2453                    mapped_qubits,
2454                    mapped_params,
2455                    op.label.as_deref(),
2456                )?;
2457                Ok(())
2458            }
2459        }
2460    }
2461
2462    fn apply_param_map(param: Parameter, map: &HashMap<String, Parameter>) -> Parameter {
2463        param.substitute_many_simultaneous(map)
2464    }
2465
2466    /// Computes the dense unitary matrix represented by this circuit.
2467    ///
2468    /// The first qubit in `qubits_order` is mapped to the least-significant
2469    /// basis-state bit. When no order is supplied, qubits are sorted by their
2470    /// numeric identifiers. The returned matrix includes the circuit's global
2471    /// phase.
2472    ///
2473    /// The matrix dimension is `2^n` and its element count is `4^n` for `n`
2474    /// qubits. This API is therefore intended only for small circuits.
2475    ///
2476    /// # Errors
2477    ///
2478    /// Returns an error when the circuit contains a non-unitary operation,
2479    /// unresolved symbolic parameter, invalid qubit order, unsupported gate
2480    /// definition, or a matrix dimension that cannot be represented.
2481    pub fn to_matrix(
2482        &self,
2483        qubits_order: Option<&[usize]>,
2484    ) -> Result<Array2<Complex64>, CircuitError> {
2485        circuit_to_matrix(self, qubits_order)
2486    }
2487
2488    /// Returns a new circuit with the supplied symbols replaced by numeric values.
2489    ///
2490    /// Parameters that remain symbolic are simplified and re-interned in the
2491    /// returned circuit. The original circuit is not modified.
2492    /// Internal classical references are remapped to the returned circuit's
2493    /// identity; handles from the original circuit remain owned by it.
2494    ///
2495    /// # Errors
2496    ///
2497    /// Returns an error for unsupported classical control flow, failed symbolic
2498    /// simplification, or an invalid stored parameter index.
2499    pub fn assign_parameters(
2500        &self,
2501        bindings: &Option<HashMap<&str, f64>>,
2502    ) -> Result<Circuit, CircuitError> {
2503        if self
2504            .data
2505            .iter()
2506            .any(|op| matches!(op.instruction, Instruction::ClassicalControl(_)))
2507        {
2508            return Err(CircuitError::InvalidOperation(
2509                "assigning parameters in circuits with classical control is not yet supported"
2510                    .to_string(),
2511            ));
2512        }
2513
2514        let mut new_circuit = Circuit::from_qubits(self.qubits())?;
2515        new_circuit.classical_vars = self.classical_vars.clone();
2516        new_circuit.classical_values = self.classical_values.clone();
2517
2518        let var_map = self
2519            .classical_vars
2520            .iter()
2521            .enumerate()
2522            .map(|(index, ty)| {
2523                (
2524                    ClassicalVar::new(self.circuit_id, index as u32, *ty),
2525                    ClassicalVar::new(new_circuit.circuit_id, index as u32, *ty),
2526                )
2527            })
2528            .collect();
2529        let value_map = self
2530            .classical_values
2531            .iter()
2532            .enumerate()
2533            .map(|(index, ty)| {
2534                (
2535                    ClassicalValue::new(self.circuit_id, index as u32, *ty),
2536                    ClassicalValue::new(new_circuit.circuit_id, index as u32, *ty),
2537                )
2538            })
2539            .collect();
2540
2541        // Map from old parameter index to new CircuitParam (either Fixed or Index)
2542        let mut index_map: Vec<CircuitParam> = Vec::with_capacity(self.parameters.len());
2543
2544        for param in self.parameters.iter() {
2545            if let Ok(val) = param.evaluate(bindings) {
2546                index_map.push(CircuitParam::Fixed(val));
2547                continue;
2548            }
2549
2550            let mut tp = param.clone();
2551            if let Some(bindings) = bindings {
2552                for (k, v) in bindings.iter() {
2553                    tp = tp.replace(k, Parameter::from(*v));
2554                }
2555                let s = tp.simplify();
2556                tp = s.map_err(|e| CircuitError::UnresolvedParameter(format!("{:?}", e)))?;
2557            }
2558
2559            if tp.get_symbols().is_empty() {
2560                index_map.push(CircuitParam::Fixed(tp.evaluate(&None)?));
2561            } else {
2562                // Intern the new parameter (deduplicates automatically)
2563                let (idx, is_new) = new_circuit.parameters.insert_full(tp.clone());
2564
2565                // If it's a new symbolic parameter, track its symbols
2566                if is_new {
2567                    for sym in tp.get_symbols() {
2568                        new_circuit.symbols.insert(sym);
2569                    }
2570                }
2571                index_map.push(CircuitParam::Index(idx as u32));
2572            }
2573        }
2574
2575        // Remap parameter indices and all classical references in the same pass.
2576        let new_data = Arc::make_mut(&mut new_circuit.data);
2577        new_data.reserve(self.data.len());
2578        for op in self.data.iter() {
2579            new_data.push(Self::remap_operation(
2580                op,
2581                &|qubit| Ok(qubit),
2582                &|param| match param {
2583                    CircuitParam::Index(old_idx) => index_map
2584                        .get(*old_idx as usize)
2585                        .cloned()
2586                        .ok_or(CircuitError::InvalidParameterIndex(*old_idx)),
2587                    CircuitParam::Fixed(value) => Ok(CircuitParam::Fixed(*value)),
2588                },
2589                &var_map,
2590                &value_map,
2591            )?);
2592        }
2593
2594        // Remap global phase
2595        match self.global_phase {
2596            CircuitParam::Index(old_idx) => {
2597                new_circuit.global_phase = index_map
2598                    .get(old_idx as usize)
2599                    .cloned()
2600                    .ok_or(CircuitError::InvalidParameterIndex(old_idx))?;
2601            }
2602            CircuitParam::Fixed(val) => {
2603                new_circuit.global_phase = CircuitParam::Fixed(val);
2604            }
2605        }
2606
2607        Ok(new_circuit)
2608    }
2609
2610    fn remap_compose_operation(
2611        op: &Operation,
2612        qubit_mapping: &HashMap<Qubit, Qubit>,
2613        param_index_map: &[CircuitParam],
2614        var_map: &HashMap<ClassicalVar, ClassicalVar>,
2615        value_map: &HashMap<ClassicalValue, ClassicalValue>,
2616    ) -> Result<Operation, CircuitError> {
2617        Self::remap_operation(
2618            op,
2619            &|qubit| {
2620                qubit_mapping
2621                    .get(&qubit)
2622                    .copied()
2623                    .ok_or(CircuitError::QubitNotFound(qubit.id()))
2624            },
2625            &|param| match param {
2626                CircuitParam::Index(old_idx) => param_index_map
2627                    .get(*old_idx as usize)
2628                    .cloned()
2629                    .ok_or(CircuitError::InvalidParameterIndex(*old_idx)),
2630                CircuitParam::Fixed(value) => Ok(CircuitParam::Fixed(*value)),
2631            },
2632            var_map,
2633            value_map,
2634        )
2635    }
2636
2637    fn remap_operation<QubitMapper, ParamMapper>(
2638        op: &Operation,
2639        map_qubit: &QubitMapper,
2640        map_param: &ParamMapper,
2641        var_map: &HashMap<ClassicalVar, ClassicalVar>,
2642        value_map: &HashMap<ClassicalValue, ClassicalValue>,
2643    ) -> Result<Operation, CircuitError>
2644    where
2645        QubitMapper: Fn(Qubit) -> Result<Qubit, CircuitError>,
2646        ParamMapper: Fn(&CircuitParam) -> Result<CircuitParam, CircuitError>,
2647    {
2648        let qubits = op
2649            .qubits
2650            .iter()
2651            .copied()
2652            .map(map_qubit)
2653            .collect::<Result<_, _>>()?;
2654        let params = op.params.iter().map(map_param).collect::<Result<_, _>>()?;
2655        let instruction = match &op.instruction {
2656            Instruction::ClassicalData(classical_op) => Instruction::ClassicalData(
2657                Self::remap_classical_data_op(classical_op, var_map, value_map)?,
2658            ),
2659            Instruction::ClassicalControl(op) => Instruction::ClassicalControl(
2660                Self::remap_control_op(op, map_qubit, map_param, var_map, value_map)?,
2661            ),
2662            _ => op.instruction.clone(),
2663        };
2664
2665        Ok(Operation {
2666            instruction,
2667            qubits,
2668            params,
2669            label: op.label.clone(),
2670        })
2671    }
2672
2673    fn remap_classical_data_op(
2674        op: &ClassicalDataOp,
2675        var_map: &HashMap<ClassicalVar, ClassicalVar>,
2676        value_map: &HashMap<ClassicalValue, ClassicalValue>,
2677    ) -> Result<ClassicalDataOp, CircuitError> {
2678        match op {
2679            ClassicalDataOp::Store { target, value } => Ok(ClassicalDataOp::Store {
2680                target: var_map.get(target).copied().ok_or_else(|| {
2681                    CircuitError::InvalidOperation(format!(
2682                        "missing classical variable remap for id {}",
2683                        target.id()
2684                    ))
2685                })?,
2686                value: value.remap_classical_ids(var_map, value_map)?,
2687            }),
2688            ClassicalDataOp::MeasureBit { result } => Ok(ClassicalDataOp::MeasureBit {
2689                result: value_map.get(result).copied().ok_or_else(|| {
2690                    CircuitError::InvalidOperation(format!(
2691                        "missing classical value remap for id {}",
2692                        result.index()
2693                    ))
2694                })?,
2695            }),
2696            ClassicalDataOp::MeasureBits { result } => Ok(ClassicalDataOp::MeasureBits {
2697                result: value_map.get(result).copied().ok_or_else(|| {
2698                    CircuitError::InvalidOperation(format!(
2699                        "missing classical value remap for id {}",
2700                        result.index()
2701                    ))
2702                })?,
2703            }),
2704        }
2705    }
2706
2707    fn remap_control_body<QubitMapper, ParamMapper>(
2708        body: &ControlBody,
2709        map_qubit: &QubitMapper,
2710        map_param: &ParamMapper,
2711        var_map: &HashMap<ClassicalVar, ClassicalVar>,
2712        value_map: &HashMap<ClassicalValue, ClassicalValue>,
2713    ) -> Result<ControlBody, CircuitError>
2714    where
2715        QubitMapper: Fn(Qubit) -> Result<Qubit, CircuitError>,
2716        ParamMapper: Fn(&CircuitParam) -> Result<CircuitParam, CircuitError>,
2717    {
2718        body.operations()
2719            .iter()
2720            .map(|op| Self::remap_operation(op, map_qubit, map_param, var_map, value_map))
2721            .collect::<Result<Vec<_>, _>>()
2722            .map(ControlBody::new)
2723    }
2724
2725    fn remap_control_op<QubitMapper, ParamMapper>(
2726        op: &ClassicalControlOp,
2727        map_qubit: &QubitMapper,
2728        map_param: &ParamMapper,
2729        var_map: &HashMap<ClassicalVar, ClassicalVar>,
2730        value_map: &HashMap<ClassicalValue, ClassicalValue>,
2731    ) -> Result<ClassicalControlOp, CircuitError>
2732    where
2733        QubitMapper: Fn(Qubit) -> Result<Qubit, CircuitError>,
2734        ParamMapper: Fn(&CircuitParam) -> Result<CircuitParam, CircuitError>,
2735    {
2736        match op {
2737            ClassicalControlOp::If(op) => {
2738                let condition = op.condition().remap_classical_ids(var_map, value_map)?;
2739                let then_body = Self::remap_control_body(
2740                    op.then_body(),
2741                    map_qubit,
2742                    map_param,
2743                    var_map,
2744                    value_map,
2745                )?;
2746                let else_body = op
2747                    .else_body()
2748                    .map(|body| {
2749                        Self::remap_control_body(body, map_qubit, map_param, var_map, value_map)
2750                    })
2751                    .transpose()?;
2752                IfOp::new(condition, then_body, else_body).map(ClassicalControlOp::If)
2753            }
2754            ClassicalControlOp::While(op) => {
2755                let condition = op.condition().remap_classical_ids(var_map, value_map)?;
2756                let body =
2757                    Self::remap_control_body(op.body(), map_qubit, map_param, var_map, value_map)?;
2758                WhileOp::new(condition, body).map(ClassicalControlOp::While)
2759            }
2760            ClassicalControlOp::For(op) => {
2761                let var = var_map.get(&op.var()).copied().ok_or_else(|| {
2762                    CircuitError::InvalidOperation(format!(
2763                        "missing classical variable remap for id {}",
2764                        op.var().id()
2765                    ))
2766                })?;
2767                let start = op.start().remap_classical_ids(var_map, value_map)?;
2768                let stop = op.stop().remap_classical_ids(var_map, value_map)?;
2769                let step = op.step().remap_classical_ids(var_map, value_map)?;
2770                let body =
2771                    Self::remap_control_body(op.body(), map_qubit, map_param, var_map, value_map)?;
2772                ForOp::new(var, start, stop, step, body).map(ClassicalControlOp::For)
2773            }
2774            ClassicalControlOp::Switch(op) => {
2775                let target = op.target().remap_classical_ids(var_map, value_map)?;
2776                let cases = op
2777                    .cases()
2778                    .iter()
2779                    .map(|case| {
2780                        Ok(SwitchCase::new(
2781                            case.value(),
2782                            Self::remap_control_body(
2783                                case.body(),
2784                                map_qubit,
2785                                map_param,
2786                                var_map,
2787                                value_map,
2788                            )?,
2789                        ))
2790                    })
2791                    .collect::<Result<Vec<_>, CircuitError>>()?;
2792                let default = op
2793                    .default()
2794                    .map(|body| {
2795                        Self::remap_control_body(body, map_qubit, map_param, var_map, value_map)
2796                    })
2797                    .transpose()?;
2798                SwitchOp::new(target, cases, default).map(ClassicalControlOp::Switch)
2799            }
2800            ClassicalControlOp::Break => Ok(ClassicalControlOp::Break),
2801            ClassicalControlOp::Continue => Ok(ClassicalControlOp::Continue),
2802        }
2803    }
2804
2805    /// Composes another circuit into this circuit.
2806    ///
2807    /// This method merges the operations from `other` circuit into `self`. Qubits from `other`
2808    /// can either be mapped to existing qubits in `self` (via `qubits_map`) or merged by their
2809    /// [`Qubit`] IDs.
2810    /// Circuit-local classical variables and values from `other` are appended to `self`'s
2811    /// classical tables, and all classical data/control references inside copied operations are
2812    /// remapped to those new local IDs. The identity of `self` and its existing classical
2813    /// handles are preserved. If composition returns an error, `self` is unchanged.
2814    ///
2815    /// # Arguments
2816    ///
2817    /// * `other` - The circuit to compose into this circuit.
2818    /// * `qubits_map` - An optional slice mapping qubits from `other` to qubits in `self`.
2819    ///   - If `Some(mapping)` is provided, each qubit in `other` (in insertion order)
2820    ///     is mapped to the corresponding qubit in `mapping`. Target qubits must be distinct,
2821    ///     including targets corresponding to idle source qubits.
2822    ///   - If `None` is provided, qubits are mapped by ID. IDs not already present in `self` are
2823    ///     appended, while matching IDs reuse the existing qubits.
2824    ///
2825    /// # Returns
2826    ///
2827    /// * `Ok(())` - If composition succeeds.
2828    /// * `Err(CircuitError)` - If the mapping has the wrong length, contains duplicate targets
2829    ///   ([`CircuitError::DuplicateQubits`]), references non-existent qubits, or stored circuit
2830    ///   references cannot be remapped.
2831    ///
2832    /// # Examples
2833    ///
2834    /// ```rust
2835    /// use cqlib_core::circuit::circuit_impl::Circuit;
2836    /// use cqlib_core::circuit::Qubit;
2837    ///
2838    /// // Create qc1 with qubits 1, 3, 5
2839    /// let mut qc1 = Circuit::new(0);
2840    /// let q1 = Qubit::new(1);
2841    /// let q3 = Qubit::new(3);
2842    /// let q5 = Qubit::new(5);
2843    /// qc1.add_qubits(vec![q1, q3, q5]).unwrap();
2844    /// qc1.h(q1).unwrap();
2845    ///
2846    /// // Create qc2 with qubits 1, 2
2847    /// let mut qc2 = Circuit::new(0);
2848    /// let q2 = Qubit::new(2);
2849    /// qc2.add_qubits(vec![q1, q2]).unwrap();
2850    /// qc2.x(q1).unwrap();
2851    ///
2852    /// // Compose qc2 into qc1, mapping qc2's qubits: q1->q3, q2->q1
2853    /// qc1.compose(&qc2, Some(&[q3, q1])).unwrap();
2854    /// ```
2855    pub fn compose(
2856        &mut self,
2857        other: &Circuit,
2858        qubits_map: Option<&[Qubit]>,
2859    ) -> Result<(), CircuitError> {
2860        // Build qubit mapping: other_qubit -> target_qubit
2861        let qubit_mapping: HashMap<Qubit, Qubit> = if let Some(mapping) = qubits_map {
2862            // Validate mapping length
2863            if mapping.len() != other.qubits.len() {
2864                return Err(CircuitError::QubitCountMismatch {
2865                    expected: other.qubits.len(),
2866                    actual: mapping.len(),
2867                });
2868            }
2869            if !Self::check_qubits_unique(mapping) {
2870                return Err(CircuitError::DuplicateQubits);
2871            }
2872
2873            // Build map and validate target qubits exist in self
2874            let mut map = HashMap::with_capacity(mapping.len());
2875            for (other_qubit, target_qubit) in other.qubits.iter().zip(mapping.iter()) {
2876                if !self.qubits.contains(target_qubit) {
2877                    return Err(CircuitError::QubitNotFound(target_qubit.id()));
2878                }
2879                map.insert(*other_qubit, *target_qubit);
2880            }
2881            map
2882        } else {
2883            // No explicit mapping: merge qubits by ID.
2884            let mut map = HashMap::with_capacity(other.qubits.len());
2885            for other_qubit in other.qubits.iter() {
2886                map.insert(*other_qubit, *other_qubit);
2887            }
2888            map
2889        };
2890
2891        // Build circuit-local classical ID mappings. Handles from `other` are
2892        // remapped to the appended table range owned by `self`.
2893        let var_base = self.classical_vars.len();
2894        let mut var_map = HashMap::with_capacity(other.classical_vars.len());
2895        for (idx, ty) in other.classical_vars.iter().enumerate() {
2896            var_map.insert(
2897                ClassicalVar::new(other.circuit_id, idx as u32, *ty),
2898                ClassicalVar::new(self.circuit_id, (var_base + idx) as u32, *ty),
2899            );
2900        }
2901
2902        let value_base = self.classical_values.len();
2903        let mut value_map = HashMap::with_capacity(other.classical_values.len());
2904        for (idx, ty) in other.classical_values.iter().enumerate() {
2905            value_map.insert(
2906                ClassicalValue::new(other.circuit_id, idx as u32, *ty),
2907                ClassicalValue::new(self.circuit_id, (value_base + idx) as u32, *ty),
2908            );
2909        }
2910
2911        // Stage only new parameters: remapping and phase resolution can still fail.
2912        // Keep the existing registry read-only rather than copying it on each composition.
2913        // Staged indices follow the existing entries and remain valid after the final append.
2914        let parameter_base = self.parameters.len();
2915        let mut new_parameters = IndexSet::new();
2916        let mut new_symbols = IndexSet::new();
2917        let mut param_index_map: Vec<CircuitParam> = Vec::with_capacity(other.parameters.len());
2918        for param in other.parameters.iter() {
2919            let idx = if let Some(idx) = self.parameters.get_index_of(param) {
2920                idx
2921            } else {
2922                new_symbols.extend(param.get_symbols());
2923                parameter_base + new_parameters.insert_full(param.clone()).0
2924            };
2925            param_index_map.push(CircuitParam::Index(idx as u32));
2926        }
2927
2928        let remapped_ops = other
2929            .data
2930            .iter()
2931            .map(|op| {
2932                Self::remap_compose_operation(
2933                    op,
2934                    &qubit_mapping,
2935                    &param_index_map,
2936                    &var_map,
2937                    &value_map,
2938                )
2939            })
2940            .collect::<Result<Vec<_>, _>>()?;
2941
2942        // Resolve against the original registries. A new source parameter must not make an
2943        // invalid pre-existing destination index appear valid in the merged registry.
2944        let global_phase = match (&self.global_phase, &other.global_phase) {
2945            (CircuitParam::Fixed(a), CircuitParam::Fixed(b)) => CircuitParam::Fixed(a + b),
2946            _ => {
2947                let new_expr = self.resolve_parameter(&self.global_phase)?
2948                    + other.resolve_parameter(&other.global_phase)?;
2949                // Every symbol in the sum already belongs to one of the merged registries.
2950                let new_idx = self
2951                    .parameters
2952                    .get_index_of(&new_expr)
2953                    .unwrap_or_else(|| parameter_base + new_parameters.insert_full(new_expr).0);
2954                CircuitParam::Index(new_idx as u32)
2955            }
2956        };
2957
2958        // Commit only after all fallible preparation succeeds. Preserve insertion order and
2959        // the active control-builder scope, as well as all existing classical handle IDs.
2960        if qubits_map.is_none() {
2961            self.qubits.extend(other.qubits.iter().copied());
2962        }
2963        if !new_parameters.is_empty() {
2964            self.parameters.extend(new_parameters);
2965        }
2966        self.symbols.extend(new_symbols);
2967        self.global_phase = global_phase;
2968        self.classical_vars
2969            .extend(other.classical_vars.iter().copied());
2970        self.classical_values
2971            .extend(other.classical_values.iter().copied());
2972        let data = Arc::make_mut(&mut self.data);
2973        data.reserve(remapped_ops.len());
2974        data.extend(remapped_ops);
2975
2976        Ok(())
2977    }
2978
2979    /// Returns an operation with its parameters resolved to value-level representations.
2980    ///
2981    /// Unlike [`Circuit::operations`], which exposes the compact storage IR,
2982    /// this method converts indexed parameters back into [`ParameterValue`]s.
2983    ///
2984    /// # Errors
2985    ///
2986    /// Returns [`CircuitError::OperationIndexOutOfBounds`] when `i` is not a
2987    /// valid operation index. Returns [`CircuitError::InvalidParameterIndex`]
2988    /// if the stored operation references a missing entry in this circuit's
2989    /// parameter table.
2990    ///
2991    /// # Examples
2992    ///
2993    /// ```rust
2994    /// use cqlib_core::circuit::{Circuit, CircuitError};
2995    ///
2996    /// let circuit = Circuit::new(1);
2997    /// assert!(matches!(
2998    ///     circuit.index(0),
2999    ///     Err(CircuitError::OperationIndexOutOfBounds { index: 0, len: 0 })
3000    /// ));
3001    /// ```
3002    pub fn index(&self, i: usize) -> Result<ValueOperation, CircuitError> {
3003        let operation =
3004            self.data
3005                .get(i)
3006                .cloned()
3007                .ok_or(CircuitError::OperationIndexOutOfBounds {
3008                    index: i,
3009                    len: self.data.len(),
3010                })?;
3011        storage_operation_to_value(operation, &|param| self.parameter_value(param))
3012    }
3013}
3014
3015impl Circuit {
3016    fn lower_instruction(
3017        &mut self,
3018        instruction: ValueInstruction,
3019    ) -> Result<Instruction, CircuitError> {
3020        let op = match instruction {
3021            ValueInstruction::Instruction(Instruction::ClassicalControl(_)) => {
3022                return Err(CircuitError::InvalidOperation(
3023                    "ValueInstruction::Instruction cannot wrap Instruction::ClassicalControl"
3024                        .to_string(),
3025                ));
3026            }
3027            ValueInstruction::Instruction(instruction) => return Ok(instruction),
3028            ValueInstruction::ClassicalControl(op) => op,
3029        };
3030
3031        let op = match op {
3032            ValueClassicalControlOp::If {
3033                condition,
3034                then_body,
3035                else_body,
3036            } => {
3037                let then_body = self.lower_body(then_body)?;
3038                let else_body = else_body.map(|body| self.lower_body(body)).transpose()?;
3039                IfOp::new(condition, then_body, else_body).map(ClassicalControlOp::If)?
3040            }
3041            ValueClassicalControlOp::While { condition, body } => {
3042                let body = self.lower_body(body)?;
3043                WhileOp::new(condition, body).map(ClassicalControlOp::While)?
3044            }
3045            ValueClassicalControlOp::For {
3046                var,
3047                start,
3048                stop,
3049                step,
3050                body,
3051            } => {
3052                let body = self.lower_body(body)?;
3053                ForOp::new(var, start, stop, step, body).map(ClassicalControlOp::For)?
3054            }
3055            ValueClassicalControlOp::Switch {
3056                target,
3057                cases,
3058                default,
3059            } => {
3060                let cases = cases
3061                    .into_iter()
3062                    .map(|case| Ok(SwitchCase::new(case.value, self.lower_body(case.body)?)))
3063                    .collect::<Result<Vec<_>, CircuitError>>()?;
3064                let default = default.map(|body| self.lower_body(body)).transpose()?;
3065                SwitchOp::new(target, cases, default).map(ClassicalControlOp::Switch)?
3066            }
3067            ValueClassicalControlOp::Break => ClassicalControlOp::Break,
3068            ValueClassicalControlOp::Continue => ClassicalControlOp::Continue,
3069        };
3070        Ok(Instruction::ClassicalControl(op))
3071    }
3072
3073    fn lower_operation(&mut self, operation: ValueOperation) -> Result<Operation, CircuitError> {
3074        let instruction = self.lower_instruction(operation.instruction)?;
3075        let params = operation
3076            .params
3077            .into_iter()
3078            .enumerate()
3079            .map(
3080                |(param_index, param)| -> Result<CircuitParam, CircuitError> {
3081                    match param {
3082                        ParameterValue::Param(param) => {
3083                            let (index, is_new) = self.parameters.insert_full(param.clone());
3084                            if is_new {
3085                                for sym in param.get_symbols() {
3086                                    self.symbols.insert(sym);
3087                                }
3088                            }
3089                            Ok(CircuitParam::Index(index as u32))
3090                        }
3091                        ParameterValue::Fixed(value) => {
3092                            if !value.is_finite() {
3093                                return Err(CircuitError::InvalidParameterValue(
3094                                    param_index,
3095                                    value,
3096                                ));
3097                            }
3098                            Ok(CircuitParam::Fixed(value))
3099                        }
3100                    }
3101                },
3102            )
3103            .collect::<Result<_, _>>()?;
3104        Ok(Operation {
3105            instruction,
3106            qubits: operation.qubits,
3107            params,
3108            label: operation.label,
3109        })
3110    }
3111
3112    fn lower_body(&mut self, body: ValueControlBody) -> Result<ControlBody, CircuitError> {
3113        body.operations()
3114            .iter()
3115            .cloned()
3116            .map(|operation| self.lower_operation(operation))
3117            .collect::<Result<Vec<_>, _>>()
3118            .map(ControlBody::new)
3119    }
3120
3121    fn validate_operation_parameters(&self, operations: &[Operation]) -> Result<(), CircuitError> {
3122        for operation in operations {
3123            for param in &operation.params {
3124                match param {
3125                    CircuitParam::Fixed(value) => {
3126                        if !value.is_finite() {
3127                            return Err(CircuitError::InvalidParameterValue(0, *value));
3128                        }
3129                    }
3130                    CircuitParam::Index(index) => {
3131                        if self.parameters.get_index(*index as usize).is_none() {
3132                            return Err(CircuitError::InvalidParameterIndex(*index));
3133                        }
3134                    }
3135                }
3136            }
3137            if let Instruction::ClassicalControl(op) = &operation.instruction {
3138                match op {
3139                    ClassicalControlOp::If(op) => {
3140                        self.validate_operation_parameters(op.then_body().operations())?;
3141                        if let Some(body) = op.else_body() {
3142                            self.validate_operation_parameters(body.operations())?;
3143                        }
3144                    }
3145                    ClassicalControlOp::While(op) => {
3146                        self.validate_operation_parameters(op.body().operations())?;
3147                    }
3148                    ClassicalControlOp::For(op) => {
3149                        self.validate_operation_parameters(op.body().operations())?;
3150                    }
3151                    ClassicalControlOp::Switch(op) => {
3152                        for case in op.cases() {
3153                            self.validate_operation_parameters(case.body().operations())?;
3154                        }
3155                        if let Some(body) = op.default() {
3156                            self.validate_operation_parameters(body.operations())?;
3157                        }
3158                    }
3159                    ClassicalControlOp::Break | ClassicalControlOp::Continue => {}
3160                }
3161            }
3162        }
3163        Ok(())
3164    }
3165}
3166
3167fn infer_classical_circuit_id(
3168    operations: &[ValueOperation],
3169) -> Result<Option<CircuitId>, CircuitError> {
3170    fn collect_instruction(instruction: &Instruction, identities: &mut HashSet<CircuitId>) {
3171        match instruction {
3172            Instruction::ClassicalData(op) => match op {
3173                ClassicalDataOp::Store { target, value } => {
3174                    identities.insert(target.circuit_id());
3175                    identities.extend(value.vars().into_iter().map(ClassicalVar::circuit_id));
3176                    identities.extend(value.values().into_iter().map(ClassicalValue::circuit_id));
3177                }
3178                ClassicalDataOp::MeasureBit { result }
3179                | ClassicalDataOp::MeasureBits { result } => {
3180                    identities.insert(result.circuit_id());
3181                }
3182            },
3183            Instruction::ClassicalControl(op) => {
3184                identities.extend(
3185                    op.classical_var_reads()
3186                        .into_iter()
3187                        .map(ClassicalVar::circuit_id),
3188                );
3189                identities.extend(
3190                    op.classical_value_reads()
3191                        .into_iter()
3192                        .map(ClassicalValue::circuit_id),
3193                );
3194                identities.extend(
3195                    op.classical_writes()
3196                        .into_iter()
3197                        .map(ClassicalVar::circuit_id),
3198                );
3199                match op {
3200                    ClassicalControlOp::If(op) => {
3201                        for operation in op
3202                            .then_body()
3203                            .operations()
3204                            .iter()
3205                            .chain(op.else_body().into_iter().flat_map(ControlBody::operations))
3206                        {
3207                            collect_instruction(&operation.instruction, identities);
3208                        }
3209                    }
3210                    ClassicalControlOp::While(op) => {
3211                        for operation in op.body().operations() {
3212                            collect_instruction(&operation.instruction, identities);
3213                        }
3214                    }
3215                    ClassicalControlOp::For(op) => {
3216                        for operation in op.body().operations() {
3217                            collect_instruction(&operation.instruction, identities);
3218                        }
3219                    }
3220                    ClassicalControlOp::Switch(op) => {
3221                        for operation in op
3222                            .cases()
3223                            .iter()
3224                            .flat_map(|case| case.body().operations())
3225                            .chain(op.default().into_iter().flat_map(ControlBody::operations))
3226                        {
3227                            collect_instruction(&operation.instruction, identities);
3228                        }
3229                    }
3230                    ClassicalControlOp::Break | ClassicalControlOp::Continue => {}
3231                }
3232            }
3233            _ => {}
3234        }
3235    }
3236
3237    fn collect_value(instruction: &ValueInstruction, identities: &mut HashSet<CircuitId>) {
3238        match instruction {
3239            ValueInstruction::Instruction(instruction) => {
3240                collect_instruction(instruction, identities);
3241            }
3242            ValueInstruction::ClassicalControl(op) => {
3243                identities.extend(
3244                    op.classical_var_reads()
3245                        .into_iter()
3246                        .map(ClassicalVar::circuit_id),
3247                );
3248                identities.extend(
3249                    op.classical_value_reads()
3250                        .into_iter()
3251                        .map(ClassicalValue::circuit_id),
3252                );
3253                identities.extend(
3254                    op.classical_writes()
3255                        .into_iter()
3256                        .map(ClassicalVar::circuit_id),
3257                );
3258                match op {
3259                    ValueClassicalControlOp::If {
3260                        then_body,
3261                        else_body,
3262                        ..
3263                    } => {
3264                        for operation in then_body
3265                            .operations()
3266                            .iter()
3267                            .chain(else_body.iter().flat_map(|body| body.operations()))
3268                        {
3269                            collect_value(&operation.instruction, identities);
3270                        }
3271                    }
3272                    ValueClassicalControlOp::While { body, .. }
3273                    | ValueClassicalControlOp::For { body, .. } => {
3274                        for operation in body.operations() {
3275                            collect_value(&operation.instruction, identities);
3276                        }
3277                    }
3278                    ValueClassicalControlOp::Switch { cases, default, .. } => {
3279                        for operation in cases
3280                            .iter()
3281                            .flat_map(|case| case.body.operations())
3282                            .chain(default.iter().flat_map(|body| body.operations()))
3283                        {
3284                            collect_value(&operation.instruction, identities);
3285                        }
3286                    }
3287                    ValueClassicalControlOp::Break | ValueClassicalControlOp::Continue => {}
3288                }
3289            }
3290        }
3291    }
3292
3293    let mut identities = HashSet::new();
3294    for operation in operations {
3295        collect_value(&operation.instruction, &mut identities);
3296    }
3297    if identities.len() > 1 {
3298        return Err(CircuitError::InvalidOperation(
3299            "operations contain classical handles from multiple circuits".to_string(),
3300        ));
3301    }
3302    Ok(identities.into_iter().next())
3303}
3304
3305#[cfg(test)]
3306#[path = "./circuit_test.rs"]
3307mod circuit_test;