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