use num_complex::Complex64;
use crate::circuit::{smallvec, Circuit, ClassicalCondition, Instruction, SmallVec};
use crate::error::{PrismError, Result};
use crate::gates::Gate;
use std::collections::HashMap;
pub fn parse(input: &str) -> Result<Circuit> {
Parser::new(input).parse()
}
enum Modifier {
Inv,
Ctrl,
Pow(i64),
}
struct Register {
offset: usize,
size: usize,
}
struct GateDefinition {
params: Vec<String>,
qubits: Vec<String>,
body: Vec<String>,
}
struct Parser<'a> {
input: &'a str,
qregs: HashMap<String, Register>,
cregs: HashMap<String, Register>,
gate_defs: HashMap<String, GateDefinition>,
total_qubits: usize,
total_cbits: usize,
gate_expansion_depth: usize,
param_vars: Option<HashMap<String, f64>>,
}
const MAX_GATE_EXPANSION_DEPTH: usize = 32;
use super::expr::{eval_expr, replace_word, split_top_level_commas};
impl<'a> Parser<'a> {
fn new(input: &'a str) -> Self {
Self {
input,
qregs: HashMap::new(),
cregs: HashMap::new(),
gate_defs: HashMap::new(),
total_qubits: 0,
total_cbits: 0,
gate_expansion_depth: 0,
param_vars: None,
}
}
fn parse(mut self) -> Result<Circuit> {
let mut instructions = Vec::new();
let mut gate_def_buf: Option<(String, usize)> = None;
for (line_idx, raw_line) in self.input.lines().enumerate() {
let line_num = line_idx + 1;
let line = match raw_line.find("//") {
Some(pos) => &raw_line[..pos],
None => raw_line,
};
let line = line.trim();
if line.is_empty() {
continue;
}
if let Some((ref mut buf, _start_line)) = gate_def_buf {
buf.push(' ');
buf.push_str(line);
if line.contains('}') {
let (name, start) = gate_def_buf.take().unwrap();
self.parse_gate_def(&name, start)?;
}
continue;
}
let line = line.strip_suffix(';').unwrap_or(line).trim();
if line.is_empty() {
continue;
}
if line.starts_with("OPENQASM") {
continue;
}
if line.starts_with("include") {
continue;
}
if line.starts_with("qubit") {
self.parse_qubit_decl(line, line_num)?;
continue;
}
if line.starts_with("bit") && !line.starts_with("bits") {
self.parse_bit_decl(line, line_num)?;
continue;
}
if line.starts_with("qreg") {
self.parse_qreg_legacy(line, line_num)?;
continue;
}
if line.starts_with("creg") {
self.parse_creg_legacy(line, line_num)?;
continue;
}
if line.starts_with("measure") {
instructions.extend(self.parse_measure_arrow(line, line_num)?);
continue;
}
if line.contains("= measure") || line.contains("=measure") {
instructions.extend(self.parse_measure_assign(line, line_num)?);
continue;
}
if line.starts_with("barrier") {
instructions.push(self.parse_barrier(line, line_num)?);
continue;
}
if line.starts_with("reset") {
instructions.extend(self.parse_reset(line, line_num)?);
continue;
}
if line.starts_with("if") {
instructions.extend(self.parse_if_statement(line, line_num)?);
continue;
}
if line.starts_with("gate ") || line.starts_with("gate(") {
if line.contains('}') {
self.parse_gate_def(line, line_num)?;
} else {
gate_def_buf = Some((line.to_string(), line_num));
}
continue;
}
let first_word = line
.split(|c: char| c.is_whitespace() || c == '(')
.next()
.unwrap_or(line);
if matches!(
first_word,
"def" | "defcal" | "opaque" | "for" | "while" | "box" | "extern" | "return"
) {
return Err(PrismError::UnsupportedConstruct {
construct: first_word.to_string(),
line: line_num,
});
}
instructions.extend(self.parse_gate_application(line, line_num)?);
}
Ok(Circuit {
num_qubits: self.total_qubits,
num_classical_bits: self.total_cbits,
instructions,
})
}
fn parse_qubit_decl(&mut self, line: &str, line_num: usize) -> Result<()> {
let rest = line.strip_prefix("qubit").unwrap();
if rest.trim_start().starts_with('[') {
let bracket_content = Self::extract_bracket(rest).ok_or(PrismError::Parse {
line: line_num,
message: "missing `]` in qubit declaration".to_string(),
})?;
let size: usize = bracket_content.parse().map_err(|_| PrismError::Parse {
line: line_num,
message: format!("invalid qubit count: `{bracket_content}`"),
})?;
if size == 0 {
return Err(PrismError::Parse {
line: line_num,
message: "qubit count must be > 0".to_string(),
});
}
let end = rest.find(']').unwrap(); let after_bracket = rest[end + 1..].trim();
let name = after_bracket.to_string();
if name.is_empty() {
return Err(PrismError::Parse {
line: line_num,
message: "qubit declaration missing name".to_string(),
});
}
let offset = self.total_qubits;
self.total_qubits += size;
self.qregs.insert(name, Register { offset, size });
} else {
let name = rest.trim().to_string();
if name.is_empty() {
return Err(PrismError::Parse {
line: line_num,
message: "qubit declaration missing name".to_string(),
});
}
let offset = self.total_qubits;
self.total_qubits += 1;
self.qregs.insert(name, Register { offset, size: 1 });
}
Ok(())
}
fn parse_bit_decl(&mut self, line: &str, line_num: usize) -> Result<()> {
let rest = line.strip_prefix("bit").unwrap();
if rest.trim_start().starts_with('[') {
let bracket_content = Self::extract_bracket(rest).ok_or(PrismError::Parse {
line: line_num,
message: "missing `]` in bit declaration".to_string(),
})?;
let size: usize = bracket_content.parse().map_err(|_| PrismError::Parse {
line: line_num,
message: format!("invalid bit count: `{bracket_content}`"),
})?;
if size == 0 {
return Err(PrismError::Parse {
line: line_num,
message: "bit count must be > 0".to_string(),
});
}
let end = rest.find(']').unwrap(); let after_bracket = rest[end + 1..].trim();
let name = after_bracket.to_string();
if name.is_empty() {
return Err(PrismError::Parse {
line: line_num,
message: "bit declaration missing name".to_string(),
});
}
let offset = self.total_cbits;
self.total_cbits += size;
self.cregs.insert(name, Register { offset, size });
} else {
let name = rest.trim().to_string();
if name.is_empty() {
return Err(PrismError::Parse {
line: line_num,
message: "bit declaration missing name".to_string(),
});
}
let offset = self.total_cbits;
self.total_cbits += 1;
self.cregs.insert(name, Register { offset, size: 1 });
}
Ok(())
}
fn extract_bracket(s: &str) -> Option<&str> {
let start = s.find('[')?;
let end = s.find(']')?;
Some(s[start + 1..end].trim())
}
fn parse_qreg_legacy(&mut self, line: &str, line_num: usize) -> Result<()> {
let rest = line.strip_prefix("qreg").unwrap().trim();
let (name, size) = Self::parse_legacy_register_decl(rest, line_num)?;
let offset = self.total_qubits;
self.total_qubits += size;
self.qregs.insert(name, Register { offset, size });
Ok(())
}
fn parse_creg_legacy(&mut self, line: &str, line_num: usize) -> Result<()> {
let rest = line.strip_prefix("creg").unwrap().trim();
let (name, size) = Self::parse_legacy_register_decl(rest, line_num)?;
let offset = self.total_cbits;
self.total_cbits += size;
self.cregs.insert(name, Register { offset, size });
Ok(())
}
fn parse_legacy_register_decl(s: &str, line_num: usize) -> Result<(String, usize)> {
let bracket_start = s.find('[').ok_or_else(|| PrismError::Parse {
line: line_num,
message: format!("expected `[` in register declaration: `{s}`"),
})?;
let bracket_end = s.find(']').ok_or_else(|| PrismError::Parse {
line: line_num,
message: format!("expected `]` in register declaration: `{s}`"),
})?;
let name = s[..bracket_start].trim().to_string();
if name.is_empty() {
return Err(PrismError::Parse {
line: line_num,
message: "register name is empty".to_string(),
});
}
let size_str = s[bracket_start + 1..bracket_end].trim();
let size: usize = size_str.parse().map_err(|_| PrismError::Parse {
line: line_num,
message: format!("invalid register size: `{size_str}`"),
})?;
if size == 0 {
return Err(PrismError::Parse {
line: line_num,
message: "register size must be > 0".to_string(),
});
}
Ok((name, size))
}
fn resolve_qubit(&self, token: &str, line_num: usize) -> Result<usize> {
let (name, idx) = Self::parse_indexed_ref(token, line_num)?;
let reg = self
.qregs
.get(name)
.ok_or_else(|| PrismError::UndefinedRegister {
name: name.to_string(),
line: line_num,
})?;
if idx >= reg.size {
return Err(PrismError::InvalidQubit {
index: idx,
register_size: reg.size,
});
}
Ok(reg.offset + idx)
}
fn resolve_qubit_arg(&self, token: &str, line_num: usize) -> Result<SmallVec<[usize; 4]>> {
if token.contains('[') {
Ok(smallvec![self.resolve_qubit(token, line_num)?])
} else {
let reg = self
.qregs
.get(token)
.ok_or_else(|| PrismError::UndefinedRegister {
name: token.to_string(),
line: line_num,
})?;
Ok((0..reg.size).map(|i| reg.offset + i).collect())
}
}
fn resolve_cbit_arg(&self, token: &str, line_num: usize) -> Result<SmallVec<[usize; 4]>> {
if token.contains('[') {
Ok(smallvec![self.resolve_cbit(token, line_num)?])
} else {
let reg = self
.cregs
.get(token)
.ok_or_else(|| PrismError::UndefinedRegister {
name: token.to_string(),
line: line_num,
})?;
Ok((0..reg.size).map(|i| reg.offset + i).collect())
}
}
fn resolve_cbit(&self, token: &str, line_num: usize) -> Result<usize> {
let (name, idx) = Self::parse_indexed_ref(token, line_num)?;
let reg = self
.cregs
.get(name)
.ok_or_else(|| PrismError::UndefinedRegister {
name: name.to_string(),
line: line_num,
})?;
if idx >= reg.size {
return Err(PrismError::InvalidClassicalBit {
index: idx,
register_size: reg.size,
});
}
Ok(reg.offset + idx)
}
fn parse_indexed_ref(token: &str, line_num: usize) -> Result<(&str, usize)> {
let bracket = token.find('[').ok_or_else(|| PrismError::Parse {
line: line_num,
message: format!("expected indexed reference (e.g. `q[0]`), got: `{token}`"),
})?;
let end = token.find(']').ok_or_else(|| PrismError::Parse {
line: line_num,
message: format!("expected `]` in reference: `{token}`"),
})?;
let name = token[..bracket].trim();
let idx: usize = token[bracket + 1..end]
.trim()
.parse()
.map_err(|_| PrismError::Parse {
line: line_num,
message: format!("invalid index in `{token}`"),
})?;
Ok((name, idx))
}
fn parse_measure_arrow(&self, line: &str, line_num: usize) -> Result<Vec<Instruction>> {
let rest = line.strip_prefix("measure").unwrap().trim();
let parts: Vec<&str> = rest.split("->").collect();
if parts.len() != 2 {
return Err(PrismError::Parse {
line: line_num,
message: "expected `measure qubit -> cbit`".to_string(),
});
}
let qubits = self.resolve_qubit_arg(parts[0].trim(), line_num)?;
let cbits = self.resolve_cbit_arg(parts[1].trim(), line_num)?;
if qubits.len() != cbits.len() {
return Err(PrismError::Parse {
line: line_num,
message: format!(
"register size mismatch in measure: {} qubits vs {} classical bits",
qubits.len(),
cbits.len()
),
});
}
Ok(qubits
.iter()
.zip(cbits.iter())
.map(|(&qubit, &classical_bit)| Instruction::Measure {
qubit,
classical_bit,
})
.collect())
}
fn parse_measure_assign(&self, line: &str, line_num: usize) -> Result<Vec<Instruction>> {
let parts: Vec<&str> = line.splitn(2, '=').collect();
if parts.len() != 2 {
return Err(PrismError::Parse {
line: line_num,
message: "expected `cbit = measure qubit`".to_string(),
});
}
let cbit_token = parts[0].trim();
let measure_part = parts[1].trim();
let qubit_token = measure_part
.strip_prefix("measure")
.ok_or_else(|| PrismError::Parse {
line: line_num,
message: "expected `measure` after `=`".to_string(),
})?
.trim();
let cbits = self.resolve_cbit_arg(cbit_token, line_num)?;
let qubits = self.resolve_qubit_arg(qubit_token, line_num)?;
if qubits.len() != cbits.len() {
return Err(PrismError::Parse {
line: line_num,
message: format!(
"register size mismatch in measure: {} qubits vs {} classical bits",
qubits.len(),
cbits.len()
),
});
}
Ok(qubits
.iter()
.zip(cbits.iter())
.map(|(&qubit, &classical_bit)| Instruction::Measure {
qubit,
classical_bit,
})
.collect())
}
fn parse_gate_def(&mut self, line: &str, line_num: usize) -> Result<()> {
let rest = line.strip_prefix("gate").unwrap().trim();
let brace_open = rest.find('{').ok_or_else(|| PrismError::Parse {
line: line_num,
message: "expected `{` in gate definition".to_string(),
})?;
let brace_close = rest.rfind('}').ok_or_else(|| PrismError::Parse {
line: line_num,
message: "expected `}` in gate definition".to_string(),
})?;
let header = rest[..brace_open].trim();
let body_str = rest[brace_open + 1..brace_close].trim();
let (name, params, qubit_names) = if let Some(paren_open) = header.find('(') {
let paren_close = header.find(')').ok_or_else(|| PrismError::Parse {
line: line_num,
message: "expected `)` in gate parameters".to_string(),
})?;
let name = header[..paren_open].trim().to_string();
let params: Vec<String> = header[paren_open + 1..paren_close]
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
let qubit_names: Vec<String> = header[paren_close + 1..]
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
(name, params, qubit_names)
} else {
let parts: Vec<&str> = header.split_whitespace().collect();
let name = parts[0].to_string();
let qubit_names: Vec<String> = parts[1..]
.iter()
.flat_map(|s| s.split(','))
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
(name, Vec::new(), qubit_names)
};
let body: Vec<String> = body_str
.split(';')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
if body.is_empty() {
return Err(PrismError::Parse {
line: line_num,
message: format!("gate '{}' has an empty body", name),
});
}
self.gate_defs.insert(
name,
GateDefinition {
params,
qubits: qubit_names,
body,
},
);
Ok(())
}
fn parse_barrier(&self, line: &str, line_num: usize) -> Result<Instruction> {
let rest = line.strip_prefix("barrier").unwrap().trim();
let mut qubits = SmallVec::<[usize; 4]>::new();
for token in rest.split(',') {
qubits.extend(self.resolve_qubit_arg(token.trim(), line_num)?);
}
Ok(Instruction::Barrier { qubits })
}
fn parse_reset(&self, line: &str, line_num: usize) -> Result<Vec<Instruction>> {
let rest = line.strip_prefix("reset").unwrap().trim();
if rest.is_empty() {
return Err(PrismError::Parse {
line: line_num,
message: "expected qubit argument after `reset`".to_string(),
});
}
let mut out = Vec::new();
for token in rest.split(',') {
let qubits = self.resolve_qubit_arg(token.trim(), line_num)?;
for q in qubits {
out.push(Instruction::Reset { qubit: q });
}
}
Ok(out)
}
fn parse_if_statement(&self, line: &str, line_num: usize) -> Result<Vec<Instruction>> {
let rest = line.strip_prefix("if").unwrap().trim();
let open = rest.find('(').ok_or_else(|| PrismError::Parse {
line: line_num,
message: "expected `(` after `if`".to_string(),
})?;
let close = rest.find(')').ok_or_else(|| PrismError::Parse {
line: line_num,
message: "expected `)` in `if` condition".to_string(),
})?;
let cond_str = rest[open + 1..close].trim();
let body_str = rest[close + 1..].trim();
if body_str.is_empty() {
return Err(PrismError::Parse {
line: line_num,
message: "expected gate after `if(...)` condition".to_string(),
});
}
let condition = if let Some(eq_pos) = cond_str.find("==") {
let reg_name = cond_str[..eq_pos].trim();
let value_str = cond_str[eq_pos + 2..].trim();
let value: u64 = value_str.parse().map_err(|_| PrismError::Parse {
line: line_num,
message: format!("invalid integer in `if` condition: `{value_str}`"),
})?;
let reg = self
.cregs
.get(reg_name)
.ok_or_else(|| PrismError::UndefinedRegister {
name: reg_name.to_string(),
line: line_num,
})?;
ClassicalCondition::RegisterEquals {
offset: reg.offset,
size: reg.size,
value,
}
} else if cond_str.contains('[') {
let bit = self.resolve_cbit(cond_str, line_num)?;
ClassicalCondition::BitIsOne(bit)
} else {
return Err(PrismError::Parse {
line: line_num,
message: format!(
"expected `creg==value` or `c[i]` in `if` condition, got: `{cond_str}`"
),
});
};
let gate_instrs = self.parse_gate_application(body_str, line_num)?;
Ok(gate_instrs
.into_iter()
.map(|inst| match inst {
Instruction::Gate { gate, targets } => Instruction::Conditional {
condition: condition.clone(),
gate,
targets,
},
other => other,
})
.collect())
}
fn parse_gate_application(&self, line: &str, line_num: usize) -> Result<Vec<Instruction>> {
let (modifiers, gate_line) = Self::strip_modifiers(line, line_num)?;
let (gate_name, params, args_str) = self.split_gate_line(gate_line, line_num)?;
let qubit_tokens: Vec<&str> = args_str
.split(',')
.map(|s| s.trim())
.filter(|t| !t.is_empty())
.collect();
let resolved: Vec<SmallVec<[usize; 4]>> = qubit_tokens
.iter()
.map(|t| self.resolve_qubit_arg(t, line_num))
.collect::<Result<Vec<_>>>()?;
let broadcast_len = self.broadcast_length(&resolved, &gate_name, line_num)?;
if broadcast_len <= 1 {
let qubits: SmallVec<[usize; 4]> = resolved.iter().map(|v| v[0]).collect();
if let Some(instrs) =
Self::resolve_decomposed_gate(&gate_name, ¶ms, &qubits, line_num)?
{
if !modifiers.is_empty() {
return Err(PrismError::UnsupportedConstruct {
construct: format!("modifier on decomposed gate `{gate_name}`"),
line: line_num,
});
}
return Ok(instrs);
}
if let Some(instrs) = self.expand_user_gate(&gate_name, ¶ms, &qubits, line_num)? {
return Ok(instrs);
}
let mut gate = Self::resolve_gate(&gate_name, ¶ms, line_num)?;
for modifier in modifiers.iter().rev() {
gate = Self::apply_modifier(gate, modifier, line_num)?;
}
let expected = gate.num_qubits();
if qubits.len() != expected {
return Err(PrismError::GateArity {
gate: gate_name,
expected,
got: qubits.len(),
});
}
return Ok(vec![Instruction::Gate {
gate,
targets: qubits,
}]);
}
let mut all_instrs = Vec::with_capacity(broadcast_len);
for i in 0..broadcast_len {
let qubits: SmallVec<[usize; 4]> = resolved
.iter()
.map(|v| if v.len() == 1 { v[0] } else { v[i] })
.collect();
if let Some(mut instrs) =
Self::resolve_decomposed_gate(&gate_name, ¶ms, &qubits, line_num)?
{
if !modifiers.is_empty() {
return Err(PrismError::UnsupportedConstruct {
construct: format!("modifier on decomposed gate `{gate_name}`"),
line: line_num,
});
}
all_instrs.append(&mut instrs);
continue;
}
if let Some(mut instrs) =
self.expand_user_gate(&gate_name, ¶ms, &qubits, line_num)?
{
all_instrs.append(&mut instrs);
continue;
}
let mut gate = Self::resolve_gate(&gate_name, ¶ms, line_num)?;
for modifier in modifiers.iter().rev() {
gate = Self::apply_modifier(gate, modifier, line_num)?;
}
all_instrs.push(Instruction::Gate {
gate,
targets: qubits,
});
}
Ok(all_instrs)
}
fn broadcast_length(
&self,
resolved: &[SmallVec<[usize; 4]>],
gate_name: &str,
line_num: usize,
) -> Result<usize> {
let mut broadcast_len = 1usize;
for arg in resolved {
if arg.len() > 1 {
if broadcast_len == 1 {
broadcast_len = arg.len();
} else if arg.len() != broadcast_len {
return Err(PrismError::Parse {
line: line_num,
message: format!(
"register size mismatch in `{gate_name}`: \
expected {broadcast_len} qubits but got {}",
arg.len()
),
});
}
}
}
Ok(broadcast_len)
}
fn expand_user_gate(
&self,
name: &str,
call_params: &[f64],
call_qubits: &SmallVec<[usize; 4]>,
line_num: usize,
) -> Result<Option<Vec<Instruction>>> {
if self.gate_expansion_depth >= MAX_GATE_EXPANSION_DEPTH {
return Err(PrismError::Parse {
line: line_num,
message: format!(
"gate expansion depth exceeds maximum ({MAX_GATE_EXPANSION_DEPTH}); \
possible recursive gate definition for `{name}`"
),
});
}
let def = match self.gate_defs.get(name) {
Some(d) => d,
None => return Ok(None),
};
if call_params.len() != def.params.len() {
return Err(PrismError::Parse {
line: line_num,
message: format!(
"gate `{name}` expects {} parameters, got {}",
def.params.len(),
call_params.len()
),
});
}
if call_qubits.len() != def.qubits.len() {
return Err(PrismError::GateArity {
gate: name.to_string(),
expected: def.qubits.len(),
got: call_qubits.len(),
});
}
let mut var_map = HashMap::new();
for (i, param_name) in def.params.iter().enumerate() {
var_map.insert(param_name.clone(), call_params[i]);
}
let mut all_instrs = Vec::new();
for stmt in &def.body {
let mut expanded = stmt.clone();
for (i, qubit_name) in def.qubits.iter().enumerate() {
expanded =
replace_word(&expanded, qubit_name, &format!("__q__[{}]", call_qubits[i]));
}
let saved_qregs = &self.qregs;
let max_qubit = call_qubits.iter().max().copied().unwrap_or(0) + 1;
let mut sub_parser = Parser {
input: "",
qregs: HashMap::new(),
cregs: HashMap::new(),
gate_defs: HashMap::new(),
total_qubits: max_qubit,
total_cbits: 0,
gate_expansion_depth: self.gate_expansion_depth + 1,
param_vars: Some(var_map.clone()),
};
sub_parser.qregs.insert(
"__q__".to_string(),
Register {
offset: 0,
size: max_qubit,
},
);
for (k, v) in saved_qregs {
sub_parser.qregs.insert(
k.clone(),
Register {
offset: v.offset,
size: v.size,
},
);
}
for (k, v) in &self.gate_defs {
sub_parser.gate_defs.insert(
k.clone(),
GateDefinition {
params: v.params.clone(),
qubits: v.qubits.clone(),
body: v.body.clone(),
},
);
}
let instrs = sub_parser.parse_gate_application(expanded.trim(), line_num)?;
all_instrs.extend(instrs);
}
Ok(Some(all_instrs))
}
fn strip_modifiers(line: &str, line_num: usize) -> Result<(Vec<Modifier>, &str)> {
if !line.contains(" @ ") {
return Ok((vec![], line));
}
let parts: Vec<&str> = line.split(" @ ").collect();
let gate_line = parts[parts.len() - 1];
let mut modifiers = Vec::with_capacity(parts.len() - 1);
for part in &parts[..parts.len() - 1] {
let token = part.trim();
if token == "inv" {
modifiers.push(Modifier::Inv);
} else if token == "ctrl" {
modifiers.push(Modifier::Ctrl);
} else if let Some(rest) = token.strip_prefix("pow(") {
let rest = rest.strip_suffix(')').ok_or_else(|| PrismError::Parse {
line: line_num,
message: format!("unmatched `(` in pow modifier: `{token}`"),
})?;
let k: i64 = rest
.trim()
.parse()
.map_err(|_| PrismError::UnsupportedConstruct {
construct: format!("pow({rest})"),
line: line_num,
})?;
modifiers.push(Modifier::Pow(k));
} else {
return Err(PrismError::UnsupportedConstruct {
construct: token.to_string(),
line: line_num,
});
}
}
Ok((modifiers, gate_line))
}
fn apply_modifier(gate: Gate, modifier: &Modifier, line_num: usize) -> Result<Gate> {
match modifier {
Modifier::Inv => Ok(gate.inverse()),
Modifier::Pow(k) => {
if gate.num_qubits() != 1 {
return Err(PrismError::UnsupportedConstruct {
construct: format!("pow({k}) @ {} (only single-qubit gates)", gate.name()),
line: line_num,
});
}
Ok(gate.matrix_power(*k))
}
Modifier::Ctrl => match &gate {
g if g.num_qubits() == 1 => {
let mat = gate.matrix_2x2();
Ok(Self::resolve_controlled(mat))
}
Gate::Cu(mat) => Ok(Gate::mcu(**mat, 2)),
Gate::Cx => Ok(Gate::mcu(Gate::X.matrix_2x2(), 2)),
Gate::Cz => Ok(Gate::mcu(Gate::Z.matrix_2x2(), 2)),
Gate::Mcu(data) => Ok(Gate::mcu(data.mat, data.num_controls + 1)),
_ => Err(PrismError::UnsupportedConstruct {
construct: format!("ctrl @ {} (unsupported gate type)", gate.name()),
line: line_num,
}),
},
}
}
fn resolve_controlled(mat: [[num_complex::Complex64; 2]; 2]) -> Gate {
use num_complex::Complex64;
let zero = Complex64::new(0.0, 0.0);
let one = Complex64::new(1.0, 0.0);
let eps = 1e-12;
if (mat[0][0] - zero).norm() < eps
&& (mat[0][1] - one).norm() < eps
&& (mat[1][0] - one).norm() < eps
&& (mat[1][1] - zero).norm() < eps
{
return Gate::Cx;
}
if (mat[0][0] - one).norm() < eps
&& (mat[0][1] - zero).norm() < eps
&& (mat[1][0] - zero).norm() < eps
&& (mat[1][1] + one).norm() < eps
{
return Gate::Cz;
}
Gate::cu(mat)
}
fn split_gate_line(&self, line: &str, line_num: usize) -> Result<(String, Vec<f64>, String)> {
if let Some(paren_start) = line.find('(') {
let mut depth = 0usize;
let mut paren_end = None;
for (i, ch) in line[paren_start..].char_indices() {
match ch {
'(' => depth += 1,
')' => {
depth = depth.saturating_sub(1);
if depth == 0 {
paren_end = Some(paren_start + i);
break;
}
}
_ => {}
}
}
let paren_end = paren_end.ok_or_else(|| PrismError::Parse {
line: line_num,
message: "unmatched `(` in gate application".to_string(),
})?;
let gate_name = line[..paren_start].trim().to_string();
let params_str = &line[paren_start + 1..paren_end];
let params =
Self::parse_params_with_vars(params_str, line_num, self.param_vars.as_ref())?;
let args_str = line[paren_end + 1..].trim().to_string();
Ok((gate_name, params, args_str))
} else {
let first_space = line
.find(char::is_whitespace)
.ok_or_else(|| PrismError::Parse {
line: line_num,
message: format!("cannot parse instruction: `{line}`"),
})?;
let gate_name = line[..first_space].trim().to_string();
let args_str = line[first_space..].trim().to_string();
Ok((gate_name, vec![], args_str))
}
}
fn parse_params_with_vars(
params_str: &str,
line_num: usize,
vars: Option<&HashMap<String, f64>>,
) -> Result<Vec<f64>> {
split_top_level_commas(params_str)
.iter()
.map(|p| eval_expr(p.trim(), line_num, vars))
.collect()
}
fn resolve_gate(name: &str, params: &[f64], line_num: usize) -> Result<Gate> {
match name {
"id" => Ok(Gate::Id),
"x" => Ok(Gate::X),
"y" => Ok(Gate::Y),
"z" => Ok(Gate::Z),
"h" => Ok(Gate::H),
"s" => Ok(Gate::S),
"sdg" => Ok(Gate::Sdg),
"t" => Ok(Gate::T),
"tdg" => Ok(Gate::Tdg),
"rx" => {
Self::expect_param_count(name, params, 1, line_num)?;
Ok(Gate::Rx(params[0]))
}
"ry" => {
Self::expect_param_count(name, params, 1, line_num)?;
Ok(Gate::Ry(params[0]))
}
"rz" => {
Self::expect_param_count(name, params, 1, line_num)?;
Ok(Gate::Rz(params[0]))
}
"p" | "phase" => {
Self::expect_param_count(name, params, 1, line_num)?;
Ok(Gate::P(params[0]))
}
"r" => {
Self::expect_param_count(name, params, 2, line_num)?;
Ok(Gate::Fused(Box::new(Self::r_matrix(params[0], params[1]))))
}
"sx" => Ok(Gate::SX),
"sxdg" => Ok(Gate::SXdg),
"cp" | "cphase" => {
Self::expect_param_count(name, params, 1, line_num)?;
Ok(Gate::cphase(params[0]))
}
"rzz" => {
Self::expect_param_count(name, params, 1, line_num)?;
Ok(Gate::Rzz(params[0]))
}
"cx" | "CX" | "cnot" => Ok(Gate::Cx),
"cy" => Ok(Gate::cu(Gate::Y.matrix_2x2())),
"cs" => Ok(Gate::cu(Gate::S.matrix_2x2())),
"csdg" => Ok(Gate::cu(Gate::Sdg.matrix_2x2())),
"ch" => Ok(Gate::cu(Gate::H.matrix_2x2())),
"cu" => {
Self::expect_param_count(name, params, 4, line_num)?;
Ok(Gate::cu(Self::cu_target_matrix(
params[0], params[1], params[2], params[3],
)))
}
"crx" => {
Self::expect_param_count(name, params, 1, line_num)?;
Ok(Gate::cu(Gate::Rx(params[0]).matrix_2x2()))
}
"cry" => {
Self::expect_param_count(name, params, 1, line_num)?;
Ok(Gate::cu(Gate::Ry(params[0]).matrix_2x2()))
}
"crz" => {
Self::expect_param_count(name, params, 1, line_num)?;
Ok(Gate::cu(Gate::Rz(params[0]).matrix_2x2()))
}
"csx" => Ok(Gate::cu(Gate::SX.matrix_2x2())),
"cz" => Ok(Gate::Cz),
"swap" => Ok(Gate::Swap),
"ccx" | "toffoli" => Ok(Gate::mcu(Gate::X.matrix_2x2(), 2)),
"ccz" => Ok(Gate::mcu(Gate::Z.matrix_2x2(), 2)),
"c3x" => Ok(Gate::mcu(Gate::X.matrix_2x2(), 3)),
"c4x" => Ok(Gate::mcu(Gate::X.matrix_2x2(), 4)),
"xx_plus_yy" => {
Self::expect_param_count(name, params, 2, line_num)?;
Ok(Gate::Fused2q(Box::new(Self::xx_plus_yy_matrix(
params[0], params[1],
))))
}
"xx_minus_yy" => {
Self::expect_param_count(name, params, 2, line_num)?;
Ok(Gate::Fused2q(Box::new(Self::xx_minus_yy_matrix(
params[0], params[1],
))))
}
"gpi" => {
Self::expect_param_count(name, params, 1, line_num)?;
Ok(Gate::Fused(Box::new(Self::gpi_matrix(params[0]))))
}
"gpi2" => {
Self::expect_param_count(name, params, 1, line_num)?;
Ok(Gate::Fused(Box::new(Self::gpi2_matrix(params[0]))))
}
"ms" => {
if !(params.len() == 2 || params.len() == 3) {
return Err(PrismError::InvalidParameter {
message: format!(
"`{name}` at line {line_num} requires 2 or 3 parameter(s), got {}",
params.len()
),
});
}
let theta = params.get(2).copied().unwrap_or(0.25);
Ok(Gate::Fused2q(Box::new(Self::ms_matrix(
params[0], params[1], theta,
))))
}
"syc" => Ok(Gate::Fused2q(Box::new(Self::syc_matrix()))),
"sqrt_iswap" => Ok(Gate::Fused2q(Box::new(Self::sqrt_iswap_matrix(1.0)))),
"sqrt_iswap_inv" => Ok(Gate::Fused2q(Box::new(Self::sqrt_iswap_matrix(-1.0)))),
_ => Err(PrismError::UnsupportedConstruct {
construct: name.to_string(),
line: line_num,
}),
}
}
fn resolve_decomposed_gate(
name: &str,
params: &[f64],
qubits: &[usize],
line_num: usize,
) -> Result<Option<Vec<Instruction>>> {
match name {
"mcx" => {
if qubits.len() < 2 {
return Err(PrismError::GateArity {
gate: name.to_string(),
expected: 2,
got: qubits.len(),
});
}
let controls = qubits.len() - 1;
if controls > u8::MAX as usize {
return Err(PrismError::InvalidParameter {
message: format!(
"`{name}` at line {line_num} supports at most {} controls, got {controls}",
u8::MAX
),
});
}
Ok(Some(vec![Instruction::Gate {
gate: Gate::mcu(Gate::X.matrix_2x2(), controls as u8),
targets: SmallVec::from_slice(qubits),
}]))
}
"rccx" => {
Self::check_arity(name, qubits, 3)?;
let c0 = qubits[0];
let c1 = qubits[1];
let target = qubits[2];
Ok(Some(vec![
Instruction::Gate {
gate: Gate::H,
targets: smallvec![target],
},
Instruction::Gate {
gate: Gate::T,
targets: smallvec![target],
},
Instruction::Gate {
gate: Gate::Cx,
targets: smallvec![c1, target],
},
Instruction::Gate {
gate: Gate::Tdg,
targets: smallvec![target],
},
Instruction::Gate {
gate: Gate::Cx,
targets: smallvec![c0, target],
},
Instruction::Gate {
gate: Gate::T,
targets: smallvec![target],
},
Instruction::Gate {
gate: Gate::Cx,
targets: smallvec![c1, target],
},
Instruction::Gate {
gate: Gate::Tdg,
targets: smallvec![target],
},
Instruction::Gate {
gate: Gate::H,
targets: smallvec![target],
},
]))
}
"rc3x" | "rcccx" => {
Self::check_arity(name, qubits, 4)?;
let c0 = qubits[0];
let c1 = qubits[1];
let c2 = qubits[2];
let target = qubits[3];
Ok(Some(vec![
Instruction::Gate {
gate: Gate::H,
targets: smallvec![target],
},
Instruction::Gate {
gate: Gate::T,
targets: smallvec![target],
},
Instruction::Gate {
gate: Gate::Cx,
targets: smallvec![c2, target],
},
Instruction::Gate {
gate: Gate::Tdg,
targets: smallvec![target],
},
Instruction::Gate {
gate: Gate::H,
targets: smallvec![target],
},
Instruction::Gate {
gate: Gate::Cx,
targets: smallvec![c0, target],
},
Instruction::Gate {
gate: Gate::T,
targets: smallvec![target],
},
Instruction::Gate {
gate: Gate::Cx,
targets: smallvec![c1, target],
},
Instruction::Gate {
gate: Gate::Tdg,
targets: smallvec![target],
},
Instruction::Gate {
gate: Gate::Cx,
targets: smallvec![c0, target],
},
Instruction::Gate {
gate: Gate::T,
targets: smallvec![target],
},
Instruction::Gate {
gate: Gate::Cx,
targets: smallvec![c1, target],
},
Instruction::Gate {
gate: Gate::Tdg,
targets: smallvec![target],
},
Instruction::Gate {
gate: Gate::H,
targets: smallvec![target],
},
Instruction::Gate {
gate: Gate::T,
targets: smallvec![target],
},
Instruction::Gate {
gate: Gate::Cx,
targets: smallvec![c2, target],
},
Instruction::Gate {
gate: Gate::Tdg,
targets: smallvec![target],
},
Instruction::Gate {
gate: Gate::H,
targets: smallvec![target],
},
]))
}
"cswap" | "fredkin" => {
Self::check_arity(name, qubits, 3)?;
let ctrl = qubits[0];
let t1 = qubits[1];
let t2 = qubits[2];
Ok(Some(vec![
Instruction::Gate {
gate: Gate::Cx,
targets: smallvec![t2, t1],
},
Instruction::Gate {
gate: Gate::mcu(Gate::X.matrix_2x2(), 2),
targets: smallvec![ctrl, t1, t2],
},
Instruction::Gate {
gate: Gate::Cx,
targets: smallvec![t2, t1],
},
]))
}
"rxx" => {
Self::expect_param_count(name, params, 1, line_num)?;
Self::check_arity(name, qubits, 2)?;
let q0 = qubits[0];
let q1 = qubits[1];
Ok(Some(vec![
Instruction::Gate {
gate: Gate::H,
targets: smallvec![q0],
},
Instruction::Gate {
gate: Gate::H,
targets: smallvec![q1],
},
Instruction::Gate {
gate: Gate::Cx,
targets: smallvec![q0, q1],
},
Instruction::Gate {
gate: Gate::Rz(params[0]),
targets: smallvec![q1],
},
Instruction::Gate {
gate: Gate::Cx,
targets: smallvec![q0, q1],
},
Instruction::Gate {
gate: Gate::H,
targets: smallvec![q0],
},
Instruction::Gate {
gate: Gate::H,
targets: smallvec![q1],
},
]))
}
"ryy" => {
Self::expect_param_count(name, params, 1, line_num)?;
Self::check_arity(name, qubits, 2)?;
let q0 = qubits[0];
let q1 = qubits[1];
let half_pi = std::f64::consts::FRAC_PI_2;
Ok(Some(vec![
Instruction::Gate {
gate: Gate::Rx(half_pi),
targets: smallvec![q0],
},
Instruction::Gate {
gate: Gate::Rx(half_pi),
targets: smallvec![q1],
},
Instruction::Gate {
gate: Gate::Cx,
targets: smallvec![q0, q1],
},
Instruction::Gate {
gate: Gate::Rz(params[0]),
targets: smallvec![q1],
},
Instruction::Gate {
gate: Gate::Cx,
targets: smallvec![q0, q1],
},
Instruction::Gate {
gate: Gate::Rx(-half_pi),
targets: smallvec![q0],
},
Instruction::Gate {
gate: Gate::Rx(-half_pi),
targets: smallvec![q1],
},
]))
}
"ecr" => {
Self::check_arity(name, qubits, 2)?;
let q0 = qubits[0];
let q1 = qubits[1];
Ok(Some(vec![
Instruction::Gate {
gate: Gate::Rz(std::f64::consts::FRAC_PI_4),
targets: smallvec![q0],
},
Instruction::Gate {
gate: Gate::Rx(std::f64::consts::FRAC_PI_2),
targets: smallvec![q0],
},
Instruction::Gate {
gate: Gate::Cx,
targets: smallvec![q0, q1],
},
Instruction::Gate {
gate: Gate::X,
targets: smallvec![q0],
},
]))
}
"iswap" => {
Self::check_arity(name, qubits, 2)?;
let q0 = qubits[0];
let q1 = qubits[1];
Ok(Some(vec![
Instruction::Gate {
gate: Gate::S,
targets: smallvec![q0],
},
Instruction::Gate {
gate: Gate::S,
targets: smallvec![q1],
},
Instruction::Gate {
gate: Gate::H,
targets: smallvec![q0],
},
Instruction::Gate {
gate: Gate::Cx,
targets: smallvec![q0, q1],
},
Instruction::Gate {
gate: Gate::Cx,
targets: smallvec![q1, q0],
},
Instruction::Gate {
gate: Gate::H,
targets: smallvec![q1],
},
]))
}
"dcx" => {
Self::check_arity(name, qubits, 2)?;
let q0 = qubits[0];
let q1 = qubits[1];
Ok(Some(vec![
Instruction::Gate {
gate: Gate::Cx,
targets: smallvec![q0, q1],
},
Instruction::Gate {
gate: Gate::Cx,
targets: smallvec![q1, q0],
},
]))
}
"u1" => {
Self::expect_param_count(name, params, 1, line_num)?;
Self::check_arity(name, qubits, 1)?;
Ok(Some(vec![Instruction::Gate {
gate: Gate::P(params[0]),
targets: smallvec![qubits[0]],
}]))
}
"u2" => {
Self::expect_param_count(name, params, 2, line_num)?;
Self::check_arity(name, qubits, 1)?;
let phi = params[0];
let lam = params[1];
let isqrt2 = std::f64::consts::FRAC_1_SQRT_2;
let one = Complex64::new(isqrt2, 0.0);
let mat = [
[one, -Complex64::from_polar(isqrt2, lam)],
[
Complex64::from_polar(isqrt2, phi),
Complex64::from_polar(isqrt2, phi + lam),
],
];
Ok(Some(vec![Instruction::Gate {
gate: Gate::Fused(Box::new(mat)),
targets: smallvec![qubits[0]],
}]))
}
"u3" | "u" | "U" => {
Self::expect_param_count(name, params, 3, line_num)?;
Self::check_arity(name, qubits, 1)?;
let theta = params[0];
let phi = params[1];
let lam = params[2];
let mat = Self::u_matrix(theta, phi, lam);
Ok(Some(vec![Instruction::Gate {
gate: Gate::Fused(Box::new(mat)),
targets: smallvec![qubits[0]],
}]))
}
_ => Ok(None),
}
}
fn check_arity(name: &str, qubits: &[usize], expected: usize) -> Result<()> {
if qubits.len() != expected {
return Err(PrismError::GateArity {
gate: name.to_string(),
expected,
got: qubits.len(),
});
}
Ok(())
}
fn u_matrix(theta: f64, phi: f64, lam: f64) -> [[Complex64; 2]; 2] {
let c = (theta / 2.0).cos();
let s = (theta / 2.0).sin();
[
[Complex64::new(c, 0.0), -Complex64::from_polar(s, lam)],
[
Complex64::from_polar(s, phi),
Complex64::from_polar(c, phi + lam),
],
]
}
fn r_matrix(theta: f64, phi: f64) -> [[Complex64; 2]; 2] {
let zero_phase = Complex64::new((theta / 2.0).cos(), 0.0);
let off = Complex64::new(0.0, -1.0) * (theta / 2.0).sin();
[
[zero_phase, off * Complex64::from_polar(1.0, -phi)],
[off * Complex64::from_polar(1.0, phi), zero_phase],
]
}
fn cu_target_matrix(theta: f64, phi: f64, lam: f64, gamma: f64) -> [[Complex64; 2]; 2] {
let phase = Complex64::from_polar(1.0, gamma);
let u = Self::u_matrix(theta, phi, lam);
[
[phase * u[0][0], phase * u[0][1]],
[phase * u[1][0], phase * u[1][1]],
]
}
fn xx_plus_yy_matrix(theta: f64, beta: f64) -> [[Complex64; 4]; 4] {
let zero = Complex64::new(0.0, 0.0);
let one = Complex64::new(1.0, 0.0);
let c = Complex64::new((theta / 2.0).cos(), 0.0);
let s = Complex64::new(0.0, -(theta / 2.0).sin());
[
[one, zero, zero, zero],
[zero, c, s * Complex64::from_polar(1.0, -beta), zero],
[zero, s * Complex64::from_polar(1.0, beta), c, zero],
[zero, zero, zero, one],
]
}
fn xx_minus_yy_matrix(theta: f64, beta: f64) -> [[Complex64; 4]; 4] {
let zero = Complex64::new(0.0, 0.0);
let one = Complex64::new(1.0, 0.0);
let c = Complex64::new((theta / 2.0).cos(), 0.0);
let s = Complex64::new(0.0, -(theta / 2.0).sin());
[
[c, zero, zero, s * Complex64::from_polar(1.0, -beta)],
[zero, one, zero, zero],
[zero, zero, one, zero],
[s * Complex64::from_polar(1.0, beta), zero, zero, c],
]
}
fn gpi_matrix(phi: f64) -> [[Complex64; 2]; 2] {
let zero = Complex64::new(0.0, 0.0);
[
[
zero,
Complex64::from_polar(1.0, -std::f64::consts::TAU * phi),
],
[
Complex64::from_polar(1.0, std::f64::consts::TAU * phi),
zero,
],
]
}
fn gpi2_matrix(phi: f64) -> [[Complex64; 2]; 2] {
let one = Complex64::new(std::f64::consts::FRAC_1_SQRT_2, 0.0);
let off = Complex64::new(0.0, -std::f64::consts::FRAC_1_SQRT_2);
[
[
one,
off * Complex64::from_polar(1.0, -std::f64::consts::TAU * phi),
],
[
off * Complex64::from_polar(1.0, std::f64::consts::TAU * phi),
one,
],
]
}
fn ms_matrix(phi0: f64, phi1: f64, theta: f64) -> [[Complex64; 4]; 4] {
let zero = Complex64::new(0.0, 0.0);
let c = Complex64::new((std::f64::consts::PI * theta).cos(), 0.0);
let s = Complex64::new(0.0, -(std::f64::consts::PI * theta).sin());
let sum = phi0 + phi1;
let diff = phi0 - phi1;
[
[
c,
zero,
zero,
s * Complex64::from_polar(1.0, -std::f64::consts::TAU * sum),
],
[
zero,
c,
s * Complex64::from_polar(1.0, -std::f64::consts::TAU * diff),
zero,
],
[
zero,
s * Complex64::from_polar(1.0, std::f64::consts::TAU * diff),
c,
zero,
],
[
s * Complex64::from_polar(1.0, std::f64::consts::TAU * sum),
zero,
zero,
c,
],
]
}
fn syc_matrix() -> [[Complex64; 4]; 4] {
let zero = Complex64::new(0.0, 0.0);
let one = Complex64::new(1.0, 0.0);
let neg_i = Complex64::new(0.0, -1.0);
[
[one, zero, zero, zero],
[zero, zero, neg_i, zero],
[zero, neg_i, zero, zero],
[
zero,
zero,
zero,
Complex64::from_polar(1.0, -std::f64::consts::PI / 6.0),
],
]
}
fn sqrt_iswap_matrix(sign: f64) -> [[Complex64; 4]; 4] {
let zero = Complex64::new(0.0, 0.0);
let one = Complex64::new(1.0, 0.0);
let half = Complex64::new(std::f64::consts::FRAC_1_SQRT_2, 0.0);
let off = Complex64::new(0.0, sign * std::f64::consts::FRAC_1_SQRT_2);
[
[one, zero, zero, zero],
[zero, half, off, zero],
[zero, off, half, zero],
[zero, zero, zero, one],
]
}
fn expect_param_count(
gate: &str,
params: &[f64],
expected: usize,
line_num: usize,
) -> Result<()> {
if params.len() != expected {
return Err(PrismError::InvalidParameter {
message: format!(
"`{gate}` at line {line_num} requires {expected} parameter(s), got {}",
params.len()
),
});
}
Ok(())
}
}
#[cfg(test)]
#[path = "openqasm_tests.rs"]
mod tests;