1use crate::circuit::{Circuit, Instruction, Operation, StandardGate, ValueOperation};
22use crate::compile::CompilerError;
23use crate::compile::transform::analysis::WorkflowCircuitAnalysis;
24use crate::compile::transform::rebuild::CircuitRebuildContext;
25use crate::compile::transform::transformer::{PassApplicability, WorkflowPass};
26use crate::compile::transform::{
27 CircuitAnalysis, KnowledgeRewriter, RewriteConfig, TransformOutcome, Transformer,
28};
29
30#[derive(Debug, Clone, Default)]
37pub struct LowerToRoutingBasis {
38 preferred_basis: Option<Vec<Instruction>>,
39}
40
41impl LowerToRoutingBasis {
42 pub fn new(preferred_basis: Option<Vec<Instruction>>) -> Self {
48 Self { preferred_basis }
49 }
50
51 fn routing_pre_basis(&self, circuit: &Circuit) -> Result<Vec<Instruction>, CompilerError> {
52 let mut basis = Vec::new();
53
54 for gate in StandardGate::all()
55 .iter()
56 .copied()
57 .filter(|gate| gate.num_qubits() <= 1)
58 {
59 if !basis.contains(&gate) {
60 basis.push(gate);
61 }
62 }
63
64 collect_routable_two_qubit_gates(circuit.operations(), &mut basis);
65
66 let ccx_gate = self.preferred_ccx_two_qubit_gate();
67 if !basis.contains(&ccx_gate) {
68 basis.push(ccx_gate);
69 }
70
71 Ok(basis.into_iter().map(Instruction::Standard).collect())
72 }
73
74 fn preferred_ccx_two_qubit_gate(&self) -> StandardGate {
75 if let Some(basis) = self.preferred_basis.as_deref() {
76 let has_cz = basis
77 .iter()
78 .any(|instruction| matches!(instruction, Instruction::Standard(StandardGate::CZ)));
79 let has_cx = basis
80 .iter()
81 .any(|instruction| matches!(instruction, Instruction::Standard(StandardGate::CX)));
82 if has_cz && !has_cx {
83 return StandardGate::CZ;
84 }
85 }
86
87 StandardGate::CX
88 }
89}
90
91impl WorkflowPass for LowerToRoutingBasis {
92 fn applicability(&self, analysis: &WorkflowCircuitAnalysis) -> PassApplicability {
93 if analysis.has_gate_like_operation_over_two_qubits() {
94 PassApplicability::Run
95 } else {
96 PassApplicability::ProvenNoOp(
97 "circuit contains no gate-like operation over more than two qubits",
98 )
99 }
100 }
101}
102
103impl Transformer for LowerToRoutingBasis {
104 fn name(&self) -> &'static str {
105 "lower_to_routing_basis"
106 }
107
108 fn transform(
109 &self,
110 circuit: &Circuit,
111 analysis: Option<&CircuitAnalysis>,
112 ) -> Result<TransformOutcome, CompilerError> {
113 if !has_unroutable_gate_like_operation(circuit.operations()) {
114 return Ok(TransformOutcome::Unchanged);
115 }
116
117 if can_directly_lower_top_level_ccx(circuit.operations()) {
118 let circuit =
119 directly_lower_top_level_ccx(circuit, self.preferred_ccx_two_qubit_gate())?;
120 validate_routing_basis_contract(circuit.operations())?;
121 return Ok(TransformOutcome::Changed(circuit));
122 }
123
124 let config =
125 RewriteConfig::lowering().with_target_instructions(self.routing_pre_basis(circuit)?)?;
126 let result = match KnowledgeRewriter::new(config).transform(circuit, analysis) {
127 Ok(result) => result,
128 Err(err) => {
129 validate_routing_basis_contract(circuit.operations())?;
130 return Err(err);
131 }
132 };
133
134 match result {
135 TransformOutcome::Unchanged => {
136 validate_routing_basis_contract(circuit.operations())?;
137 Ok(TransformOutcome::Unchanged)
138 }
139 TransformOutcome::Changed(lowered) => {
140 validate_routing_basis_contract(lowered.operations())?;
141 Ok(TransformOutcome::Changed(lowered))
142 }
143 }
144 }
145}
146
147fn can_directly_lower_top_level_ccx(operations: &[Operation]) -> bool {
148 let mut found = false;
149 for operation in operations {
150 match &operation.instruction {
151 Instruction::Standard(StandardGate::CCX)
152 if operation.qubits.len() == 3
153 && operation.params.is_empty()
154 && operation.label.is_none() =>
155 {
156 found = true;
157 }
158 Instruction::Standard(_)
159 | Instruction::McGate(_)
160 | Instruction::UnitaryGate(_)
161 | Instruction::CircuitGate(_)
162 if operation.qubits.len() > 2 =>
163 {
164 return false;
165 }
166 Instruction::ClassicalControl(op)
167 if op
168 .bodies()
169 .any(|body| has_unroutable_gate_like_operation(body.operations())) =>
170 {
171 return false;
172 }
173 _ => {}
174 }
175 }
176 found
177}
178
179fn directly_lower_top_level_ccx(
180 source: &Circuit,
181 two_qubit_gate: StandardGate,
182) -> Result<Circuit, CompilerError> {
183 let rebuild = CircuitRebuildContext::new(source);
184 let mut operations = Vec::with_capacity(source.operations().len());
185
186 for operation in source.operations() {
187 if matches!(
188 operation.instruction,
189 Instruction::Standard(StandardGate::CCX)
190 ) {
191 let [control_0, control_1, target] = operation.qubits.as_slice() else {
192 return Err(CompilerError::InvariantViolation(
193 "CCX operation must have exactly three qubits".to_string(),
194 ));
195 };
196 append_direct_ccx_template(
197 &mut operations,
198 *control_0,
199 *control_1,
200 *target,
201 two_qubit_gate,
202 );
203 } else {
204 operations.push(rebuild.remap_preserved_operation(
205 source,
206 operation,
207 rebuild.root_classical(),
208 )?);
209 }
210 }
211
212 rebuild.finish(source.qubits(), operations, source.global_phase())
213}
214
215fn append_direct_ccx_template(
216 operations: &mut Vec<ValueOperation>,
217 control_0: crate::circuit::Qubit,
218 control_1: crate::circuit::Qubit,
219 target: crate::circuit::Qubit,
220 two_qubit_gate: StandardGate,
221) {
222 if two_qubit_gate == StandardGate::CZ {
223 let gates = [
224 (StandardGate::CZ, control_1, Some(target)),
225 (StandardGate::H, target, None),
226 (StandardGate::TDG, target, None),
227 (StandardGate::H, target, None),
228 (StandardGate::CZ, control_0, Some(target)),
229 (StandardGate::H, target, None),
230 (StandardGate::T, target, None),
231 (StandardGate::H, target, None),
232 (StandardGate::CZ, control_1, Some(target)),
233 (StandardGate::H, target, None),
234 (StandardGate::TDG, target, None),
235 (StandardGate::H, target, None),
236 (StandardGate::CZ, control_0, Some(target)),
237 (StandardGate::H, target, None),
238 (StandardGate::T, control_1, None),
239 (StandardGate::T, target, None),
240 (StandardGate::H, target, None),
241 (StandardGate::H, control_1, None),
242 (StandardGate::CZ, control_0, Some(control_1)),
243 (StandardGate::H, control_1, None),
244 (StandardGate::T, control_0, None),
245 (StandardGate::TDG, control_1, None),
246 (StandardGate::H, control_1, None),
247 (StandardGate::CZ, control_0, Some(control_1)),
248 (StandardGate::H, control_1, None),
249 ];
250 append_fixed_gate_sequence(operations, gates);
251 return;
252 }
253
254 let gates = [
255 (StandardGate::H, target, None),
256 (StandardGate::CX, control_1, Some(target)),
257 (StandardGate::TDG, target, None),
258 (StandardGate::CX, control_0, Some(target)),
259 (StandardGate::T, target, None),
260 (StandardGate::CX, control_1, Some(target)),
261 (StandardGate::TDG, target, None),
262 (StandardGate::CX, control_0, Some(target)),
263 (StandardGate::T, control_1, None),
264 (StandardGate::T, target, None),
265 (StandardGate::H, target, None),
266 (StandardGate::CX, control_0, Some(control_1)),
267 (StandardGate::T, control_0, None),
268 (StandardGate::TDG, control_1, None),
269 (StandardGate::CX, control_0, Some(control_1)),
270 ];
271 append_fixed_gate_sequence(operations, gates);
272}
273
274fn append_fixed_gate_sequence<const N: usize>(
275 operations: &mut Vec<ValueOperation>,
276 gates: [(
277 StandardGate,
278 crate::circuit::Qubit,
279 Option<crate::circuit::Qubit>,
280 ); N],
281) {
282 for (gate, first, second) in gates {
283 let qubits = second.map_or_else(|| vec![first], |second| vec![first, second]);
284 operations.push(ValueOperation::from_standard(gate, qubits, []));
285 }
286}
287
288fn has_unroutable_gate_like_operation(operations: &[Operation]) -> bool {
289 for operation in operations {
290 match &operation.instruction {
291 Instruction::Standard(_)
292 | Instruction::McGate(_)
293 | Instruction::UnitaryGate(_)
294 | Instruction::CircuitGate(_) => {
295 if operation.qubits.len() > 2 {
296 return true;
297 }
298 }
299 Instruction::ClassicalControl(op)
300 if op
301 .bodies()
302 .any(|body| has_unroutable_gate_like_operation(body.operations())) =>
303 {
304 return true;
305 }
306 _ => {}
307 }
308 }
309
310 false
311}
312
313fn collect_routable_two_qubit_gates(operations: &[Operation], basis: &mut Vec<StandardGate>) {
314 for operation in operations {
315 match &operation.instruction {
316 Instruction::Standard(gate) if operation.qubits.len() == 2 => {
317 if !basis.contains(gate) {
318 basis.push(*gate);
319 }
320 }
321 Instruction::ClassicalControl(op) => {
322 for body in op.bodies() {
323 collect_routable_two_qubit_gates(body.operations(), basis);
324 }
325 }
326 _ => {}
327 }
328 }
329}
330
331fn validate_routing_basis_contract(operations: &[Operation]) -> Result<(), CompilerError> {
332 for operation in operations {
333 match &operation.instruction {
334 Instruction::Standard(_)
335 | Instruction::McGate(_)
336 | Instruction::UnitaryGate(_)
337 | Instruction::CircuitGate(_)
338 if operation.qubits.len() > 2 =>
339 {
340 return Err(CompilerError::InvalidInput(format!(
341 "routing-basis lowering did not satisfy route.sabre input contract: found {}-qubit operation {}",
342 operation.qubits.len(),
343 operation.instruction
344 )));
345 }
346 Instruction::ClassicalControl(op) => {
347 for body in op.bodies() {
348 validate_routing_basis_contract(body.operations())?;
349 }
350 }
351 _ => {}
352 }
353 }
354
355 Ok(())
356}
357
358#[cfg(test)]
359#[path = "./routing_basis_test.rs"]
360mod routing_basis_test;