Skip to main content

cqlib_core/compile/transform/
routing_basis.rs

1// This code is part of Cqlib.
2//
3// (C) Copyright China Telecom Quantum Group 2026
4//
5// This code is licensed under the Apache License, Version 2.0.
6// You may obtain a copy of this license in the LICENSE.txt file in
7// the root directory of this source tree or at
8// http://www.apache.org/licenses/LICENSE-2.0.
9//
10// Any modifications or derivative works of this code must retain this
11// copyright notice, and modified files need to carry a notice indicating
12// that they have been altered from the originals.
13
14//! Pre-routing legalization for SABRE's gate-arity contract.
15//!
16//! This pass is intentionally narrower than final target-basis lowering. It
17//! only removes gate-like operations that SABRE cannot route because they act
18//! on more than two qubits, while preserving existing 2-qubit standard gates
19//! that are already routable.
20
21use 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/// Lowers gate-like operations to SABRE's 0/1/2-qubit input contract.
31///
32/// `preferred_basis` is a hint used only to choose the 2-qubit family for CCX
33/// lowering. When it contains CZ and does not contain CX, CCX is lowered through
34/// CZ rules; otherwise CX is preferred. Final native-basis translation remains a
35/// separate post-routing workflow stage.
36#[derive(Debug, Clone, Default)]
37pub struct LowerToRoutingBasis {
38    preferred_basis: Option<Vec<Instruction>>,
39}
40
41impl LowerToRoutingBasis {
42    /// Creates a routing-basis lowering transform.
43    ///
44    /// The optional `preferred_basis` should come from the explicit compile
45    /// target basis or from device native gates. It is not used as an exact
46    /// output basis for this transform.
47    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;