1use 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#[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
75pub 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 #[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 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 #[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}