use clap::Parser;
use inquire::{error::InquireError, validator::Validation, Select, Text};
use std::{collections::HashMap, fmt::Display, path::Path, vec};
use quaru::{
openqasm,
operation::{self, Operation},
register::Register,
};
enum Choice {
Show,
Apply,
Measure,
Create,
OpenQASM,
Exit,
}
impl Choice {
fn choices(state: &State) -> Vec<Choice> {
let mut choices = Vec::new();
if !state.q_regs.is_empty() || !state.c_regs.is_empty() {
choices.append(&mut vec![Choice::Show]);
}
if !state.q_regs.is_empty() {
choices.append(&mut vec![Choice::Apply, Choice::Measure]);
}
choices.append(&mut vec![Choice::Create, Choice::OpenQASM, Choice::Exit]);
choices
}
}
impl Display for Choice {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Choice::Apply => write!(f, "Apply"),
Choice::Measure => write!(f, "Measure"),
Choice::Show => write!(f, "Show"),
Choice::Create => write!(f, "Create register"),
Choice::OpenQASM => write!(f, "Run OpenQASM program"),
Choice::Exit => write!(f, "Exit"),
}
}
}
#[derive(Debug)]
enum OperationType {
Unary,
Binary,
}
impl OperationType {
fn types() -> Vec<OperationType> {
vec![OperationType::Unary, OperationType::Binary]
}
fn size(&self) -> usize {
match self {
OperationType::Unary => 1,
OperationType::Binary => 2,
}
}
}
impl Display for OperationType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
OperationType::Unary => write!(f, "Unary"),
OperationType::Binary => write!(f, "Binary"),
}
}
}
enum UnaryOperation {
Identity,
Hadamard,
Phase,
Not,
PauliY,
PauliZ,
}
impl UnaryOperation {
fn operations() -> Vec<UnaryOperation> {
vec![
UnaryOperation::Identity,
UnaryOperation::Hadamard,
UnaryOperation::Phase,
UnaryOperation::Not,
UnaryOperation::PauliY,
UnaryOperation::PauliZ,
]
}
}
impl Display for UnaryOperation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
UnaryOperation::Identity => write!(f, "Identity"),
UnaryOperation::Hadamard => write!(f, "Hadamard"),
UnaryOperation::Phase => write!(f, "Phase"),
UnaryOperation::Not => write!(f, "NOT"),
UnaryOperation::PauliY => write!(f, "Pauli Y"),
UnaryOperation::PauliZ => write!(f, "Pauli Z"),
}
}
}
fn unary_operation_target_name(_: &UnaryOperation) -> [&str; 1] {
["target"]
}
enum BinaryOperation {
CNot,
Swap,
}
impl BinaryOperation {
fn operations() -> Vec<BinaryOperation> {
vec![BinaryOperation::CNot, BinaryOperation::Swap]
}
}
impl Display for BinaryOperation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BinaryOperation::CNot => write!(f, "CNOT"),
BinaryOperation::Swap => write!(f, "Swap"),
}
}
}
fn binary_operation_target_names(op: &BinaryOperation) -> [&str; 2] {
match *op {
BinaryOperation::CNot => ["control", "target"],
_ => ["target"; 2],
}
}
fn size_prompt(max: usize) -> Result<usize, InquireError> {
assert!(max > 0, "Register size must be atleast 1");
let options: Vec<usize> = (1..=max).collect();
Select::new("Select a register size: ", options).prompt()
}
fn init_prompt(state: &State) -> Result<Choice, InquireError> {
let options = Choice::choices(state);
Select::new("Select an option: ", options).prompt()
}
fn operation_prompt(size: usize) -> Result<OperationType, InquireError> {
let options = OperationType::types()
.into_iter()
.filter(|op_type| op_type.size() <= size)
.collect();
Select::new("Select an operation type: ", options).prompt()
}
fn unary_prompt() -> Result<UnaryOperation, InquireError> {
let options = UnaryOperation::operations();
Select::new("Select an operation: ", options).prompt()
}
fn binary_prompt() -> Result<BinaryOperation, InquireError> {
let options = BinaryOperation::operations();
Select::new("Select an operation: ", options).prompt()
}
fn indicies_prompt<const N: usize>(
target_names: [&str; N],
size: usize,
) -> Result<Vec<usize>, InquireError> {
assert!(
N <= size,
"Cannot call operation on more qubits than register size! ({N} > {size}"
);
let options: Vec<usize> = (0..size).collect();
let mut targets: Vec<usize> = Vec::new();
for name in target_names.iter().take(N) {
let target = Select::new(
format!("Select a {name} index: ").as_str(),
options
.clone()
.into_iter()
.filter(|o| !targets.contains(o))
.collect(),
)
.prompt()?;
targets.push(target);
}
Ok(targets)
}
#[derive(Debug)]
enum RegisterType {
Classical,
Quantum,
}
impl Display for RegisterType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(self, f)
}
}
fn register_type_prompt() -> Result<RegisterType, InquireError> {
Select::new(
"Select the register type",
vec![RegisterType::Quantum, RegisterType::Classical],
)
.prompt()
}
fn register_name_prompt(state: &State) -> Result<String, InquireError> {
let empty_str_validator = |s: &str| {
if !s.is_empty() {
Ok(Validation::Valid)
} else {
Ok(Validation::Invalid("Register name cannot be empty".into()))
}
};
let qreg_names: Vec<String> = state.q_regs.keys().cloned().collect();
let creg_names: Vec<String> = state.q_regs.keys().cloned().collect();
let no_duplicate_validator = move |s: &str| {
let s = &s.to_string();
if qreg_names.contains(s) || creg_names.contains(s) {
Ok(Validation::Invalid("Register name is already used".into()))
} else {
Ok(Validation::Valid)
}
};
Text::new("Register name: ")
.with_validator(empty_str_validator)
.with_validator(no_duplicate_validator)
.prompt()
}
fn reg_prompt<T>(
message: String,
registers: &mut RegCollection<T>,
autoselect: bool,
skipable: bool,
) -> Result<&mut T, InquireError> {
let options: Vec<String> = registers.keys().cloned().collect();
let choice = if options.len() == 1 && autoselect {
options[0].clone()
} else if skipable {
Select::new(&message, options)
.prompt_skippable()?
.ok_or(InquireError::OperationCanceled)?
} else {
Select::new(&message, options).prompt()?
};
registers
.get_mut(&choice)
.ok_or(InquireError::InvalidConfiguration(
"Invalid quantum register".to_string(),
))
}
fn qreg_prompt(registers: &mut QRegCollection) -> Result<&mut Register, InquireError> {
reg_prompt(
"Select quantum register".to_string(),
registers,
true,
false,
)
}
fn creg_prompt(registers: &mut CRegCollection) -> Result<&mut Vec<bool>, InquireError> {
reg_prompt(
"Select classical register".to_string(),
registers,
true,
false,
)
}
fn creg_prompt_skippable(registers: &mut CRegCollection) -> Result<&mut Vec<bool>, InquireError> {
reg_prompt(
"Select classical register (ESC to cancel)".to_string(),
registers,
false,
true,
)
}
fn get_unary(size: usize) -> Result<Operation, InquireError> {
let unary_op = unary_prompt().expect("Problem encountered when selecting unary operation");
let target = indicies_prompt(unary_operation_target_name(&unary_op), size)
.expect("Problem encountered when selecting index")[0];
let op = match unary_op {
UnaryOperation::Identity => operation::identity(target),
UnaryOperation::Hadamard => operation::hadamard(target),
UnaryOperation::Phase => operation::phase(target),
UnaryOperation::Not => operation::not(target),
UnaryOperation::PauliY => operation::pauli_y(target),
UnaryOperation::PauliZ => operation::pauli_z(target),
};
Ok(op)
}
fn get_binary(size: usize) -> Result<Operation, InquireError> {
let binary_op = binary_prompt().expect("Problem encountered when selecting binary operation");
let targets = indicies_prompt(binary_operation_target_names(&binary_op), size)
.expect("Problem encountered when selecting index");
let a = targets[0];
let b = targets[1];
let op = match binary_op {
BinaryOperation::CNot => operation::cnot(a, b),
BinaryOperation::Swap => operation::swap(a, b),
};
Ok(op)
}
fn handle_apply(state: &mut State) {
let reg = qreg_prompt(&mut state.q_regs)
.expect("Problem encountered when selecting a quantum register");
let op_type =
operation_prompt(reg.size()).expect("Problem encountered during operation type selection");
let result = match op_type {
OperationType::Unary => get_unary(reg.size()),
OperationType::Binary => get_binary(reg.size()),
};
match result {
Ok(op) => reg.apply(&op),
Err(e) => panic!("Problem encountered when applying operation: {e:?}"),
};
}
fn handle_measure(state: &mut State) {
let q_reg = qreg_prompt(&mut state.q_regs)
.expect("Problem encountered when selecting a quantum register");
let q_index = indicies_prompt(["qubit"], q_reg.size())
.expect("Problem encountered when selecting a qubit")[0];
let result = q_reg.measure(q_index);
if let Ok(c_reg) = creg_prompt_skippable(&mut state.c_regs) {
let c_index = indicies_prompt(["bit"], c_reg.len())
.expect("Problem encountered when selecting a classical bit")[0];
c_reg[c_index] = result;
}
println!("Qubit at measured {result}");
}
fn handle_show(state: &mut State) {
let reg_type = if state.q_regs.is_empty() {
RegisterType::Classical
} else if state.c_regs.is_empty() {
RegisterType::Quantum
} else {
register_type_prompt().expect("Problem encountered when selecting a register type")
};
match reg_type {
RegisterType::Classical => {
let reg = creg_prompt(&mut state.c_regs)
.expect("Problem encountered when selecting a quantum register");
println!("Classical register of size: {}", reg.len());
println!("{:?}", reg);
}
RegisterType::Quantum => {
let reg = qreg_prompt(&mut state.q_regs)
.expect("Problem encountered when selecting a quantum register");
println!("Quantum register of size: {}", reg.size());
reg.print_state();
}
}
}
fn handle_create(state: &mut State) {
let reg_type =
register_type_prompt().expect("Problem encountered when selecting a register type");
let reg_name =
register_name_prompt(state).expect("Problem encountered when entering a register name");
let reg_size = size_prompt(4).expect("Problem encountered when selecting register size");
let reg_state = &[false].repeat(reg_size);
match reg_type {
RegisterType::Classical => {
state.c_regs.insert(reg_name, reg_state.clone());
}
RegisterType::Quantum => {
let reg = Register::new(reg_state.as_slice());
state.q_regs.insert(reg_name, reg);
}
}
}
fn handle_openqasm(state: &mut State) {
let openqasm_validator = |s: &str| {
let path = Path::new(s);
match openqasm::run_openqasm(path) {
Ok(_) => Ok(Validation::Valid),
Err(e) => Ok(Validation::Invalid(
format!("Parsing error: {:?}", e).into(),
)),
}
};
let filepath = Text::new("OpenQASM file path:")
.with_validator(openqasm_validator)
.prompt()
.expect("Problem encountered when specifying filepath");
let res = openqasm::run_openqasm(Path::new(&filepath))
.expect("Problem encountered when running OpenQASM program");
state.q_regs.extend(res.qregs);
state.c_regs.extend(res.cregs);
}
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
#[arg(short, long)]
size: Option<usize>,
}
type RegCollection<T> = HashMap<String, T>;
type QRegCollection = RegCollection<Register>;
type CRegCollection = RegCollection<Vec<bool>>;
struct State {
q_regs: QRegCollection,
c_regs: CRegCollection,
}
fn main() {
let args = Args::parse();
println!("{QUARU}");
let mut state = State {
q_regs: HashMap::new(),
c_regs: HashMap::new(),
};
if let Some(n) = args.size {
let init_state = &[false].repeat(n);
let reg = Register::new(init_state.as_slice());
state.q_regs.insert("qreg0".to_string(), reg);
}
print!("{esc}[2J{esc}[1;1H", esc = 27 as char);
loop {
let init = init_prompt(&state).expect("Problem selecting an option");
print!("{esc}[2J{esc}[1;1H", esc = 27 as char);
match init {
Choice::Show => handle_show(&mut state),
Choice::Apply => handle_apply(&mut state),
Choice::Measure => handle_measure(&mut state),
Choice::Create => handle_create(&mut state),
Choice::OpenQASM => handle_openqasm(&mut state),
Choice::Exit => break,
};
}
}
const QUARU: &str = "
______ __ __ ___ .______ __ __
/ __ \\ | | | | / \\ | _ \\ | | | |
| | | | | | | | / ^ \\ | |_) | | | | |
| | | | | | | | / /_\\ \\ | / | | | |
| `--' '--.| `--' | / _____ \\ | |\\ \\----.| `--' |
\\_____\\_____\\\\______/ /__/ \\__\\ | _| `._____| \\______/
";