use crate::circuit::Circuit;
use crate::circuit::bit::Qubit;
use crate::circuit::circuit_param::CircuitParam;
use crate::circuit::gate::directive::Directive;
use crate::circuit::gate::standard_gate::StandardGate;
use crate::circuit::gate::{ClassicalDataOp, Instruction};
use crate::circuit::operation::Operation;
use std::fmt::Write;
use std::path::Path;
const PI: f64 = std::f64::consts::PI;
const PI_2: f64 = std::f64::consts::PI / 2.0;
const PI_4: f64 = std::f64::consts::PI / 4.0;
#[derive(Debug)]
pub enum QcisDumpError {
IoError(std::io::Error),
UnsupportedGate(String),
UnsupportedClassicalData(String),
UnsupportedClassicalControl(String),
SymbolicParameter(String),
InvalidDelayParameter(String),
}
impl std::fmt::Display for QcisDumpError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
QcisDumpError::IoError(e) => write!(f, "IO error: {}", e),
QcisDumpError::UnsupportedGate(g) => {
write!(
f,
"Unsupported gate '{g}': it is not represented by the QCIS circuit format"
)
}
QcisDumpError::UnsupportedClassicalData(operation) => write!(
f,
"Unsupported classical data operation '{operation}': QCIS does not represent classical storage"
),
QcisDumpError::UnsupportedClassicalControl(operation) => write!(
f,
"Unsupported classical control operation '{operation}': QCIS does not represent classical control flow"
),
QcisDumpError::SymbolicParameter(p) => {
write!(
f,
"Symbolic parameter '{}': QCIS only supports numeric parameters. Please bind all symbolic parameters to numeric values before dumping.",
p
)
}
QcisDumpError::InvalidDelayParameter(reason) => {
write!(f, "Invalid QCIS delay parameter: {reason}")
}
}
}
}
impl std::error::Error for QcisDumpError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
QcisDumpError::IoError(e) => Some(e),
_ => None,
}
}
}
impl From<std::io::Error> for QcisDumpError {
fn from(e: std::io::Error) -> Self {
QcisDumpError::IoError(e)
}
}
pub fn dump<P: AsRef<Path>>(circuit: &Circuit, path: P) -> Result<(), QcisDumpError> {
let content = dumps(circuit)?;
std::fs::write(path, content)?;
Ok(())
}
pub fn to_path<P: AsRef<Path>>(circuit: &Circuit, path: P) -> Result<(), QcisDumpError> {
dump(circuit, path)
}
pub fn dumps(circuit: &Circuit) -> Result<String, QcisDumpError> {
let mut output = String::new();
let operations = circuit.operations();
for op in operations {
let line = operation_to_qcis(op, circuit)?;
if !line.is_empty() {
writeln!(output, "{}", line)
.map_err(|e| QcisDumpError::IoError(std::io::Error::other(e)))?;
}
}
Ok(output)
}
pub fn to_string(circuit: &Circuit) -> Result<String, QcisDumpError> {
dumps(circuit)
}
fn operation_to_qcis(op: &Operation, circuit: &Circuit) -> Result<String, QcisDumpError> {
match &op.instruction {
Instruction::Standard(gate) => {
standard_gate_to_qcis(*gate, &op.qubits, &op.params, circuit)
}
Instruction::McGate(_) => Err(QcisDumpError::UnsupportedGate(
"Multi-controlled gate".to_string(),
)),
Instruction::UnitaryGate(_) => {
Err(QcisDumpError::UnsupportedGate("UnitaryGate".to_string()))
}
Instruction::CircuitGate(_) => Err(QcisDumpError::UnsupportedGate(
"CircuitGate (custom gate)".to_string(),
)),
Instruction::ClassicalData(ClassicalDataOp::MeasureBit { .. })
| Instruction::ClassicalData(ClassicalDataOp::MeasureBits { .. }) => {
Ok(format!("M {}", format_qubits(&op.qubits)))
}
Instruction::ClassicalData(ClassicalDataOp::Store { .. }) => {
Err(QcisDumpError::UnsupportedClassicalData("store".to_string()))
}
Instruction::ClassicalControl(control) => Err(QcisDumpError::UnsupportedClassicalControl(
format!("{control:?}"),
)),
Instruction::Directive(dir) => directive_to_qcis(*dir, &op.qubits),
Instruction::Delay => delay_to_qcis(&op.qubits, &op.params, circuit),
}
}
fn format_qubits(qubits: &[Qubit]) -> String {
qubits
.iter()
.map(|q| format!("Q{}", q.id()))
.collect::<Vec<_>>()
.join(" ")
}
fn format_params(params: &[CircuitParam], circuit: &Circuit) -> Result<String, QcisDumpError> {
let formatted: Result<Vec<_>, _> = params
.iter()
.map(|p| format_circuit_param(p, circuit))
.collect();
Ok(formatted?.join(" "))
}
fn format_circuit_param(param: &CircuitParam, circuit: &Circuit) -> Result<String, QcisDumpError> {
match param {
CircuitParam::Fixed(v) => Ok(format_float(*v)),
CircuitParam::Index(idx) => {
if let Some(param) = circuit.parameters().iter().nth(*idx as usize) {
match param.evaluate(&None) {
Ok(value) => Ok(format_float(value)),
Err(_) => Ok(param.to_string().replace("π", "pi")),
}
} else {
Err(QcisDumpError::SymbolicParameter(format!("p{}", idx)))
}
}
}
}
fn format_float(v: f64) -> String {
if (v - PI).abs() < 1e-10 {
"pi".to_string()
} else if (v - (-PI)).abs() < 1e-10 {
"-pi".to_string()
} else if (v - PI_2).abs() < 1e-10 {
"pi/2".to_string()
} else if (v - (-PI_2)).abs() < 1e-10 {
"-pi/2".to_string()
} else if (v - PI_4).abs() < 1e-10 {
"pi/4".to_string()
} else if (v - (-PI_4)).abs() < 1e-10 {
"-pi/4".to_string()
} else if v == 0.0 {
"0".to_string()
} else if v == 1.0 {
"1".to_string()
} else if v == -1.0 {
"-1".to_string()
} else if v.fract() == 0.0 {
format!("{:.0}", v)
} else {
format!("{:.10}", v)
.trim_end_matches('0')
.trim_end_matches('.')
.to_string()
}
}
fn standard_gate_to_qcis(
gate: StandardGate,
qubits: &[Qubit],
params: &[CircuitParam],
circuit: &Circuit,
) -> Result<String, QcisDumpError> {
let qubit_str = format_qubits(qubits);
let param_str = format_params(params, circuit)?;
let gate_name = match gate {
StandardGate::H => "H",
StandardGate::RX => "RX",
StandardGate::RXX => "RXX",
StandardGate::RXY => "RXY",
StandardGate::RY => "RY",
StandardGate::RYY => "RYY",
StandardGate::RZ => "RZ",
StandardGate::RZX => "RZX",
StandardGate::RZZ => "RZZ",
StandardGate::S => "S",
StandardGate::SDG => "SD",
StandardGate::SWAP => "SWAP",
StandardGate::T => "T",
StandardGate::TDG => "TD",
StandardGate::U => "U",
StandardGate::X => "X",
StandardGate::XY => "XY",
StandardGate::X2P => "X2P",
StandardGate::X2M => "X2M",
StandardGate::XY2P => "XY2P",
StandardGate::XY2M => "XY2M",
StandardGate::Y => "Y",
StandardGate::Y2P => "Y2P",
StandardGate::Y2M => "Y2M",
StandardGate::Z => "Z",
StandardGate::Phase => "PHASE",
StandardGate::CX => "CX",
StandardGate::CCX => "CCX",
StandardGate::CY => "CY",
StandardGate::CZ => "CZ",
StandardGate::CRX => "CRX",
StandardGate::CRY => "CRY",
StandardGate::CRZ => "CRZ",
StandardGate::FSIM => "FSIM",
StandardGate::I | StandardGate::GPhase => {
return Err(QcisDumpError::UnsupportedGate(format!("{gate:?}")));
}
};
let line = if param_str.is_empty() {
format!("{} {}", gate_name, qubit_str)
} else {
format!("{} {} {}", gate_name, qubit_str, param_str)
};
Ok(line)
}
fn directive_to_qcis(dir: Directive, qubits: &[Qubit]) -> Result<String, QcisDumpError> {
match dir {
Directive::Measure => {
let qubit_str = format_qubits(qubits);
Ok(format!("M {}", qubit_str))
}
Directive::Barrier => {
let qubit_str = format_qubits(qubits);
Ok(format!("B {}", qubit_str))
}
Directive::Reset => Err(QcisDumpError::UnsupportedGate("Reset".to_string())),
}
}
fn delay_to_qcis(
qubits: &[Qubit],
params: &[CircuitParam],
circuit: &Circuit,
) -> Result<String, QcisDumpError> {
let qubit_str = format_qubits(qubits);
let param_str = format_delay_param(params, circuit)?;
Ok(format!("I {} {}", qubit_str, param_str))
}
fn format_delay_param(params: &[CircuitParam], circuit: &Circuit) -> Result<String, QcisDumpError> {
let [param] = params else {
return Err(QcisDumpError::InvalidDelayParameter(
"QCIS delay requires exactly one tick parameter".to_string(),
));
};
let value = match param {
CircuitParam::Fixed(value) => *value,
CircuitParam::Index(idx) => {
let param = circuit
.parameters()
.iter()
.nth(*idx as usize)
.ok_or_else(|| QcisDumpError::SymbolicParameter(format!("p{}", idx)))?;
param
.evaluate(&None)
.map_err(|_| QcisDumpError::SymbolicParameter(param.to_string()))?
}
};
if !value.is_finite() || value < 0.0 || value.fract().abs() >= 1e-10 {
return Err(QcisDumpError::InvalidDelayParameter(format!(
"{value} is not a non-negative integer number of 0.5 ns ticks"
)));
}
Ok(format_float(value))
}
#[cfg(test)]
#[path = "./dump_test.rs"]
mod dump_test;