Skip to content

Commit 142954b

Browse files
OnyxBrumeSkyOnyxBrumeSkyOnyxBrumeSkyOnyxBrumeSkyeliarbel
authored
fix: PI format for rust Circuit display (#15917)
* fix: added a intermediate function format_float_pi to find closet pi ratio up to a certain limit. also added relative tests named as test_pi_float_format. files modified :crates/circuit/src/circuit_drawer.rs .This concerns the issue #15849 * fix: changed the code according to the comments left. Added more parameters to the function to copy the use of pi_check. Added the possibility of choosing a specific format. Next step is to add the features to support other format. * fix: moved the function format_float_pi to its own file 'py_check.rs' for better reading. Added support of formats according to the original py_check.py. Added tests for the rust function. Modified the lib.rs to be able to use the function accross files. * fixe: moved the function to circuit_drawer.rs as asked. Added test to the same file. Removed function parameters as asked and modified output to only have text format * fix: forgot to remove py_check.rs * fix: added the fix needed. Merged checks 5 and 6. Rewrite test part into an array for better reading. * fix: forgot a '}'. Ran all tests * Apply minor touch-ups to comments and tests --------- Co-authored-by: OnyxBrumeSky <cowbeez@MacBook-Pro-de-CowBeez.local> Co-authored-by: OnyxBrumeSky <cowbeez@f6r14s10.clusters.42paris.fr> Co-authored-by: OnyxBrumeSky <cowbeez@f6r14s14.clusters.42paris.fr> Co-authored-by: Eli Arbel <arbel@il.ibm.com>
1 parent d4c1e68 commit 142954b

1 file changed

Lines changed: 148 additions & 2 deletions

File tree

crates/circuit/src/circuit_drawer.rs

Lines changed: 148 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use crossterm::terminal;
2020
use hashbrown::HashSet;
2121
use itertools::{Itertools, MinMaxResult};
2222
use pyo3::prelude::*;
23+
use std::f64::consts::PI;
2324
use std::fmt::Debug;
2425
use std::ops::Index;
2526
use unicode_segmentation::UnicodeSegmentation;
@@ -60,7 +61,10 @@ pub fn draw_circuit(
6061
if approx::abs_diff_eq!(*f, 0.) {
6162
String::new()
6263
} else {
63-
format!("global phase: {}\n", f)
64+
format!(
65+
"global phase: {}\n",
66+
format_float_pi(*f).unwrap_or_else(|| f.to_string())
67+
)
6468
}
6569
}
6670
Param::ParameterExpression(expr) => {
@@ -763,7 +767,7 @@ impl TextDrawer {
763767
.params_view()
764768
.iter()
765769
.map(|param| match param {
766-
Param::Float(f) => f.to_string(),
770+
Param::Float(f) => format_float_pi(*f).unwrap_or_else(|| f.to_string()),
767771
Param::ParameterExpression(expr) => expr.to_string(),
768772
_ => format!("{:?}", param),
769773
})
@@ -1202,6 +1206,93 @@ impl TextDrawer {
12021206
}
12031207
}
12041208

1209+
/// Computes if a number is close to an integer
1210+
/// fraction or multiple of PI and returns the
1211+
/// corresponding string.
1212+
///
1213+
/// Args:
1214+
/// f : Number to check.
1215+
///
1216+
/// Returns:
1217+
/// The string representation of output. None if no Pi formatting is found.
1218+
pub fn format_float_pi(f: f64) -> Option<String> {
1219+
const DENOMINATOR: i64 = 16;
1220+
// epsilon value defines the threshold to detect pi.
1221+
const EPS: f64 = 1e-9;
1222+
1223+
// pi_str is needed to match the output expected according to the format needed
1224+
let pi_str = "π";
1225+
1226+
// f_abs and sign help us working through each steps
1227+
let f_abs = f.abs();
1228+
let sign = if f < 0.0 { "-" } else { "" };
1229+
1230+
// Detecting 0 before moving on
1231+
if f_abs < EPS {
1232+
return Some("0".to_string());
1233+
}
1234+
1235+
// First check is for whole multiples of pi
1236+
let val = f_abs / PI;
1237+
let round = val.round();
1238+
if val >= 1.0 - EPS && (val - round).abs() < EPS {
1239+
let round = round as usize;
1240+
return Some(if round == 1 {
1241+
format!("{}{}", sign, pi_str)
1242+
} else {
1243+
format!("{}{}{}", sign, round, pi_str)
1244+
});
1245+
}
1246+
1247+
// Second is a check for powers of pi
1248+
if f_abs > PI {
1249+
if let Some(k) = (2..=4).find(|k| (f_abs - PI.powi(*k)).abs() < EPS) {
1250+
return Some(format!("{}{}^{}", sign, pi_str, k));
1251+
}
1252+
}
1253+
1254+
// Third is a check for a number larger than DENOMINATOR * pi, not a
1255+
// multiple or power of pi, since no fractions will exceed DENOMINATOR * pi
1256+
if f_abs > (DENOMINATOR as f64 * PI) {
1257+
return None;
1258+
}
1259+
1260+
// Fourth check is for fractions for 1*pi in the numer and any
1261+
// number in the denom.
1262+
let val = PI / f_abs;
1263+
let round = val.round();
1264+
if round >= 1.0 && (val - round).abs() < EPS {
1265+
let d = round as usize;
1266+
let str_out = format!("{}{}/{}", sign, pi_str, d);
1267+
return Some(str_out);
1268+
}
1269+
1270+
// Fifth check is for fractions of the form (numer/denom) * pi or (numer/denom) / pi
1271+
// where 1 <= numer,denom <= DENOMINATOR, which are not covered in the previous checks.
1272+
// Ex. 15pi/16, 2pi/5, 15pi/2, 16pi/9 or 15/16pi, 2/5pi, 15/2pi, 16/9pi
1273+
for denom in 1..=DENOMINATOR {
1274+
for numer in 1..=DENOMINATOR {
1275+
let up = numer as f64 / denom as f64;
1276+
let val = up * PI;
1277+
if (f_abs - val).abs() < EPS {
1278+
let str_out = format!("{}{}{}/{}", sign, numer, pi_str, denom);
1279+
return Some(str_out);
1280+
}
1281+
let val = up / PI;
1282+
if (f_abs - val).abs() < EPS {
1283+
let str_out = match denom {
1284+
1 => format!("{}{}/{}", sign, numer, pi_str),
1285+
d => format!("{}{}/{}{}", sign, numer, d, pi_str),
1286+
};
1287+
return Some(str_out);
1288+
}
1289+
}
1290+
}
1291+
1292+
// fall back when no conversion is possible
1293+
None
1294+
}
1295+
12051296
#[cfg(test)]
12061297
mod tests {
12071298
use ndarray::Array2;
@@ -2000,4 +2091,59 @@ q_1: ┤ Ry(🎩) ├┤1 ├┤ 💶🔉(🎩) ├┤1 ├
20002091
";
20012092
assert_eq!(result, expected.trim_start_matches("\n"));
20022093
}
2094+
2095+
#[test]
2096+
fn test_format_float_pi() {
2097+
let test_points = [
2098+
(0.0, Some("0")),
2099+
(-0.0, Some("0")),
2100+
(1e-12, Some("0")),
2101+
(PI, Some("π")),
2102+
(-PI, Some("-π")),
2103+
(2.0 * PI, Some("2π")),
2104+
(3.0 * PI, Some("3π")),
2105+
(10.0 * PI, Some("10π")),
2106+
(16.0 * PI, Some("16π")),
2107+
(-2.0 * PI, Some("-2π")),
2108+
(-5.0 * PI, Some("-5π")),
2109+
(PI.powi(2), Some("π^2")),
2110+
(-PI.powi(2), Some("-π^2")),
2111+
(PI.powi(3), Some("π^3")),
2112+
(PI.powi(4), Some("π^4")),
2113+
(PI / 2.0, Some("π/2")),
2114+
(PI / 3.0, Some("π/3")),
2115+
(PI / 4.0, Some("π/4")),
2116+
(PI / 6.0, Some("π/6")),
2117+
(-PI / 2.0, Some("-π/2")),
2118+
(2.0 * PI / 3.0, Some("2π/3")),
2119+
(3.0 * PI / 4.0, Some("3π/4")),
2120+
(5.0 * PI / 6.0, Some("5π/6")),
2121+
(7.0 * PI / 4.0, Some("7π/4")),
2122+
(15.0 * PI / 16.0, Some("15π/16")),
2123+
(-2.0 * PI / 3.0, Some("-2π/3")),
2124+
(1.0 / PI, Some("1/π")),
2125+
(2.0 / PI, Some("2/π")),
2126+
(1.0 / (2.0 * PI), Some("1/2π")),
2127+
(3.0 / (4.0 * PI), Some("3/4π")),
2128+
(-1.0 / PI, Some("-1/π")),
2129+
(-1.0 / (2.0 * PI), Some("-1/2π")),
2130+
(-18.0 / 16.0 * PI, Some("-9π/8")),
2131+
(60.0 / 44.0 / PI, Some("15/11π")),
2132+
(17.0 * PI + 1.0, None),
2133+
(100.0, None),
2134+
(1.0, None),
2135+
(2.0, None),
2136+
(1.5, None),
2137+
(-7.3, None),
2138+
(PI + 1e-6, None),
2139+
(PI - 1e-6, None),
2140+
(PI / 2.0 + 1e-6, None),
2141+
(17.0 * PI / 2.0, None),
2142+
(9.0 / (17.0 * PI), None),
2143+
];
2144+
2145+
for test in test_points {
2146+
assert_eq!(format_float_pi(test.0), test.1.map(|s| s.to_string()));
2147+
}
2148+
}
20032149
}

0 commit comments

Comments
 (0)