Skip to main content

cqlib_core/compile/transform/decompose/
definition.rs

1// This code is part of Cqlib.
2//
3// (C) Copyright China Telecom Quantum Group 2025-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//! Definition-based decomposition.
14//!
15//! This module expands operations whose implementation is already represented
16//! by another circuit. It intentionally does not synthesize matrix-only
17//! unitaries or lower standard gates to a target basis.
18
19use crate::circuit::{
20    Circuit, CircuitParam, ClassicalControlOp, Instruction, Operation, Parameter, ParameterValue,
21    Qubit, StandardGate, ValueClassicalControlOp, ValueControlBody, ValueInstruction,
22    ValueOperation, ValueSwitchCase,
23};
24use crate::compile::CompilerError;
25use crate::compile::transform::analysis::WorkflowCircuitAnalysis;
26use crate::compile::transform::rebuild::{CircuitRebuildContext, ClassicalRemap};
27use crate::compile::transform::transformer::{PassApplicability, WorkflowPass};
28use crate::compile::transform::{CircuitAnalysis, TransformOutcome, Transformer};
29use smallvec::{SmallVec, smallvec};
30use std::collections::{HashMap, HashSet};
31
32const MAX_DEFINITION_DEPTH: usize = 64;
33
34/// [`Transformer`] adapter for [`expand_definitions`].
35///
36/// Parameter-free — definition expansion never needs configuration.
37#[derive(Debug, Clone, Copy, Default)]
38pub struct DecomposeDefinitions;
39
40impl WorkflowPass for DecomposeDefinitions {
41    fn applicability(&self, analysis: &WorkflowCircuitAnalysis) -> PassApplicability {
42        if analysis.has_unexpanded_definitions() {
43            PassApplicability::Run
44        } else {
45            PassApplicability::ProvenNoOp("circuit contains no unexpanded gate definitions")
46        }
47    }
48}
49
50impl Transformer for DecomposeDefinitions {
51    fn name(&self) -> &'static str {
52        "decompose_definitions"
53    }
54
55    fn transform(
56        &self,
57        circuit: &Circuit,
58        analysis: Option<&CircuitAnalysis>,
59    ) -> Result<TransformOutcome, CompilerError> {
60        let local_analysis;
61        let analysis = match analysis {
62            Some(analysis) => analysis,
63            None => {
64                local_analysis = CircuitAnalysis::analyze(circuit);
65                &local_analysis
66            }
67        };
68        if !analysis.has_circuit_gate_definitions && !analysis.has_unitary_circuit_definitions {
69            return Ok(TransformOutcome::Unchanged);
70        }
71        expand_definitions_transform(circuit)
72    }
73}
74
75/// Expands all circuit-backed definitions in `circuit`.
76///
77/// The expansion handles [`Instruction::CircuitGate`] and circuit-backed
78/// [`Instruction::UnitaryGate`] operations. Matrix-only unitary gates are left
79/// unchanged for a later synthesis stage.
80///
81/// # Examples
82///
83/// ```rust
84/// use cqlib_core::circuit::{Circuit, Instruction, Qubit, StandardGate};
85/// use cqlib_core::compile::transform::decompose::expand_definitions;
86///
87/// let mut definition = Circuit::new(1);
88/// definition.h(Qubit::new(0)).unwrap();
89/// let h_definition = definition.to_gate("h_definition").unwrap();
90///
91/// let mut circuit = Circuit::new(1);
92/// circuit
93///     .append(h_definition, [Qubit::new(0)], [], None)
94///     .unwrap();
95///
96/// let expanded = expand_definitions(&circuit).unwrap();
97/// assert!(matches!(
98///     expanded.operations()[0].instruction,
99///     Instruction::Standard(StandardGate::H),
100/// ));
101/// ```
102pub fn expand_definitions(circuit: &Circuit) -> Result<Circuit, CompilerError> {
103    Ok(expand_definitions_transform(circuit)?.into_circuit(circuit))
104}
105
106fn expand_definitions_transform(circuit: &Circuit) -> Result<TransformOutcome, CompilerError> {
107    DefinitionExpander::new(circuit)?.run()
108}
109
110struct DefinitionExpander<'a> {
111    source: &'a Circuit,
112    rebuild: CircuitRebuildContext,
113    top_phase: Parameter,
114    changed: bool,
115}
116
117impl<'a> DefinitionExpander<'a> {
118    fn new(source: &'a Circuit) -> Result<Self, CompilerError> {
119        let rebuild = CircuitRebuildContext::new(source);
120        Ok(Self {
121            source,
122            rebuild,
123            top_phase: source.global_phase(),
124            changed: false,
125        })
126    }
127
128    fn run(mut self) -> Result<TransformOutcome, CompilerError> {
129        let qubit_map = QubitMap::Identity;
130        let symbol_bindings = HashMap::new();
131        let root_classical = self.rebuild.root_classical().clone();
132        let mut operations = Vec::with_capacity(self.source.operations().len());
133
134        for operation in self.source.operations() {
135            let phase = self.expand_operation(
136                operation,
137                self.source,
138                &qubit_map,
139                &root_classical,
140                &symbol_bindings,
141                0,
142                &mut operations,
143            )?;
144            self.top_phase = self.top_phase.clone() + phase;
145        }
146
147        let target = self
148            .rebuild
149            .finish(self.source.qubits(), operations, self.top_phase)?;
150        Ok(if self.changed {
151            TransformOutcome::Changed(target)
152        } else {
153            TransformOutcome::Unchanged
154        })
155    }
156
157    #[allow(clippy::too_many_arguments)]
158    fn expand_operation(
159        &mut self,
160        operation: &Operation,
161        context: &Circuit,
162        qubit_map: &QubitMap<'_>,
163        classical_remap: &ClassicalRemap,
164        symbol_bindings: &HashMap<String, Parameter>,
165        depth: usize,
166        operations: &mut Vec<ValueOperation>,
167    ) -> Result<Parameter, CompilerError> {
168        match &operation.instruction {
169            Instruction::CircuitGate(gate) => {
170                self.changed = true;
171                let definition = gate.circuit();
172                self.expand_definition(
173                    gate.name(),
174                    definition.circuit(),
175                    gate.num_qubits(),
176                    gate.num_params(),
177                    operation,
178                    context,
179                    qubit_map,
180                    classical_remap,
181                    symbol_bindings,
182                    depth,
183                    operations,
184                )
185            }
186            Instruction::UnitaryGate(gate) => {
187                if let Some(definition) = gate.circuit().as_ref() {
188                    self.changed = true;
189                    self.expand_definition(
190                        gate.label(),
191                        definition.circuit(),
192                        gate.num_qubits() as usize,
193                        gate.num_params() as usize,
194                        operation,
195                        context,
196                        qubit_map,
197                        classical_remap,
198                        symbol_bindings,
199                        depth,
200                        operations,
201                    )
202                } else {
203                    self.keep_operation(
204                        operation,
205                        context,
206                        qubit_map,
207                        classical_remap,
208                        symbol_bindings,
209                        operations,
210                    )
211                }
212            }
213            Instruction::ClassicalControl(control) => self.expand_control_flow(
214                operation,
215                control,
216                context,
217                qubit_map,
218                classical_remap,
219                symbol_bindings,
220                depth,
221                operations,
222            ),
223            _ => self.keep_operation(
224                operation,
225                context,
226                qubit_map,
227                classical_remap,
228                symbol_bindings,
229                operations,
230            ),
231        }
232    }
233
234    // Definition expansion carries the recursive source context, qubit map,
235    // symbol bindings, depth counter, and output buffer together. Grouping
236    // them into a one-off context type would hide the dataflow without reducing
237    // the actual contract of this recursive call.
238    #[allow(clippy::too_many_arguments)]
239    fn expand_definition(
240        &mut self,
241        name: &str,
242        definition: &Circuit,
243        expected_qubits: usize,
244        expected_params: usize,
245        operation: &Operation,
246        context: &Circuit,
247        qubit_map: &QubitMap<'_>,
248        _classical_remap: &ClassicalRemap,
249        symbol_bindings: &HashMap<String, Parameter>,
250        depth: usize,
251        operations: &mut Vec<ValueOperation>,
252    ) -> Result<Parameter, CompilerError> {
253        if depth >= MAX_DEFINITION_DEPTH {
254            return Err(CompilerError::InvalidInput(format!(
255                "definition expansion exceeded maximum recursion depth {MAX_DEFINITION_DEPTH} while expanding '{name}'"
256            )));
257        }
258
259        if operation.qubits.len() != expected_qubits {
260            return Err(CompilerError::InvalidInput(format!(
261                "definition '{name}' expects {expected_qubits} qubits, got {}",
262                operation.qubits.len()
263            )));
264        }
265        if operation.params.len() != expected_params {
266            return Err(CompilerError::InvalidInput(format!(
267                "definition '{name}' expects {expected_params} parameters, got {}",
268                operation.params.len()
269            )));
270        }
271
272        let definition_symbols = definition.symbols();
273        if definition_symbols.len() != expected_params {
274            return Err(CompilerError::InvariantViolation(format!(
275                "definition '{name}' signature has {expected_params} parameters but backing circuit has {} symbols",
276                definition_symbols.len()
277            )));
278        }
279
280        let resolved_params = self.resolve_params(context, &operation.params, symbol_bindings)?;
281
282        // Each definition owns its formal-symbol scope.  The caller arguments
283        // have already been resolved through the current scope above, so the
284        // nested definition must receive a fresh binding table instead of a
285        // merged parent table. Merging would make equal symbol names in nested
286        // definitions accidentally capture outer bindings.
287        let mut next_symbol_bindings = HashMap::with_capacity(definition_symbols.len());
288        for (symbol, value) in definition_symbols.iter().zip(resolved_params) {
289            next_symbol_bindings.insert(symbol.clone(), value);
290        }
291
292        let definition_qubits = definition.qubits();
293        if definition_qubits.len() != expected_qubits {
294            return Err(CompilerError::InvariantViolation(format!(
295                "definition '{name}' signature has {expected_qubits} qubits but backing circuit has {} qubits",
296                definition_qubits.len()
297            )));
298        }
299
300        let mut next_qubit_map = HashMap::with_capacity(definition_qubits.len());
301        for (inner, callsite) in definition_qubits.iter().zip(operation.qubits.iter()) {
302            next_qubit_map.insert(*inner, qubit_map.map_one(*callsite)?);
303        }
304        let next_qubit_map = QubitMap::Mapped(&next_qubit_map);
305        let next_classical_remap = self.rebuild.allocate_classical_instance(definition);
306
307        let mut phase = apply_symbol_bindings(definition.global_phase(), &next_symbol_bindings);
308        for inner_operation in definition.operations() {
309            let inner_phase = self.expand_operation(
310                inner_operation,
311                definition,
312                &next_qubit_map,
313                &next_classical_remap,
314                &next_symbol_bindings,
315                depth + 1,
316                operations,
317            )?;
318            phase = phase + inner_phase;
319        }
320
321        Ok(phase)
322    }
323
324    // Control-flow expansion carries the recursive source context, qubit map,
325    // symbol bindings, depth counter, and output buffer together.
326    #[allow(clippy::too_many_arguments)]
327    fn expand_control_flow(
328        &mut self,
329        operation: &Operation,
330        control: &ClassicalControlOp,
331        context: &Circuit,
332        qubit_map: &QubitMap<'_>,
333        classical_remap: &ClassicalRemap,
334        symbol_bindings: &HashMap<String, Parameter>,
335        depth: usize,
336        operations: &mut Vec<ValueOperation>,
337    ) -> Result<Parameter, CompilerError> {
338        let instruction = match control {
339            ClassicalControlOp::If(op) => ValueClassicalControlOp::If {
340                condition: classical_remap.remap_expr(op.condition())?,
341                then_body: ValueControlBody::new(self.expand_body(
342                    op.then_body().operations(),
343                    context,
344                    qubit_map,
345                    classical_remap,
346                    symbol_bindings,
347                    depth,
348                )?),
349                else_body: op
350                    .else_body()
351                    .map(|body| {
352                        self.expand_body(
353                            body.operations(),
354                            context,
355                            qubit_map,
356                            classical_remap,
357                            symbol_bindings,
358                            depth,
359                        )
360                        .map(ValueControlBody::new)
361                    })
362                    .transpose()?,
363            },
364            ClassicalControlOp::While(op) => ValueClassicalControlOp::While {
365                condition: classical_remap.remap_expr(op.condition())?,
366                body: ValueControlBody::new(self.expand_body(
367                    op.body().operations(),
368                    context,
369                    qubit_map,
370                    classical_remap,
371                    symbol_bindings,
372                    depth,
373                )?),
374            },
375            ClassicalControlOp::For(op) => ValueClassicalControlOp::For {
376                var: classical_remap.remap_var(op.var())?,
377                start: classical_remap.remap_expr(op.start())?,
378                stop: classical_remap.remap_expr(op.stop())?,
379                step: classical_remap.remap_expr(op.step())?,
380                body: ValueControlBody::new(self.expand_body(
381                    op.body().operations(),
382                    context,
383                    qubit_map,
384                    classical_remap,
385                    symbol_bindings,
386                    depth,
387                )?),
388            },
389            ClassicalControlOp::Switch(op) => ValueClassicalControlOp::Switch {
390                target: classical_remap.remap_expr(op.target())?,
391                cases: op
392                    .cases()
393                    .iter()
394                    .map(|case| {
395                        Ok(ValueSwitchCase::new(
396                            case.value(),
397                            ValueControlBody::new(self.expand_body(
398                                case.body().operations(),
399                                context,
400                                qubit_map,
401                                classical_remap,
402                                symbol_bindings,
403                                depth,
404                            )?),
405                        ))
406                    })
407                    .collect::<Result<Vec<_>, CompilerError>>()?,
408                default: op
409                    .default()
410                    .map(|body| {
411                        self.expand_body(
412                            body.operations(),
413                            context,
414                            qubit_map,
415                            classical_remap,
416                            symbol_bindings,
417                            depth,
418                        )
419                        .map(ValueControlBody::new)
420                    })
421                    .transpose()?,
422            },
423            ClassicalControlOp::Break => ValueClassicalControlOp::Break,
424            ClassicalControlOp::Continue => ValueClassicalControlOp::Continue,
425        };
426
427        operations.push(ValueOperation {
428            instruction: ValueInstruction::ClassicalControl(instruction),
429            qubits: qubit_map.map_all(&operation.qubits)?,
430            params: smallvec![],
431            label: operation.label.clone(),
432        });
433        Ok(Parameter::from(0.0))
434    }
435
436    fn expand_body(
437        &mut self,
438        body: &[Operation],
439        context: &Circuit,
440        qubit_map: &QubitMap<'_>,
441        classical_remap: &ClassicalRemap,
442        symbol_bindings: &HashMap<String, Parameter>,
443        depth: usize,
444    ) -> Result<Vec<ValueOperation>, CompilerError> {
445        let mut operations = Vec::with_capacity(body.len());
446        let mut phase = Parameter::from(0.0);
447
448        for operation in body {
449            let operation_phase = self.expand_operation(
450                operation,
451                context,
452                qubit_map,
453                classical_remap,
454                symbol_bindings,
455                depth,
456                &mut operations,
457            )?;
458            phase = phase + operation_phase;
459        }
460
461        if !phase.is_zero() {
462            operations.insert(
463                0,
464                ValueOperation {
465                    instruction: ValueInstruction::from_instruction(Instruction::Standard(
466                        StandardGate::GPhase,
467                    )),
468                    qubits: smallvec![],
469                    params: smallvec![ParameterValue::from(phase)],
470                    label: None,
471                },
472            );
473        }
474
475        Ok(operations)
476    }
477
478    fn keep_operation(
479        &mut self,
480        operation: &Operation,
481        context: &Circuit,
482        qubit_map: &QubitMap<'_>,
483        classical_remap: &ClassicalRemap,
484        symbol_bindings: &HashMap<String, Parameter>,
485        operations: &mut Vec<ValueOperation>,
486    ) -> Result<Parameter, CompilerError> {
487        let params = self
488            .resolve_params(context, &operation.params, symbol_bindings)?
489            .into_iter()
490            .map(ParameterValue::from)
491            .collect::<SmallVec<[ParameterValue; 1]>>();
492
493        operations.push(ValueOperation {
494            instruction: self
495                .rebuild
496                .remap_non_control_instruction(&operation.instruction, classical_remap)?,
497            qubits: qubit_map.map_all(&operation.qubits)?,
498            params,
499            label: operation.label.clone(),
500        });
501        Ok(Parameter::from(0.0))
502    }
503
504    fn resolve_params(
505        &self,
506        context: &Circuit,
507        params: &[CircuitParam],
508        symbol_bindings: &HashMap<String, Parameter>,
509    ) -> Result<Vec<Parameter>, CompilerError> {
510        params
511            .iter()
512            .map(|param| {
513                let resolved = context.resolve_parameter(param)?;
514                Ok(apply_symbol_bindings(resolved, symbol_bindings))
515            })
516            .collect()
517    }
518}
519
520enum QubitMap<'a> {
521    Identity,
522    Mapped(&'a HashMap<Qubit, Qubit>),
523}
524
525impl QubitMap<'_> {
526    fn map_all(&self, qubits: &[Qubit]) -> Result<SmallVec<[Qubit; 3]>, CompilerError> {
527        if matches!(self, Self::Identity) {
528            return Ok(qubits.iter().copied().collect());
529        }
530
531        let mut mapped = SmallVec::with_capacity(qubits.len());
532        for qubit in qubits {
533            mapped.push(self.map_one(*qubit)?);
534        }
535        Ok(mapped)
536    }
537
538    fn map_one(&self, qubit: Qubit) -> Result<Qubit, CompilerError> {
539        match self {
540            Self::Identity => Ok(qubit),
541            Self::Mapped(qubit_map) => qubit_map.get(&qubit).copied().ok_or_else(|| {
542                CompilerError::InvalidInput(format!(
543                    "definition expansion references unmapped qubit {qubit}"
544                ))
545            }),
546        }
547    }
548}
549
550fn apply_symbol_bindings(mut parameter: Parameter, map: &HashMap<String, Parameter>) -> Parameter {
551    if map.is_empty() {
552        return parameter;
553    }
554
555    if !binding_values_reference_bound_symbols(map) {
556        for (symbol, value) in map {
557            parameter = parameter.replace(symbol, value.clone());
558        }
559        return parameter;
560    }
561
562    let mut occupied = parameter.get_symbols();
563    for (symbol, value) in map {
564        occupied.insert(symbol.clone());
565        occupied.extend(value.get_symbols());
566    }
567
568    let mut temp_map = Vec::with_capacity(map.len());
569    for (index, (symbol, value)) in map.iter().enumerate() {
570        let temp = fresh_temp_symbol(index, symbol, &mut occupied);
571        parameter = parameter.replace(symbol, Parameter::symbol(&temp));
572        temp_map.push((temp, value.clone()));
573    }
574
575    for (temp, value) in temp_map {
576        parameter = parameter.replace(&temp, value);
577    }
578
579    parameter
580}
581
582fn binding_values_reference_bound_symbols(map: &HashMap<String, Parameter>) -> bool {
583    map.values()
584        .flat_map(Parameter::get_symbols)
585        .any(|symbol| map.contains_key(&symbol))
586}
587
588fn fresh_temp_symbol(index: usize, symbol: &str, occupied: &mut HashSet<String>) -> String {
589    let mut attempt = 0;
590    loop {
591        let candidate = format!("__CQLIB_DECOMPOSE_TMP_{index}_{attempt}_{symbol}");
592        if occupied.insert(candidate.clone()) {
593            return candidate;
594        }
595        attempt += 1;
596    }
597}
598
599#[cfg(test)]
600mod tests {
601    use super::*;
602    use crate::circuit::ParameterValue;
603    use crate::circuit::gate::{FrozenCircuit, UnitaryGate};
604    use crate::circuit::{ClassicalDataOp, ClassicalExpr};
605    use crate::compile::transform::TransformerTestExt;
606    use ndarray::array;
607    use num_complex::Complex;
608    use std::collections::HashMap;
609    use std::sync::Arc;
610
611    fn op_param(circuit: &Circuit, param: &CircuitParam) -> Parameter {
612        match param {
613            CircuitParam::Fixed(value) => Parameter::from(*value),
614            CircuitParam::Index(index) => circuit.parameters()[*index as usize].clone(),
615        }
616    }
617
618    #[test]
619    fn transform_reports_unchanged_without_circuit_backed_definitions() {
620        let mut circuit = Circuit::new(1);
621        circuit.h(Qubit::new(0)).unwrap();
622
623        let result = DecomposeDefinitions
624            .transform_resolved(&circuit, None)
625            .unwrap();
626
627        assert!(!result.changed);
628        assert_eq!(result.circuit.operations().len(), 1);
629        assert!(matches!(
630            result.circuit.operations()[0].instruction,
631            Instruction::Standard(StandardGate::H)
632        ));
633    }
634
635    #[test]
636    fn transform_keeps_measurement_circuit_without_definitions() {
637        let mut circuit = Circuit::new(2);
638        circuit
639            .measure_bits([Qubit::new(0), Qubit::new(1)])
640            .unwrap();
641
642        let result = DecomposeDefinitions
643            .transform_resolved(&circuit, None)
644            .unwrap();
645
646        assert!(!result.changed);
647        assert_eq!(result.circuit.operations().len(), 1);
648        assert_eq!(
649            result.circuit.classical_values(),
650            circuit.classical_values()
651        );
652        assert!(matches!(
653            result.circuit.operations()[0].instruction,
654            Instruction::ClassicalData(ClassicalDataOp::MeasureBits { .. })
655        ));
656    }
657
658    #[test]
659    fn expands_circuit_gate_with_qubit_and_parameter_mapping() {
660        let mut inner = Circuit::new(2);
661        let theta = Parameter::symbol("theta");
662        let beta = Parameter::symbol("beta");
663        inner.h(Qubit::new(0)).unwrap();
664        inner.rx(Qubit::new(0), theta).unwrap();
665        inner.rz(Qubit::new(1), beta + 1.0).unwrap();
666        let gate = inner.to_gate("inner").unwrap();
667
668        let mut outer = Circuit::new(2);
669        let gamma = Parameter::symbol("gamma");
670        let delta = Parameter::symbol("delta");
671        outer
672            .append(
673                gate,
674                [Qubit::new(1), Qubit::new(0)],
675                [
676                    ParameterValue::Param(gamma.clone()),
677                    ParameterValue::Param(gamma + delta),
678                ],
679                None,
680            )
681            .unwrap();
682
683        let expanded = expand_definitions(&outer).unwrap();
684        let operations = expanded.operations();
685        assert_eq!(operations.len(), 3);
686        assert!(matches!(
687            operations[0].instruction,
688            Instruction::Standard(StandardGate::H)
689        ));
690        assert_eq!(operations[0].qubits.as_slice(), &[Qubit::new(1)]);
691        assert!(matches!(
692            operations[1].instruction,
693            Instruction::Standard(StandardGate::RX)
694        ));
695        assert_eq!(operations[1].qubits.as_slice(), &[Qubit::new(1)]);
696        assert!(matches!(
697            operations[2].instruction,
698            Instruction::Standard(StandardGate::RZ)
699        ));
700        assert_eq!(operations[2].qubits.as_slice(), &[Qubit::new(0)]);
701
702        let rx_param = op_param(&expanded, &operations[1].params[0]);
703        let rz_param = op_param(&expanded, &operations[2].params[0]);
704        let bindings = HashMap::from([("gamma", 2.0), ("delta", 3.0)]);
705        assert_eq!(rx_param.evaluate(&Some(bindings.clone())).unwrap(), 2.0);
706        assert_eq!(rz_param.evaluate(&Some(bindings)).unwrap(), 6.0);
707    }
708
709    #[test]
710    fn uses_simultaneous_parameter_substitution() {
711        let mut inner = Circuit::new(1);
712        inner.rx(Qubit::new(0), Parameter::symbol("a")).unwrap();
713        inner.rz(Qubit::new(0), Parameter::symbol("b")).unwrap();
714        let gate = inner.to_gate("swap_params").unwrap();
715
716        let mut outer = Circuit::new(1);
717        outer
718            .append(
719                gate,
720                [Qubit::new(0)],
721                [
722                    ParameterValue::Param(Parameter::symbol("b")),
723                    ParameterValue::Param(Parameter::symbol("a")),
724                ],
725                None,
726            )
727            .unwrap();
728
729        let expanded = expand_definitions(&outer).unwrap();
730        let operations = expanded.operations();
731        let rx_param = op_param(&expanded, &operations[0].params[0]);
732        let rz_param = op_param(&expanded, &operations[1].params[0]);
733        assert_eq!(rx_param.as_symbol().as_deref(), Some("b"));
734        assert_eq!(rz_param.as_symbol().as_deref(), Some("a"));
735    }
736
737    #[test]
738    fn expands_nested_circuit_gates() {
739        let mut leaf = Circuit::new(1);
740        leaf.h(Qubit::new(0)).unwrap();
741        let leaf_gate = leaf.to_gate("leaf").unwrap();
742
743        let mut middle = Circuit::new(1);
744        middle
745            .rx(Qubit::new(0), Parameter::symbol("theta"))
746            .unwrap();
747        middle.append(leaf_gate, [Qubit::new(0)], [], None).unwrap();
748        let middle_gate = middle.to_gate("middle").unwrap();
749
750        let mut outer = Circuit::new(1);
751        outer
752            .append(
753                middle_gate,
754                [Qubit::new(0)],
755                [ParameterValue::Param(Parameter::symbol("phi"))],
756                None,
757            )
758            .unwrap();
759
760        let expanded = expand_definitions(&outer).unwrap();
761        let operations = expanded.operations();
762        assert_eq!(operations.len(), 2);
763        assert!(matches!(
764            operations[0].instruction,
765            Instruction::Standard(StandardGate::RX)
766        ));
767        assert!(matches!(
768            operations[1].instruction,
769            Instruction::Standard(StandardGate::H)
770        ));
771        assert_eq!(
772            op_param(&expanded, &operations[0].params[0])
773                .as_symbol()
774                .as_deref(),
775            Some("phi")
776        );
777    }
778
779    #[test]
780    fn resolves_nested_definition_symbol_scopes_without_capture() {
781        let mut leaf = Circuit::new(1);
782        leaf.rx(Qubit::new(0), Parameter::symbol("theta")).unwrap();
783        let leaf_gate = leaf.to_gate("leaf").unwrap();
784
785        let mut middle = Circuit::new(1);
786        middle
787            .append(
788                leaf_gate,
789                [Qubit::new(0)],
790                [ParameterValue::Param(Parameter::symbol("theta") + 1.0)],
791                None,
792            )
793            .unwrap();
794        let middle_gate = middle.to_gate("middle").unwrap();
795
796        let mut outer = Circuit::new(1);
797        outer
798            .append(
799                middle_gate,
800                [Qubit::new(0)],
801                [ParameterValue::Param(Parameter::symbol("alpha"))],
802                None,
803            )
804            .unwrap();
805
806        let expanded = expand_definitions(&outer).unwrap();
807        let operations = expanded.operations();
808        assert_eq!(operations.len(), 1);
809        assert!(matches!(
810            operations[0].instruction,
811            Instruction::Standard(StandardGate::RX)
812        ));
813
814        let param = op_param(&expanded, &operations[0].params[0]);
815        let bindings = HashMap::from([("alpha", 2.0)]);
816        assert_eq!(param.evaluate(&Some(bindings)).unwrap(), 3.0);
817        assert!(param.get_symbols().contains("alpha"));
818        assert!(!param.get_symbols().contains("theta"));
819    }
820
821    #[test]
822    fn merges_definition_global_phase_at_top_level() {
823        let mut inner = Circuit::new(1);
824        inner.set_global_phase(Parameter::from(0.5));
825        inner.x(Qubit::new(0)).unwrap();
826        let gate = inner.to_gate("phase_x").unwrap();
827
828        let mut outer = Circuit::new(1);
829        outer.set_global_phase(Parameter::from(0.25));
830        outer.append(gate, [Qubit::new(0)], [], None).unwrap();
831
832        let expanded = expand_definitions(&outer).unwrap();
833        assert_eq!(expanded.operations().len(), 1);
834        assert!(matches!(
835            expanded.operations()[0].instruction,
836            Instruction::Standard(StandardGate::X)
837        ));
838        assert!((expanded.global_phase().evaluate(&None).unwrap() - 0.75).abs() < 1e-12);
839    }
840
841    #[test]
842    fn expands_circuit_backed_unitary_gate() {
843        let mut inner = Circuit::new(1);
844        inner.rx(Qubit::new(0), Parameter::symbol("theta")).unwrap();
845        let gate = UnitaryGate::new("unitary_rx", 1, 1)
846            .with_circuit(Arc::new(FrozenCircuit::new(inner)))
847            .unwrap();
848
849        let mut outer = Circuit::new(1);
850        outer
851            .unitary_with_params(
852                gate,
853                vec![Qubit::new(0)],
854                [ParameterValue::Param(Parameter::symbol("phi"))],
855            )
856            .unwrap();
857
858        let expanded = expand_definitions(&outer).unwrap();
859        let operations = expanded.operations();
860        assert_eq!(operations.len(), 1);
861        assert!(matches!(
862            operations[0].instruction,
863            Instruction::Standard(StandardGate::RX)
864        ));
865        assert_eq!(
866            op_param(&expanded, &operations[0].params[0])
867                .as_symbol()
868                .as_deref(),
869            Some("phi")
870        );
871    }
872
873    #[test]
874    fn keeps_matrix_only_unitary_gate() {
875        let matrix = array![
876            [Complex::new(0.0, 0.0), Complex::new(1.0, 0.0)],
877            [Complex::new(1.0, 0.0), Complex::new(0.0, 0.0)]
878        ];
879        let gate = UnitaryGate::new("matrix_x", 1, 0)
880            .with_matrix(matrix)
881            .unwrap();
882
883        let mut circuit = Circuit::new(1);
884        circuit.unitary(gate, vec![Qubit::new(0)]).unwrap();
885
886        let expanded = expand_definitions(&circuit).unwrap();
887        assert_eq!(expanded.operations().len(), 1);
888        assert!(matches!(
889            expanded.operations()[0].instruction,
890            Instruction::UnitaryGate(_)
891        ));
892    }
893
894    #[test]
895    fn expands_definitions_in_control_flow_body_with_body_local_phase() {
896        let mut inner = Circuit::new(1);
897        inner.set_global_phase(Parameter::from(0.5));
898        inner.x(Qubit::new(0)).unwrap();
899        let gate = inner.to_gate("phase_x").unwrap();
900
901        let mut circuit = Circuit::new(2);
902        circuit
903            .if_else(
904                ClassicalExpr::bool_literal(true),
905                |body| body.append(gate, [Qubit::new(1)], [], None),
906                |_body| Ok(()),
907            )
908            .unwrap();
909
910        let expanded = expand_definitions(&circuit).unwrap();
911        assert!(expanded.global_phase().is_zero());
912        let Instruction::ClassicalControl(ClassicalControlOp::If(op)) =
913            &expanded.operations()[0].instruction
914        else {
915            panic!("expected if operation");
916        };
917        let then_body = op.then_body().operations();
918        assert_eq!(then_body.len(), 2);
919        assert!(matches!(
920            then_body[0].instruction,
921            Instruction::Standard(StandardGate::GPhase)
922        ));
923        assert!(matches!(
924            then_body[1].instruction,
925            Instruction::Standard(StandardGate::X)
926        ));
927    }
928
929    #[test]
930    fn expands_definition_with_measurement_and_classical_control() {
931        let mut inner = Circuit::new(1);
932        let measured = inner.measure(Qubit::new(0)).unwrap();
933        inner
934            .if_(
935                ClassicalExpr::bit_to_bool(measured.expr()).unwrap(),
936                |body| body.x(Qubit::new(0)),
937            )
938            .unwrap();
939        let gate = inner.to_gate("measure_then_x").unwrap();
940
941        let mut outer = Circuit::new(1);
942        outer.append(gate, [Qubit::new(0)], [], None).unwrap();
943
944        let expanded = expand_definitions(&outer).unwrap();
945        assert_eq!(expanded.classical_values().len(), 1);
946        assert!(expanded.validate().is_ok());
947        assert!(matches!(
948            expanded.operations()[0].instruction,
949            Instruction::ClassicalData(ClassicalDataOp::MeasureBit { .. })
950        ));
951        let Instruction::ClassicalControl(ClassicalControlOp::If(op)) =
952            &expanded.operations()[1].instruction
953        else {
954            panic!("expected expanded if operation");
955        };
956        assert_eq!(op.then_body().operations().len(), 1);
957        assert!(matches!(
958            op.then_body().operations()[0].instruction,
959            Instruction::Standard(StandardGate::X)
960        ));
961    }
962
963    #[test]
964    fn expands_multiple_definition_calls_with_distinct_classical_handles() {
965        let mut inner = Circuit::new(1);
966        let measured = inner.measure(Qubit::new(0)).unwrap();
967        inner
968            .if_(
969                ClassicalExpr::bit_to_bool(measured.expr()).unwrap(),
970                |body| body.x(Qubit::new(0)),
971            )
972            .unwrap();
973        let gate = inner.to_gate("measure_then_x").unwrap();
974
975        let mut outer = Circuit::new(2);
976        outer
977            .append(gate.clone(), [Qubit::new(0)], [], None)
978            .unwrap();
979        outer.append(gate, [Qubit::new(1)], [], None).unwrap();
980
981        let expanded = expand_definitions(&outer).unwrap();
982        assert_eq!(expanded.classical_values().len(), 2);
983        assert!(expanded.validate().is_ok());
984
985        let Instruction::ClassicalData(ClassicalDataOp::MeasureBit { result: first }) =
986            expanded.operations()[0].instruction
987        else {
988            panic!("expected first measurement");
989        };
990        let Instruction::ClassicalData(ClassicalDataOp::MeasureBit { result: second }) =
991            expanded.operations()[2].instruction
992        else {
993            panic!("expected second measurement");
994        };
995
996        assert_ne!(first.index(), second.index());
997        let Instruction::ClassicalControl(ClassicalControlOp::If(first_if)) =
998            &expanded.operations()[1].instruction
999        else {
1000            panic!("expected first if");
1001        };
1002        let Instruction::ClassicalControl(ClassicalControlOp::If(second_if)) =
1003            &expanded.operations()[3].instruction
1004        else {
1005            panic!("expected second if");
1006        };
1007        assert!(first_if.condition().values().contains(&first));
1008        assert!(second_if.condition().values().contains(&second));
1009    }
1010
1011    #[test]
1012    fn rejects_excessive_definition_depth() {
1013        let mut circuit = Circuit::new(1);
1014        circuit.x(Qubit::new(0)).unwrap();
1015        let mut gate = circuit.to_gate("level_0").unwrap();
1016
1017        for index in 1..=MAX_DEFINITION_DEPTH {
1018            let mut next = Circuit::new(1);
1019            next.append(gate, [Qubit::new(0)], [], None).unwrap();
1020            gate = next.to_gate(format!("level_{index}")).unwrap();
1021        }
1022
1023        let mut top = Circuit::new(1);
1024        top.append(gate, [Qubit::new(0)], [], None).unwrap();
1025
1026        let err = expand_definitions(&top).unwrap_err();
1027        assert!(
1028            matches!(err, CompilerError::InvalidInput(message) if message.contains("maximum recursion depth"))
1029        );
1030    }
1031}