//! Generate a file full of expressions for benchmarking simplification. //! //! Expressions will be written to a specified file as "\t", where is the hex //! value corresponding to the hashed version of the expression, and is an overly //! parenthesized string version of the expression to ensure consistent parsing. use clap::Parser; use quil_rs::{ expression::{ Expression, ExpressionFunction, FunctionCallExpression, InfixExpression, InfixOperator, PrefixExpression, PrefixOperator, }, instruction::MemoryReference, quil::Quil, reserved::ReservedToken, }; use rand::{distributions::Alphanumeric, rngs::StdRng, Rng, SeedableRng}; use std::{ collections::hash_map::DefaultHasher, fs::File, hash::{Hash, Hasher}, io::{BufWriter, Write}, path::{Path, PathBuf}, str::FromStr, }; /// Default output path for writing the test expressions. fn get_default_output_path() -> PathBuf { Path::new(env!("CARGO_MANIFEST_DIR")) .join("benches") .join("test_expressions.txt") } /// Generate a file full of expressions for benchmarking simplification. #[derive(Parser)] struct Args { /// Output path #[arg(short, long, default_value=get_default_output_path().into_os_string())] output_path: PathBuf, /// Number of Expressions to generate #[arg(short, long, default_value_t = 25)] number_of_expressions: u64, /// Maximum depth of expressions #[arg(short, long, default_value_t = 10)] maximum_depth: u64, /// Seed for PRNG (default from random.org) #[arg(short, long, default_value_t = 27586845)] seed: u64, } /// Parenthesized version of [`Expression::to_string()`] to ensure consistent re-parsing. fn parenthesized(expression: &Expression) -> String { use Expression::*; match expression { Address(memory_reference) => format!("({memory_reference})"), FunctionCall(FunctionCallExpression { function, expression, }) => format!("({function}({}))", parenthesized(expression)), Infix(InfixExpression { left, operator, right, }) => format!( "({}{}{})", parenthesized(left), operator, parenthesized(right) ), Number(_) => format!("({})", expression.to_quil_or_debug()), PiConstant => "pi".to_string(), Prefix(PrefixExpression { operator, expression, }) => format!("({}{})", operator, parenthesized(expression)), Variable(identifier) => format!("(%{identifier})"), } } /// Build a random [`Expression`] fn build(rng: &mut impl Rng, depth: u64) -> Expression { if depth == 0 { match rng.gen_range(0..4) { 0 => addr(rng), 1 => number(rng), 2 => Expression::PiConstant, 3 => var(rng), _ => unreachable!(), } } else { let d = depth - 1; match rng.gen_range(0..3) { 0 => func(rng, d), 1 => infix(rng, d), 2 => prefix(rng, d), _ => unreachable!(), } } } /// Random usable name. fn name(rng: &mut impl Rng) -> String { let len = rng.gen_range(1..=10); let mut name = rng .sample_iter(&Alphanumeric) .take(len) .map(char::from) .collect::(); while ReservedToken::from_str(&name).is_ok() || name.to_lowercase() == "nan" || name .chars() .next() .map(|c| c.is_ascii_digit()) .unwrap_or(false) { name = rng .sample_iter(&Alphanumeric) .take(len) .map(char::from) .collect(); } name } /// Random address. fn addr(rng: &mut impl Rng) -> Expression { Expression::Address(MemoryReference { name: name(rng), index: rng.gen(), }) } /// Random complex number. fn number(rng: &mut impl Rng) -> Expression { Expression::Number(num_complex::Complex:: { re: rng.gen(), im: rng.gen(), }) } /// Random variable. fn var(rng: &mut impl Rng) -> Expression { Expression::Variable(name(rng)) } /// Random function. fn func(rng: &mut impl Rng, depth: u64) -> Expression { let function = match rng.gen_range(0..5) { 0 => ExpressionFunction::Cis, 1 => ExpressionFunction::Cosine, 2 => ExpressionFunction::Exponent, 3 => ExpressionFunction::Sine, 4 => ExpressionFunction::SquareRoot, _ => unreachable!(), }; Expression::FunctionCall(FunctionCallExpression { function, expression: build(rng, depth).into(), }) } /// Random infix expression. fn infix(rng: &mut impl Rng, depth: u64) -> Expression { let operator = match rng.gen_range(0..5) { 0 => InfixOperator::Caret, 1 => InfixOperator::Plus, 2 => InfixOperator::Minus, 3 => InfixOperator::Slash, 4 => InfixOperator::Star, _ => unreachable!(), }; Expression::Infix(InfixExpression { left: build(rng, depth).into(), operator, right: build(rng, depth).into(), }) } /// Random prefix expression. fn prefix(rng: &mut impl Rng, depth: u64) -> Expression { let operator = if rng.gen() { PrefixOperator::Plus } else { PrefixOperator::Minus }; Expression::Prefix(PrefixExpression { operator, expression: build(rng, depth).into(), }) } /// Hash a hashable thing to a number. fn hash(t: &T) -> u64 { let mut s = DefaultHasher::new(); t.hash(&mut s); s.finish() } /// Generate a file full of expressions for benchmarking simplification. fn main() -> Result<(), String> { let args = Args::parse(); let mut rng = StdRng::seed_from_u64(args.seed); let file = File::create(args.output_path) .map_err(|e| format!("Error in creating output file: {e:?}"))?; let mut buf = BufWriter::new(file); for _ in 0..args.number_of_expressions { let e = build(&mut rng, args.maximum_depth); let h = hash(&e); writeln!(buf, "{:x}\t{}", h, parenthesized(&e),) .map_err(|e| format!("Error in writing to output file: {e:?}"))?; } Ok(()) }