1use crate::circuit::{
39 Circuit, ClassicalControlOp, Directive, Instruction, Operation, Parameter, ParameterValue,
40 Qubit, StandardGate, ValueClassicalControlOp, ValueControlBody, ValueInstruction,
41 ValueOperation, ValueSwitchCase,
42};
43use crate::compile::CompilerError;
44use crate::compile::device_planning::DevicePlanningSession;
45use crate::compile::device_planning::cost::{RobustDurationKey, RobustErrorKey};
46use crate::compile::numeric_matrix::one_qubit_run_matrix;
47use crate::compile::sabre::MetricAvailability;
48use crate::compile::transform::decompose::unitary::{
49 DeviceContextCostFailure, DeviceSynthesisPlacement, DeviceTwoQubitSynthesisContext,
50 OneQubitUnitaryDecomposition, synthesize_numeric_1q_unitary,
51};
52use crate::compile::transform::native_quality::{
53 NativeQualityPolicy, NativeQualityVector, NativeQualityViolation,
54};
55use crate::compile::transform::rebuild::{CircuitRebuildContext, ClassicalRemap};
56use crate::compile::transform::resynthesis::{
57 NativeResynthesisPolicy, NativeResynthesisSession, NativeWorksetStats,
58 TwoQubitBlockResynthesisConfig, resynthesize_two_qubit_blocks_incremental,
59};
60use crate::compile::transform::target_basis::{TargetBasisCost, TargetBasisCostModel};
61use crate::compile::transform::{
62 Canonicalizer, CircuitAnalysis, DeviceLowerer, RewriteEdits, TransformOutcome, Transformer,
63};
64use crate::device::Device;
65use smallvec::{SmallVec, smallvec};
66use std::borrow::Cow;
67use std::cmp::Ordering;
68use std::collections::{BTreeMap, HashMap, HashSet};
69use std::f64::consts::{FRAC_PI_2, FRAC_PI_4};
70use std::sync::Arc;
71
72const PHASE_EPS: f64 = 1e-12;
73
74struct NativeStageCandidate {
75 circuit: Arc<Circuit>,
76 costs: Vec<NativeQualityVector>,
77 context: DeviceTwoQubitSynthesisContext,
78 improves_best: bool,
79}
80
81enum NativeStageOutcome {
82 Unchanged,
83 Unavailable,
84 Candidate(NativeStageCandidate),
85}
86
87struct NativeBestCheckpoint<'a> {
88 circuit: &'a mut Arc<Circuit>,
89 costs: &'a mut Vec<NativeQualityVector>,
90 context: &'a mut DeviceTwoQubitSynthesisContext,
91}
92
93#[derive(Debug, Clone, Copy, PartialEq)]
94pub struct NativeOptimizationSummary {
95 pub native_two_qubit_ops: u64,
97 pub native_two_qubit_depth: u64,
99 pub total_native_depth: u64,
101 pub native_total_ops: u64,
103 pub predicted_log_error: Option<f64>,
105 pub unavailable_error_count: u64,
107 pub imputed_error_count: u64,
109}
110
111#[derive(Debug, Clone, PartialEq)]
116pub(crate) struct NativeExactQualityCheckpoint {
117 scopes: Vec<NativeQualityVector>,
118}
119
120impl NativeExactQualityCheckpoint {
121 fn new(scopes: Vec<NativeQualityVector>) -> Self {
122 Self { scopes }
123 }
124
125 pub(crate) fn strictly_dominates(&self, incumbent: &Self) -> bool {
128 if self.scopes.len() != incumbent.scopes.len() {
129 return false;
130 }
131 let mut strict = false;
132 for (candidate, incumbent) in self.scopes.iter().zip(&incumbent.scopes) {
133 let Some(scope_strict) = candidate.exact_pareto_dominance(*incumbent) else {
134 return false;
135 };
136 strict |= scope_strict;
137 }
138 strict
139 }
140
141 pub(crate) fn strictly_dominates_with_two_qubit_gain(&self, incumbent: &Self) -> bool {
144 if !self.strictly_dominates(incumbent) {
145 return false;
146 }
147 self.scopes
148 .iter()
149 .zip(&incumbent.scopes)
150 .any(|(candidate, incumbent)| {
151 candidate.physical.native_two_qubit_ops < incumbent.physical.native_two_qubit_ops
152 || candidate.physical.native_two_qubit_depth
153 < incumbent.physical.native_two_qubit_depth
154 })
155 }
156
157 pub(crate) fn compare_for_sabre_beam(&self, other: &Self) -> Ordering {
159 let left = self.summary();
160 let right = other.summary();
161 left.native_two_qubit_ops
162 .cmp(&right.native_two_qubit_ops)
163 .then_with(|| {
164 left.native_two_qubit_depth
165 .cmp(&right.native_two_qubit_depth)
166 })
167 .then_with(|| left.total_native_depth.cmp(&right.total_native_depth))
168 .then_with(|| left.native_total_ops.cmp(&right.native_total_ops))
169 .then_with(|| {
170 aggregate_error(&self.scopes)
171 .compare_by(aggregate_error(&other.scopes), RobustErrorKey::compare)
172 })
173 .then_with(|| {
174 aggregate_duration(&self.scopes).compare_by(
175 aggregate_duration(&other.scopes),
176 RobustDurationKey::compare,
177 )
178 })
179 .then_with(|| {
180 aggregate_makespan(&self.scopes)
181 .compare_by(aggregate_makespan(&other.scopes), |left, right| {
182 left.total_cmp(&right)
183 })
184 })
185 }
186
187 pub(crate) fn summary(&self) -> NativeOptimizationSummary {
188 summarize_scope_costs(&self.scopes)
189 }
190}
191
192fn aggregate_error(scopes: &[NativeQualityVector]) -> MetricAvailability<RobustErrorKey> {
193 aggregate_optional_metric(
194 scopes.iter().map(|quality| quality.physical.error),
195 |left, right| left.combine(right),
196 )
197}
198
199fn aggregate_duration(scopes: &[NativeQualityVector]) -> MetricAvailability<RobustDurationKey> {
200 aggregate_optional_metric(
201 scopes.iter().map(|quality| quality.physical.duration),
202 |left, right| left.combine(right),
203 )
204}
205
206fn aggregate_makespan(scopes: &[NativeQualityVector]) -> MetricAvailability<f64> {
207 aggregate_optional_metric(
208 scopes.iter().map(|quality| quality.physical.makespan),
209 |left, right| left + right,
210 )
211}
212
213fn aggregate_optional_metric<T: Copy>(
214 metrics: impl IntoIterator<Item = MetricAvailability<T>>,
215 combine: impl Fn(T, T) -> T,
216) -> MetricAvailability<T> {
217 let mut aggregate = None;
218 for metric in metrics {
219 aggregate = Some(match (aggregate, metric) {
220 (None, metric) => metric,
221 (Some(MetricAvailability::Disabled), MetricAvailability::Disabled) => {
222 MetricAvailability::Disabled
223 }
224 (Some(MetricAvailability::Available(left)), MetricAvailability::Available(right)) => {
225 MetricAvailability::Available(combine(left, right))
226 }
227 (Some(MetricAvailability::Inconsistent), _)
228 | (_, MetricAvailability::Inconsistent)
229 | (Some(MetricAvailability::Disabled), MetricAvailability::Available(_))
230 | (Some(MetricAvailability::Available(_)), MetricAvailability::Disabled) => {
231 MetricAvailability::Inconsistent
232 }
233 });
234 }
235 aggregate.unwrap_or(MetricAvailability::Disabled)
236}
237
238#[derive(Debug, Clone)]
240pub struct NativeOptimizationResult {
241 pub circuit: Circuit,
243 pub changed: bool,
245 pub rounds: u8,
247 pub restored_best: bool,
250 pub before: NativeOptimizationSummary,
252 pub after: NativeOptimizationSummary,
254}
255
256pub struct NativeOptimizer<'a> {
262 device: Cow<'a, Device>,
263 planning_session: Arc<DevicePlanningSession>,
264 resynthesis: TwoQubitBlockResynthesisConfig,
265 max_rounds: u8,
266 max_stale_rounds: u8,
267 quality_policy: NativeQualityPolicy,
268}
269
270impl<'a> NativeOptimizer<'a> {
271 pub const NORMAL_MAX_ROUNDS: u8 = 2;
273 pub const NORMAL_MAX_STALE_ROUNDS: u8 = 1;
275 pub const ENHANCED_MAX_ROUNDS: u8 = 8;
277 pub const ENHANCED_MAX_STALE_ROUNDS: u8 = 3;
279
280 pub fn new(
282 device: &'a Device,
283 resynthesis: TwoQubitBlockResynthesisConfig,
284 max_rounds: u8,
285 max_stale_rounds: u8,
286 ) -> Result<Self, CompilerError> {
287 validate_native_optimization_budgets(max_rounds, max_stale_rounds)?;
288 Ok(Self {
289 device: Cow::Borrowed(device),
290 planning_session: Arc::new(DevicePlanningSession::new(device)),
291 resynthesis,
292 max_rounds,
293 max_stale_rounds,
294 quality_policy: NativeQualityPolicy::EntanglerFirst,
295 })
296 }
297
298 pub fn new_owned(
304 device: Device,
305 resynthesis: TwoQubitBlockResynthesisConfig,
306 max_rounds: u8,
307 max_stale_rounds: u8,
308 ) -> Result<NativeOptimizer<'static>, CompilerError> {
309 validate_native_optimization_budgets(max_rounds, max_stale_rounds)?;
310 let planning_session = Arc::new(DevicePlanningSession::new(&device));
311 Ok(NativeOptimizer {
312 device: Cow::Owned(device),
313 planning_session,
314 resynthesis,
315 max_rounds,
316 max_stale_rounds,
317 quality_policy: NativeQualityPolicy::EntanglerFirst,
318 })
319 }
320
321 pub fn normal(device: &'a Device) -> Self {
323 Self::new(
324 device,
325 TwoQubitBlockResynthesisConfig::normal(Default::default()),
326 Self::NORMAL_MAX_ROUNDS,
327 Self::NORMAL_MAX_STALE_ROUNDS,
328 )
329 .expect("production native optimization budgets must be valid")
330 }
331
332 pub fn enhanced(device: &'a Device) -> Self {
334 Self::new(
335 device,
336 TwoQubitBlockResynthesisConfig::enhanced(Default::default()),
337 Self::ENHANCED_MAX_ROUNDS,
338 Self::ENHANCED_MAX_STALE_ROUNDS,
339 )
340 .expect("production native optimization budgets must be valid")
341 }
342
343 pub fn device(&self) -> &Device {
345 self.device.as_ref()
346 }
347
348 pub fn resynthesis(&self) -> &TwoQubitBlockResynthesisConfig {
350 &self.resynthesis
351 }
352
353 pub const fn max_rounds(&self) -> u8 {
355 self.max_rounds
356 }
357
358 pub const fn max_stale_rounds(&self) -> u8 {
360 self.max_stale_rounds
361 }
362
363 pub const fn quality_policy(&self) -> NativeQualityPolicy {
365 self.quality_policy
366 }
367
368 pub fn with_quality_policy(mut self, quality_policy: NativeQualityPolicy) -> Self {
370 self.quality_policy = quality_policy;
371 self
372 }
373
374 pub(crate) fn with_session(
375 device: &'a Device,
376 resynthesis: TwoQubitBlockResynthesisConfig,
377 max_rounds: u8,
378 max_stale_rounds: u8,
379 planning_session: Arc<DevicePlanningSession>,
380 ) -> Self {
381 debug_assert!(max_rounds > 0);
382 debug_assert!(max_stale_rounds > 0);
383 Self {
384 device: Cow::Borrowed(device),
385 planning_session,
386 resynthesis,
387 max_rounds,
388 max_stale_rounds,
389 quality_policy: NativeQualityPolicy::EntanglerFirst,
390 }
391 }
392
393 pub fn run(&self, circuit: &Circuit) -> Result<NativeOptimizationResult, CompilerError> {
394 self.run_with_policy(circuit, NativeResynthesisPolicy::Incremental)
395 .map(|(result, _)| result)
396 }
397
398 pub(crate) fn run_with_exact_quality_and_stats(
399 &self,
400 circuit: &Circuit,
401 ) -> Result<
402 (
403 NativeOptimizationResult,
404 NativeExactQualityCheckpoint,
405 NativeWorksetStats,
406 ),
407 CompilerError,
408 > {
409 let initial = Canonicalizer::production()
410 .transform(circuit, None)?
411 .into_circuit(circuit);
412 self.run_canonicalized(circuit, initial, NativeResynthesisPolicy::Incremental)
413 }
414
415 pub(crate) fn run_with_proven_canonical_input_exact_quality_and_stats(
416 &self,
417 circuit: &Circuit,
418 ) -> Result<
419 (
420 NativeOptimizationResult,
421 NativeExactQualityCheckpoint,
422 NativeWorksetStats,
423 ),
424 CompilerError,
425 > {
426 self.run_canonicalized(
427 circuit,
428 circuit.clone(),
429 NativeResynthesisPolicy::Incremental,
430 )
431 }
432
433 pub(crate) fn run_with_policy(
434 &self,
435 circuit: &Circuit,
436 policy: NativeResynthesisPolicy,
437 ) -> Result<(NativeOptimizationResult, NativeWorksetStats), CompilerError> {
438 let initial = Canonicalizer::production()
439 .transform(circuit, None)?
440 .into_circuit(circuit);
441 self.run_canonicalized(circuit, initial, policy)
442 .map(|(result, _, stats)| (result, stats))
443 }
444
445 fn run_canonicalized(
448 &self,
449 source: &Circuit,
450 initial: Circuit,
451 policy: NativeResynthesisPolicy,
452 ) -> Result<
453 (
454 NativeOptimizationResult,
455 NativeExactQualityCheckpoint,
456 NativeWorksetStats,
457 ),
458 CompilerError,
459 > {
460 self.device().validate_circuit(&initial)?;
461 let initial = Arc::new(initial);
465 let mut current = initial.clone();
466 let mut best = initial;
467 let mut context = DeviceTwoQubitSynthesisContext::build_with_session(
471 self.device(),
472 ¤t,
473 DeviceSynthesisPlacement::ExactPhysical,
474 Arc::clone(&self.planning_session),
475 )?;
476 let mut best_context = context.clone();
477 let mut best_costs = scope_costs_with_context(&best, &context).map_err(scope_cost_error)?;
478 let entry_costs = best_costs.clone();
479 let before = summarize_scope_costs(&best_costs);
480 let mut rounds = 0;
481 let mut stale = 0_u8;
482 let mut baseline_session = NativeResynthesisSession::new(policy);
486 let mut after_local_session = NativeResynthesisSession::new(policy);
487 let mut seen_states = vec![current.clone()];
488
489 'optimization: while rounds < self.max_rounds && stale < self.max_stale_rounds {
490 rounds += 1;
491 let round_start = current.clone();
492 let round_context = context.clone();
493 let mut improved_in_round = false;
494
495 let phase_a_branch = match OptimizeNativeLocalGates::with_quality_policy(
499 round_context.clone(),
500 self.quality_policy,
501 )
502 .transform(&round_start, None)?
503 {
504 TransformOutcome::Unchanged => None,
505 TransformOutcome::Changed(candidate) => match self.evaluate_stage_candidate(
506 &round_start,
507 Arc::new(candidate),
508 &best_costs,
509 &entry_costs,
510 &round_context,
511 &mut after_local_session,
512 )? {
513 NativeStageOutcome::Candidate(candidate) => {
514 let branch = (candidate.circuit.clone(), candidate.context.clone());
515 improved_in_round |= install_best_checkpoint(
516 &candidate,
517 &mut best,
518 &mut best_costs,
519 &mut best_context,
520 );
521 Some(branch)
522 }
523 NativeStageOutcome::Unchanged | NativeStageOutcome::Unavailable => None,
524 },
525 };
526
527 improved_in_round |= self.explore_resynthesis_branch(
530 &round_start,
531 &round_context,
532 NativeBestCheckpoint {
533 circuit: &mut best,
534 costs: &mut best_costs,
535 context: &mut best_context,
536 },
537 &entry_costs,
538 &mut baseline_session,
539 )?;
540
541 if let Some((phase_a, phase_a_context)) = phase_a_branch {
542 baseline_session.swap_synthesis_cache(&mut after_local_session);
545 let branch_result = self.explore_resynthesis_branch(
546 &phase_a,
547 &phase_a_context,
548 NativeBestCheckpoint {
549 circuit: &mut best,
550 costs: &mut best_costs,
551 context: &mut best_context,
552 },
553 &entry_costs,
554 &mut after_local_session,
555 );
556 baseline_session.swap_synthesis_cache(&mut after_local_session);
557 improved_in_round |= branch_result?;
558 }
559
560 current = best.clone();
564 context = best_context.clone();
565
566 if current.as_ref() == round_start.as_ref() {
567 break;
568 }
569
570 if let Some(seen) = seen_states
574 .iter()
575 .find(|seen| seen.as_ref() == current.as_ref())
576 {
577 current = seen.clone();
578 baseline_session.record_cycle_early_exit();
579 break 'optimization;
580 }
581 seen_states.push(current.clone());
582
583 if improved_in_round {
584 stale = 0;
585 } else {
586 stale = stale.saturating_add(1);
587 }
588 }
589
590 let restored_best = current.as_ref() != best.as_ref();
591 let after = summarize_scope_costs(&best_costs);
592 let exact_quality = NativeExactQualityCheckpoint::new(best_costs);
593 drop(current);
594 drop(seen_states);
595 let best = Arc::try_unwrap(best).unwrap_or_else(|shared| shared.as_ref().clone());
596 let result = NativeOptimizationResult {
597 changed: best != *source,
598 circuit: best,
599 rounds,
600 restored_best,
601 before,
602 after,
603 };
604 let mut stats = baseline_session.stats();
605 stats.merge_workset_from(after_local_session.stats());
606 Ok((result, exact_quality, stats))
607 }
608
609 fn explore_resynthesis_branch(
613 &self,
614 branch_start: &Arc<Circuit>,
615 branch_context: &DeviceTwoQubitSynthesisContext,
616 best: NativeBestCheckpoint<'_>,
617 entry_costs: &[NativeQualityVector],
618 session: &mut NativeResynthesisSession,
619 ) -> Result<bool, CompilerError> {
620 let resynthesis_outcome = resynthesize_two_qubit_blocks_incremental(
621 branch_start,
622 self.resynthesis.clone(),
623 branch_context.clone(),
624 session,
625 self.quality_policy,
626 )?;
627 let TransformOutcome::Changed(raw_phase_b) = resynthesis_outcome else {
628 return Ok(false);
629 };
630 let raw_phase_b = Arc::new(raw_phase_b);
631 let phase_b_outcome = self.evaluate_stage_candidate(
632 branch_start,
633 raw_phase_b.clone(),
634 best.costs,
635 entry_costs,
636 branch_context,
637 session,
638 )?;
639 let cleanup_context = match phase_b_outcome {
640 NativeStageOutcome::Candidate(candidate) => {
641 let context = candidate.context.clone();
642 let improved =
643 install_best_checkpoint(&candidate, best.circuit, best.costs, best.context);
644 (context, improved)
645 }
646 NativeStageOutcome::Unchanged | NativeStageOutcome::Unavailable => {
647 (branch_context.clone(), false)
648 }
649 };
650 let (cleanup_context, mut improved) = cleanup_context;
651
652 if let TransformOutcome::Changed(cleaned) =
655 OptimizeNativeLocalGates::with_quality_policy(cleanup_context, self.quality_policy)
656 .transform(&raw_phase_b, None)?
657 && let NativeStageOutcome::Candidate(candidate) = self.evaluate_stage_candidate(
658 branch_start,
659 Arc::new(cleaned),
660 best.costs,
661 entry_costs,
662 branch_context,
663 session,
664 )?
665 {
666 improved |= install_best_checkpoint(&candidate, best.circuit, best.costs, best.context);
667 }
668 Ok(improved)
669 }
670
671 fn evaluate_stage_candidate(
676 &self,
677 baseline: &Arc<Circuit>,
678 generated: Arc<Circuit>,
679 best_costs: &[NativeQualityVector],
680 entry_costs: &[NativeQualityVector],
681 context: &DeviceTwoQubitSynthesisContext,
682 resynthesis_session: &mut NativeResynthesisSession,
683 ) -> Result<NativeStageOutcome, CompilerError> {
684 let legalized = match DeviceLowerer::with_session(self.device(), &self.planning_session)
685 .transform(&generated, None)
686 {
687 Ok(TransformOutcome::Unchanged) => generated,
688 Ok(TransformOutcome::Changed(legalized)) => Arc::new(legalized),
689 Err(CompilerError::DeviceLoweringFailed(_)) => {
692 return Ok(NativeStageOutcome::Unavailable);
693 }
694 Err(error) => return Err(error),
695 };
696 let candidate = match Canonicalizer::production().transform(&legalized, None)? {
697 TransformOutcome::Unchanged => legalized,
698 TransformOutcome::Changed(candidate) => Arc::new(candidate),
699 };
700 if candidate.as_ref() == baseline.as_ref() {
701 return Ok(NativeStageOutcome::Unchanged);
702 }
703
704 self.device().validate_circuit(&candidate)?;
707 let mut candidate_context = context.clone();
708 let candidate_costs =
709 self.candidate_costs_with_reuse(&candidate, &mut candidate_context)?;
710 let improves_best = match scope_quality_decision(
711 &candidate_costs,
712 best_costs,
713 entry_costs,
714 self.quality_policy,
715 ) {
716 Ok(()) => true,
717 Err(violation) => {
718 resynthesis_session.record_quality_rejection(violation);
719 false
720 }
721 };
722 Ok(NativeStageOutcome::Candidate(NativeStageCandidate {
723 circuit: candidate,
724 costs: candidate_costs,
725 context: candidate_context,
726 improves_best,
727 }))
728 }
729
730 fn candidate_costs_with_reuse(
733 &self,
734 candidate: &Circuit,
735 context: &mut DeviceTwoQubitSynthesisContext,
736 ) -> Result<Vec<NativeQualityVector>, CompilerError> {
737 match scope_costs_with_context(candidate, context) {
738 Ok(costs) => Ok(costs),
739 Err(ScopeCostError::Context(DeviceContextCostFailure::Unprepared(_))) => {
740 let rebuilt = DeviceTwoQubitSynthesisContext::build_with_session(
741 self.device(),
742 candidate,
743 DeviceSynthesisPlacement::ExactPhysical,
744 Arc::clone(&self.planning_session),
745 )?;
746 let costs = match scope_costs_with_context(candidate, &rebuilt) {
747 Ok(costs) => costs,
748 Err(ScopeCostError::Context(DeviceContextCostFailure::Unprepared(state))) => {
749 return Err(CompilerError::InvariantViolation(format!(
750 "rebuilt native optimizer context was not prepared for {state:?}"
751 )));
752 }
753 Err(error) => return Err(scope_cost_error(error)),
754 };
755 *context = rebuilt;
756 Ok(costs)
757 }
758 Err(error) => Err(scope_cost_error(error)),
759 }
760 }
761}
762
763fn install_best_checkpoint(
764 candidate: &NativeStageCandidate,
765 best: &mut Arc<Circuit>,
766 best_costs: &mut Vec<NativeQualityVector>,
767 best_context: &mut DeviceTwoQubitSynthesisContext,
768) -> bool {
769 if !candidate.improves_best {
770 return false;
771 }
772 *best = candidate.circuit.clone();
773 best_costs.clone_from(&candidate.costs);
774 *best_context = candidate.context.clone();
775 true
776}
777
778fn validate_native_optimization_budgets(
779 max_rounds: u8,
780 max_stale_rounds: u8,
781) -> Result<(), CompilerError> {
782 if max_rounds == 0 {
783 return Err(CompilerError::InvalidInput(
784 "native optimizer max_rounds must be greater than zero".to_string(),
785 ));
786 }
787 if max_stale_rounds == 0 {
788 return Err(CompilerError::InvalidInput(
789 "native optimizer max_stale_rounds must be greater than zero".to_string(),
790 ));
791 }
792 Ok(())
793}
794
795#[derive(Debug)]
796enum ScopeCostError {
797 Compiler(CompilerError),
798 Context(DeviceContextCostFailure),
799}
800
801impl From<CompilerError> for ScopeCostError {
802 fn from(error: CompilerError) -> Self {
803 Self::Compiler(error)
804 }
805}
806
807fn scope_cost_error(error: ScopeCostError) -> CompilerError {
808 match error {
809 ScopeCostError::Compiler(error) => error,
810 ScopeCostError::Context(_) => CompilerError::InvariantViolation(
811 "native optimizer could not cost a legalized control-flow scope".to_string(),
812 ),
813 }
814}
815
816fn scope_costs_with_context(
817 circuit: &Circuit,
818 context: &DeviceTwoQubitSynthesisContext,
819) -> Result<Vec<NativeQualityVector>, ScopeCostError> {
820 let mut costs = Vec::new();
821 collect_scope_costs(circuit.operations(), context, &mut costs)?;
822 Ok(costs)
823}
824
825fn collect_scope_costs(
826 operations: &[Operation],
827 context: &DeviceTwoQubitSynthesisContext,
828 output: &mut Vec<NativeQualityVector>,
829) -> Result<(), ScopeCostError> {
830 let mut accumulator = context
831 .exact_sequence_cost_accumulator()
832 .map_err(ScopeCostError::Context)?;
833 for operation in operations {
834 match &operation.instruction {
835 Instruction::Standard(_) | Instruction::McGate(_) => accumulator
836 .add_gate(&operation.instruction, &operation.qubits)
837 .map_err(ScopeCostError::Context)?,
838 Instruction::ClassicalControl(control) => {
839 for body in control.bodies() {
840 collect_scope_costs(body.operations(), context, output)?;
841 }
842 }
843 Instruction::UnitaryGate(_)
844 | Instruction::CircuitGate(_)
845 | Instruction::ClassicalData(_)
846 | Instruction::Directive(_)
847 | Instruction::Delay => {}
848 }
849 }
850 output.push(NativeQualityVector::for_operations(
851 accumulator.finish(),
852 operations,
853 ));
854 Ok(())
855}
856
857fn scope_quality_decision(
858 candidate: &[NativeQualityVector],
859 current: &[NativeQualityVector],
860 entry: &[NativeQualityVector],
861 policy: NativeQualityPolicy,
862) -> Result<(), NativeQualityViolation> {
863 if candidate.len() != current.len() || candidate.len() != entry.len() {
864 return Err(NativeQualityViolation::ScopeShape);
865 }
866 let mut improved = false;
867 for ((candidate, current), entry) in candidate.iter().zip(current).zip(entry) {
868 if let Some(violation) = candidate.admissibility_violation_against(*entry, policy) {
869 return Err(violation);
870 }
871 match candidate.compare(*current, policy) {
872 std::cmp::Ordering::Less => improved = true,
873 std::cmp::Ordering::Equal => {}
874 std::cmp::Ordering::Greater => return Err(NativeQualityViolation::Rank),
875 }
876 }
877 if improved {
878 Ok(())
879 } else {
880 Err(NativeQualityViolation::Rank)
881 }
882}
883
884fn summarize_scope_costs(costs: &[NativeQualityVector]) -> NativeOptimizationSummary {
885 let mut predicted_log_error = Some(0.0);
886 let mut unavailable_error_count = 0;
887 let mut imputed_error_count = 0;
888 for quality in costs {
889 let cost = quality.physical;
890 match cost.error {
891 MetricAvailability::Available(error) => {
892 if let Some(total) = &mut predicted_log_error {
893 *total += error.log_error;
894 }
895 unavailable_error_count += u64::from(error.unavailable_count);
896 imputed_error_count += u64::from(error.imputed_count);
897 }
898 MetricAvailability::Disabled | MetricAvailability::Inconsistent => {
899 predicted_log_error = None;
900 }
901 }
902 }
903 NativeOptimizationSummary {
904 native_two_qubit_ops: costs
905 .iter()
906 .map(|quality| u64::from(quality.physical.native_two_qubit_ops))
907 .sum(),
908 native_two_qubit_depth: costs
909 .iter()
910 .map(|quality| u64::from(quality.physical.native_two_qubit_depth))
911 .sum(),
912 total_native_depth: costs
913 .iter()
914 .map(|quality| u64::from(quality.physical.total_native_depth))
915 .sum(),
916 native_total_ops: costs
917 .iter()
918 .map(|quality| u64::from(quality.physical.native_total_ops))
919 .sum(),
920 predicted_log_error,
921 unavailable_error_count,
922 imputed_error_count,
923 }
924}
925
926#[derive(Debug, Clone)]
928pub(crate) struct OptimizeNativeLocalGates {
929 device_context: DeviceTwoQubitSynthesisContext,
930 quality_policy: NativeQualityPolicy,
931}
932
933impl OptimizeNativeLocalGates {
934 pub(crate) fn with_quality_policy(
935 device_context: DeviceTwoQubitSynthesisContext,
936 quality_policy: NativeQualityPolicy,
937 ) -> Self {
938 Self {
939 device_context,
940 quality_policy,
941 }
942 }
943}
944
945impl Transformer for OptimizeNativeLocalGates {
946 fn name(&self) -> &'static str {
947 "optimize.native_local_gates"
948 }
949
950 fn transform(
951 &self,
952 circuit: &Circuit,
953 _analysis: Option<&CircuitAnalysis>,
954 ) -> Result<TransformOutcome, CompilerError> {
955 let policy = LocalOptimizationPolicy::Device {
956 context: self.device_context.clone(),
957 quality_policy: self.quality_policy,
958 };
959 LocalOneQPass::run(circuit, &policy)
960 }
961}
962
963#[derive(Debug, Clone)]
965pub(crate) enum LocalOptimizationPolicy {
966 Logical,
967 Basis(Arc<TargetBasisCostModel>),
968 Device {
969 context: DeviceTwoQubitSynthesisContext,
970 quality_policy: NativeQualityPolicy,
971 },
972}
973
974pub(crate) fn optimize_one_qubit_runs_with_policy(
976 circuit: &Circuit,
977 policy: &LocalOptimizationPolicy,
978) -> Result<TransformOutcome, CompilerError> {
979 LocalOneQPass::run(circuit, policy)
980}
981
982pub(crate) fn optimize_one_qubit_runs_with_policy_and_edits(
983 circuit: &Circuit,
984 policy: &LocalOptimizationPolicy,
985) -> Result<(TransformOutcome, RewriteEdits), CompilerError> {
986 LocalOneQPass::run_with_rewrite_edits(circuit, policy)
987}
988
989struct LocalOneQPass<'source, 'policy> {
990 source: &'source Circuit,
991 policy: &'policy LocalOptimizationPolicy,
992 rebuild: CircuitRebuildContext,
993}
994
995struct SequenceRewrite {
996 operations: Vec<ValueOperation>,
997 provenance: Option<Vec<Option<usize>>>,
1001 phase_delta: f64,
1002 changed: bool,
1003}
1004
1005impl<'source, 'policy> LocalOneQPass<'source, 'policy> {
1006 fn run(
1007 source: &'source Circuit,
1008 policy: &'policy LocalOptimizationPolicy,
1009 ) -> Result<TransformOutcome, CompilerError> {
1010 Self::run_internal(source, policy, false).map(|(outcome, _)| outcome)
1011 }
1012
1013 fn run_with_rewrite_edits(
1014 source: &'source Circuit,
1015 policy: &'policy LocalOptimizationPolicy,
1016 ) -> Result<(TransformOutcome, RewriteEdits), CompilerError> {
1017 let (outcome, edits) = Self::run_internal(source, policy, true)?;
1018 Ok((
1019 outcome,
1020 edits.expect("rewrite edits were requested for the one-qubit pass"),
1021 ))
1022 }
1023
1024 fn run_internal(
1025 source: &'source Circuit,
1026 policy: &'policy LocalOptimizationPolicy,
1027 track_rewrite_edits: bool,
1028 ) -> Result<(TransformOutcome, Option<RewriteEdits>), CompilerError> {
1029 let rebuild = CircuitRebuildContext::new(source);
1030 let root_classical = rebuild.root_classical().clone();
1031 let mut pass = Self {
1032 source,
1033 policy,
1034 rebuild,
1035 };
1036 let rewrite =
1037 pass.process_sequence(source.operations(), &root_classical, track_rewrite_edits)?;
1038 if !rewrite.changed {
1039 return Ok((
1040 TransformOutcome::Unchanged,
1041 track_rewrite_edits.then(|| {
1042 RewriteEdits::linear(
1043 source.operations().len(),
1044 source.operations().len(),
1045 Vec::new(),
1046 )
1047 }),
1048 ));
1049 }
1050 debug_assert!(
1051 rewrite
1052 .provenance
1053 .as_ref()
1054 .is_none_or(|provenance| rewrite.operations.len() == provenance.len())
1055 );
1056 let edits = rewrite.provenance.as_ref().map(|provenance| {
1057 RewriteEdits::from_operation_provenance(source.operations().len(), provenance)
1058 });
1059 let mut global_phase = source.global_phase();
1060 if rewrite.phase_delta.abs() > PHASE_EPS {
1061 global_phase = global_phase + Parameter::from(rewrite.phase_delta);
1062 }
1063 let circuit = pass
1064 .rebuild
1065 .finish(source.qubits(), rewrite.operations, global_phase)?;
1066 Ok((TransformOutcome::Changed(circuit), edits))
1067 }
1068
1069 fn process_sequence(
1070 &mut self,
1071 operations: &[Operation],
1072 classical_remap: &ClassicalRemap,
1073 track_provenance: bool,
1074 ) -> Result<SequenceRewrite, CompilerError> {
1075 let mut values = Vec::with_capacity(operations.len());
1076 let mut provenance = track_provenance.then(|| Vec::with_capacity(operations.len()));
1077 let mut nested_changed = false;
1078 for (order, operation) in operations.iter().enumerate() {
1079 if let Instruction::ClassicalControl(control) = &operation.instruction {
1080 let (instruction, changed) = self.rebuild_control_flow(control, classical_remap)?;
1081 values.push(ValueOperation {
1082 qubits: instruction.used_qubits().into_iter().collect(),
1083 instruction: ValueInstruction::ClassicalControl(instruction),
1084 params: CircuitRebuildContext::resolve_source_params(
1085 self.source,
1086 &operation.params,
1087 )?,
1088 label: operation.label.clone(),
1089 });
1090 if let Some(provenance) = &mut provenance {
1091 provenance.push((!changed).then_some(order));
1092 }
1093 nested_changed |= changed;
1094 } else {
1095 values.push(self.rebuild.remap_preserved_operation(
1096 self.source,
1097 operation,
1098 classical_remap,
1099 )?);
1100 if let Some(provenance) = &mut provenance {
1101 provenance.push(Some(order));
1102 }
1103 }
1104 }
1105
1106 let optimized = match self.policy {
1107 LocalOptimizationPolicy::Device { .. } => {
1108 let framed = propagate_frames(values, provenance)?;
1112 let fused = fuse_one_qubit_runs(framed.operations, framed.provenance, self.policy)?;
1113 ValueRewrite {
1114 operations: fused.operations,
1115 provenance: fused.provenance,
1116 phase_delta: framed.phase_delta + fused.phase_delta,
1117 changed: framed.changed || fused.changed,
1118 }
1119 }
1120 LocalOptimizationPolicy::Logical | LocalOptimizationPolicy::Basis(_) => {
1121 optimize_transactional(values, provenance, self.policy)?
1122 }
1123 };
1124 Ok(SequenceRewrite {
1125 operations: optimized.operations,
1126 provenance: optimized.provenance,
1127 phase_delta: optimized.phase_delta,
1128 changed: nested_changed || optimized.changed,
1129 })
1130 }
1131
1132 fn rebuild_body(
1133 &mut self,
1134 operations: &[Operation],
1135 classical_remap: &ClassicalRemap,
1136 ) -> Result<(ValueControlBody, bool), CompilerError> {
1137 let mut rewrite = self.process_sequence(operations, classical_remap, false)?;
1138 if rewrite.phase_delta.abs() > PHASE_EPS {
1139 rewrite.operations.insert(
1140 0,
1141 ValueOperation {
1142 instruction: ValueInstruction::from_instruction(Instruction::Standard(
1143 StandardGate::GPhase,
1144 )),
1145 qubits: SmallVec::new(),
1146 params: smallvec![ParameterValue::Fixed(rewrite.phase_delta)],
1147 label: None,
1148 },
1149 );
1150 rewrite.changed = true;
1151 }
1152 Ok((ValueControlBody::new(rewrite.operations), rewrite.changed))
1153 }
1154
1155 fn rebuild_control_flow(
1156 &mut self,
1157 control: &ClassicalControlOp,
1158 classical_remap: &ClassicalRemap,
1159 ) -> Result<(ValueClassicalControlOp, bool), CompilerError> {
1160 Ok(match control {
1161 ClassicalControlOp::If(op) => {
1162 let (then_body, then_changed) =
1163 self.rebuild_body(op.then_body().operations(), classical_remap)?;
1164 let else_rewrite = op
1165 .else_body()
1166 .map(|body| self.rebuild_body(body.operations(), classical_remap))
1167 .transpose()?;
1168 let else_changed = else_rewrite.as_ref().is_some_and(|(_, changed)| *changed);
1169 (
1170 ValueClassicalControlOp::If {
1171 condition: classical_remap.remap_expr(op.condition())?,
1172 then_body,
1173 else_body: else_rewrite.map(|(body, _)| body),
1174 },
1175 then_changed || else_changed,
1176 )
1177 }
1178 ClassicalControlOp::While(op) => {
1179 let (body, changed) = self.rebuild_body(op.body().operations(), classical_remap)?;
1180 (
1181 ValueClassicalControlOp::While {
1182 condition: classical_remap.remap_expr(op.condition())?,
1183 body,
1184 },
1185 changed,
1186 )
1187 }
1188 ClassicalControlOp::For(op) => {
1189 let (body, changed) = self.rebuild_body(op.body().operations(), classical_remap)?;
1190 (
1191 ValueClassicalControlOp::For {
1192 var: classical_remap.remap_var(op.var())?,
1193 start: classical_remap.remap_expr(op.start())?,
1194 stop: classical_remap.remap_expr(op.stop())?,
1195 step: classical_remap.remap_expr(op.step())?,
1196 body,
1197 },
1198 changed,
1199 )
1200 }
1201 ClassicalControlOp::Switch(op) => {
1202 let mut changed = false;
1203 let cases = op
1204 .cases()
1205 .iter()
1206 .map(|case| {
1207 let (body, body_changed) =
1208 self.rebuild_body(case.body().operations(), classical_remap)?;
1209 changed |= body_changed;
1210 Ok(ValueSwitchCase::new(case.value(), body))
1211 })
1212 .collect::<Result<Vec<_>, CompilerError>>()?;
1213 let default_rewrite = op
1214 .default()
1215 .map(|body| self.rebuild_body(body.operations(), classical_remap))
1216 .transpose()?;
1217 changed |= default_rewrite
1218 .as_ref()
1219 .is_some_and(|(_, body_changed)| *body_changed);
1220 (
1221 ValueClassicalControlOp::Switch {
1222 target: classical_remap.remap_expr(op.target())?,
1223 cases,
1224 default: default_rewrite.map(|(body, _)| body),
1225 },
1226 changed,
1227 )
1228 }
1229 ClassicalControlOp::Break => (ValueClassicalControlOp::Break, false),
1230 ClassicalControlOp::Continue => (ValueClassicalControlOp::Continue, false),
1231 })
1232 }
1233}
1234
1235struct ValueRewrite {
1236 operations: Vec<ValueOperation>,
1237 provenance: Option<Vec<Option<usize>>>,
1238 phase_delta: f64,
1239 changed: bool,
1240}
1241
1242#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
1243struct LogicalOneQCost {
1244 one_qubit_ops: usize,
1245 affected_region_depth: usize,
1246 total_gate_ops: usize,
1247}
1248
1249impl LocalOptimizationPolicy {
1250 fn strictly_better(
1251 &self,
1252 candidate: &[ValueOperation],
1253 source: &[ValueOperation],
1254 ) -> Result<bool, CompilerError> {
1255 Ok(match self {
1256 Self::Logical => logical_one_qubit_cost(candidate) < logical_one_qubit_cost(source),
1257 Self::Basis(model) => {
1258 let Some(before) = basis_cost(model, source)? else {
1259 return Ok(false);
1260 };
1261 let Some(after) = basis_cost(model, candidate)? else {
1262 return Ok(false);
1263 };
1264 after.two_qubit_ops <= before.two_qubit_ops
1265 && compare_basis_cost(after, before).is_lt()
1266 }
1267 Self::Device {
1268 context,
1269 quality_policy,
1270 } => {
1271 let Some(before) = exact_local_sequence_cost(context, source, "source")? else {
1272 return Ok(false);
1273 };
1274 let Some(after) = exact_local_sequence_cost(context, candidate, "candidate")?
1275 else {
1276 return Ok(false);
1277 };
1278 let before = NativeQualityVector::for_value_operations(before, source);
1279 let after = NativeQualityVector::for_value_operations(after, candidate);
1280 after.admissible_against(before, *quality_policy)
1281 && after.compare(before, *quality_policy).is_lt()
1282 }
1283 })
1284 }
1285}
1286
1287fn exact_local_sequence_cost(
1288 context: &DeviceTwoQubitSynthesisContext,
1289 operations: &[ValueOperation],
1290 role: &str,
1291) -> Result<Option<crate::compile::transform::decompose::unitary::DevicePhysicalCost>, CompilerError>
1292{
1293 match context.exact_sequence_cost_diagnostic(operations) {
1294 Ok(cost) => Ok(Some(cost)),
1295 Err(DeviceContextCostFailure::Unsupported(failure)) => {
1296 let _ = failure;
1297 Ok(None)
1298 }
1299 Err(DeviceContextCostFailure::Unprepared(state)) => {
1300 Err(CompilerError::InvariantViolation(format!(
1301 "native local optimization context was not prepared for {role} state {state:?}"
1302 )))
1303 }
1304 Err(DeviceContextCostFailure::WrongPlacement) => Err(CompilerError::InvariantViolation(
1305 "native local optimization requires an exact-physical context".to_string(),
1306 )),
1307 Err(DeviceContextCostFailure::InvalidOperation(reason)) => {
1308 Err(CompilerError::InvariantViolation(format!(
1309 "invalid native local optimization {role}: {reason}"
1310 )))
1311 }
1312 }
1313}
1314
1315fn optimize_transactional(
1316 operations: Vec<ValueOperation>,
1317 provenance: Option<Vec<Option<usize>>>,
1318 policy: &LocalOptimizationPolicy,
1319) -> Result<ValueRewrite, CompilerError> {
1320 debug_assert!(
1321 provenance
1322 .as_ref()
1323 .is_none_or(|provenance| operations.len() == provenance.len())
1324 );
1325 let framed = propagate_frames(operations.clone(), provenance.clone())?;
1326 let fused_after_frames = fuse_one_qubit_runs(framed.operations, framed.provenance, policy)?;
1327 let combined_phase = framed.phase_delta + fused_after_frames.phase_delta;
1328 let combined_changed = framed.changed || fused_after_frames.changed;
1329 if combined_changed && policy.strictly_better(&fused_after_frames.operations, &operations)? {
1330 return Ok(ValueRewrite {
1331 operations: fused_after_frames.operations,
1332 provenance: fused_after_frames.provenance,
1333 phase_delta: combined_phase,
1334 changed: true,
1335 });
1336 }
1337
1338 fuse_one_qubit_runs(operations, provenance, policy)
1341}
1342
1343fn logical_one_qubit_cost(operations: &[ValueOperation]) -> LogicalOneQCost {
1344 let mut depths = HashMap::<Qubit, usize>::new();
1345 let mut cost = LogicalOneQCost::default();
1346 for operation in operations {
1347 let ValueInstruction::Instruction(Instruction::Standard(gate)) = operation.instruction
1348 else {
1349 continue;
1350 };
1351 if gate == StandardGate::GPhase {
1352 continue;
1353 }
1354 cost.total_gate_ops += 1;
1355 if gate.num_qubits() == 1 && operation.qubits.len() == 1 {
1356 cost.one_qubit_ops += 1;
1357 }
1358 if operation.qubits.is_empty() {
1359 continue;
1360 }
1361 let next = operation
1362 .qubits
1363 .iter()
1364 .filter_map(|qubit| depths.get(qubit))
1365 .max()
1366 .copied()
1367 .unwrap_or(0)
1368 + 1;
1369 for &qubit in &operation.qubits {
1370 depths.insert(qubit, next);
1371 }
1372 cost.affected_region_depth = cost.affected_region_depth.max(next);
1373 }
1374 cost
1375}
1376
1377fn basis_cost(
1378 model: &TargetBasisCostModel,
1379 operations: &[ValueOperation],
1380) -> Result<Option<TargetBasisCost>, CompilerError> {
1381 let operations = operations
1382 .iter()
1383 .filter(|operation| {
1384 matches!(
1385 operation.instruction,
1386 ValueInstruction::Instruction(Instruction::Standard(_))
1387 ) && operation.params.iter().all(
1388 |parameter| matches!(parameter, ParameterValue::Fixed(value) if value.is_finite()),
1389 )
1390 })
1391 .cloned()
1392 .collect::<Vec<_>>();
1393 if operations.is_empty() {
1394 return Ok(Some(TargetBasisCost::default()));
1395 }
1396 let mut qubits = operations
1397 .iter()
1398 .flat_map(|operation| operation.qubits.iter().copied())
1399 .collect::<Vec<_>>();
1400 qubits.sort_by_key(|qubit| qubit.index());
1401 qubits.dedup();
1402 match model.cost_of_fixed_operations(qubits, operations) {
1403 Ok(cost) => Ok(Some(cost)),
1404 Err(CompilerError::InvalidInput(_)) | Err(CompilerError::TransformFailed { .. }) => {
1405 Ok(None)
1406 }
1407 Err(error) => Err(error),
1408 }
1409}
1410
1411fn compare_basis_cost(left: TargetBasisCost, right: TargetBasisCost) -> std::cmp::Ordering {
1412 left.two_qubit_ops
1413 .cmp(&right.two_qubit_ops)
1414 .then_with(|| left.depth.cmp(&right.depth))
1415 .then_with(|| left.total_ops.cmp(&right.total_ops))
1416 .then_with(|| left.parameterized_ops.cmp(&right.parameterized_ops))
1417}
1418
1419fn fuse_one_qubit_runs(
1422 operations: Vec<ValueOperation>,
1423 provenance: Option<Vec<Option<usize>>>,
1424 policy: &LocalOptimizationPolicy,
1425) -> Result<ValueRewrite, CompilerError> {
1426 debug_assert!(
1427 provenance
1428 .as_ref()
1429 .is_none_or(|provenance| operations.len() == provenance.len())
1430 );
1431 let runs = collect_one_qubit_runs(&operations);
1432 let mut replacements = HashMap::<usize, (Vec<usize>, Vec<ValueOperation>)>::new();
1433 let mut phase_delta = 0.0;
1434
1435 for run in runs.into_iter().filter(|run| run.len() >= 2) {
1436 let source_ops = run
1437 .iter()
1438 .map(|order| operations[*order].clone())
1439 .collect::<Vec<_>>();
1440 let Some(matrix) = one_qubit_run_matrix(&source_ops) else {
1441 continue;
1442 };
1443 let Ok(decomposition) = synthesize_numeric_1q_unitary(&matrix) else {
1444 continue;
1445 };
1446 let qubit = source_ops[0].qubits[0];
1447 let mut candidate = Vec::new();
1448 if decomposition.theta.abs() > PHASE_EPS
1449 || decomposition.phi.abs() > PHASE_EPS
1450 || decomposition.lambda.abs() > PHASE_EPS
1451 {
1452 candidate.push(u_operation(qubit, decomposition));
1453 }
1454 if !policy.strictly_better(&candidate, &source_ops)? {
1455 continue;
1456 }
1457 phase_delta += decomposition.global_phase;
1458 replacements.insert(run[0], (run, candidate));
1459 }
1460
1461 if replacements.is_empty() {
1462 return Ok(ValueRewrite {
1463 operations,
1464 provenance,
1465 phase_delta: 0.0,
1466 changed: false,
1467 });
1468 }
1469
1470 let mut skipped = HashSet::new();
1471 for (first, (orders, _)) in &replacements {
1472 skipped.extend(orders.iter().copied().filter(|order| order != first));
1473 }
1474 let mut output = Vec::with_capacity(operations.len());
1475 let mut output_provenance = provenance
1476 .as_ref()
1477 .map(|provenance| Vec::with_capacity(provenance.len()));
1478 let mut source_orders = provenance.unwrap_or_default().into_iter();
1479 for (order, operation) in operations.into_iter().enumerate() {
1480 let source_order = source_orders.next().flatten();
1481 if let Some((_, replacement)) = replacements.remove(&order) {
1482 if let Some(output_provenance) = &mut output_provenance {
1483 output_provenance.extend(std::iter::repeat_n(None, replacement.len()));
1484 }
1485 output.extend(replacement);
1486 } else if !skipped.contains(&order) {
1487 output.push(operation);
1488 if let Some(output_provenance) = &mut output_provenance {
1489 output_provenance.push(source_order);
1490 }
1491 }
1492 }
1493 Ok(ValueRewrite {
1494 operations: output,
1495 provenance: output_provenance,
1496 phase_delta,
1497 changed: true,
1498 })
1499}
1500
1501fn collect_one_qubit_runs(operations: &[ValueOperation]) -> Vec<Vec<usize>> {
1502 let mut active = BTreeMap::<Qubit, Vec<usize>>::new();
1503 let mut runs = Vec::new();
1504 for (order, operation) in operations.iter().enumerate() {
1505 if is_fixed_numeric_one_qubit_gate(operation) {
1506 active.entry(operation.qubits[0]).or_default().push(order);
1507 continue;
1508 }
1509
1510 let global_boundary = operation.qubits.is_empty()
1511 || matches!(operation.instruction, ValueInstruction::ClassicalControl(_))
1512 || matches!(
1513 operation.instruction,
1514 ValueInstruction::Instruction(Instruction::Directive(Directive::Barrier))
1515 );
1516 if global_boundary {
1517 runs.extend(std::mem::take(&mut active).into_values());
1518 } else {
1519 for qubit in &operation.qubits {
1520 if let Some(run) = active.remove(qubit) {
1521 runs.push(run);
1522 }
1523 }
1524 }
1525 }
1526 runs.extend(active.into_values());
1527 runs
1528}
1529
1530fn is_fixed_numeric_one_qubit_gate(operation: &ValueOperation) -> bool {
1531 matches!(
1532 &operation.instruction,
1533 ValueInstruction::Instruction(Instruction::Standard(gate))
1534 if gate.num_qubits() == 1
1535 && operation.qubits.len() == 1
1536 && operation.label.is_none()
1537 && operation.params.iter().all(|param| {
1538 matches!(param, ParameterValue::Fixed(value) if value.is_finite())
1539 })
1540 && gate.matrix(&fixed_params(operation).unwrap_or_default()).is_ok()
1541 )
1542}
1543
1544fn fixed_params(operation: &ValueOperation) -> Option<Vec<f64>> {
1545 operation
1546 .params
1547 .iter()
1548 .map(|param| match param {
1549 ParameterValue::Fixed(value) if value.is_finite() => Some(*value),
1550 ParameterValue::Fixed(_) | ParameterValue::Param(_) => None,
1551 })
1552 .collect()
1553}
1554
1555fn u_operation(qubit: Qubit, decomposition: OneQubitUnitaryDecomposition) -> ValueOperation {
1556 ValueOperation {
1557 instruction: ValueInstruction::from_instruction(Instruction::Standard(StandardGate::U)),
1558 qubits: smallvec![qubit],
1559 params: smallvec![
1560 ParameterValue::Fixed(decomposition.theta),
1561 ParameterValue::Fixed(decomposition.phi),
1562 ParameterValue::Fixed(decomposition.lambda),
1563 ],
1564 label: None,
1565 }
1566}
1567
1568#[derive(Debug, Clone, Copy, Default)]
1569struct QubitFrame {
1570 z_angle: f64,
1571 pauli_x: bool,
1572 pauli_z: bool,
1573}
1574
1575impl QubitFrame {
1576 fn is_empty(self) -> bool {
1577 self.z_angle.abs() <= PHASE_EPS && !self.pauli_x && !self.pauli_z
1578 }
1579
1580 fn multiply_pauli(&mut self, x: bool, z: bool) -> u8 {
1581 let phase = if z && self.pauli_x { 2 } else { 0 };
1585 self.pauli_x ^= x;
1586 self.pauli_z ^= z;
1587 phase
1588 }
1589}
1590
1591fn propagate_frames(
1598 operations: Vec<ValueOperation>,
1599 provenance: Option<Vec<Option<usize>>>,
1600) -> Result<ValueRewrite, CompilerError> {
1601 debug_assert!(
1602 provenance
1603 .as_ref()
1604 .is_none_or(|provenance| operations.len() == provenance.len())
1605 );
1606 let mut frames = BTreeMap::<Qubit, QubitFrame>::new();
1607 let mut output = Vec::with_capacity(operations.len());
1608 let mut output_provenance = provenance
1609 .as_ref()
1610 .map(|provenance| Vec::with_capacity(provenance.len()));
1611 let mut source_orders = provenance.unwrap_or_default().into_iter();
1612 let mut phase_delta = 0.0;
1613 let mut changed = false;
1614
1615 for mut operation in operations {
1616 let source_order = source_orders.next().flatten();
1617 if operation.label.is_none()
1618 && let Some((qubit, z_angle, phase)) = z_carrier(&operation)
1619 {
1620 flush_pauli(
1621 qubit,
1622 &mut frames,
1623 &mut output,
1624 &mut output_provenance,
1625 &mut phase_delta,
1626 );
1627 frames.entry(qubit).or_default().z_angle += z_angle;
1628 phase_delta += phase;
1629 changed = true;
1630 continue;
1631 }
1632 if operation.label.is_none()
1633 && let Some((qubit, x, z, phase)) = pauli_carrier(&operation)
1634 {
1635 flush_z(qubit, &mut frames, &mut output, &mut output_provenance);
1636 let frame = frames.entry(qubit).or_default();
1637 let extra_phase = frame.multiply_pauli(x, z);
1638 phase_delta += phase + f64::from(extra_phase) * FRAC_PI_2;
1639 changed = true;
1640 continue;
1641 }
1642
1643 let instruction = match &operation.instruction {
1644 ValueInstruction::Instruction(instruction) => Some(instruction.clone()),
1645 ValueInstruction::ClassicalControl(_) => None,
1646 };
1647
1648 if operation.label.is_none()
1649 && matches!(
1650 instruction.as_ref(),
1651 Some(Instruction::Standard(StandardGate::SWAP))
1652 )
1653 && operation.qubits.len() == 2
1654 {
1655 let left = frames.remove(&operation.qubits[0]).unwrap_or_default();
1656 let right = frames.remove(&operation.qubits[1]).unwrap_or_default();
1657 frames.insert(operation.qubits[0], right);
1658 frames.insert(operation.qubits[1], left);
1659 output.push(operation);
1660 if let Some(output_provenance) = &mut output_provenance {
1661 output_provenance.push(source_order);
1662 }
1663 changed |= !left.is_empty() || !right.is_empty();
1664 continue;
1665 }
1666
1667 if operation.label.is_none()
1668 && let Some(gate @ (StandardGate::CX | StandardGate::CZ)) =
1669 instruction.as_ref().and_then(|i| {
1670 let Instruction::Standard(gate @ (StandardGate::CX | StandardGate::CZ)) = i
1671 else {
1672 return None;
1673 };
1674 Some(*gate)
1675 })
1676 && operation.qubits.len() == 2
1677 {
1678 let pair = [operation.qubits[0], operation.qubits[1]];
1679 if gate == StandardGate::CX {
1680 flush_z(pair[1], &mut frames, &mut output, &mut output_provenance);
1681 }
1682 if pair.iter().any(|qubit| {
1683 frames
1684 .get(qubit)
1685 .is_some_and(|frame| frame.pauli_x || frame.pauli_z)
1686 }) {
1687 for qubit in pair {
1688 flush_z(qubit, &mut frames, &mut output, &mut output_provenance);
1689 }
1690 phase_delta += propagate_clifford_paulis(gate, pair, &mut frames);
1691 changed = true;
1692 }
1693 output.push(operation);
1694 if let Some(output_provenance) = &mut output_provenance {
1695 output_provenance.push(source_order);
1696 }
1697 continue;
1698 }
1699
1700 if operation.label.is_none()
1701 && let Some(Instruction::Standard(gate)) = instruction.as_ref()
1702 && is_z_diagonal(*gate)
1703 {
1704 let blocked = operation
1705 .qubits
1706 .iter()
1707 .any(|qubit| frames.get(qubit).is_some_and(|frame| frame.pauli_x));
1708 if !blocked {
1709 output.push(operation);
1710 if let Some(output_provenance) = &mut output_provenance {
1711 output_provenance.push(source_order);
1712 }
1713 continue;
1714 }
1715 }
1716
1717 if operation.label.is_none() && absorb_z_into_xy_axis(&mut operation, &frames) {
1718 output.push(operation);
1719 if let Some(output_provenance) = &mut output_provenance {
1720 output_provenance.push(None);
1721 }
1722 changed = true;
1723 continue;
1724 }
1725
1726 if instruction
1727 .as_ref()
1728 .is_some_and(|instruction| instruction.has_measurement())
1729 {
1730 for qubit in &operation.qubits {
1731 if frames.get(qubit).is_some_and(|frame| frame.pauli_x) {
1732 flush_frame(
1733 *qubit,
1734 &mut frames,
1735 &mut output,
1736 &mut output_provenance,
1737 &mut phase_delta,
1738 );
1739 } else {
1740 changed |= frames.remove(qubit).is_some_and(|frame| !frame.is_empty());
1741 }
1742 }
1743 output.push(operation);
1744 if let Some(output_provenance) = &mut output_provenance {
1745 output_provenance.push(source_order);
1746 }
1747 continue;
1748 }
1749
1750 let global_boundary = operation.qubits.is_empty()
1751 || operation.label.is_some()
1752 || matches!(operation.instruction, ValueInstruction::ClassicalControl(_))
1753 || matches!(
1754 instruction.as_ref(),
1755 Some(Instruction::Directive(
1756 Directive::Barrier | Directive::Reset
1757 ))
1758 );
1759 if global_boundary {
1760 flush_all(
1761 &mut frames,
1762 &mut output,
1763 &mut output_provenance,
1764 &mut phase_delta,
1765 );
1766 } else {
1767 for qubit in &operation.qubits {
1768 flush_frame(
1769 *qubit,
1770 &mut frames,
1771 &mut output,
1772 &mut output_provenance,
1773 &mut phase_delta,
1774 );
1775 }
1776 }
1777 output.push(operation);
1778 if let Some(output_provenance) = &mut output_provenance {
1779 output_provenance.push(source_order);
1780 }
1781 }
1782 flush_all(
1783 &mut frames,
1784 &mut output,
1785 &mut output_provenance,
1786 &mut phase_delta,
1787 );
1788
1789 Ok(ValueRewrite {
1790 operations: output,
1791 provenance: output_provenance,
1792 phase_delta,
1793 changed,
1794 })
1795}
1796
1797fn z_carrier(operation: &ValueOperation) -> Option<(Qubit, f64, f64)> {
1798 let ValueInstruction::Instruction(Instruction::Standard(gate)) = &operation.instruction else {
1799 return None;
1800 };
1801 let qubit = *operation.qubits.first()?;
1802 Some(match gate {
1803 StandardGate::RZ => (qubit, *fixed_params(operation)?.first()?, 0.0),
1804 StandardGate::Phase => {
1805 let angle = *fixed_params(operation)?.first()?;
1806 (qubit, angle, angle / 2.0)
1807 }
1808 StandardGate::S => (qubit, FRAC_PI_2, FRAC_PI_4),
1809 StandardGate::SDG => (qubit, -FRAC_PI_2, -FRAC_PI_4),
1810 StandardGate::T => (qubit, FRAC_PI_4, FRAC_PI_4 / 2.0),
1811 StandardGate::TDG => (qubit, -FRAC_PI_4, -FRAC_PI_4 / 2.0),
1812 _ => return None,
1813 })
1814}
1815
1816fn pauli_carrier(operation: &ValueOperation) -> Option<(Qubit, bool, bool, f64)> {
1817 let ValueInstruction::Instruction(Instruction::Standard(gate)) = &operation.instruction else {
1818 return None;
1819 };
1820 let qubit = *operation.qubits.first()?;
1821 Some(match gate {
1822 StandardGate::X => (qubit, true, false, 0.0),
1823 StandardGate::Y => (qubit, true, true, FRAC_PI_2),
1824 StandardGate::Z => (qubit, false, true, 0.0),
1825 _ => return None,
1826 })
1827}
1828
1829fn absorb_z_into_xy_axis(
1830 operation: &mut ValueOperation,
1831 frames: &BTreeMap<Qubit, QubitFrame>,
1832) -> bool {
1833 let Some(&qubit) = operation.qubits.first() else {
1834 return false;
1835 };
1836 if operation.qubits.len() != 1 {
1837 return false;
1838 }
1839 let Some(frame) = frames.get(&qubit) else {
1840 return false;
1841 };
1842 if frame.z_angle.abs() <= PHASE_EPS || frame.pauli_x || frame.pauli_z {
1843 return false;
1844 }
1845 let ValueInstruction::Instruction(Instruction::Standard(gate)) = &operation.instruction else {
1846 return false;
1847 };
1848 let axis_index = match gate {
1849 StandardGate::RXY => 1,
1850 StandardGate::XY | StandardGate::XY2P | StandardGate::XY2M => 0,
1851 _ => return false,
1852 };
1853 let Some(ParameterValue::Fixed(axis)) = operation.params.get_mut(axis_index) else {
1854 return false;
1855 };
1856 *axis -= frame.z_angle;
1858 true
1859}
1860
1861fn is_z_diagonal(gate: StandardGate) -> bool {
1862 matches!(
1863 gate,
1864 StandardGate::CZ | StandardGate::CRZ | StandardGate::RZZ
1865 )
1866}
1867
1868#[derive(Clone, Copy, Default)]
1869struct TwoQubitPauli {
1870 phase: u8,
1871 x: [bool; 2],
1872 z: [bool; 2],
1873}
1874
1875impl TwoQubitPauli {
1876 fn multiply(self, right: Self) -> Self {
1877 let anti = self
1878 .z
1879 .iter()
1880 .zip(right.x)
1881 .filter(|(z, x)| **z && *x)
1882 .count() as u8;
1883 Self {
1884 phase: (self.phase + right.phase + 2 * anti) % 4,
1885 x: [self.x[0] ^ right.x[0], self.x[1] ^ right.x[1]],
1886 z: [self.z[0] ^ right.z[0], self.z[1] ^ right.z[1]],
1887 }
1888 }
1889}
1890
1891fn propagate_clifford_paulis(
1894 gate: StandardGate,
1895 qubits: [Qubit; 2],
1896 frames: &mut BTreeMap<Qubit, QubitFrame>,
1897) -> f64 {
1898 let input = TwoQubitPauli {
1899 phase: 0,
1900 x: qubits.map(|qubit| frames.get(&qubit).is_some_and(|frame| frame.pauli_x)),
1901 z: qubits.map(|qubit| frames.get(&qubit).is_some_and(|frame| frame.pauli_z)),
1902 };
1903 let generators = match gate {
1904 StandardGate::CX => [
1905 TwoQubitPauli {
1906 x: [true, true],
1907 ..Default::default()
1908 },
1909 TwoQubitPauli {
1910 x: [false, true],
1911 ..Default::default()
1912 },
1913 TwoQubitPauli {
1914 z: [true, false],
1915 ..Default::default()
1916 },
1917 TwoQubitPauli {
1918 z: [true, true],
1919 ..Default::default()
1920 },
1921 ],
1922 StandardGate::CZ => [
1923 TwoQubitPauli {
1924 x: [true, false],
1925 z: [false, true],
1926 ..Default::default()
1927 },
1928 TwoQubitPauli {
1929 x: [false, true],
1930 z: [true, false],
1931 ..Default::default()
1932 },
1933 TwoQubitPauli {
1934 z: [true, false],
1935 ..Default::default()
1936 },
1937 TwoQubitPauli {
1938 z: [false, true],
1939 ..Default::default()
1940 },
1941 ],
1942 _ => unreachable!("only Clifford propagation gates are passed here"),
1943 };
1944 let enabled = [input.x[0], input.x[1], input.z[0], input.z[1]];
1945 let result = generators
1946 .into_iter()
1947 .zip(enabled)
1948 .filter(|(_, enabled)| *enabled)
1949 .fold(TwoQubitPauli::default(), |acc, (generator, _)| {
1950 acc.multiply(generator)
1951 });
1952 for (index, qubit) in qubits.into_iter().enumerate() {
1953 let frame = frames.entry(qubit).or_default();
1954 frame.pauli_x = result.x[index];
1955 frame.pauli_z = result.z[index];
1956 }
1957 f64::from(result.phase) * FRAC_PI_2
1958}
1959
1960fn flush_all(
1961 frames: &mut BTreeMap<Qubit, QubitFrame>,
1962 output: &mut Vec<ValueOperation>,
1963 provenance: &mut Option<Vec<Option<usize>>>,
1964 phase_delta: &mut f64,
1965) {
1966 let qubits = frames.keys().copied().collect::<Vec<_>>();
1967 for qubit in qubits {
1968 flush_frame(qubit, frames, output, provenance, phase_delta);
1969 }
1970}
1971
1972fn flush_frame(
1973 qubit: Qubit,
1974 frames: &mut BTreeMap<Qubit, QubitFrame>,
1975 output: &mut Vec<ValueOperation>,
1976 provenance: &mut Option<Vec<Option<usize>>>,
1977 phase_delta: &mut f64,
1978) {
1979 flush_pauli(qubit, frames, output, provenance, phase_delta);
1980 flush_z(qubit, frames, output, provenance);
1981 if frames.get(&qubit).is_some_and(|frame| frame.is_empty()) {
1982 frames.remove(&qubit);
1983 }
1984}
1985
1986fn flush_pauli(
1987 qubit: Qubit,
1988 frames: &mut BTreeMap<Qubit, QubitFrame>,
1989 output: &mut Vec<ValueOperation>,
1990 provenance: &mut Option<Vec<Option<usize>>>,
1991 phase_delta: &mut f64,
1992) {
1993 let Some(frame) = frames.get_mut(&qubit) else {
1994 return;
1995 };
1996 let gate = match (frame.pauli_x, frame.pauli_z) {
1997 (false, false) => None,
1998 (true, false) => Some(StandardGate::X),
1999 (false, true) => Some(StandardGate::Z),
2000 (true, true) => {
2001 *phase_delta -= FRAC_PI_2;
2002 Some(StandardGate::Y)
2003 }
2004 };
2005 if let Some(gate) = gate {
2006 output.push(ValueOperation {
2007 instruction: ValueInstruction::from_instruction(Instruction::Standard(gate)),
2008 qubits: smallvec![qubit],
2009 params: SmallVec::new(),
2010 label: None,
2011 });
2012 if let Some(provenance) = provenance {
2013 provenance.push(None);
2014 }
2015 }
2016 frame.pauli_x = false;
2017 frame.pauli_z = false;
2018}
2019
2020fn flush_z(
2021 qubit: Qubit,
2022 frames: &mut BTreeMap<Qubit, QubitFrame>,
2023 output: &mut Vec<ValueOperation>,
2024 provenance: &mut Option<Vec<Option<usize>>>,
2025) {
2026 let Some(frame) = frames.get_mut(&qubit) else {
2027 return;
2028 };
2029 if frame.z_angle.abs() > PHASE_EPS {
2030 output.push(ValueOperation {
2031 instruction: ValueInstruction::from_instruction(Instruction::Standard(
2032 StandardGate::RZ,
2033 )),
2034 qubits: smallvec![qubit],
2035 params: smallvec![ParameterValue::Fixed(frame.z_angle)],
2036 label: None,
2037 });
2038 if let Some(provenance) = provenance {
2039 provenance.push(None);
2040 }
2041 }
2042 frame.z_angle = 0.0;
2043}
2044
2045#[cfg(test)]
2046#[path = "native_optimization_test.rs"]
2047mod native_optimization_test;