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 indexmap::IndexSet;
30use smallvec::{SmallVec, smallvec};
31use std::collections::{HashMap, HashSet};
32
33const MAX_DEFINITION_DEPTH: usize = 64;
34
35#[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
76pub 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 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 #[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 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 #[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}