use num_complex::Complex64;
use super::braket::{self, NoiseSpec, ResultSpec};
use crate::circuit::qasm::ast::{self, DefParam};
use crate::circuit::synthesis;
use crate::circuit::{
Circuit, ClassicalCondition, Instruction, MAX_REGION_DEPTH, ParamLink, Parameters, SmallVec,
guarded, pauli_rotation_gate, smallvec,
};
use crate::error::{PrismError, Result};
use crate::gates::{Gate, spectral};
use crate::sim::noise::{NoiseEvent, NoiseModel};
use crate::sim::unified_pauli::{PauliAxis, PauliTerm};
use std::collections::HashMap;
fn parse_error(line: usize, message: impl Into<String>) -> PrismError {
PrismError::Parse {
line,
message: message.into(),
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum Dialect {
#[default]
Native,
Braket,
}
impl Dialect {
fn native_turns(self, angle: f64) -> f64 {
match self {
Dialect::Native => angle,
Dialect::Braket => angle / std::f64::consts::TAU,
}
}
}
pub fn parse(input: &str) -> Result<Circuit> {
parse_with(input, Dialect::Native)
}
pub fn parse_with(input: &str, dialect: Dialect) -> Result<Circuit> {
let (circuit, params) = parse_parametric_with(input, dialect)?;
if params.num_slots() > 0 {
return Err(PrismError::InvalidParameter {
message: format!(
"program declares {} `input` parameter(s); parse it with `parse_parametric` and bind them",
params.num_slots()
),
});
}
Ok(circuit)
}
pub fn parse_parametric(input: &str) -> Result<(Circuit, Parameters)> {
parse_parametric_with(input, Dialect::Native)
}
pub fn parse_parametric_with(input: &str, dialect: Dialect) -> Result<(Circuit, Parameters)> {
Parser::new_with(input, dialect).parse()
}
#[derive(Debug, Clone)]
pub struct BraketProgram {
pub circuit: Circuit,
pub parameters: Parameters,
pub results: Vec<ResultSpec>,
pub noise: Option<NoiseModel>,
}
pub fn parse_braket(input: &str) -> Result<BraketProgram> {
let mut parser = Parser::new_with(input, Dialect::Braket);
let (circuit, parameters, results, noise) = parser.parse_program()?;
Ok(BraketProgram {
circuit,
parameters,
results,
noise,
})
}
#[derive(Clone, Copy)]
enum Modifier {
Inv,
Pow(f64),
Ctrl {
negated: bool,
},
}
impl Modifier {
fn control(&self) -> Option<bool> {
match self {
Modifier::Ctrl { negated } => Some(*negated),
_ => None,
}
}
}
struct Register {
offset: usize,
size: usize,
}
struct GateDefinition<'a> {
params: Vec<&'a str>,
qubits: Vec<&'a str>,
body: ast::Block<'a>,
}
struct DefDefinition<'a> {
args: Vec<DefParam<'a>>,
body: ast::Block<'a>,
}
struct Alias {
kind: ast::RegisterKind,
indices: Vec<usize>,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum ClassicalType {
Int,
Bool,
Float,
}
struct ClassicalDecl {
ty: ClassicalType,
constant: bool,
}
fn invalid_index(kind: ast::RegisterKind, index: usize, register_size: usize) -> PrismError {
match kind {
ast::RegisterKind::Qubit => PrismError::InvalidQubit {
index,
register_size,
},
ast::RegisterKind::Classical => PrismError::InvalidClassicalBit {
index,
register_size,
},
}
}
pub(crate) struct Parser<'a> {
input: &'a str,
qregs: HashMap<&'a str, Register>,
cregs: HashMap<&'a str, Register>,
gate_defs: HashMap<&'a str, GateDefinition<'a>>,
def_defs: HashMap<&'a str, DefDefinition<'a>>,
total_qubits: usize,
total_cbits: usize,
gate_expansion_depth: usize,
region_depth: usize,
param_vars: Option<HashMap<&'a str, f64>>,
inputs: HashMap<&'a str, usize>,
input_names: Vec<String>,
links: Vec<ParamLink>,
pending_input_slot: Option<usize>,
nested: bool,
dialect: Dialect,
results: Vec<ResultSpec>,
noise_specs: Vec<(usize, NoiseSpec)>,
pending_noise: Option<NoiseSpec>,
verbatim_pending: bool,
physical: bool,
aliases: HashMap<&'a str, Alias>,
classical: HashMap<&'a str, ClassicalDecl>,
}
const MAX_GATE_EXPANSION_DEPTH: usize = 32;
const MAX_FOR_ITERATIONS: i64 = 1_000_000;
const MAX_POW_REPEATS: i64 = 1_000_000;
fn pauli_rotation_axes(name: &str) -> Option<Vec<PauliAxis>> {
let letters = name.strip_prefix('r')?;
if letters.len() < 2 {
return None;
}
letters.chars().map(PauliAxis::from_letter).collect()
}
fn bare_pauli_rotation(name: &str) -> Option<&'static str> {
match name {
"xx" => Some("rxx"),
"yy" => Some("ryy"),
"zz" => Some("rzz"),
_ => None,
}
}
fn nest_default_arm(
offset: usize,
size: usize,
labels: &[u64],
body: Vec<Instruction>,
region_depth: usize,
line_num: usize,
) -> Result<Vec<Instruction>> {
if region_depth + labels.len() > MAX_REGION_DEPTH {
return Err(parse_error(
line_num,
format!(
"`switch` with a `default` nests one region per case label and would \
pass the depth bound of {MAX_REGION_DEPTH} at {} labels",
labels.len()
),
));
}
let mut current = body;
for &value in labels.iter().rev() {
let condition = ClassicalCondition::RegisterNotEquals {
offset,
size,
value,
};
current = guarded(condition, current).into_iter().collect();
}
Ok(current)
}
impl<'a> Parser<'a> {
fn new_with(input: &'a str, dialect: Dialect) -> Self {
Self {
dialect,
input,
qregs: HashMap::new(),
cregs: HashMap::new(),
gate_defs: HashMap::new(),
def_defs: HashMap::new(),
total_qubits: 0,
total_cbits: 0,
gate_expansion_depth: 0,
region_depth: 0,
param_vars: None,
inputs: HashMap::new(),
input_names: Vec::new(),
links: Vec::new(),
pending_input_slot: None,
nested: false,
results: Vec::new(),
noise_specs: Vec::new(),
pending_noise: None,
verbatim_pending: false,
physical: false,
aliases: HashMap::new(),
classical: HashMap::new(),
}
}
fn parse(mut self) -> Result<(Circuit, Parameters)> {
let (circuit, params, _, _) = self.parse_program()?;
Ok((circuit, params))
}
fn build_noise_model(&mut self, circuit: &Circuit) -> Result<Option<NoiseModel>> {
if self.noise_specs.is_empty() {
return Ok(None);
}
let mut model = NoiseModel {
after_gate: vec![Vec::new(); circuit.instructions.len()],
readout: vec![None; circuit.num_classical_bits],
};
for (index, spec) in std::mem::take(&mut self.noise_specs) {
model.after_gate[index].push(NoiseEvent {
channel: spec.channel,
qubits: spec.qubits.into_iter().collect(),
});
}
model.validate_for(circuit)?;
Ok(Some(model))
}
fn bind_classical(&mut self, name: &'a str, ty: ClassicalType, value: f64, constant: bool) {
self.classical.insert(name, ClassicalDecl { ty, constant });
self.param_vars
.get_or_insert_with(HashMap::new)
.insert(name, value);
}
fn reject_redeclaration(&self, name: &str, line_num: usize) -> Result<()> {
let clash = if self.classical.contains_key(name) {
"a classical variable"
} else if self.aliases.contains_key(name) {
"an alias"
} else if self.qregs.contains_key(name) || self.cregs.contains_key(name) {
"a register"
} else if self.inputs.contains_key(name) {
"an input"
} else {
return Ok(());
};
Err(parse_error(
line_num,
format!("`{name}` is already {clash}"),
))
}
fn reject_mixed_addressing(&self, line_num: usize) -> Result<()> {
if self.physical {
return Err(PrismError::UnsupportedConstruct {
construct:
"a qubit register beside physical qubits (`$0`), which address the same hardware two ways"
.to_string(),
line: line_num,
});
}
Ok(())
}
fn build_measurements(
qubits: SmallVec<[usize; 4]>,
cbits: SmallVec<[usize; 4]>,
line_num: usize,
) -> Result<Vec<Instruction>> {
if qubits.len() != cbits.len() {
return Err(parse_error(
line_num,
format!(
"register size mismatch in measure: {} qubits vs {} classical bits",
qubits.len(),
cbits.len()
),
));
}
Ok(qubits
.into_iter()
.zip(cbits)
.map(|(qubit, classical_bit)| Instruction::Measure {
qubit,
classical_bit,
})
.collect())
}
fn resolve_global_phase(
&self,
params: &[f64],
modifiers: &[Modifier],
qubits: &[usize],
line_num: usize,
) -> Result<Vec<Instruction>> {
Self::expect_param_count("gphase", params, 1, line_num)?;
let controls: Vec<bool> = modifiers.iter().filter_map(Modifier::control).collect();
if qubits.len() != controls.len() {
return Err(PrismError::GateArity {
gate: "gphase".to_string(),
expected: controls.len(),
got: qubits.len(),
});
}
let mut angle = params[0];
for modifier in modifiers {
match modifier {
Modifier::Inv => angle = -angle,
Modifier::Pow(k) => angle *= k,
Modifier::Ctrl { .. } => {}
}
}
if controls.is_empty() {
if self.total_qubits == 0 {
return Err(parse_error(
line_num,
"`gphase` needs a qubit to carry the phase, and none is declared",
));
}
let scale = Complex64::from_polar(1.0, angle);
let zero = Complex64::new(0.0, 0.0);
return Ok(vec![Self::ig(
Gate::Fused(Box::new([[scale, zero], [zero, scale]])),
&[0],
)]);
}
let negated: Vec<usize> = qubits
.iter()
.zip(&controls)
.filter(|&(_, &negated)| negated)
.map(|(&qubit, _)| qubit)
.collect();
let mut out: Vec<Instruction> = negated.iter().map(|&q| Self::ig(Gate::X, &[q])).collect();
let phase = Gate::P(angle);
out.push(match qubits.split_last() {
Some((&last, [])) => Self::ig(phase, &[last]),
Some((_, rest)) => Self::ig(Gate::mcu(phase.matrix_2x2(), rest.len() as u8), qubits),
None => unreachable!("the control-free case returned above"),
});
out.extend(negated.iter().map(|&q| Self::ig(Gate::X, &[q])));
Ok(out)
}
fn resolve_pauli_rotation(
gate_name: &str,
axes: &[PauliAxis],
params: &[f64],
qubits: &SmallVec<[usize; 4]>,
line_num: usize,
) -> Result<Instruction> {
Self::expect_param_count(gate_name, params, 1, line_num)?;
if qubits.len() != axes.len() {
return Err(PrismError::GateArity {
gate: gate_name.to_string(),
expected: axes.len(),
got: qubits.len(),
});
}
let mut seen: SmallVec<[usize; 4]> = qubits.clone();
seen.sort_unstable();
if let Some(pair) = seen.windows(2).find(|pair| pair[0] == pair[1]) {
return Err(parse_error(
line_num,
format!("Pauli rotation `{gate_name}` names qubit {} twice", pair[0]),
));
}
let factors: Vec<PauliTerm> = qubits
.iter()
.zip(axes)
.map(|(&qubit, &axis)| PauliTerm::new(qubit, axis))
.collect();
let (gate, targets) = pauli_rotation_gate(params[0], &factors);
Ok(Instruction::Gate { gate, targets })
}
fn check_every_instruction_is_bindable(
instrs: &[Instruction],
gate_name: &str,
line_num: usize,
) -> Result<()> {
let bindable = instrs.iter().all(|instr| {
matches!(instr, Instruction::Gate { gate, .. } if gate.pauli_generator().is_some())
});
if !bindable {
return Err(PrismError::UnsupportedConstruct {
construct: format!(
"`input` on `{gate_name}`, which carries no rotation angle to bind"
),
line: line_num,
});
}
Ok(())
}
fn resolve_gate_application_once(
&self,
gate_name: &str,
params: &[f64],
modifiers: &[Modifier],
qubits: &SmallVec<[usize; 4]>,
has_input: bool,
line_num: usize,
) -> Result<Vec<Instruction>> {
let polarity: Vec<bool> = modifiers.iter().filter_map(Modifier::control).collect();
if polarity.is_empty() {
return self
.resolve_gate_body(gate_name, params, modifiers, qubits, has_input, line_num);
}
if qubits.len() <= polarity.len() {
return Err(PrismError::GateArity {
gate: gate_name.to_string(),
expected: polarity.len() + 1,
got: qubits.len(),
});
}
let (controls, rest) = qubits.split_at(polarity.len());
let unitary: Vec<Modifier> = modifiers
.iter()
.filter(|m| m.control().is_none())
.copied()
.collect();
let body = self.resolve_gate_body(
gate_name,
params,
&unitary,
&rest.iter().copied().collect(),
has_input,
line_num,
)?;
let controlled = Self::control_expansion(body, controls, line_num)?;
Ok(Self::conjugate_negations(controlled, controls, &polarity))
}
fn resolve_gate_body(
&self,
gate_name: &str,
params: &[f64],
modifiers: &[Modifier],
qubits: &SmallVec<[usize; 4]>,
has_input: bool,
line_num: usize,
) -> Result<Vec<Instruction>> {
if let Some(instrs) = Self::resolve_decomposed_gate(gate_name, params, qubits, line_num)? {
if has_input {
return Err(PrismError::UnsupportedConstruct {
construct: format!("`input` on `{gate_name}`, which lowers to a gate sequence"),
line: line_num,
});
}
return Self::modify_expansion(instrs, modifiers, gate_name, line_num);
}
if let Some(instrs) = self.expand_user_gate(gate_name, params, qubits, line_num)? {
if has_input {
return Err(PrismError::UnsupportedConstruct {
construct: format!("`input` on user-defined gate `{gate_name}`"),
line: line_num,
});
}
return Self::modify_expansion(instrs, modifiers, gate_name, line_num);
}
if let Some(axes) = pauli_rotation_axes(bare_pauli_rotation(gate_name).unwrap_or(gate_name))
{
let instr = Self::resolve_pauli_rotation(gate_name, &axes, params, qubits, line_num)?;
return Self::modify_expansion(vec![instr], modifiers, gate_name, line_num);
}
let mut gate = Self::resolve_gate(gate_name, params, self.dialect, line_num)?;
let expected = gate.num_qubits();
if qubits.len() != expected {
return Err(PrismError::GateArity {
gate: gate_name.to_string(),
expected,
got: qubits.len(),
});
}
let foldable = expected == 1 || modifiers.iter().all(|m| !matches!(m, Modifier::Pow(_)));
if !foldable {
let single = vec![Instruction::Gate {
gate,
targets: qubits.clone(),
}];
return Self::modify_expansion(single, modifiers, gate_name, line_num);
}
for modifier in modifiers.iter().rev() {
gate = Self::apply_modifier(gate, modifier);
}
Ok(vec![Instruction::Gate {
gate,
targets: qubits.clone(),
}])
}
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(parse_error(
line_num,
format!(
"register size mismatch in `{gate_name}`: \
expected {broadcast_len} qubits but got {}",
arg.len()
),
));
}
}
}
Ok(broadcast_len)
}
fn classical_copy(&self) -> HashMap<&'a str, ClassicalDecl> {
self.classical
.iter()
.map(|(name, decl)| {
(
*name,
ClassicalDecl {
ty: decl.ty,
constant: decl.constant,
},
)
})
.collect()
}
fn check_version_number(version: &str, line_num: usize) -> Result<()> {
let major = version
.split_once('.')
.map_or(version, |(major, _)| major)
.trim();
match major.parse::<u32>() {
Ok(2) | Ok(3) => Ok(()),
Ok(_) => Err(PrismError::UnsupportedConstruct {
construct: format!("OPENQASM {version}, where this parser reads 2 and 3"),
line: line_num,
}),
Err(_) => Err(parse_error(
line_num,
format!("`{version}` is not an OpenQASM version"),
)),
}
}
fn transposed(matrix: &[Complex64], dim: usize) -> Vec<Complex64> {
let mut out = vec![Complex64::new(0.0, 0.0); dim * dim];
for row in 0..dim {
for column in 0..dim {
out[column * dim + row] = matrix[row * dim + column];
}
}
out
}
fn ctrl_on_expansion_error(name: &str, line_num: usize) -> PrismError {
PrismError::UnsupportedConstruct {
construct: format!(
"ctrl @ `{name}`, a subroutine rather than a gate, so it has no controlled form"
),
line: line_num,
}
}
fn repeat_expansion(
instrs: Vec<Instruction>,
k: f64,
name: &str,
line_num: usize,
) -> Result<Vec<Instruction>> {
if k.fract() != 0.0 {
let Some((span, matrix)) = synthesis::expansion_matrix(&instrs) else {
return Err(PrismError::UnsupportedConstruct {
construct: format!(
"pow({k}) @ `{name}`, whose lowering spans more than {} qubits or \
carries an instruction with no matrix",
synthesis::MAX_COMPOSED_QUBITS
),
line: line_num,
});
};
let dim = 1usize << span.len();
let powered = spectral::unitary_power(&Self::transposed(&matrix, dim), dim, k);
return Ok(synthesis::dense_unitary(
&Self::transposed(&powered, dim),
&span,
));
}
let base = if k < 0.0 {
Self::invert_expansion(instrs, name, line_num)?
} else {
instrs
};
let repeats = k.abs() as usize;
let mut powered = Vec::with_capacity(base.len() * repeats);
for _ in 0..repeats {
powered.extend(base.iter().cloned());
}
Ok(powered)
}
fn control_expansion(
instrs: Vec<Instruction>,
controls: &[usize],
line_num: usize,
) -> Result<Vec<Instruction>> {
let mut out = Vec::with_capacity(instrs.len());
for instr in instrs {
if let Instruction::Gate { targets, .. } = &instr
&& let Some(&shared) = targets.iter().find(|target| controls.contains(target))
{
return Err(parse_error(
line_num,
format!("qubit {shared} is both a control and a target"),
));
}
out.extend(Self::control_instruction(instr, controls, line_num)?);
}
Ok(out)
}
fn control_instruction(
instr: Instruction,
controls: &[usize],
line_num: usize,
) -> Result<Vec<Instruction>> {
let Instruction::Gate { gate, targets } = instr else {
return Err(PrismError::UnsupportedConstruct {
construct: "ctrl @ a body that measures, resets, or branches".to_string(),
line: line_num,
});
};
let extra = controls.len();
let prefixed = |rest: &[usize]| -> SmallVec<[usize; 4]> {
controls
.iter()
.copied()
.chain(rest.iter().copied())
.collect()
};
let widen = |num_controls: usize| -> Result<u8> {
num_controls
.checked_add(extra)
.and_then(|total| u8::try_from(total).ok())
.ok_or_else(|| PrismError::UnsupportedConstruct {
construct: format!("ctrl @ chain past {} controls", u8::MAX),
line: line_num,
})
};
let controlled = match &gate {
g if g.num_qubits() == 1 => {
let mat = gate.matrix_2x2();
vec![Self::ig(
Self::controlled_unitary(mat, widen(0)?),
&prefixed(&targets),
)]
}
Gate::Cx => vec![Self::ig(
Self::controlled_unitary(Gate::X.matrix_2x2(), widen(1)?),
&prefixed(&targets),
)],
Gate::Cz => vec![Self::ig(
Self::controlled_unitary(Gate::Z.matrix_2x2(), widen(1)?),
&prefixed(&targets),
)],
Gate::Cu(mat) => vec![Self::ig(
Self::controlled_unitary(**mat, widen(1)?),
&prefixed(&targets),
)],
Gate::Mcu(data) => vec![Self::ig(
Self::controlled_unitary(data.mat, widen(data.num_controls as usize)?),
&prefixed(&targets),
)],
Gate::Swap => {
let (a, b) = (targets[0], targets[1]);
vec![
Self::ig(Gate::Cx, &[b, a]),
Self::ig(
Self::controlled_unitary(Gate::X.matrix_2x2(), widen(1)?),
&prefixed(&[a, b]),
),
Self::ig(Gate::Cx, &[b, a]),
]
}
Gate::Rzz(theta) => {
let (a, b) = (targets[0], targets[1]);
vec![
Self::ig(Gate::Cx, &[a, b]),
Self::ig(
Self::controlled_unitary(Gate::Rz(*theta).matrix_2x2(), widen(0)?),
&prefixed(&[b]),
),
Self::ig(Gate::Cx, &[a, b]),
]
}
other if other.num_qubits() == 2 => {
let mat = other.matrix_4x4();
let flat: Vec<Complex64> = mat.iter().flat_map(|row| row.iter().copied()).collect();
synthesis::controlled_dense(&flat, controls, &targets)
}
Gate::PauliRot(data) => {
let mut ladder = Vec::new();
crate::circuit::pauli_rotation_lowering(
data.theta(),
&targets,
data.axes(),
|gate, wires| ladder.push(Self::ig(gate, wires)),
);
Self::control_expansion(ladder, controls, line_num)?
}
other => {
return Err(PrismError::UnsupportedConstruct {
construct: format!(
"ctrl @ {}, which has no controlled form and no lowering to control",
other.name()
),
line: line_num,
});
}
};
Ok(controlled)
}
fn controlled_unitary(mat: [[num_complex::Complex64; 2]; 2], num_controls: u8) -> Gate {
if num_controls == 1 {
Self::resolve_controlled(mat)
} else {
Gate::mcu(mat, num_controls)
}
}
fn conjugate_negations(
instrs: Vec<Instruction>,
controls: &[usize],
polarity: &[bool],
) -> Vec<Instruction> {
let negated: Vec<usize> = polarity
.iter()
.zip(controls)
.filter(|(negated, _)| **negated)
.map(|(_, &qubit)| qubit)
.collect();
if negated.is_empty() {
return instrs;
}
let flips = || negated.iter().map(|&qubit| Self::ig(Gate::X, &[qubit]));
flips().chain(instrs).chain(flips()).collect()
}
fn modify_expansion(
instrs: Vec<Instruction>,
modifiers: &[Modifier],
name: &str,
line_num: usize,
) -> Result<Vec<Instruction>> {
let mut instrs = instrs;
for modifier in modifiers.iter().rev() {
instrs = match modifier {
Modifier::Inv => Self::invert_expansion(instrs, name, line_num)?,
Modifier::Pow(k) => Self::repeat_expansion(instrs, *k, name, line_num)?,
Modifier::Ctrl { .. } => return Err(Self::ctrl_on_expansion_error(name, line_num)),
};
}
Ok(instrs)
}
fn invert_expansion(
instrs: Vec<Instruction>,
name: &str,
line_num: usize,
) -> Result<Vec<Instruction>> {
instrs
.into_iter()
.rev()
.map(|instr| match instr {
Instruction::Gate { gate, targets } => Ok(Instruction::Gate {
gate: gate.inverse(),
targets,
}),
_ => Err(PrismError::UnsupportedConstruct {
construct: format!("inv @ `{name}`, whose body is not a gate sequence"),
line: line_num,
}),
})
.collect()
}
fn apply_modifier(gate: Gate, modifier: &Modifier) -> Gate {
match modifier {
Modifier::Inv => gate.inverse(),
Modifier::Pow(k) => gate.matrix_power_real(*k),
Modifier::Ctrl { .. } => unreachable!("controls are applied to the expansion"),
}
}
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 resolve_gate(name: &str, params: &[f64], dialect: Dialect, line_num: usize) -> Result<Gate> {
match name {
"id" | "i" => Ok(Gate::Id),
"x" => Ok(Gate::X),
"y" => Ok(Gate::Y),
"z" => Ok(Gate::Z),
"h" => Ok(Gate::H),
"s" => Ok(Gate::S),
"sdg" | "si" => Ok(Gate::Sdg),
"t" => Ok(Gate::T),
"tdg" | "ti" => 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" | "phaseshift" => {
Self::expect_param_count(name, params, 1, line_num)?;
Ok(Gate::P(params[0]))
}
"r" | "prx" => {
Self::expect_param_count(name, params, 2, line_num)?;
Ok(Gate::Fused(Box::new(Self::r_matrix(params[0], params[1]))))
}
"sx" | "v" => Ok(Gate::SX),
"sxdg" | "vi" => Ok(Gate::SXdg),
"cp" | "cphase" | "cphaseshift" => {
Self::expect_param_count(name, params, 1, line_num)?;
Ok(Gate::cphase(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" | "cv" => Ok(Gate::cu(Gate::SX.matrix_2x2())),
"cz" => Ok(Gate::Cz),
"swap" => Ok(Gate::Swap),
"ccx" | "toffoli" | "ccnot" => 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)?;
let phi = dialect.native_turns(params[0]);
Ok(Gate::Fused(Box::new(Self::gpi_matrix(phi))))
}
"gpi2" => {
Self::expect_param_count(name, params, 1, line_num)?;
let phi = dialect.native_turns(params[0]);
Ok(Gate::Fused(Box::new(Self::gpi2_matrix(phi))))
}
"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 = match params.get(2) {
Some(value) => dialect.native_turns(*value),
None => 0.25,
};
Ok(Gate::Fused2q(Box::new(Self::ms_matrix(
dialect.native_turns(params[0]),
dialect.native_turns(params[1]),
theta,
))))
}
"ecr" => Ok(Gate::Fused2q(Box::new(Self::ecr_matrix()))),
"xy" => {
Self::expect_param_count(name, params, 1, line_num)?;
Ok(Gate::Fused2q(Box::new(Self::xy_matrix(params[0]))))
}
"pswap" => {
Self::expect_param_count(name, params, 1, line_num)?;
Ok(Gate::Fused2q(Box::new(Self::pswap_matrix(params[0]))))
}
"cphaseshift00" => {
Self::expect_param_count(name, params, 1, line_num)?;
Ok(Gate::Fused2q(Box::new(Self::cphaseshift_matrix(
0, params[0],
))))
}
"cphaseshift01" => {
Self::expect_param_count(name, params, 1, line_num)?;
Ok(Gate::Fused2q(Box::new(Self::cphaseshift_matrix(
1, params[0],
))))
}
"cphaseshift10" => {
Self::expect_param_count(name, params, 1, line_num)?;
Ok(Gate::Fused2q(Box::new(Self::cphaseshift_matrix(
2, params[0],
))))
}
"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 ig(gate: Gate, targets: &[usize]) -> Instruction {
Instruction::Gate {
gate,
targets: SmallVec::from_slice(targets),
}
}
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![Self::ig(
Gate::mcu(Gate::X.matrix_2x2(), controls as u8),
qubits,
)]))
}
"rccx" => {
Self::check_arity(name, qubits, 3)?;
let c0 = qubits[0];
let c1 = qubits[1];
let target = qubits[2];
Ok(Some(vec![
Self::ig(Gate::H, &[target]),
Self::ig(Gate::T, &[target]),
Self::ig(Gate::Cx, &[c1, target]),
Self::ig(Gate::Tdg, &[target]),
Self::ig(Gate::Cx, &[c0, target]),
Self::ig(Gate::T, &[target]),
Self::ig(Gate::Cx, &[c1, target]),
Self::ig(Gate::Tdg, &[target]),
Self::ig(Gate::H, &[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![
Self::ig(Gate::H, &[target]),
Self::ig(Gate::T, &[target]),
Self::ig(Gate::Cx, &[c2, target]),
Self::ig(Gate::Tdg, &[target]),
Self::ig(Gate::H, &[target]),
Self::ig(Gate::Cx, &[c0, target]),
Self::ig(Gate::T, &[target]),
Self::ig(Gate::Cx, &[c1, target]),
Self::ig(Gate::Tdg, &[target]),
Self::ig(Gate::Cx, &[c0, target]),
Self::ig(Gate::T, &[target]),
Self::ig(Gate::Cx, &[c1, target]),
Self::ig(Gate::Tdg, &[target]),
Self::ig(Gate::H, &[target]),
Self::ig(Gate::T, &[target]),
Self::ig(Gate::Cx, &[c2, target]),
Self::ig(Gate::Tdg, &[target]),
Self::ig(Gate::H, &[target]),
]))
}
"cswap" | "fredkin" => {
Self::check_arity(name, qubits, 3)?;
let ctrl = qubits[0];
let t1 = qubits[1];
let t2 = qubits[2];
Ok(Some(vec![
Self::ig(Gate::Cx, &[t2, t1]),
Self::ig(Gate::mcu(Gate::X.matrix_2x2(), 2), &[ctrl, t1, t2]),
Self::ig(Gate::Cx, &[t2, t1]),
]))
}
"iswap" => {
Self::check_arity(name, qubits, 2)?;
let q0 = qubits[0];
let q1 = qubits[1];
Ok(Some(vec![
Self::ig(Gate::S, &[q0]),
Self::ig(Gate::S, &[q1]),
Self::ig(Gate::H, &[q0]),
Self::ig(Gate::Cx, &[q0, q1]),
Self::ig(Gate::Cx, &[q1, q0]),
Self::ig(Gate::H, &[q1]),
]))
}
"dcx" => {
Self::check_arity(name, qubits, 2)?;
let q0 = qubits[0];
let q1 = qubits[1];
Ok(Some(vec![
Self::ig(Gate::Cx, &[q0, q1]),
Self::ig(Gate::Cx, &[q1, q0]),
]))
}
"u1" => {
Self::expect_param_count(name, params, 1, line_num)?;
Self::check_arity(name, qubits, 1)?;
Ok(Some(vec![Self::ig(Gate::P(params[0]), &[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![Self::ig(
Gate::Fused(Box::new(mat)),
&[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![Self::ig(
Gate::Fused(Box::new(mat)),
&[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(())
}
pub(crate) 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],
]
}
pub(crate) 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]],
]
}
pub(crate) 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],
]
}
pub(crate) 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,
],
]
}
pub(crate) 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,
],
]
}
pub(crate) fn ecr_matrix() -> [[Complex64; 4]; 4] {
let zero = Complex64::new(0.0, 0.0);
let r = Complex64::new(std::f64::consts::FRAC_1_SQRT_2, 0.0);
let i = Complex64::new(0.0, std::f64::consts::FRAC_1_SQRT_2);
[
[zero, zero, r, i],
[zero, zero, i, r],
[r, -i, zero, zero],
[-i, r, zero, zero],
]
}
pub(crate) fn xy_matrix(theta: 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, zero],
[zero, s, c, zero],
[zero, zero, zero, one],
]
}
pub(crate) fn pswap_matrix(theta: f64) -> [[Complex64; 4]; 4] {
let zero = Complex64::new(0.0, 0.0);
let one = Complex64::new(1.0, 0.0);
let phase = Complex64::from_polar(1.0, theta);
[
[one, zero, zero, zero],
[zero, zero, phase, zero],
[zero, phase, zero, zero],
[zero, zero, zero, one],
]
}
pub(crate) fn cphaseshift_matrix(index: usize, theta: f64) -> [[Complex64; 4]; 4] {
let mut mat = [[Complex64::new(0.0, 0.0); 4]; 4];
for (i, row) in mat.iter_mut().enumerate() {
row[i] = if i == index {
Complex64::from_polar(1.0, theta)
} else {
Complex64::new(1.0, 0.0)
};
}
mat
}
pub(crate) 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),
],
]
}
pub(crate) 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(())
}
}
#[path = "openqasm_eval.rs"]
mod eval;
#[cfg(test)]
#[path = "openqasm_tests.rs"]
mod tests;