Skip to content
Merged
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
300 changes: 298 additions & 2 deletions crates/circuit/src/circuit_drawer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,10 @@ pub fn draw_circuit(
if approx::abs_diff_eq!(*f, 0.) {
String::new()
} else {
format!("global phase: {}\n", f)
format!(
"global phase: {}\n",
format_float_pi(*f).unwrap_or_default()
Comment thread
OnyxBrumeSky marked this conversation as resolved.
Outdated
)
}
}
Param::ParameterExpression(expr) => {
Expand Down Expand Up @@ -763,7 +766,7 @@ impl TextDrawer {
.params_view()
.iter()
.map(|param| match param {
Param::Float(f) => f.to_string(),
Param::Float(f) => format_float_pi(*f).unwrap_or_default(),
Comment thread
OnyxBrumeSky marked this conversation as resolved.
Outdated
Param::ParameterExpression(expr) => expr.to_string(),
_ => format!("{:?}", param),
})
Expand Down Expand Up @@ -1202,6 +1205,108 @@ impl TextDrawer {
}
}

/// """Computes if a number is close to an integer
Comment thread
OnyxBrumeSky marked this conversation as resolved.
Outdated
/// fraction or multiple of PI and returns the
/// corresponding string.

/// Args:
/// f (float): Number to check.
Comment thread
OnyxBrumeSky marked this conversation as resolved.
Outdated

/// Returns:
/// Option<String>: string representation of output. None if no pi format is found
Comment thread
OnyxBrumeSky marked this conversation as resolved.
Outdated
use std::f64::consts::PI;
Comment thread
OnyxBrumeSky marked this conversation as resolved.
Outdated

pub fn format_float_pi(f: f64) -> Option<String> {
const DENOMINATOR: i64 = 16;
// epsilon value defines the treshold to detect pi.
Comment thread
OnyxBrumeSky marked this conversation as resolved.
Outdated
const EPS: f64 = 1e-9;

// pi_str is needed to match the output expected according to the format needed
let pi_str: String = String::from("π");
Comment thread
OnyxBrumeSky marked this conversation as resolved.
Outdated

// f_abs and sign help us working trough each steps
let f_abs = f.abs();
let sign: &str = if f < 0.0 { "-" } else { "" };
Comment thread
OnyxBrumeSky marked this conversation as resolved.
Outdated

// Detecting 0 before moving on
if f_abs < EPS {
return Some("0".to_string());
}

// First check is for whole multiples of pi
let val = f_abs / PI;
if val >= 1.0 - EPS {
let round = val.round();
if (val - round).abs() < EPS {
let n = round as i64;
let str_out = match n {
1 => format!("{}{}", sign, pi_str),
n => format!("{}{}{}", sign, n, pi_str),
};
return Some(str_out);
}
}
Comment thread
OnyxBrumeSky marked this conversation as resolved.
Outdated

// Second is a check for powers of pi
if f_abs > PI {
for k in 2i32..=4 {
let pow = PI.powi(k);
if (f_abs - pow).abs() < EPS {
let str_out = format!("{}{}^{}", sign, pi_str, k);
return Some(str_out);
}
}
}
Comment thread
OnyxBrumeSky marked this conversation as resolved.

// Third is a check for a number larger than MAX_FRAC * pi, not a
Comment thread
OnyxBrumeSky marked this conversation as resolved.
Outdated
// multiple or power of pi, since no fractions will exceed MAX_FRAC * pi
if f_abs > (DENOMINATOR as f64 * PI) {
return None;
}

// Fourth check is for fractions for 1*pi in the numer and any
// number in the denom.
let val = PI / f_abs;
let round = val.round();
if round >= 1.0 && (val - round).abs() < EPS {
let d = round as i64;
let str_out = format!("{}{}/{}", sign, pi_str, d);
return Some(str_out);
}

// Fifth check is for fractions where the numer > 1*pi and numer
// is up to DENOMINATOR*pi and denom is up to DENOMINATOR and all
// fractions are reduced. Ex. 15pi/16, 2pi/5, 15pi/2, 16pi/9.
for d in 1i64..=DENOMINATOR {
for p in 1i64..=DENOMINATOR {
Comment thread
OnyxBrumeSky marked this conversation as resolved.
Outdated
let val = (p as f64 / d as f64) * PI;
if (f_abs - val).abs() < EPS {
let str_out = format!("{}{}{}/{}", sign, p, pi_str, d);
return Some(str_out);
}
}
}

// Sixth check is for fractions where the numer > 1 and numer
// is up to DENOMINATOR and denom is up to DENOMINATOR*pi and all
// fractions are reduced. Ex. 15/16pi, 2/5pi, 15/2pi, 16/9pi
for d in 1i64..=DENOMINATOR {
for p in 1i64..=DENOMINATOR {
let val = (p as f64 / d as f64) / PI;
if (f_abs - val).abs() < EPS {
let str_out = match d {
1 => format!("{}{}/{}", sign, p, pi_str),
d => format!("{}{}/{}{}", sign, p, d, pi_str),
};
return Some(str_out);
}
}
}
Comment thread
OnyxBrumeSky marked this conversation as resolved.
Outdated

// fall back when no conversion is possible
None
}

#[cfg(test)]
mod tests {
use ndarray::Array2;
Expand Down Expand Up @@ -1998,4 +2103,195 @@ q_1: ┤ Ry(🎩) ├┤1 ├┤ 💶🔉(🎩) ├┤1 ├
";
assert_eq!(result, expected.trim_start_matches("\n"));
}

// ──pi formating part──────────────────────────────────────────────────────────────

use std::f64::consts::PI;

// ── helpers ──────────────────────────────────────────────────────────────
fn check(input: f64, expected: &str) {
assert_eq!(
format_float_pi(input).as_deref(),
Some(expected),
"format_float_pi({input}) should be Some({expected:?})"
);
}
fn check_none(input: f64) {
assert_eq!(
format_float_pi(input),
None,
"format_float_pi({input}) should be None"
);
}

// ── zero ─────────────────────────────────────────────────────────────────
#[test]
fn test_zero() {
check(0.0, "0");
check(-0.0, "0");
check(1e-12, "0");
}

// ── integers multiple of pi ────────────────────────────────────────────────
#[test]
fn test_pi_itself() {
check(PI, "π");
}

#[test]
fn test_negative_pi() {
check(-PI, "-π");
}

#[test]
fn test_integer_multiples() {
check(2.0 * PI, "2π");
check(3.0 * PI, "3π");
check(10.0 * PI, "10π");
check(16.0 * PI, "16π");
}

#[test]
fn test_negative_integer_multiples() {
check(-2.0 * PI, "-2π");
check(-5.0 * PI, "-5π");
}

// ── power of pi ───────────────────────────────────────────────────────
#[test]
fn test_pi_squared() {
check(PI * PI, "π^2");
}

#[test]
fn test_pi_cubed() {
check(PI.powi(3), "π^3");
}

#[test]
fn test_pi_pow_4() {
check(PI.powi(4), "π^4");
}

#[test]
fn test_negative_pi_squared() {
check(-PI * PI, "-π^2");
}

// ── fractions π/d ───────────────────────────────────────
#[test]
fn test_pi_over_2() {
check(PI / 2.0, "π/2");
}

#[test]
fn test_pi_over_3() {
check(PI / 3.0, "π/3");
}

#[test]
fn test_pi_over_4() {
check(PI / 4.0, "π/4");
}

#[test]
fn test_pi_over_6() {
check(PI / 6.0, "π/6");
}

#[test]
fn test_negative_pi_over_2() {
check(-PI / 2.0, "-π/2");
}

// ── fractions p*π/d ─────────────────────────────────────
#[test]
fn test_2pi_over_3() {
check(2.0 * PI / 3.0, "2π/3");
}

#[test]
fn test_3pi_over_4() {
check(3.0 * PI / 4.0, "3π/4");
}

#[test]
fn test_5pi_over_6() {
check(5.0 * PI / 6.0, "5π/6");
}

#[test]
fn test_7pi_over_4() {
check(7.0 * PI / 4.0, "7π/4");
}

#[test]
fn test_15pi_over_16() {
check(15.0 * PI / 16.0, "15π/16");
}

#[test]
fn test_negative_2pi_over_3() {
check(-2.0 * PI / 3.0, "-2π/3");
}

// ── p/(d*π) ─────────────────────────────────────────────────────
#[test]
fn test_1_over_pi() {
// d == 1 → format "p/π"
check(1.0 / PI, "1/π");
}

#[test]
fn test_2_over_pi() {
check(2.0 / PI, "2/π");
}

#[test]
fn test_1_over_2pi() {
// d > 1 → format "p/dπ"
check(1.0 / (2.0 * PI), "1/2π");
}

#[test]
fn test_3_over_4pi() {
check(3.0 / (4.0 * PI), "3/4π");
}

#[test]
fn test_negative_1_over_pi() {
check(-1.0 / PI, "-1/π");
}

#[test]
fn test_negative_1_over_2pi() {
check(-1.0 / (2.0 * PI), "-1/2π");
}

// ── out bounds ───────────────────────────────────────────────────
#[test]
fn test_large_non_multiple() {
check_none(17.0 * PI + 1.0);
check_none(100.0);
}

#[test]
fn test_arbitrary_numbers() {
check_none(1.0);
check_none(2.0);
check_none(1.5);
check_none(-7.3);
}

// ── near pi values ──────────────
#[test]
fn test_near_pi_but_not_pi() {
check_none(PI + 1e-6);
check_none(PI - 1e-6);
}

#[test]
fn test_near_pi_over_2_but_not() {
check_none(PI / 2.0 + 1e-6);
}
Comment thread
OnyxBrumeSky marked this conversation as resolved.
Outdated
}