-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Format floating-point values in the Rust circuit drawer #15899
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,8 @@ use approx; | |
| use crossterm::terminal; | ||
| use hashbrown::HashSet; | ||
| use itertools::{Itertools, MinMaxResult}; | ||
| use lexical_core::ToLexicalWithOptions; | ||
| use lexical_write_float::{self, format::STANDARD}; | ||
| use pyo3::prelude::*; | ||
| use std::f64::consts::PI; | ||
| use std::fmt::Debug; | ||
|
|
@@ -63,7 +65,7 @@ pub fn draw_circuit( | |
| } else { | ||
| format!( | ||
| "global phase: {}\n", | ||
| format_float_pi(*f).unwrap_or_else(|| f.to_string()) | ||
| F64UiFormatter::new(5).format_with_pi(*f) | ||
| ) | ||
| } | ||
| } | ||
|
|
@@ -673,6 +675,55 @@ impl TextWireElement { | |
| } | ||
| } | ||
|
|
||
| /// A formatter for UI rendering of floating-point numbers | ||
| /// | ||
| /// Supports formatting similar to Python's `g` or C printf's `%g` format specifiers | ||
| /// as well as formatting of multiples and fractions of pi. | ||
| /// | ||
| /// Example outputs: | ||
| /// ```text | ||
| /// F64UiFormatter::new(4).format(1.23456) → 1.235 | ||
| /// F64UiFormatter::new(4).format(123.456) → 123.5 | ||
| /// F64UiFormatter::new(5).format(12345678.0) → 1.2346e7 | ||
| /// F64UiFormatter::new(5).format(-0.00001234) → -1.234e-5 | ||
| /// F64UiFormatter::new(5).format_with_pi(5π/6) → 5π/6 | ||
| /// ``` | ||
| struct F64UiFormatter { | ||
| buffer: Vec<u8>, | ||
| options: lexical_write_float::Options, | ||
| } | ||
|
|
||
| impl F64UiFormatter { | ||
| fn new(num_significant_digits: usize) -> Self { | ||
| let options = lexical_write_float::Options::builder() | ||
| .max_significant_digits(core::num::NonZeroUsize::new(num_significant_digits)) | ||
| .positive_exponent_break(core::num::NonZeroI32::new(num_significant_digits as i32)) | ||
| .negative_exponent_break(core::num::NonZeroI32::new( | ||
| -(num_significant_digits as i32) + 1, | ||
| )) | ||
| .trim_floats(true) | ||
| .build_strict(); | ||
|
|
||
| F64UiFormatter { | ||
| buffer: vec![0u8; options.buffer_size_const::<f64, STANDARD>()], | ||
| options, | ||
| } | ||
| } | ||
|
|
||
| /// Formats the input number based on the formatting options. | ||
| /// This Can be called multiple times, but the internal buffer is overwritten on each call. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we testing anywhere that this can be called multiple times? Looking at the code it seems that a new formatter is instantiated every time when we call
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While I did call one formatter multiple times while I was testing this manually, I thought that since the crate is built around the notion of using a static buffer anyway, it's not worth to add explicit tests for that and that we can just go with the other circuit output tests which for the formatting aspect itself. |
||
| fn format(&mut self, num: f64) -> &str { | ||
| let buf = num.to_lexical_with_options::<STANDARD>(&mut self.buffer, &self.options); | ||
| std::str::from_utf8_mut(buf).expect("Byte representation should be valid") | ||
| } | ||
|
|
||
| /// Tries to format the string as a multiple or simple fraction of pi if possible, | ||
| /// otherwise falls back to the simpler [F64UiFormatter::format] logic | ||
| fn format_with_pi(&mut self, num: f64) -> String { | ||
| format_float_pi(num).unwrap_or_else(|| self.format(num).to_owned()) | ||
| } | ||
| } | ||
|
|
||
| pub const Q_WIRE: char = '─'; | ||
| pub const C_WIRE: char = '═'; | ||
| pub const TOP_CON: char = '┴'; | ||
|
|
@@ -735,7 +786,11 @@ impl TextDrawer { | |
| StandardInstruction::Delay(delay_unit) => { | ||
| match instruction.params_view().first().unwrap() { | ||
| Param::Float(duration) => { | ||
| format!("Delay({}[{}])", duration, delay_unit) | ||
| format!( | ||
| "Delay({}[{}])", | ||
| F64UiFormatter::new(5).format(*duration), | ||
| delay_unit | ||
| ) | ||
| } | ||
| Param::ParameterExpression(expr) => { | ||
| format!("Delay({}[{}])", expr, delay_unit) | ||
|
|
@@ -767,7 +822,9 @@ impl TextDrawer { | |
| .params_view() | ||
| .iter() | ||
| .map(|param| match param { | ||
| Param::Float(f) => format_float_pi(*f).unwrap_or_else(|| f.to_string()), | ||
| Param::Float(f) => { | ||
| F64UiFormatter::new(5).format_with_pi(*f).to_string() | ||
| } | ||
| Param::ParameterExpression(expr) => expr.to_string(), | ||
| _ => format!("{:?}", param), | ||
| }) | ||
|
|
@@ -2092,6 +2149,60 @@ q_1: ┤ Ry(🎩) ├┤1 ├┤ 💶🔉(🎩) ├┤1 ├ | |
| assert_eq!(result, expected.trim_start_matches("\n")); | ||
| } | ||
|
|
||
| #[cfg(not(miri))] | ||
|
Cryoris marked this conversation as resolved.
|
||
| #[test] | ||
| fn test_f64_formatting() { | ||
| let qubits = vec![ | ||
| ShareableQubit::new_anonymous(), | ||
| ShareableQubit::new_anonymous(), | ||
| ]; | ||
| let mut circuit = CircuitData::new(Some(qubits), None, Param::Float(0.8 * PI)).unwrap(); | ||
|
|
||
| circuit | ||
| .push_standard_gate(StandardGate::RX, &[Param::Float(1.234567)], &[Qubit(0)]) | ||
| .unwrap(); | ||
| circuit | ||
| .push_standard_gate(StandardGate::RX, &[Param::Float(123.4567)], &[Qubit(0)]) | ||
| .unwrap(); | ||
|
|
||
| let expr = ParameterExpression::from_symbol(Symbol::new("ϕ", None, None)) | ||
| .mul(&ParameterExpression::from_f64(1.23456)) | ||
| .unwrap(); | ||
| let param = Param::ParameterExpression(Arc::new(expr)); | ||
| circuit | ||
| .push_standard_gate(StandardGate::RY, &[param], &[Qubit(0)]) | ||
| .unwrap(); | ||
| circuit | ||
| .push_standard_gate(StandardGate::RZ, &[Param::Float(123456789f64)], &[Qubit(1)]) | ||
| .unwrap(); | ||
|
|
||
| circuit | ||
| .push_standard_gate(StandardGate::RX, &[Param::Float(0.1234567)], &[Qubit(1)]) | ||
| .unwrap(); | ||
| circuit | ||
| .push_standard_gate(StandardGate::RX, &[Param::Float(0.0000123456)], &[Qubit(1)]) | ||
| .unwrap(); | ||
| circuit | ||
| .push_standard_gate( | ||
| StandardGate::RX, | ||
| &[Param::Float(2.0 / 3.0 * PI)], | ||
| &[Qubit(1)], | ||
| ) | ||
| .unwrap(); | ||
|
|
||
| let result = draw_circuit(&circuit, true, true, None).unwrap(); | ||
| let expected = " | ||
| global phase: 4π/5 | ||
| ┌────────────┐ ┌────────────┐ ┌───────────────┐ | ||
| q_0: ─┤ Rx(1.2346) ├─┤ Rx(123.46) ├─┤ Ry(1.23456*ϕ) ├──────────── | ||
| ┌┴────────────┴┐├────────────┴┐├───────────────┤┌──────────┐ | ||
| q_1: ┤ Rz(1.2346e8) ├┤ Rx(0.12346) ├┤ Rx(1.2346e-5) ├┤ Rx(2π/3) ├ | ||
| └──────────────┘└─────────────┘└───────────────┘└──────────┘ | ||
| "; | ||
|
|
||
| assert_eq!(result, expected.trim_start_matches("\n")); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_format_float_pi() { | ||
| let test_points = [ | ||
|
|
@@ -2146,4 +2257,25 @@ q_1: ┤ Ry(🎩) ├┤1 ├┤ 💶🔉(🎩) ├┤1 ├ | |
| assert_eq!(format_float_pi(test.0), test.1.map(|s| s.to_string())); | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_f64_ui_formatter() { | ||
| let test_data_5_sig_digits = [ | ||
| (-1.23, "-1.23"), | ||
| (1.23456, "1.2346"), | ||
| (-12.34567, "-12.346"), | ||
| (123456.78, "123460"), | ||
| (-0.0001, "-0.0001"), | ||
| (12.34 * 1_000_000.0, "1.234e7"), | ||
| (-0.00001, "-1e-5"), | ||
| (12345678.000001, "1.2346e7"), | ||
| (15.0 * PI / 16.0, "15π/16"), | ||
| (-2.0 * PI / 3.0, "-2π/3"), | ||
| ]; | ||
|
|
||
| let mut formatter = F64UiFormatter::new(5); | ||
| for test in test_data_5_sig_digits { | ||
| assert_eq!(test.1.to_owned(), formatter.format_with_pi(test.0)); | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.