1use crate::circuit::gate::StandardGate;
16use crate::circuit::{Instruction, MCGate, ParameterValue};
17use crate::compile::knowledge::rule::{Rule, RuleItem, RuleValidationError};
18use crate::compile::knowledge::rule_dsl::ast::{GatePattern, GateSpec, RuleDef};
19use smallvec::SmallVec;
20use std::collections::HashSet;
21
22#[derive(Debug, Clone, PartialEq, thiserror::Error)]
24pub enum LowerError {
25 #[error("rule match block is empty")]
27 EmptyMatch,
28 #[error("unknown gate: {0}")]
30 UnknownGate(String),
31 #[error("invalid expression: {0}")]
33 InvalidExpr(String),
34 #[error("duplicate qubit {qubit} in gate {gate}")]
36 DuplicateQubit {
37 gate: String,
39 qubit: u32,
41 },
42 #[error("unbound symbol {symbol} in {context}")]
44 UnboundSymbol {
45 symbol: String,
47 context: &'static str,
49 },
50 #[error("unbound qubit {qubit} in {context}")]
52 UnboundQubit {
53 qubit: u32,
55 context: &'static str,
57 },
58 #[error("wrong qubit count for {gate}: expected {expected}, got {got}")]
60 WrongQubitCount {
61 gate: String,
63 expected: usize,
65 got: usize,
67 },
68 #[error("wrong parameter count for {gate}: expected {expected}, got {got}")]
70 WrongParamCount {
71 gate: String,
73 expected: usize,
75 got: usize,
77 },
78 #[error("invalid lowered rule: {0}")]
80 InvalidRule(RuleValidationError),
81}
82
83impl GatePattern {
84 pub fn into_pattern_op(self) -> Result<RuleItem, LowerError> {
93 let lowered = lower_gate_pattern(self)?;
94
95 Ok(RuleItem {
96 instruction: lowered.instruction,
97 qubits: lowered.qubits,
98 params: if lowered.params.is_empty() {
99 None
100 } else {
101 Some(lowered.params)
102 },
103 })
104 }
105
106 pub fn into_build_op(self) -> Result<RuleItem, LowerError> {
111 let lowered = lower_gate_pattern(self)?;
112
113 Ok(RuleItem {
114 instruction: lowered.instruction,
115 qubits: lowered.qubits,
116 params: if lowered.params.is_empty() {
117 None
118 } else {
119 Some(lowered.params)
120 },
121 })
122 }
123}
124
125impl RuleDef {
126 pub fn into_rule(self) -> Result<Rule, LowerError> {
129 if self.match_ops.is_empty() {
130 return Err(LowerError::EmptyMatch);
131 }
132
133 let mut bound_symbols = HashSet::new();
134 for op in &self.match_ops {
135 collect_bound_symbols(op, &mut bound_symbols);
136 }
137
138 let mut bound_qubits = HashSet::new();
139 for op in &self.match_ops {
140 collect_bound_qubits(op, &mut bound_qubits);
141 }
142
143 for op in &self.rewrite_ops {
144 validate_gate_symbols(op, &bound_symbols, "rewrite")?;
145 validate_gate_qubits(op, &bound_qubits, "rewrite")?;
146 }
147 for condition in &self.conditions {
148 validate_condition_symbols(condition, &bound_symbols)?;
149 }
150
151 let name = self.name;
152 let operations = self
153 .match_ops
154 .into_iter()
155 .map(GatePattern::into_pattern_op)
156 .collect::<Result<SmallVec<[_; 4]>, _>>()?;
157 let target = self
158 .rewrite_ops
159 .into_iter()
160 .map(GatePattern::into_build_op)
161 .collect::<Result<SmallVec<[_; 4]>, _>>()?;
162
163 let rule = Rule {
164 name,
165 operations,
166 conditions: if self.conditions.is_empty() {
167 None
168 } else {
169 Some(SmallVec::from_vec(self.conditions))
170 },
171 target,
172 };
173 rule.validate().map_err(LowerError::InvalidRule)?;
174 Ok(rule)
175 }
176}
177
178struct LoweredGate {
179 instruction: Instruction,
180 qubits: SmallVec<[u32; 3]>,
181 params: SmallVec<[ParameterValue; 1]>,
182}
183
184fn lower_gate_pattern(pattern: GatePattern) -> Result<LoweredGate, LowerError> {
185 let display_name = pattern.gate.display_name();
186 let instruction = lower_gate_spec(pattern.gate)?;
187 let (expected_qubits, expected_params) = instruction
188 .gate_arity()
189 .expect("rule DSL lowering emits only fixed-arity gates");
190
191 if pattern.qubits.len() != expected_qubits {
192 return Err(LowerError::WrongQubitCount {
193 gate: display_name,
194 expected: expected_qubits,
195 got: pattern.qubits.len(),
196 });
197 }
198 if pattern.params.len() != expected_params {
199 return Err(LowerError::WrongParamCount {
200 gate: display_name,
201 expected: expected_params,
202 got: pattern.params.len(),
203 });
204 }
205 validate_unique_qubits(&display_name, &pattern.qubits)?;
206
207 let params = pattern
208 .params
209 .into_iter()
210 .map(ParameterValue::from)
211 .collect();
212
213 Ok(LoweredGate {
214 instruction,
215 qubits: SmallVec::from_vec(pattern.qubits),
216 params,
217 })
218}
219
220fn lower_gate_spec(gate: GateSpec) -> Result<Instruction, LowerError> {
221 match gate {
222 GateSpec::Standard { gate_name } => Ok(Instruction::Standard(parse_gate_name(&gate_name)?)),
223 GateSpec::MultiControlled {
224 base_gate_name,
225 added_controls,
226 } => {
227 let base_gate = parse_gate_name(&base_gate_name)?;
228 Ok(Instruction::McGate(Box::new(MCGate::new(
229 added_controls,
230 base_gate,
231 ))))
232 }
233 }
234}
235
236fn validate_unique_qubits(gate: &str, qubits: &[u32]) -> Result<(), LowerError> {
237 let mut seen = SmallVec::<[u32; 3]>::new();
238 for &qubit in qubits {
239 if seen.contains(&qubit) {
240 return Err(LowerError::DuplicateQubit {
241 gate: gate.to_string(),
242 qubit,
243 });
244 }
245 seen.push(qubit);
246 }
247 Ok(())
248}
249
250fn collect_bound_symbols(pattern: &GatePattern, bound_symbols: &mut HashSet<String>) {
251 for param in &pattern.params {
252 for symbol in param.get_symbols() {
253 if !is_builtin_symbol(&symbol) {
254 bound_symbols.insert(symbol);
255 }
256 }
257 }
258}
259
260fn collect_bound_qubits(pattern: &GatePattern, bound_qubits: &mut HashSet<u32>) {
261 bound_qubits.extend(pattern.qubits.iter().copied());
262}
263
264fn validate_gate_symbols(
265 pattern: &GatePattern,
266 bound_symbols: &HashSet<String>,
267 context: &'static str,
268) -> Result<(), LowerError> {
269 for param in &pattern.params {
270 validate_param_symbols(param, bound_symbols, context)?;
271 }
272 Ok(())
273}
274
275fn validate_gate_qubits(
276 pattern: &GatePattern,
277 bound_qubits: &HashSet<u32>,
278 context: &'static str,
279) -> Result<(), LowerError> {
280 for &qubit in &pattern.qubits {
281 if !bound_qubits.contains(&qubit) {
282 return Err(LowerError::UnboundQubit { qubit, context });
283 }
284 }
285 Ok(())
286}
287
288fn validate_condition_symbols(
289 condition: &crate::compile::knowledge::rule::Condition,
290 bound_symbols: &HashSet<String>,
291) -> Result<(), LowerError> {
292 match condition {
293 crate::compile::knowledge::rule::Condition::Eq(lhs, rhs) => {
294 validate_param_symbols(lhs, bound_symbols, "require")?;
295 validate_param_symbols(rhs, bound_symbols, "require")
296 }
297 crate::compile::knowledge::rule::Condition::EqMod(lhs, rhs, modulus) => {
298 validate_param_symbols(lhs, bound_symbols, "require")?;
299 validate_param_symbols(rhs, bound_symbols, "require")?;
300 validate_param_symbols(modulus, bound_symbols, "require")
301 }
302 }
303}
304
305fn validate_param_symbols(
306 param: &crate::circuit::Parameter,
307 bound_symbols: &HashSet<String>,
308 context: &'static str,
309) -> Result<(), LowerError> {
310 for symbol in param.get_symbols() {
311 if !is_builtin_symbol(&symbol) && !bound_symbols.contains(&symbol) {
312 return Err(LowerError::UnboundSymbol { symbol, context });
313 }
314 }
315 Ok(())
316}
317
318fn is_builtin_symbol(symbol: &str) -> bool {
319 matches!(symbol, "π" | "pi" | "e")
320}
321
322fn parse_gate_name(name: &str) -> Result<StandardGate, LowerError> {
324 match name {
325 "I" => Ok(StandardGate::I),
326 "H" => Ok(StandardGate::H),
327 "RX" => Ok(StandardGate::RX),
328 "RXX" => Ok(StandardGate::RXX),
329 "RXY" => Ok(StandardGate::RXY),
330 "RY" => Ok(StandardGate::RY),
331 "RYY" => Ok(StandardGate::RYY),
332 "RZ" => Ok(StandardGate::RZ),
333 "RZX" => Ok(StandardGate::RZX),
334 "RZZ" => Ok(StandardGate::RZZ),
335 "S" => Ok(StandardGate::S),
336 "SDG" => Ok(StandardGate::SDG),
337 "SWAP" => Ok(StandardGate::SWAP),
338 "T" => Ok(StandardGate::T),
339 "TDG" => Ok(StandardGate::TDG),
340 "U" => Ok(StandardGate::U),
341 "X" => Ok(StandardGate::X),
342 "XY" => Ok(StandardGate::XY),
343 "X2P" => Ok(StandardGate::X2P),
344 "X2M" => Ok(StandardGate::X2M),
345 "XY2P" => Ok(StandardGate::XY2P),
346 "XY2M" => Ok(StandardGate::XY2M),
347 "Y" => Ok(StandardGate::Y),
348 "Y2P" => Ok(StandardGate::Y2P),
349 "Y2M" => Ok(StandardGate::Y2M),
350 "Z" => Ok(StandardGate::Z),
351 "Phase" => Ok(StandardGate::Phase),
352 "GPhase" => Ok(StandardGate::GPhase),
353 "CX" => Ok(StandardGate::CX),
354 "CCX" => Ok(StandardGate::CCX),
355 "CY" => Ok(StandardGate::CY),
356 "CZ" => Ok(StandardGate::CZ),
357 "CRX" => Ok(StandardGate::CRX),
358 "CRY" => Ok(StandardGate::CRY),
359 "CRZ" => Ok(StandardGate::CRZ),
360 "FSIM" => Ok(StandardGate::FSIM),
361 _ => Err(LowerError::UnknownGate(name.to_string())),
362 }
363}
364
365#[cfg(test)]
366mod tests {
367 use super::*;
368 use crate::compile::knowledge::rule_dsl::parser::Parser;
369
370 fn lower_single_rule(source: &str) -> Result<Rule, LowerError> {
371 let mut parser = Parser::new(source).unwrap();
372 let mut rules = parser.parse_rule_file().unwrap();
373 assert_eq!(rules.len(), 1);
374 rules.remove(0).into_rule()
375 }
376
377 #[test]
378 fn reject_empty_match_block() {
379 let err = lower_single_rule(
380 r#"rule bad {
381 match {}
382 rewrite { H 0 }
383 }"#,
384 )
385 .unwrap_err();
386 assert!(matches!(err, LowerError::EmptyMatch));
387 }
388
389 #[test]
390 fn reject_duplicate_qubits() {
391 let err = lower_single_rule(
392 r#"rule bad {
393 match { CX 0 0 }
394 rewrite {}
395 }"#,
396 )
397 .unwrap_err();
398 assert!(matches!(
399 err,
400 LowerError::DuplicateQubit { gate, qubit } if gate == "CX" && qubit == 0
401 ));
402 }
403
404 #[test]
405 fn reject_unbound_rewrite_symbol() {
406 let err = lower_single_rule(
407 r#"rule bad {
408 match { RZ(a) 0 }
409 rewrite { RZ(c) 0 }
410 }"#,
411 )
412 .unwrap_err();
413 assert!(matches!(
414 err,
415 LowerError::UnboundSymbol { symbol, context } if symbol == "c" && context == "rewrite"
416 ));
417 }
418
419 #[test]
420 fn reject_unbound_rewrite_qubit() {
421 let err = lower_single_rule(
422 r#"rule bad {
423 match { H 0 }
424 rewrite { H 1 }
425 }"#,
426 )
427 .unwrap_err();
428 assert!(matches!(
429 err,
430 LowerError::UnboundQubit { qubit, context } if qubit == 1 && context == "rewrite"
431 ));
432 }
433
434 #[test]
435 fn allow_rewrite_to_reuse_match_qubit() {
436 let rule = lower_single_rule(
437 r#"rule ok {
438 match { H 0 }
439 rewrite { X 0 }
440 }"#,
441 )
442 .unwrap();
443 assert_eq!(rule.target[0].qubits.as_slice(), &[0]);
444 }
445
446 #[test]
447 fn allow_rewrite_to_reorder_bound_match_qubits() {
448 let rule = lower_single_rule(
449 r#"rule ok {
450 match { CX 0 1 }
451 rewrite { CX 1 0 }
452 }"#,
453 )
454 .unwrap();
455 assert_eq!(rule.target[0].qubits.as_slice(), &[1, 0]);
456 }
457
458 #[test]
459 fn lower_gphase_rule_ok() {
460 let rule = lower_single_rule(
461 r#"rule merge_gphase {
462 match { GPhase(a), GPhase(b) }
463 rewrite { GPhase(a + b) }
464 }"#,
465 )
466 .unwrap();
467 assert_eq!(rule.name, "merge_gphase");
468 assert_eq!(rule.operations.len(), 2);
469 assert!(rule.operations[0].qubits.is_empty());
470 assert_eq!(rule.target.len(), 1);
471 assert!(rule.target[0].qubits.is_empty());
472 }
473
474 #[test]
475 fn lower_multi_controlled_gate_rule_ok() {
476 let rule = lower_single_rule(
477 r#"rule decompose_m3cx {
478 match { MCX[3] 0 1 2 3 }
479 rewrite { CCX 0 1 2 }
480 }"#,
481 )
482 .unwrap();
483
484 let Instruction::McGate(gate) = &rule.operations[0].instruction else {
485 panic!("expected MCGate");
486 };
487 assert_eq!(gate.num_ctrl_qubits(), 3);
488 assert_eq!(*gate.base_gate(), StandardGate::X);
489 assert_eq!(rule.operations[0].qubits.as_slice(), &[0, 1, 2, 3]);
490 }
491
492 #[test]
493 fn lower_parameterized_multi_controlled_gate_rule_ok() {
494 let rule = lower_single_rule(
495 r#"rule decompose_m2rz {
496 match { MCRZ[2](theta) 0 1 2 }
497 rewrite { CRZ(theta) 1 2 }
498 }"#,
499 )
500 .unwrap();
501
502 let Instruction::McGate(gate) = &rule.operations[0].instruction else {
503 panic!("expected MCGate");
504 };
505 assert_eq!(gate.num_ctrl_qubits(), 2);
506 assert_eq!(*gate.base_gate(), StandardGate::RZ);
507 assert_eq!(rule.operations[0].params.as_ref().unwrap().len(), 1);
508 }
509
510 #[test]
511 fn reject_multi_controlled_gate_wrong_qubit_count() {
512 let err = lower_single_rule(
513 r#"rule bad {
514 match { MCX[3] 0 1 2 }
515 rewrite {}
516 }"#,
517 )
518 .unwrap_err();
519 assert!(matches!(
520 err,
521 LowerError::WrongQubitCount { gate, expected: 4, got: 3 } if gate == "MCX[3]"
522 ));
523 }
524
525 #[test]
526 fn reject_multi_controlled_gate_wrong_param_count() {
527 let err = lower_single_rule(
528 r#"rule bad {
529 match { MCRZ[2] 0 1 2 }
530 rewrite {}
531 }"#,
532 )
533 .unwrap_err();
534 assert!(matches!(
535 err,
536 LowerError::WrongParamCount { gate, expected: 1, got: 0 } if gate == "MCRZ[2]"
537 ));
538 }
539
540 #[test]
541 fn reject_multi_controlled_gate_unknown_base_gate() {
542 let err = lower_single_rule(
543 r#"rule bad {
544 match { MCNOPE[1] 0 1 }
545 rewrite {}
546 }"#,
547 )
548 .unwrap_err();
549 assert!(matches!(err, LowerError::UnknownGate(gate) if gate == "NOPE"));
550 }
551
552 #[test]
553 fn reject_gphase_with_qubit() {
554 let err = lower_single_rule(
555 r#"rule bad {
556 match { GPhase(a) 0 }
557 rewrite {}
558 }"#,
559 )
560 .unwrap_err();
561 assert!(matches!(
562 err,
563 LowerError::WrongQubitCount { gate, expected: 0, got: 1 } if gate == "GPhase"
564 ));
565 }
566
567 #[test]
568 fn reject_unbound_condition_symbol() {
569 let err = lower_single_rule(
570 r#"rule bad {
571 match { RZ(a) 0 }
572 require { c == 0 }
573 rewrite {}
574 }"#,
575 )
576 .unwrap_err();
577 assert!(matches!(
578 err,
579 LowerError::UnboundSymbol { symbol, context } if symbol == "c" && context == "require"
580 ));
581 }
582}