From 03d13109c99aba5d2e8056fa8cb20da3786ea863 Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Tue, 25 Jun 2024 22:30:44 -0700 Subject: [PATCH 01/32] WIP: DumpOperation --- compiler/qsc/src/bin/qsi.rs | 10 +++++ compiler/qsc_eval/src/intrinsic.rs | 18 +++++++++ compiler/qsc_eval/src/intrinsic/utils.rs | 25 ++++++++++++ compiler/qsc_eval/src/output.rs | 23 ++++++++++++ library/src/tests/diagnostics.rs | 48 ++++++++++++++++++++++++ library/std/src/diagnostics.qs | 39 ++++++++++++++++++- pip/src/interpreter.rs | 4 ++ wasm/src/lib.rs | 4 ++ 8 files changed, 170 insertions(+), 1 deletion(-) diff --git a/compiler/qsc/src/bin/qsi.rs b/compiler/qsc/src/bin/qsi.rs index b316746364..d2f7a455e6 100644 --- a/compiler/qsc/src/bin/qsi.rs +++ b/compiler/qsc/src/bin/qsi.rs @@ -81,6 +81,16 @@ impl Receiver for TerminalReceiver { Ok(()) } + fn matrix(&mut self, matrix: Vec>) -> std::result::Result<(), output::Error> { + println!("Matrix:"); + for row in matrix { + let row = row.iter().map(|elem| format!("[{}, {}]", elem.re, elem.im)); + println!("{}", row.collect::>().join(", ")); + } + + Ok(()) + } + fn message(&mut self, msg: &str) -> Result<(), output::Error> { println!("{msg}"); Ok(()) diff --git a/compiler/qsc_eval/src/intrinsic.rs b/compiler/qsc_eval/src/intrinsic.rs index 7b5a3420ce..871340e816 100644 --- a/compiler/qsc_eval/src/intrinsic.rs +++ b/compiler/qsc_eval/src/intrinsic.rs @@ -77,6 +77,24 @@ pub(crate) fn call( Err(_) => Err(Error::OutputFail(name_span)), } } + "DumpMatrix" => { + let qubits = arg.unwrap_array(); + let qubits = qubits + .iter() + .map(|q| q.clone().unwrap_qubit().0) + .collect::>(); + if qubits.len() != qubits.iter().collect::>().len() { + return Err(Error::QubitUniqueness(arg_span)); + } + let (state, qubit_count) = sim.capture_quantum_state(); + let state = utils::split_state(&qubits, &state, qubit_count) + .map_err(|()| Error::QubitsNotSeparable(arg_span))?; + let matrix = utils::state_to_matrix(state, qubit_count / 2); + match out.matrix(matrix) { + Ok(()) => Ok(Value::unit()), + Err(_) => Err(Error::OutputFail(name_span)), + } + } "Message" => match out.message(&arg.unwrap_string()) { Ok(()) => Ok(Value::unit()), Err(_) => Err(Error::OutputFail(name_span)), diff --git a/compiler/qsc_eval/src/intrinsic/utils.rs b/compiler/qsc_eval/src/intrinsic/utils.rs index 79020689da..c5d327dab7 100644 --- a/compiler/qsc_eval/src/intrinsic/utils.rs +++ b/compiler/qsc_eval/src/intrinsic/utils.rs @@ -185,3 +185,28 @@ where self.re.is_nearly_zero() && self.im.is_nearly_zero() } } + +pub(crate) fn state_to_matrix( + state: Vec<(BigUint, Complex64)>, + qubit_count: usize, +) -> Vec> { + let state: FxHashMap> = state.into_iter().collect(); + let mut matrix = Vec::new(); + let num_entries: usize = 1 << qubit_count; + #[allow(clippy::cast_precision_loss)] + let factor = (num_entries as f64).sqrt(); + for i in 0..num_entries { + let mut row = Vec::new(); + for j in 0..num_entries { + let key = BigUint::from(i * num_entries + j); + let val = match state.get(&key) { + Some(val) => val * factor, + None => Complex::zero(), + }; + row.push(val); + } + matrix.push(row); + } + + matrix +} diff --git a/compiler/qsc_eval/src/output.rs b/compiler/qsc_eval/src/output.rs index 4c4f569f34..cb4a9cced7 100644 --- a/compiler/qsc_eval/src/output.rs +++ b/compiler/qsc_eval/src/output.rs @@ -16,6 +16,11 @@ pub trait Receiver { /// This will return an error if handling the output fails. fn state(&mut self, state: Vec<(BigUint, Complex64)>, qubit_count: usize) -> Result<(), Error>; + /// Receive matrix output + /// # Errors + /// This will return an error if handling the output fails. + fn matrix(&mut self, matrix: Vec>) -> Result<(), Error>; + /// Receive generic message output /// # Errors /// This will return an error if handling the output fails. @@ -47,6 +52,15 @@ impl<'a> Receiver for GenericReceiver<'a> { Ok(()) } + fn matrix(&mut self, matrix: Vec>) -> Result<(), Error> { + writeln!(self.writer, "MATRIX:").map_err(|_| Error)?; + for row in matrix { + let row_str = row.iter().map(fmt_complex).collect::>().join(" "); + writeln!(self.writer, "{row_str}").map_err(|_| Error)?; + } + Ok(()) + } + fn message(&mut self, msg: &str) -> Result<(), Error> { writeln!(self.writer, "{msg}").map_err(|_| Error) } @@ -86,6 +100,15 @@ impl<'a> Receiver for CursorReceiver<'a> { Ok(()) } + fn matrix(&mut self, matrix: Vec>) -> Result<(), Error> { + writeln!(self.cursor, "MATRIX:").map_err(|_| Error)?; + for row in matrix { + let row_str = row.iter().map(fmt_complex).collect::>().join(" "); + writeln!(self.cursor, "{row_str}").map_err(|_| Error)?; + } + Ok(()) + } + fn message(&mut self, msg: &str) -> Result<(), Error> { writeln!(self.cursor, "{msg}").map_err(|_| Error) } diff --git a/library/src/tests/diagnostics.rs b/library/src/tests/diagnostics.rs index 7aed5a9029..346a4426b9 100644 --- a/library/src/tests/diagnostics.rs +++ b/library/src/tests/diagnostics.rs @@ -2,6 +2,7 @@ // Licensed under the MIT License. use super::test_expression; +use expect_test::expect; use qsc::interpret::Value; #[test] @@ -40,3 +41,50 @@ fn check_operations_are_equal() { ), ); } + +#[test] +fn check_dumpoperation_for_x() { + let output = test_expression( + "Microsoft.Quantum.Diagnostics.DumpOperation(1, qs => X(qs[0]))", + &Value::unit(), + ); + expect![[r#" + MATRIX: + 0.0000+0.0000𝑖 1.0000+0.0000𝑖 + 1.0000+0.0000𝑖 0.0000+0.0000𝑖 + "#]] + .assert_eq(&output); +} + +#[test] +fn check_dumpoperation_for_h() { + let output = test_expression( + "Microsoft.Quantum.Diagnostics.DumpOperation(1, qs => H(qs[0]))", + &Value::unit(), + ); + expect![[r#" + MATRIX: + 0.7071+0.0000𝑖 0.7071+0.0000𝑖 + 0.7071+0.0000𝑖 βˆ’0.7071+0.0000𝑖 + "#]] + .assert_eq(&output); +} + +#[test] +fn check_dumpoperation_for_ccnot() { + let output = test_expression( + "Microsoft.Quantum.Diagnostics.DumpOperation(3, qs => CCNOT(qs[0], qs[1], qs[2]))", + &Value::unit(), + ); + expect![[r#" + MATRIX: + 1.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 + 0.0000+0.0000𝑖 1.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 + 0.0000+0.0000𝑖 0.0000+0.0000𝑖 1.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 + 0.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 1.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 + 0.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 1.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 + 0.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 1.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 + 0.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 1.0000+0.0000𝑖 + 0.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 1.0000+0.0000𝑖 0.0000+0.0000𝑖 + "#]].assert_eq(&output); +} diff --git a/library/std/src/diagnostics.qs b/library/std/src/diagnostics.qs index 7d67e2c80b..2b3ac4cf00 100644 --- a/library/std/src/diagnostics.qs +++ b/library/std/src/diagnostics.qs @@ -69,6 +69,43 @@ namespace Microsoft.Quantum.Diagnostics { body intrinsic; } + /// # Summary + /// Given an operation, dumps the matrix representation of the operation actiong on the given + /// number of qubits. + /// + /// # Input + /// ## nQubits + /// The number of qubits on which the given operation acts. + /// ## op + /// The operation that is to be diagnosed. + /// + /// # Remarks + /// When run on the sparse-state simulator, the following snippet + /// will output the matrix + /// $\left(\begin{matrix} 0.0 & 0.707 \\\\ 0.707 & 0.0\end{matrix}\right)$: + /// + /// ```qsharp + /// operation DumpH() : Unit { + /// DumpOperation(1, qs => H(qs[0])); + /// } + /// ``` + /// Calling this operation has no observable effect from within Q#. The exact diagnostics that are displayed, + /// if any, are dependent on the current execution target and editor environment. + operation DumpOperation(nQubits : Int, op : Qubit[] => Unit) : Unit { + use (targets, extra) = (Qubit[nQubits], Qubit[nQubits]); + for i in 0..nQubits - 1 { + H(targets[i]); + CNOT(targets[i], extra[i]); + } + op(targets); + DumpMatrix(targets + extra); + ResetAll(targets + extra); + } + + function DumpMatrix(qs : Qubit[]) : Unit { + body intrinsic; + } + /// # Summary /// Checks whether a qubit is in the |0⟩ state, returning true if it is. /// @@ -194,5 +231,5 @@ namespace Microsoft.Quantum.Diagnostics { areEqual } - export DumpMachine, DumpRegister, CheckZero, CheckAllZero, Fact, CheckOperationsAreEqual; + export DumpMachine, DumpRegister, DumpOperation, CheckZero, CheckAllZero, Fact, CheckOperationsAreEqual; } diff --git a/pip/src/interpreter.rs b/pip/src/interpreter.rs index bbaab8cfc2..9e5914ac60 100644 --- a/pip/src/interpreter.rs +++ b/pip/src/interpreter.rs @@ -531,6 +531,10 @@ impl Receiver for OptionalCallbackReceiver<'_> { Ok(()) } + fn matrix(&mut self, matrix: Vec>) -> std::result::Result<(), Error> { + todo!() + } + fn message(&mut self, msg: &str) -> core::result::Result<(), Error> { if let Some(callback) = &self.callback { let out = DisplayableOutput::Message(msg.to_owned()); diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs index 961b93d502..7e8983f5d5 100644 --- a/wasm/src/lib.rs +++ b/wasm/src/lib.rs @@ -269,6 +269,10 @@ where Ok(()) } + fn matrix(&mut self, matrix: Vec>) -> Result<(), output::Error> { + todo!() + } + fn message(&mut self, msg: &str) -> Result<(), output::Error> { let msg_json = json!({"type": "Message", "message": msg}); (self.event_cb)(&msg_json.to_string()); From 9bdb923b5d406cbd72cdaaab087deadac9e44a22 Mon Sep 17 00:00:00 2001 From: Bill Ticehurst Date: Fri, 16 Aug 2024 18:00:41 -0700 Subject: [PATCH 02/32] Added matrix event to wasm and npm --- npm/qsharp/src/compiler/common.ts | 28 +++++++++++++++-- npm/qsharp/src/compiler/compiler.ts | 5 ++- npm/qsharp/src/compiler/events.ts | 11 +++++++ npm/qsharp/src/debug-service/debug-service.ts | 3 ++ wasm/src/lib.rs | 31 ++++++++++++++++++- 5 files changed, 73 insertions(+), 5 deletions(-) diff --git a/npm/qsharp/src/compiler/common.ts b/npm/qsharp/src/compiler/common.ts index 1656e5f3a5..769e1b9244 100644 --- a/npm/qsharp/src/compiler/common.ts +++ b/npm/qsharp/src/compiler/common.ts @@ -19,6 +19,11 @@ interface DumpMsg { stateLatex: string | null; } +interface MatrixMsg { + type: "Matrix"; + matrix: number[][][]; // Array or rows, which are an array of elements, which are complex numbers as [re, im] +} + interface MessageMsg { type: "Message"; message: string; @@ -29,7 +34,7 @@ interface ResultMsg { result: Result; } -type EventMsg = ResultMsg | DumpMsg | MessageMsg; +type EventMsg = ResultMsg | DumpMsg | MatrixMsg | MessageMsg; function outputAsResult(msg: string): ResultMsg | null { try { @@ -73,12 +78,29 @@ function outputAsDump(msg: string): DumpMsg | null { return null; } +function outputAsMatrix(msg: string): MatrixMsg | null { + try { + const obj = JSON.parse(msg); + if (obj?.type == "Matrix" && Array.isArray(obj.matrix)) { + return obj as MatrixMsg; + } + } catch { + return null; + } + return null; +} + export function eventStringToMsg(msg: string): EventMsg | null { - return outputAsResult(msg) || outputAsMessage(msg) || outputAsDump(msg); + return ( + outputAsResult(msg) || + outputAsMessage(msg) || + outputAsDump(msg) || + outputAsMatrix(msg) + ); } export type ShotResult = { success: boolean; result: string | VSDiagnostic; - events: Array; + events: Array; }; diff --git a/npm/qsharp/src/compiler/compiler.ts b/npm/qsharp/src/compiler/compiler.ts index a66e8c0642..a94620f606 100644 --- a/npm/qsharp/src/compiler/compiler.ts +++ b/npm/qsharp/src/compiler/compiler.ts @@ -273,6 +273,9 @@ export function onCompilerEvent(msg: string, eventTarget: IQscEventTarget) { case "Result": qscEvent = makeEvent("Result", qscMsg.result); break; + case "Matrix": + qscEvent = makeEvent("Matrix", qscMsg.matrix); + break; default: log.never(msgType); throw "Unexpected message type"; @@ -295,5 +298,5 @@ export const compilerProtocol: ServiceProtocol = { run: "requestWithProgress", checkExerciseSolution: "requestWithProgress", }, - eventNames: ["DumpMachine", "Message", "Result"], + eventNames: ["DumpMachine", "Matrix", "Message", "Result"], }; diff --git a/npm/qsharp/src/compiler/events.ts b/npm/qsharp/src/compiler/events.ts index 20c21e23fe..06bafbcee7 100644 --- a/npm/qsharp/src/compiler/events.ts +++ b/npm/qsharp/src/compiler/events.ts @@ -9,6 +9,7 @@ import { IServiceEventTarget } from "../workers/common.js"; export type QscEventData = | { type: "Message"; detail: string } | { type: "DumpMachine"; detail: { state: Dump; stateLatex: string | null } } + | { type: "Matrix"; detail: number[][][] } | { type: "Result"; detail: Result }; export type QscEvents = Event & QscEventData; @@ -81,6 +82,7 @@ export class QscEventTarget implements IQscEventTarget { this.onDumpMachine(ev.detail), ); this.addEventListener("Result", (ev) => this.onResult(ev.detail)); + this.addEventListener("Matrix", (ev) => this.onMatrix(ev.detail)); } } @@ -93,6 +95,15 @@ export class QscEventTarget implements IQscEventTarget { this.queueUiRefresh(); } + private onMatrix(matrix: number[][][]) { + this.ensureActiveShot(); + + const shotIdx = this.results.length - 1; + this.results[shotIdx].events.push({ type: "Matrix", matrix }); + + this.queueUiRefresh(); + } + private onDumpMachine(detail: { state: Dump; stateLatex: string | null }) { this.ensureActiveShot(); diff --git a/npm/qsharp/src/debug-service/debug-service.ts b/npm/qsharp/src/debug-service/debug-service.ts index dee78912f6..8eafb92288 100644 --- a/npm/qsharp/src/debug-service/debug-service.ts +++ b/npm/qsharp/src/debug-service/debug-service.ts @@ -163,6 +163,9 @@ export function onCompilerEvent(msg: string, eventTarget: IQscEventTarget) { case "Result": qscEvent = makeEvent("Result", qscMsg.result); break; + case "Matrix": + qscEvent = makeEvent("Matrix", qscMsg.matrix); + break; default: log.never(msgType); throw "Unexpected message type"; diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs index 7e8983f5d5..610a7fac7d 100644 --- a/wasm/src/lib.rs +++ b/wasm/src/lib.rs @@ -270,7 +270,36 @@ where } fn matrix(&mut self, matrix: Vec>) -> Result<(), output::Error> { - todo!() + let mut dump_json = String::new(); + + // Write the type and open the array or rows. + write!(dump_json, r#"{{"type": "Matrix","matrix": ["#) + .expect("writing to string should succeed"); + + // Map each row to a string representation of the row, and join them with commas. + // The row is an array, and each element is a tuple formatted as "[re, im]". + // e.g. {"type": "Matrix", "matrix": [ + // [[1, 2], [3, 4], [5, 6]], + // [[7, 8], [9, 10], [11, 12]] + // ]} + let row_strings = matrix + .iter() + .map(|row| { + let row_str = row + .iter() + .map(|elem| format!("[{}, {}]", elem.re, elem.im)) + .collect::>() + .join(", "); + format!("[{row_str}]") + }) + .collect::>() + .join(", "); + + // Close the array of rows and the JSON object. + write!(dump_json, r#"{row_strings}]}}"#).expect("writing to string should succeed"); + + (self.event_cb)(&dump_json); + Ok(()) } fn message(&mut self, msg: &str) -> Result<(), output::Error> { From 74ca22f00ba86060ef68d613be8f50c7d755a944 Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Mon, 19 Aug 2024 22:29:46 -0700 Subject: [PATCH 03/32] Fix bug in qubit sizing --- compiler/qsc_eval/src/intrinsic.rs | 2 +- library/src/tests/diagnostics.rs | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/compiler/qsc_eval/src/intrinsic.rs b/compiler/qsc_eval/src/intrinsic.rs index 871340e816..02d3c13523 100644 --- a/compiler/qsc_eval/src/intrinsic.rs +++ b/compiler/qsc_eval/src/intrinsic.rs @@ -89,7 +89,7 @@ pub(crate) fn call( let (state, qubit_count) = sim.capture_quantum_state(); let state = utils::split_state(&qubits, &state, qubit_count) .map_err(|()| Error::QubitsNotSeparable(arg_span))?; - let matrix = utils::state_to_matrix(state, qubit_count / 2); + let matrix = utils::state_to_matrix(state, qubits.len() / 2); match out.matrix(matrix) { Ok(()) => Ok(Value::unit()), Err(_) => Err(Error::OutputFail(name_span)), diff --git a/library/src/tests/diagnostics.rs b/library/src/tests/diagnostics.rs index 346a4426b9..8cf44279cb 100644 --- a/library/src/tests/diagnostics.rs +++ b/library/src/tests/diagnostics.rs @@ -88,3 +88,16 @@ fn check_dumpoperation_for_ccnot() { 0.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 0.0000+0.0000𝑖 1.0000+0.0000𝑖 0.0000+0.0000𝑖 "#]].assert_eq(&output); } + +#[test] +fn check_dumpoperation_with_extra_qubits_allocated() { + let output = test_expression( + "{use qs = Qubit[2]; Microsoft.Quantum.Diagnostics.DumpOperation(1, qs => H(qs[0]))}", + &Value::unit(), + ); + expect![[r#" + MATRIX: + 0.7071+0.0000𝑖 0.7071+0.0000𝑖 + 0.7071+0.0000𝑖 βˆ’0.7071+0.0000𝑖 + "#]].assert_eq(&output); +} From 7ef30c052b52d02abec3ce3d909aa83ee95c8b42 Mon Sep 17 00:00:00 2001 From: Bill Ticehurst Date: Mon, 19 Aug 2024 22:38:01 -0700 Subject: [PATCH 04/32] Crude playground and test outline --- playground/src/results.tsx | 33 ++++++++++++++++++++++++++++++++- wasm/src/tests.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/playground/src/results.tsx b/playground/src/results.tsx index 5fd9cfc5a2..37aaecc12c 100644 --- a/playground/src/results.tsx +++ b/playground/src/results.tsx @@ -53,6 +53,35 @@ function resultIsSame(a: ShotResult, b: ShotResult): boolean { return true; } +function Matrix(props: { matrix: number[][][] }) { + const style = `display: grid; grid-template-columns: repeat(${props.matrix[0].length}, 1fr);`; + function complexToString(re: number, im: number) { + // Return a max of 4 decimal places + re = Math.round(re * 10000) / 10000; + im = Math.round(im * 10000) / 10000; + if (im === 0) return re.toString(); + if (re === 0) return `${im}i`; + return `${re} + ${im}i`; + } + + return ( + <> +

Matrix

+
+ {props.matrix.map((row) => { + return row.map((cell) => { + return ( +
+ {complexToString(cell[0], cell[1])} +
+ ); + }); + })} +
+ + ); +} + export function ResultsTab(props: { evtTarget: QscEventTarget; onShotError?: (err?: VSDiagnostic) => void; @@ -240,13 +269,15 @@ export function ResultsTab(props: { {resultState.currResult?.events.map((evt) => { return evt.type === "Message" ? (
> {evt.message}
- ) : ( + ) : evt.type === "DumpMachine" ? (
+ ) : ( + ); })} diff --git a/wasm/src/tests.rs b/wasm/src/tests.rs index e470e89804..df86429aed 100644 --- a/wasm/src/tests.rs +++ b/wasm/src/tests.rs @@ -129,6 +129,32 @@ fn test_message() { ); assert!(result.is_ok()); } + +#[test] +fn test_matrix() { + let code = r"namespace Test { + import Microsoft.Quantum.Diagnostics.DumpOperation; + operation Main() : Unit { + DumpOperation(2, Bell); + } + + operation Bell(q: Qubit[]) : Unit { + H(q[0]); + CX(q[0], q[1]); + } + }"; + let expr = "Test.Main()"; + let result = run_internal( + SourceMap::new([("test.qs".into(), code.into())], Some(expr.into())), + |msg| { + // TODO: Validate the matrix output + assert!(msg.contains("Matrix") || msg.contains("result")); + }, + 1, + ); + assert!(result.is_ok()); +} + #[test] fn message_with_escape_sequences() { let code = r#"namespace Sample { From fcf123a5b4ebe19e367a99e65570fc17b84c0bed Mon Sep 17 00:00:00 2001 From: Bill Ticehurst Date: Tue, 20 Aug 2024 10:55:11 -0700 Subject: [PATCH 05/32] Crude LaTeX support --- compiler/qsc/src/lib.rs | 4 +- compiler/qsc_eval/src/state.rs | 59 ++++++++++++++++++++++++++++- npm/qsharp/src/compiler/common.ts | 1 + npm/qsharp/src/compiler/compiler.ts | 5 ++- npm/qsharp/src/compiler/events.ts | 10 +++-- playground/src/results.tsx | 17 ++++++++- wasm/src/lib.rs | 10 ++++- 7 files changed, 96 insertions(+), 10 deletions(-) diff --git a/compiler/qsc/src/lib.rs b/compiler/qsc/src/lib.rs index 5abac73206..62092ba387 100644 --- a/compiler/qsc/src/lib.rs +++ b/compiler/qsc/src/lib.rs @@ -49,7 +49,9 @@ pub mod line_column { pub use qsc_eval::{ backend::{Backend, SparseSim}, - state::{fmt_basis_state_label, fmt_complex, format_state_id, get_latex, get_phase}, + state::{ + fmt_basis_state_label, fmt_complex, format_state_id, get_latex, get_matrix_latex, get_phase, + }, }; pub mod linter { diff --git a/compiler/qsc_eval/src/state.rs b/compiler/qsc_eval/src/state.rs index 6e69c071b3..baf0b61220 100644 --- a/compiler/qsc_eval/src/state.rs +++ b/compiler/qsc_eval/src/state.rs @@ -6,7 +6,7 @@ mod tests; use num_bigint::BigUint; use num_complex::{Complex, Complex64}; -use std::fmt::Write; +use std::{f64::consts::FRAC_1_SQRT_2, fmt::Write}; #[must_use] pub fn format_state_id(id: &BigUint, qubit_count: usize) -> String { @@ -381,6 +381,63 @@ pub fn get_latex(state: &Vec<(BigUint, Complex64)>, qubit_count: usize) -> Optio Some(latex) } +#[must_use] +pub fn get_matrix_latex(matrix: &Vec>) -> String { + let mut latex: String = String::with_capacity(500); + latex.push_str("$ \\begin{bmatrix} "); + for row in matrix { + let mut is_first: bool = true; + for element in row { + if !is_first { + latex.push_str(" & "); + } + + if (element.re - 0.0).abs() < 1e-9 && (element.im - 0.0).abs() < 1e-9 { + latex.push('0'); + is_first = false; + continue; + } + // Handle 1/sqrt(2) + if ((element.re).abs() - FRAC_1_SQRT_2).abs() < 1e-9 && (element.im - 0.0).abs() < 1e-9 + { + if element.re < 0.0 { + latex.push('-'); + } + latex.push_str("\\frac{1}{\\sqrt{2}}"); + is_first = false; + continue; + } + + if (element.re - 0.0).abs() < 1e-9 && (element.im.abs() - FRAC_1_SQRT_2).abs() < 1e-9 { + if element.im < 0.0 { + latex.push('-'); + } + latex.push_str("\\frac{i}{\\sqrt{2}}"); + is_first = false; + continue; + } + + let cpl = ComplexNumber::recognize(element.re, element.im); + + match &cpl { + ComplexNumber::Cartesian(cartesian_form) => { + write_latex_for_cartesian_form(&mut latex, cartesian_form, false); + } + ComplexNumber::Polar(polar_form) => { + write_latex_for_polar_form(&mut latex, polar_form, false); + } + } + + // write!(latex, "{}", fmt_complex(element)).expect("Expected to write complex number."); + is_first = false; + } + latex.push_str(" \\\\ "); + } + latex.push_str("\\end{bmatrix} $"); + latex.shrink_to_fit(); + latex +} + /// Write latex for one term of quantum state. /// Latex is rendered for coefficient only (not for basis vector). /// + is rendered only if ``render_plus`` is true. diff --git a/npm/qsharp/src/compiler/common.ts b/npm/qsharp/src/compiler/common.ts index 769e1b9244..9522c1c7ab 100644 --- a/npm/qsharp/src/compiler/common.ts +++ b/npm/qsharp/src/compiler/common.ts @@ -22,6 +22,7 @@ interface DumpMsg { interface MatrixMsg { type: "Matrix"; matrix: number[][][]; // Array or rows, which are an array of elements, which are complex numbers as [re, im] + matrixLatex: string; } interface MessageMsg { diff --git a/npm/qsharp/src/compiler/compiler.ts b/npm/qsharp/src/compiler/compiler.ts index a94620f606..39d79432b6 100644 --- a/npm/qsharp/src/compiler/compiler.ts +++ b/npm/qsharp/src/compiler/compiler.ts @@ -274,7 +274,10 @@ export function onCompilerEvent(msg: string, eventTarget: IQscEventTarget) { qscEvent = makeEvent("Result", qscMsg.result); break; case "Matrix": - qscEvent = makeEvent("Matrix", qscMsg.matrix); + qscEvent = makeEvent("Matrix", { + matrix: qscMsg.matrix, + matrixLatex: qscMsg.matrixLatex, + }); break; default: log.never(msgType); diff --git a/npm/qsharp/src/compiler/events.ts b/npm/qsharp/src/compiler/events.ts index 06bafbcee7..7b65dec6e0 100644 --- a/npm/qsharp/src/compiler/events.ts +++ b/npm/qsharp/src/compiler/events.ts @@ -9,7 +9,7 @@ import { IServiceEventTarget } from "../workers/common.js"; export type QscEventData = | { type: "Message"; detail: string } | { type: "DumpMachine"; detail: { state: Dump; stateLatex: string | null } } - | { type: "Matrix"; detail: number[][][] } + | { type: "Matrix"; detail: { matrix: number[][][]; matrixLatex: string } } | { type: "Result"; detail: Result }; export type QscEvents = Event & QscEventData; @@ -95,11 +95,15 @@ export class QscEventTarget implements IQscEventTarget { this.queueUiRefresh(); } - private onMatrix(matrix: number[][][]) { + private onMatrix(detail: { matrix: number[][][]; matrixLatex: string }) { this.ensureActiveShot(); const shotIdx = this.results.length - 1; - this.results[shotIdx].events.push({ type: "Matrix", matrix }); + this.results[shotIdx].events.push({ + type: "Matrix", + matrix: detail.matrix, + matrixLatex: detail.matrixLatex, + }); this.queueUiRefresh(); } diff --git a/playground/src/results.tsx b/playground/src/results.tsx index 37aaecc12c..de350852cd 100644 --- a/playground/src/results.tsx +++ b/playground/src/results.tsx @@ -4,7 +4,7 @@ import { QscEventTarget, ShotResult, VSDiagnostic } from "qsharp-lang"; import { useEffect, useState } from "preact/hooks"; -import { Histogram } from "qsharp-lang/ux"; +import { Histogram, Markdown } from "qsharp-lang/ux"; import { StateTable } from "./state.js"; import { ActiveTab } from "./main.js"; @@ -53,6 +53,7 @@ function resultIsSame(a: ShotResult, b: ShotResult): boolean { return true; } +/* function Matrix(props: { matrix: number[][][] }) { const style = `display: grid; grid-template-columns: repeat(${props.matrix[0].length}, 1fr);`; function complexToString(re: number, im: number) { @@ -81,6 +82,18 @@ function Matrix(props: { matrix: number[][][] }) { ); } +*/ + +function MatrixLatex(props: { latex: string }) { + return ( + <> +
+ DumpOperation +
+ + + ); +} export function ResultsTab(props: { evtTarget: QscEventTarget; @@ -277,7 +290,7 @@ export function ResultsTab(props: { > ) : ( - + ); })} diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs index 610a7fac7d..0194a69a42 100644 --- a/wasm/src/lib.rs +++ b/wasm/src/lib.rs @@ -12,7 +12,7 @@ use num_complex::Complex64; use project_system::{into_qsc_args, ProgramConfig}; use qsc::{ compile::{self, Dependencies}, - format_state_id, get_latex, + format_state_id, get_latex, get_matrix_latex, hir::PackageId, interpret::{ self, @@ -296,7 +296,13 @@ where .join(", "); // Close the array of rows and the JSON object. - write!(dump_json, r#"{row_strings}]}}"#).expect("writing to string should succeed"); + let latex_string = serde_json::to_string(&get_matrix_latex(&matrix)) + .expect("serialization should succeed"); + write!( + dump_json, + r#"{row_strings}], "matrixLatex": {latex_string} }}"# + ) + .expect("writing to string should succeed"); (self.event_cb)(&dump_json); Ok(()) From 6bf61de5540c6682cd2f6b76bde3a350d7fa4ffa Mon Sep 17 00:00:00 2001 From: Bill Ticehurst Date: Wed, 21 Aug 2024 10:13:32 -0700 Subject: [PATCH 06/32] Number experiments --- compiler/qsc_eval/src/state.rs | 62 +++++++++++++++++++ npm/qsharp/src/debug-service/debug-service.ts | 5 +- 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/compiler/qsc_eval/src/state.rs b/compiler/qsc_eval/src/state.rs index baf0b61220..4730178b71 100644 --- a/compiler/qsc_eval/src/state.rs +++ b/compiler/qsc_eval/src/state.rs @@ -381,6 +381,68 @@ pub fn get_latex(state: &Vec<(BigUint, Complex64)>, qubit_count: usize) -> Optio Some(latex) } +fn is_close_enough(val: &Complex64, target: &Complex64) -> bool { + (val.re - target.re).abs() < 1e-9 && (val.im - target.im).abs() < 1e-9 +} + +// Quick and dirty matching for the most common matrix terms we care about rendering +// LaTeX for, e.g. 1/sqrt(2), -i/sqrt(2), etc. +// Anything not in this list gets a floating point form. +fn get_latex_for_simple_term(val: &Complex64) -> Option { + if is_close_enough(val, &Complex64::new(0.0, 0.0)) { + return Some("0".to_string()); + } + if is_close_enough(val, &Complex64::new(1.0, 0.0)) { + return Some("1".to_string()); + } + if is_close_enough(val, &Complex64::new(0.0, 1.0)) { + return Some("i".to_string()); + } + if is_close_enough(val, &Complex64::new(-1.0, 0.0)) { + return Some("-1".to_string()); + } + if is_close_enough(val, &Complex64::new(0.0, -1.0)) { + return Some("-i".to_string()); + } + if is_close_enough(val, &Complex64::new(FRAC_1_SQRT_2, 0.0)) { + return Some("\\frac{1}{\\sqrt{2}}".to_string()); + } + if is_close_enough(val, &Complex64::new(0.0, FRAC_1_SQRT_2)) { + return Some("\\frac{i}{\\sqrt{2}}".to_string()); + } + if is_close_enough(val, &Complex64::new(-FRAC_1_SQRT_2, 0.0)) { + return Some("-\\frac{1}{\\sqrt{2}}".to_string()); + } + if is_close_enough(val, &Complex64::new(0.0, -FRAC_1_SQRT_2)) { + return Some("-\\frac{i}{\\sqrt{2}}".to_string()); + } + if is_close_enough(val, &Complex64::new(0.5, 0.0)) { + return Some("\\frac{1}{2}".to_string()); + } + if is_close_enough(val, &Complex64::new(0.0, 0.5)) { + return Some("\\frac{i}{2}".to_string()); + } + if is_close_enough(val, &Complex64::new(-0.5, 0.0)) { + return Some("-\\frac{1}{2}".to_string()); + } + if is_close_enough(val, &Complex64::new(0.0, -0.5)) { + return Some("-\\frac{i}{2}".to_string()); + } + if is_close_enough(val, &Complex64::new(0.5, 0.5)) { + return Some("\\frac{1}{2} + \\frac{i}{2}".to_string()); + } + if is_close_enough(val, &Complex64::new(-0.5, -0.5)) { + return Some("-\\frac{1}{2} - \\frac{i}{2}".to_string()); + } + if is_close_enough(val, &Complex64::new(-0.5, 0.5)) { + return Some("-\\frac{1}{2} + \\frac{i}{2}".to_string()); + } + if is_close_enough(val, &Complex64::new(0.5, -0.5)) { + return Some("\\frac{1}{2} - \\frac{i}{2}".to_string()); + } + None +} + #[must_use] pub fn get_matrix_latex(matrix: &Vec>) -> String { let mut latex: String = String::with_capacity(500); diff --git a/npm/qsharp/src/debug-service/debug-service.ts b/npm/qsharp/src/debug-service/debug-service.ts index 8eafb92288..aa6684c667 100644 --- a/npm/qsharp/src/debug-service/debug-service.ts +++ b/npm/qsharp/src/debug-service/debug-service.ts @@ -164,7 +164,10 @@ export function onCompilerEvent(msg: string, eventTarget: IQscEventTarget) { qscEvent = makeEvent("Result", qscMsg.result); break; case "Matrix": - qscEvent = makeEvent("Matrix", qscMsg.matrix); + qscEvent = makeEvent("Matrix", { + matrix: qscMsg.matrix, + matrixLatex: qscMsg.matrixLatex, + }); break; default: log.never(msgType); From b62e372286dbce83a75a52770d900d01eb157f31 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Thu, 22 Aug 2024 17:06:42 -0700 Subject: [PATCH 07/32] Preserving global state when splitting state --- compiler/qsc_eval/src/intrinsic/utils.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/qsc_eval/src/intrinsic/utils.rs b/compiler/qsc_eval/src/intrinsic/utils.rs index c5d327dab7..f55f2cca29 100644 --- a/compiler/qsc_eval/src/intrinsic/utils.rs +++ b/compiler/qsc_eval/src/intrinsic/utils.rs @@ -88,11 +88,11 @@ fn collect_split_state( let (base_label, base_val) = state_iter.next().expect("state should never be empty"); let dump_base_label = base_label & dump_mask; let other_base_label = base_label & other_mask; - let mut dump_norm = 1.0_f64; + let mut dump_norm = base_val.norm().powi(2); // Start with an amplitude of 1 in the first expected split states. This becomes the basis // of the split later, but will get normalized as part of collecting the remaining states. - dump_state.insert(dump_base_label.clone(), Complex64::one()); + dump_state.insert(dump_base_label.clone(), *base_val); other_state.insert(other_base_label.clone(), Complex64::one()); for (curr_label, curr_val) in state_iter { @@ -119,7 +119,7 @@ fn collect_split_state( if let Entry::Vacant(entry) = dump_state.entry(dump_label) { // When capturing the amplitude for the dump state, we must divide out the amplitude for the other // state, and vice-versa below. - let amplitude = curr_val / other_val; + let amplitude = *curr_val; let norm = amplitude.norm().powi(2); if !norm.is_nearly_zero() { entry.insert(amplitude); From a2fe67227ca08ed70e634e2eae539200c83cb84d Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Thu, 22 Aug 2024 23:17:09 -0700 Subject: [PATCH 08/32] Fix phase issues, document difference, update tests --- compiler/qsc_eval/src/intrinsic/tests.rs | 20 +++++++ compiler/qsc_eval/src/intrinsic/utils.rs | 39 +++++-------- compiler/qsc_eval/src/tests.rs | 4 +- library/src/tests/diagnostics.rs | 73 +++++++++++++++++++++++- library/std/src/diagnostics.qs | 5 +- 5 files changed, 110 insertions(+), 31 deletions(-) diff --git a/compiler/qsc_eval/src/intrinsic/tests.rs b/compiler/qsc_eval/src/intrinsic/tests.rs index 7d8fa92029..8a3fa5c5ad 100644 --- a/compiler/qsc_eval/src/intrinsic/tests.rs +++ b/compiler/qsc_eval/src/intrinsic/tests.rs @@ -436,6 +436,26 @@ fn dump_register_other_qubits_one_state_is_separable() { ); } +#[test] +fn dump_register_other_qubits_phase_reflected_in_subset() { + check_intrinsic_output( + "", + indoc! {"{ + use qs = Qubit[3]; + H(qs[0]); + X(qs[2]); + Z(qs[2]); + Microsoft.Quantum.Diagnostics.DumpRegister(qs[...1]); + ResetAll(qs); + }"}, + &expect![[r#" + STATE: + |00⟩: βˆ’0.7071+0.0000𝑖 + |10⟩: βˆ’0.7071+0.0000𝑖 + "#]], + ); +} + #[test] fn dump_register_qubits_reorder_output() { check_intrinsic_output( diff --git a/compiler/qsc_eval/src/intrinsic/utils.rs b/compiler/qsc_eval/src/intrinsic/utils.rs index f55f2cca29..af8267e214 100644 --- a/compiler/qsc_eval/src/intrinsic/utils.rs +++ b/compiler/qsc_eval/src/intrinsic/utils.rs @@ -5,8 +5,8 @@ use std::collections::hash_map::Entry; use num_bigint::BigUint; use num_complex::{Complex, Complex64}; -use num_traits::{One, Zero}; -use rustc_hash::FxHashMap; +use num_traits::Zero; +use rustc_hash::{FxHashMap, FxHashSet}; /// Given a state and a set of qubits, split the state into two parts: the qubits to dump and the remaining qubits. /// This function will return an error if the state is not separable using the provided qubit identifiers. @@ -22,26 +22,13 @@ pub fn split_state( } let mut dump_state = FxHashMap::default(); - let mut other_state = FxHashMap::default(); // Compute the mask for the qubits to dump and the mask for the other qubits. let (dump_mask, other_mask) = compute_mask(qubit_count, qubits); // Try to split out the state for the given qubits from the whole state, detecting any entanglement // and returning an error if the qubits are not separable. - let dump_norm = collect_split_state( - state, - &dump_mask, - &other_mask, - &mut dump_state, - &mut other_state, - )?; - - // If the product of the collected states is not equal to the total number of input states, then that - // implies some states are zero amplitude that would have to be non-zero for the state to be separable. - if state.len() != dump_state.len() * other_state.len() { - return Err(()); - } + let dump_norm = collect_split_state(state, &dump_mask, &other_mask, &mut dump_state)?; let dump_norm = 1.0 / dump_norm.sqrt(); let mut dump_state = dump_state @@ -79,7 +66,6 @@ fn collect_split_state( dump_mask: &BigUint, other_mask: &BigUint, dump_state: &mut FxHashMap, - other_state: &mut FxHashMap, ) -> Result { // To ensure consistent ordering, we iterate over the vector directly (returned from the simulator in a deterministic order), // and not the map used for arbitrary lookup. @@ -89,11 +75,12 @@ fn collect_split_state( let dump_base_label = base_label & dump_mask; let other_base_label = base_label & other_mask; let mut dump_norm = base_val.norm().powi(2); + let mut other_state = FxHashSet::default(); // Start with an amplitude of 1 in the first expected split states. This becomes the basis // of the split later, but will get normalized as part of collecting the remaining states. dump_state.insert(dump_base_label.clone(), *base_val); - other_state.insert(other_base_label.clone(), Complex64::one()); + other_state.insert(other_base_label.clone()); for (curr_label, curr_val) in state_iter { let dump_label = curr_label & dump_mask; @@ -117,8 +104,6 @@ fn collect_split_state( } if let Entry::Vacant(entry) = dump_state.entry(dump_label) { - // When capturing the amplitude for the dump state, we must divide out the amplitude for the other - // state, and vice-versa below. let amplitude = *curr_val; let norm = amplitude.norm().powi(2); if !norm.is_nearly_zero() { @@ -126,14 +111,16 @@ fn collect_split_state( dump_norm += norm; } } - if let Entry::Vacant(entry) = other_state.entry(other_label) { - let amplitude = curr_val / dump_val; - let norm = amplitude.norm().powi(2); - if !norm.is_nearly_zero() { - entry.insert(amplitude); - } + if !(curr_val / dump_val).norm().powi(2).is_nearly_zero() { + other_state.insert(other_label); } } + + // If the product of the collected states is not equal to the total number of input states, then that + // implies some states are zero amplitude that would have to be non-zero for the state to be separable. + if state.len() != dump_state.len() * other_state.len() { + return Err(()); + } Ok(dump_norm) } diff --git a/compiler/qsc_eval/src/tests.rs b/compiler/qsc_eval/src/tests.rs index da44e16db0..5d6e0f3bfc 100644 --- a/compiler/qsc_eval/src/tests.rs +++ b/compiler/qsc_eval/src/tests.rs @@ -3724,7 +3724,7 @@ fn controlled_operation_with_duplicate_controls_fails() { 1, ), item: LocalItemId( - 124, + 126, ), }, caller: PackageId( @@ -3774,7 +3774,7 @@ fn controlled_operation_with_target_in_controls_fails() { 1, ), item: LocalItemId( - 124, + 126, ), }, caller: PackageId( diff --git a/library/src/tests/diagnostics.rs b/library/src/tests/diagnostics.rs index 8cf44279cb..f104dd9100 100644 --- a/library/src/tests/diagnostics.rs +++ b/library/src/tests/diagnostics.rs @@ -42,6 +42,20 @@ fn check_operations_are_equal() { ); } +#[test] +fn check_dumpoperation_for_i() { + let output = test_expression( + "Microsoft.Quantum.Diagnostics.DumpOperation(1, qs => I(qs[0]))", + &Value::unit(), + ); + expect![[r#" + MATRIX: + 1.0000+0.0000𝑖 0.0000+0.0000𝑖 + 0.0000+0.0000𝑖 1.0000+0.0000𝑖 + "#]] + .assert_eq(&output); +} + #[test] fn check_dumpoperation_for_x() { let output = test_expression( @@ -70,6 +84,20 @@ fn check_dumpoperation_for_h() { .assert_eq(&output); } +#[test] +fn check_dumpoperation_for_y() { + let output = test_expression( + "Microsoft.Quantum.Diagnostics.DumpOperation(1, qs => Y(qs[0]))", + &Value::unit(), + ); + expect![[r#" + MATRIX: + 0.0000+0.0000𝑖 0.0000βˆ’1.0000𝑖 + 0.0000+1.0000𝑖 0.0000+0.0000𝑖 + "#]] + .assert_eq(&output); +} + #[test] fn check_dumpoperation_for_ccnot() { let output = test_expression( @@ -99,5 +127,48 @@ fn check_dumpoperation_with_extra_qubits_allocated() { MATRIX: 0.7071+0.0000𝑖 0.7071+0.0000𝑖 0.7071+0.0000𝑖 βˆ’0.7071+0.0000𝑖 - "#]].assert_eq(&output); + "#]] + .assert_eq(&output); +} + +#[test] +fn check_dumpoperation_with_extra_qubits_in_superposition() { + let output = test_expression( + "{use qs = Qubit[2]; H(qs[0]); Microsoft.Quantum.Diagnostics.DumpOperation(1, qs => H(qs[0])); Reset(qs[0]);}", + &Value::unit(), + ); + expect![[r#" + MATRIX: + 0.7071+0.0000𝑖 0.7071+0.0000𝑖 + 0.7071+0.0000𝑖 βˆ’0.7071+0.0000𝑖 + "#]] + .assert_eq(&output); +} + +#[test] +fn check_dumpoperation_with_extra_qubits_global_phase_reflected_in_matrix() { + let output = test_expression( + "{use qs = Qubit[2]; R(PauliI, Std.Math.PI() / 2.0, qs[0]); Microsoft.Quantum.Diagnostics.DumpOperation(1, qs => H(qs[0])); Reset(qs[0]);}", + &Value::unit(), + ); + expect![[r#" + MATRIX: + 0.5000βˆ’0.5000𝑖 0.5000βˆ’0.5000𝑖 + 0.5000βˆ’0.5000𝑖 βˆ’0.5000+0.5000𝑖 + "#]] + .assert_eq(&output); +} + +#[test] +fn check_dumpoperation_with_extra_qubits_relative_phase_not_reflected_in_matrix() { + let output = test_expression( + "{use qs = Qubit[2]; R1(Std.Math.PI() / 2.0, qs[0]); Microsoft.Quantum.Diagnostics.DumpOperation(1, qs => H(qs[0])); Reset(qs[0]);}", + &Value::unit(), + ); + expect![[r#" + MATRIX: + 0.7071+0.0000𝑖 0.7071+0.0000𝑖 + 0.7071+0.0000𝑖 βˆ’0.7071+0.0000𝑖 + "#]] + .assert_eq(&output); } diff --git a/library/std/src/diagnostics.qs b/library/std/src/diagnostics.qs index 2b3ac4cf00..85d1025388 100644 --- a/library/std/src/diagnostics.qs +++ b/library/std/src/diagnostics.qs @@ -89,8 +89,9 @@ namespace Microsoft.Quantum.Diagnostics { /// DumpOperation(1, qs => H(qs[0])); /// } /// ``` - /// Calling this operation has no observable effect from within Q#. The exact diagnostics that are displayed, - /// if any, are dependent on the current execution target and editor environment. + /// Calling this operation has no observable effect from within Q#. + /// Note that if `DumpOperation` is called when there are other qubits allocated, + /// the matrix displayed may reflect any global phase that has accumulated on the other qubits. operation DumpOperation(nQubits : Int, op : Qubit[] => Unit) : Unit { use (targets, extra) = (Qubit[nQubits], Qubit[nQubits]); for i in 0..nQubits - 1 { From 6dd411ce195c5c23a485876186a0815acd64974c Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Thu, 22 Aug 2024 23:17:59 -0700 Subject: [PATCH 09/32] Remove out-of-date comment --- compiler/qsc_eval/src/intrinsic/utils.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/compiler/qsc_eval/src/intrinsic/utils.rs b/compiler/qsc_eval/src/intrinsic/utils.rs index af8267e214..ed1f38c4d9 100644 --- a/compiler/qsc_eval/src/intrinsic/utils.rs +++ b/compiler/qsc_eval/src/intrinsic/utils.rs @@ -77,8 +77,6 @@ fn collect_split_state( let mut dump_norm = base_val.norm().powi(2); let mut other_state = FxHashSet::default(); - // Start with an amplitude of 1 in the first expected split states. This becomes the basis - // of the split later, but will get normalized as part of collecting the remaining states. dump_state.insert(dump_base_label.clone(), *base_val); other_state.insert(other_base_label.clone()); From 29205962c0e6abd17f954167ded67aa35488e921 Mon Sep 17 00:00:00 2001 From: Bill Ticehurst Date: Fri, 23 Aug 2024 08:03:36 -0700 Subject: [PATCH 10/32] Basic python output --- compiler/qsc_eval/src/state.rs | 23 ++--------------------- compiler/qsc_eval/src/tests.rs | 4 ++-- library/std/src/diagnostics.qs | 2 +- pip/src/displayable_output.rs | 33 ++++++++++++++++++++++++++++++++- pip/src/interpreter.rs | 21 ++++++++++++++++++--- 5 files changed, 55 insertions(+), 28 deletions(-) diff --git a/compiler/qsc_eval/src/state.rs b/compiler/qsc_eval/src/state.rs index 4730178b71..facc496212 100644 --- a/compiler/qsc_eval/src/state.rs +++ b/compiler/qsc_eval/src/state.rs @@ -454,27 +454,8 @@ pub fn get_matrix_latex(matrix: &Vec>) -> String { latex.push_str(" & "); } - if (element.re - 0.0).abs() < 1e-9 && (element.im - 0.0).abs() < 1e-9 { - latex.push('0'); - is_first = false; - continue; - } - // Handle 1/sqrt(2) - if ((element.re).abs() - FRAC_1_SQRT_2).abs() < 1e-9 && (element.im - 0.0).abs() < 1e-9 - { - if element.re < 0.0 { - latex.push('-'); - } - latex.push_str("\\frac{1}{\\sqrt{2}}"); - is_first = false; - continue; - } - - if (element.re - 0.0).abs() < 1e-9 && (element.im.abs() - FRAC_1_SQRT_2).abs() < 1e-9 { - if element.im < 0.0 { - latex.push('-'); - } - latex.push_str("\\frac{i}{\\sqrt{2}}"); + if let Some(simple_latex) = get_latex_for_simple_term(element) { + latex.push_str(&simple_latex); is_first = false; continue; } diff --git a/compiler/qsc_eval/src/tests.rs b/compiler/qsc_eval/src/tests.rs index f392286e1d..56aadd3261 100644 --- a/compiler/qsc_eval/src/tests.rs +++ b/compiler/qsc_eval/src/tests.rs @@ -3724,7 +3724,7 @@ fn controlled_operation_with_duplicate_controls_fails() { 1, ), item: LocalItemId( - 130, + 132, ), }, caller: PackageId( @@ -3774,7 +3774,7 @@ fn controlled_operation_with_target_in_controls_fails() { 1, ), item: LocalItemId( - 130, + 132, ), }, caller: PackageId( diff --git a/library/std/src/diagnostics.qs b/library/std/src/diagnostics.qs index 6dff160acd..cdf6890895 100644 --- a/library/std/src/diagnostics.qs +++ b/library/std/src/diagnostics.qs @@ -369,5 +369,5 @@ namespace Microsoft.Quantum.Diagnostics { body intrinsic; } - export DumpMachine, DumpRegister, CheckZero, CheckAllZero, Fact, CheckOperationsAreEqual, StartCountingOperation, StopCountingOperation, StartCountingFunction, StopCountingFunction, StartCountingQubits, StopCountingQubits; + export DumpMachine, DumpRegister, DumpOperation, CheckZero, CheckAllZero, Fact, CheckOperationsAreEqual, StartCountingOperation, StopCountingOperation, StartCountingFunction, StopCountingFunction, StartCountingQubits, StopCountingQubits; } diff --git a/pip/src/displayable_output.rs b/pip/src/displayable_output.rs index 8edadc75d5..eabfc69c8e 100644 --- a/pip/src/displayable_output.rs +++ b/pip/src/displayable_output.rs @@ -6,11 +6,14 @@ mod tests; use num_bigint::BigUint; use num_complex::{Complex64, ComplexFloat}; -use qsc::{fmt_basis_state_label, fmt_complex, format_state_id, get_latex, get_phase}; +use qsc::{ + fmt_basis_state_label, fmt_complex, format_state_id, get_latex, get_matrix_latex, get_phase, +}; use std::fmt::Write; #[derive(Clone)] pub struct DisplayableState(pub Vec<(BigUint, Complex64)>, pub usize); +pub struct DisplayableMatrix(pub Vec>); impl DisplayableState { pub fn to_plain(&self) -> String { @@ -57,7 +60,35 @@ impl DisplayableState { } } +impl DisplayableMatrix { + pub fn to_plain(&self) -> String { + format!( + "MATRIX:{}", + self.0.iter().fold(String::new(), |mut output, row| { + let _ = write!( + output, + "\n{}", + row.iter().fold(String::new(), |mut row_output, element| { + let _ = write!(row_output, " {}", fmt_complex(element)); + row_output + }) + ); + output + }) + ) + } + + pub fn to_html(&self) -> String { + format!("
{}
", self.to_plain()) + } + + pub fn to_latex(&self) -> String { + get_matrix_latex(&self.0) + } +} + pub enum DisplayableOutput { State(DisplayableState), Message(String), + Matrix(DisplayableMatrix), } diff --git a/pip/src/interpreter.rs b/pip/src/interpreter.rs index 9e5914ac60..29092fc7c5 100644 --- a/pip/src/interpreter.rs +++ b/pip/src/interpreter.rs @@ -2,7 +2,7 @@ // Licensed under the MIT License. use crate::{ - displayable_output::{DisplayableOutput, DisplayableState}, + displayable_output::{DisplayableMatrix, DisplayableOutput, DisplayableState}, fs::file_system, noisy_simulator::register_noisy_simulator_submodule, }; @@ -328,6 +328,7 @@ impl Output { fn __repr__(&self) -> String { match &self.0 { DisplayableOutput::State(state) => state.to_plain(), + DisplayableOutput::Matrix(matrix) => matrix.to_plain(), DisplayableOutput::Message(msg) => msg.clone(), } } @@ -339,6 +340,7 @@ impl Output { fn _repr_html_(&self) -> String { match &self.0 { DisplayableOutput::State(state) => state.to_html(), + DisplayableOutput::Matrix(matrix) => matrix.to_html(), DisplayableOutput::Message(msg) => format!("

{msg}

"), } } @@ -346,6 +348,7 @@ impl Output { fn _repr_latex_(&self) -> Option { match &self.0 { DisplayableOutput::State(state) => state.to_latex(), + DisplayableOutput::Matrix(matrix) => Some(matrix.to_latex()), DisplayableOutput::Message(_) => None, } } @@ -353,7 +356,7 @@ impl Output { fn state_dump(&self) -> Option { match &self.0 { DisplayableOutput::State(state) => Some(StateDumpData(state.clone())), - DisplayableOutput::Message(_) => None, + DisplayableOutput::Matrix(_) | DisplayableOutput::Message(_) => None, } } } @@ -532,7 +535,19 @@ impl Receiver for OptionalCallbackReceiver<'_> { } fn matrix(&mut self, matrix: Vec>) -> std::result::Result<(), Error> { - todo!() + if let Some(callback) = &self.callback { + let out = DisplayableOutput::Matrix(DisplayableMatrix(matrix)); + callback + .call1( + self.py, + PyTuple::new( + self.py, + &[Py::new(self.py, Output(out)).expect("should be able to create output")], + ), + ) + .map_err(|_| Error)?; + } + Ok(()) } fn message(&mut self, msg: &str) -> core::result::Result<(), Error> { From 3991273327e514e9b82d6175c7f7453aa4e2883d Mon Sep 17 00:00:00 2001 From: Bill Ticehurst Date: Fri, 23 Aug 2024 13:34:00 -0700 Subject: [PATCH 11/32] Fix merge conflict --- compiler/qsc_eval/src/tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/qsc_eval/src/tests.rs b/compiler/qsc_eval/src/tests.rs index 5d6e0f3bfc..56aadd3261 100644 --- a/compiler/qsc_eval/src/tests.rs +++ b/compiler/qsc_eval/src/tests.rs @@ -3724,7 +3724,7 @@ fn controlled_operation_with_duplicate_controls_fails() { 1, ), item: LocalItemId( - 126, + 132, ), }, caller: PackageId( @@ -3774,7 +3774,7 @@ fn controlled_operation_with_target_in_controls_fails() { 1, ), item: LocalItemId( - 126, + 132, ), }, caller: PackageId( From b492ea32f1e0232ce97e10e159ae38f6cdd420b3 Mon Sep 17 00:00:00 2001 From: Bill Ticehurst Date: Fri, 23 Aug 2024 16:12:05 -0700 Subject: [PATCH 12/32] Pretty latex --- pip/qsharp/_qsharp.py | 14 ++++++++++++-- pip/src/displayable_output.rs | 4 ---- pip/src/interpreter.rs | 17 +++++++++++------ 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/pip/qsharp/_qsharp.py b/pip/qsharp/_qsharp.py index 1df3d81dfe..12529fc660 100644 --- a/pip/qsharp/_qsharp.py +++ b/pip/qsharp/_qsharp.py @@ -169,7 +169,12 @@ def eval(source: str) -> Any: ipython_helper() def callback(output: Output) -> None: - print(output, flush=True) + if __IPYTHON__: # type: ignore + from IPython.display import display + + display(output) + else: + print(output, flush=True) return get_interpreter().interpret(source, callback) @@ -212,7 +217,12 @@ def run( results: List[ShotResult] = [] def print_output(output: Output) -> None: - print(output, flush=True) + if __IPYTHON__: # type: ignore + from IPython.display import display + + display(output) + else: + print(output, flush=True) def on_save_events(output: Output) -> None: # Append the output to the last shot's output list diff --git a/pip/src/displayable_output.rs b/pip/src/displayable_output.rs index eabfc69c8e..9458cc7ced 100644 --- a/pip/src/displayable_output.rs +++ b/pip/src/displayable_output.rs @@ -78,10 +78,6 @@ impl DisplayableMatrix { ) } - pub fn to_html(&self) -> String { - format!("
{}
", self.to_plain()) - } - pub fn to_latex(&self) -> String { get_matrix_latex(&self.0) } diff --git a/pip/src/interpreter.rs b/pip/src/interpreter.rs index 29092fc7c5..ad5d0a2455 100644 --- a/pip/src/interpreter.rs +++ b/pip/src/interpreter.rs @@ -337,19 +337,24 @@ impl Output { self.__repr__() } - fn _repr_html_(&self) -> String { + fn _repr_html_(&self) -> Option { match &self.0 { - DisplayableOutput::State(state) => state.to_html(), - DisplayableOutput::Matrix(matrix) => matrix.to_html(), - DisplayableOutput::Message(msg) => format!("

{msg}

"), + DisplayableOutput::State(state) => Some(state.to_html()), + DisplayableOutput::Matrix(_) | DisplayableOutput::Message(_) => None, + } + } + + fn _repr_markdown_(&self) -> Option { + match &self.0 { + DisplayableOutput::State(_) | DisplayableOutput::Message(_) => None, + DisplayableOutput::Matrix(matrix) => Some(matrix.to_latex()), } } fn _repr_latex_(&self) -> Option { match &self.0 { DisplayableOutput::State(state) => state.to_latex(), - DisplayableOutput::Matrix(matrix) => Some(matrix.to_latex()), - DisplayableOutput::Message(_) => None, + DisplayableOutput::Matrix(_) | DisplayableOutput::Message(_) => None, } } From e075ff13b7c6737d5dfd871ea989f3d97c692387 Mon Sep 17 00:00:00 2001 From: Bill Ticehurst Date: Fri, 23 Aug 2024 16:37:43 -0700 Subject: [PATCH 13/32] Fix output when not in Jupyter --- pip/qsharp/_qsharp.py | 38 ++++++++++++++++++++++++++------------ pip/src/interpreter.rs | 3 ++- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/pip/qsharp/_qsharp.py b/pip/qsharp/_qsharp.py index 12529fc660..d776af2d58 100644 --- a/pip/qsharp/_qsharp.py +++ b/pip/qsharp/_qsharp.py @@ -17,6 +17,16 @@ _interpreter = None +# Check if we are running in a Jupyter notebook to use the IPython display function +_in_jupyter = False +try: + from IPython.display import display + + if get_ipython().__class__.__name__ == "ZMQInteractiveShell": # type: ignore + _in_jupyter = True # Jupyter notebook or qtconsole +except: + pass + # Reporting execution time during IPython cells requires that IPython # gets pinged to ensure it understands the cell is active. This is done by @@ -169,12 +179,14 @@ def eval(source: str) -> Any: ipython_helper() def callback(output: Output) -> None: - if __IPYTHON__: # type: ignore - from IPython.display import display - - display(output) - else: - print(output, flush=True) + if _in_jupyter: + try: + display(output) + return + except: + # If IPython is not available, fall back to printing the output + pass + print(output, flush=True) return get_interpreter().interpret(source, callback) @@ -217,12 +229,14 @@ def run( results: List[ShotResult] = [] def print_output(output: Output) -> None: - if __IPYTHON__: # type: ignore - from IPython.display import display - - display(output) - else: - print(output, flush=True) + if _in_jupyter: + try: + display(output) + return + except: + # If IPython is not available, fall back to printing the output + pass + print(output, flush=True) def on_save_events(output: Output) -> None: # Append the output to the last shot's output list diff --git a/pip/src/interpreter.rs b/pip/src/interpreter.rs index ad5d0a2455..00548a32c1 100644 --- a/pip/src/interpreter.rs +++ b/pip/src/interpreter.rs @@ -339,8 +339,9 @@ impl Output { fn _repr_html_(&self) -> Option { match &self.0 { + DisplayableOutput::Message(msg) => Some(format!("

{msg}

")), DisplayableOutput::State(state) => Some(state.to_html()), - DisplayableOutput::Matrix(_) | DisplayableOutput::Message(_) => None, + DisplayableOutput::Matrix(_) => None, } } From 4b78d38a1aaf90b4097138a2e5de56e1ffad4e1f Mon Sep 17 00:00:00 2001 From: Bill Ticehurst Date: Fri, 23 Aug 2024 20:38:21 -0700 Subject: [PATCH 14/32] Debugger output --- npm/qsharp/src/debug-service/debug-service.ts | 2 +- vscode/src/debugger/output.ts | 36 ++++++++++++------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/npm/qsharp/src/debug-service/debug-service.ts b/npm/qsharp/src/debug-service/debug-service.ts index aa6684c667..1f40daf0d5 100644 --- a/npm/qsharp/src/debug-service/debug-service.ts +++ b/npm/qsharp/src/debug-service/debug-service.ts @@ -196,5 +196,5 @@ export const debugServiceProtocol: ServiceProtocol< evalStepOut: "requestWithProgress", dispose: "request", }, - eventNames: ["DumpMachine", "Message", "Result"], + eventNames: ["DumpMachine", "Message", "Matrix", "Result"], }; diff --git a/vscode/src/debugger/output.ts b/vscode/src/debugger/output.ts index 175468b8a9..0cb478f54c 100644 --- a/vscode/src/debugger/output.ts +++ b/vscode/src/debugger/output.ts @@ -3,23 +3,23 @@ import { QscEventTarget } from "qsharp-lang"; +function formatComplex(real: number, imag: number) { + // Format -0 as 0 + // Also using Unicode Minus Sign instead of ASCII Hyphen-Minus + // and Unicode Mathematical Italic Small I instead of ASCII i. + const r = `${real <= -0.00005 ? "βˆ’" : " "}${Math.abs(real).toFixed(4)}`; + const i = `${imag <= -0.00005 ? "βˆ’" : "+"}${Math.abs(imag).toFixed(4)}𝑖`; + return `${r}${i}`; +} + export function createDebugConsoleEventTarget(out: (message: string) => void) { const eventTarget = new QscEventTarget(false); eventTarget.addEventListener("Message", (evt) => { - out(evt.detail); + out(evt.detail + "\n"); }); eventTarget.addEventListener("DumpMachine", (evt) => { - function formatComplex(real: number, imag: number) { - // Format -0 as 0 - // Also using Unicode Minus Sign instead of ASCII Hyphen-Minus - // and Unicode Mathematical Italic Small I instead of ASCII i. - const r = `${real <= -0.00005 ? "βˆ’" : ""}${Math.abs(real).toFixed(4)}`; - const i = `${imag <= -0.00005 ? "βˆ’" : "+"}${Math.abs(imag).toFixed(4)}𝑖`; - return `${r}${i}`; - } - function formatProbabilityPercent(real: number, imag: number) { const probabilityPercent = (real * real + imag * imag) * 100; return `${probabilityPercent.toFixed(4)}%`; @@ -38,8 +38,7 @@ export function createDebugConsoleEventTarget(out: (message: string) => void) { ); const basis = "Basis".padEnd(basisColumnWidth); - let out_str = "\n"; - out_str += "DumpMachine:\n\n"; + let out_str = ""; out_str += ` ${basis} | Amplitude | Probability | Phase\n`; out_str += " ".padEnd(basisColumnWidth, "-") + @@ -58,8 +57,19 @@ export function createDebugConsoleEventTarget(out: (message: string) => void) { out(out_str); }); + eventTarget.addEventListener("Matrix", (evt) => { + const out_str = evt.detail.matrix + .map((row) => + row.map((entry) => formatComplex(entry[0], entry[1])).join(", "), + ) + .join("\n"); + + out(out_str + "\n"); + }); + eventTarget.addEventListener("Result", (evt) => { - out(`\n${evt.detail.value}`); + out(`${evt.detail.value}`); }); + return eventTarget; } From f57c7bf4e31a50bd44d33603c93af6ac30f10583 Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Sat, 24 Aug 2024 00:50:42 -0700 Subject: [PATCH 15/32] Use markdown for everything, add css styling for tables --- pip/qsharp/_native.pyi | 5 ++-- pip/src/interpreter.rs | 38 ++++++++++++++---------------- pip/src/state_header_template.html | 16 +++++++++---- pip/src/state_row_template.html | 10 ++++---- 4 files changed, 37 insertions(+), 32 deletions(-) diff --git a/pip/qsharp/_native.pyi b/pip/qsharp/_native.pyi index 1deabfc449..b94b218eee 100644 --- a/pip/qsharp/_native.pyi +++ b/pip/qsharp/_native.pyi @@ -185,8 +185,7 @@ class Output: def __repr__(self) -> str: ... def __str__(self) -> str: ... - def _repr_html_(self) -> str: ... - def _repr_latex_(self) -> Optional[str]: ... + def _repr_markdown_(self) -> str: ... def state_dump(self) -> Optional[StateDumpData]: ... class StateDumpData: @@ -206,7 +205,7 @@ class StateDumpData: def get_dict(self) -> dict: ... def __repr__(self) -> str: ... def __str__(self) -> str: ... - def _repr_html_(self) -> str: ... + def _repr_markdown_(self) -> str: ... def _repr_latex_(self) -> Optional[str]: ... class Circuit: diff --git a/pip/src/interpreter.rs b/pip/src/interpreter.rs index 00548a32c1..9cd2d3c34d 100644 --- a/pip/src/interpreter.rs +++ b/pip/src/interpreter.rs @@ -337,25 +337,18 @@ impl Output { self.__repr__() } - fn _repr_html_(&self) -> Option { + fn _repr_markdown_(&self) -> String { match &self.0 { - DisplayableOutput::Message(msg) => Some(format!("

{msg}

")), - DisplayableOutput::State(state) => Some(state.to_html()), - DisplayableOutput::Matrix(_) => None, - } - } - - fn _repr_markdown_(&self) -> Option { - match &self.0 { - DisplayableOutput::State(_) | DisplayableOutput::Message(_) => None, - DisplayableOutput::Matrix(matrix) => Some(matrix.to_latex()), - } - } - - fn _repr_latex_(&self) -> Option { - match &self.0 { - DisplayableOutput::State(state) => state.to_latex(), - DisplayableOutput::Matrix(_) | DisplayableOutput::Message(_) => None, + DisplayableOutput::State(state) => { + let latex = if let Some(latex) = state.to_latex() { + format!("\n\n{latex}") + } else { + String::default() + }; + format!("{}{latex}", state.to_html()) + } + DisplayableOutput::Message(msg) => msg.clone(), + DisplayableOutput::Matrix(matrix) => matrix.to_latex(), } } @@ -414,8 +407,13 @@ impl StateDumpData { self.__repr__() } - fn _repr_html_(&self) -> String { - self.0.to_html() + fn _repr_markdown_(&self) -> String { + let latex = if let Some(latex) = self.0.to_latex() { + format!("\n\n{latex}") + } else { + String::default() + }; + format!("{}{latex}", self.0.to_html()) } fn _repr_latex_(&self) -> Option { diff --git a/pip/src/state_header_template.html b/pip/src/state_header_template.html index 2093cde699..1e17607031 100644 --- a/pip/src/state_header_template.html +++ b/pip/src/state_header_template.html @@ -1,13 +1,21 @@ - - - - + + + + + {}
Basis State
(|πœ“β‚β€¦πœ“β‚™βŸ©)
AmplitudeMeasurement ProbabilityPhaseBasis State
(|πœ“β‚β€¦πœ“β‚™βŸ©)
AmplitudeMeasurement ProbabilityPhase
diff --git a/pip/src/state_row_template.html b/pip/src/state_row_template.html index 41c65c245e..fa762c9809 100644 --- a/pip/src/state_row_template.html +++ b/pip/src/state_row_template.html @@ -1,16 +1,16 @@ - + |{}⟩ - + {} - + {:.4}% - ↑ - + ↑ + {:.4} From 942071188db1cf3be4e381b44673a4671f39f61a Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Sat, 24 Aug 2024 00:56:22 -0700 Subject: [PATCH 16/32] Fix `StateDump` wrapper --- pip/qsharp/_qsharp.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/pip/qsharp/_qsharp.py b/pip/qsharp/_qsharp.py index d776af2d58..317f13cf89 100644 --- a/pip/qsharp/_qsharp.py +++ b/pip/qsharp/_qsharp.py @@ -411,11 +411,8 @@ def __repr__(self) -> str: def __str__(self) -> str: return self.__data.__str__() - def _repr_html_(self) -> str: - return self.__data._repr_html_() - - def _repr_latex_(self) -> Optional[str]: - return self.__data._repr_latex_() + def _repr_markdown_(self) -> str: + return self.__data._repr_markdown_() def check_eq( self, state: Union[Dict[int, complex], List[complex]], tolerance: float = 1e-10 From 1effc7b753f70c8ca97fdc5b746755fb02535998 Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Sat, 24 Aug 2024 23:15:05 -0700 Subject: [PATCH 17/32] Render messages as plain text --- pip/qsharp/_native.pyi | 2 +- pip/src/interpreter.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pip/qsharp/_native.pyi b/pip/qsharp/_native.pyi index b94b218eee..5055db8f5a 100644 --- a/pip/qsharp/_native.pyi +++ b/pip/qsharp/_native.pyi @@ -185,7 +185,7 @@ class Output: def __repr__(self) -> str: ... def __str__(self) -> str: ... - def _repr_markdown_(self) -> str: ... + def _repr_markdown_(self) -> Optional[str]: ... def state_dump(self) -> Optional[StateDumpData]: ... class StateDumpData: diff --git a/pip/src/interpreter.rs b/pip/src/interpreter.rs index 9cd2d3c34d..f8de3d8dbc 100644 --- a/pip/src/interpreter.rs +++ b/pip/src/interpreter.rs @@ -337,7 +337,7 @@ impl Output { self.__repr__() } - fn _repr_markdown_(&self) -> String { + fn _repr_markdown_(&self) -> Option { match &self.0 { DisplayableOutput::State(state) => { let latex = if let Some(latex) = state.to_latex() { @@ -345,10 +345,10 @@ impl Output { } else { String::default() }; - format!("{}{latex}", state.to_html()) + Some(format!("{}{latex}", state.to_html())) } - DisplayableOutput::Message(msg) => msg.clone(), - DisplayableOutput::Matrix(matrix) => matrix.to_latex(), + DisplayableOutput::Message(_) => None, + DisplayableOutput::Matrix(matrix) => Some(matrix.to_latex()), } } From d92d69643b5363f3d1241c90e412f4f988e27a62 Mon Sep 17 00:00:00 2001 From: Bill Ticehurst Date: Sun, 25 Aug 2024 14:45:10 -0700 Subject: [PATCH 18/32] Remove dead code from results --- playground/src/results.tsx | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/playground/src/results.tsx b/playground/src/results.tsx index de350852cd..791b5dae4a 100644 --- a/playground/src/results.tsx +++ b/playground/src/results.tsx @@ -53,37 +53,6 @@ function resultIsSame(a: ShotResult, b: ShotResult): boolean { return true; } -/* -function Matrix(props: { matrix: number[][][] }) { - const style = `display: grid; grid-template-columns: repeat(${props.matrix[0].length}, 1fr);`; - function complexToString(re: number, im: number) { - // Return a max of 4 decimal places - re = Math.round(re * 10000) / 10000; - im = Math.round(im * 10000) / 10000; - if (im === 0) return re.toString(); - if (re === 0) return `${im}i`; - return `${re} + ${im}i`; - } - - return ( - <> -

Matrix

-
- {props.matrix.map((row) => { - return row.map((cell) => { - return ( -
- {complexToString(cell[0], cell[1])} -
- ); - }); - })} -
- - ); -} -*/ - function MatrixLatex(props: { latex: string }) { return ( <> From f50cea3ceb9ef48b525349a19a5e6576387d44d3 Mon Sep 17 00:00:00 2001 From: Bill Ticehurst Date: Sun, 25 Aug 2024 14:46:02 -0700 Subject: [PATCH 19/32] Remove state CSS --- pip/src/state_header_template.html | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pip/src/state_header_template.html b/pip/src/state_header_template.html index 1e17607031..e8dd0f3182 100644 --- a/pip/src/state_header_template.html +++ b/pip/src/state_header_template.html @@ -8,14 +8,6 @@ - {} From 19d52208f1347c4498d051906cbbb0ee6c2da3a6 Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Sun, 25 Aug 2024 21:16:00 -0700 Subject: [PATCH 20/32] Use styling for table --- pip/src/state_header_template.html | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/pip/src/state_header_template.html b/pip/src/state_header_template.html index e8dd0f3182..d474bbef60 100644 --- a/pip/src/state_header_template.html +++ b/pip/src/state_header_template.html @@ -1,4 +1,18 @@ - +
+ @@ -7,7 +21,7 @@ - + {}
Basis State
(|πœ“β‚β€¦πœ“β‚™βŸ©)
Phase
From 831ec40a6076edda59f8461675a1e9c8e35c3031 Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Sun, 25 Aug 2024 23:09:59 -0700 Subject: [PATCH 21/32] Consolidate styles into one place --- pip/src/state_header_template.html | 24 +++++++++++++++++++----- pip/src/state_row_template.html | 18 +++++++++--------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/pip/src/state_header_template.html b/pip/src/state_header_template.html index d474bbef60..77044e1c96 100644 --- a/pip/src/state_header_template.html +++ b/pip/src/state_header_template.html @@ -6,6 +6,20 @@ var(--jp-layout-color1, inherit) ); }} + .qs-stateTable th {{ + text-align: left; + border: none; + }} + .qs-stateTable tbody {{ + pointer-events: none; + }} + .qs-stateTable tbody td {{ + text-align: left; + border: none; + }} + .qs-stateTable tbody td span {{ + display: inline-block; + }} .qs-stateTable tbody tr:nth-child(even) {{ background-color: var( --vscode-list-hoverBackground, @@ -15,13 +29,13 @@ - Basis State
(|πœ“β‚β€¦πœ“β‚™βŸ©) - Amplitude - Measurement Probability - Phase + Basis State
(|πœ“β‚β€¦πœ“β‚™βŸ©) + Amplitude + Measurement Probability + Phase - + {} diff --git a/pip/src/state_row_template.html b/pip/src/state_row_template.html index fa762c9809..19a5e95626 100644 --- a/pip/src/state_row_template.html +++ b/pip/src/state_row_template.html @@ -1,16 +1,16 @@ - - |{}⟩ + + |{}⟩ - - {} + + {} - + - {:.4}% + {:.4}% - ↑ - - {:.4} + ↑ + + {:.4} From ea5a0f50b26dcc6c441047c915fa76b05a700ebf Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Wed, 4 Sep 2024 14:07:21 -0700 Subject: [PATCH 22/32] Treat DumpOperation as simulatable intrinsic --- compiler/qsc_partial_eval/src/lib.rs | 1 + library/std/src/diagnostics.qs | 6 ++++-- wasm/src/tests.rs | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/compiler/qsc_partial_eval/src/lib.rs b/compiler/qsc_partial_eval/src/lib.rs index ee8f234958..eb8987bbf1 100644 --- a/compiler/qsc_partial_eval/src/lib.rs +++ b/compiler/qsc_partial_eval/src/lib.rs @@ -1297,6 +1297,7 @@ impl<'a> PartialEvaluator<'a> { // The following intrinsic operations and functions are no-ops. "BeginEstimateCaching" => Ok(Value::Bool(true)), "DumpRegister" + | "DumpOperation" | "AccountForEstimatesInternal" | "BeginRepeatEstimatesInternal" | "EndRepeatEstimatesInternal" diff --git a/library/std/src/diagnostics.qs b/library/std/src/diagnostics.qs index 4e95404ab1..902437ffe1 100644 --- a/library/std/src/diagnostics.qs +++ b/library/std/src/diagnostics.qs @@ -91,8 +91,10 @@ namespace Microsoft.Quantum.Diagnostics { /// ``` /// Calling this operation has no observable effect from within Q#. /// Note that if `DumpOperation` is called when there are other qubits allocated, - /// the matrix displayed may reflect any global phase that has accumulated on the other qubits. - operation DumpOperation(nQubits : Int, op : Qubit[] => Unit) : Unit { + /// the matrix displayed may reflect any global phase that has accumulated from operations + /// on those other qubits. + @SimulatableIntrinsic() + operation DumpOperation(nQubits : Int, op : Qubit[] => Unit is Adj) : Unit { use (targets, extra) = (Qubit[nQubits], Qubit[nQubits]); for i in 0..nQubits - 1 { H(targets[i]); diff --git a/wasm/src/tests.rs b/wasm/src/tests.rs index df86429aed..a62cc89b69 100644 --- a/wasm/src/tests.rs +++ b/wasm/src/tests.rs @@ -138,7 +138,7 @@ fn test_matrix() { DumpOperation(2, Bell); } - operation Bell(q: Qubit[]) : Unit { + operation Bell(q: Qubit[]) : Unit is Adj { H(q[0]); CX(q[0], q[1]); } From 9b6046ebcef900be3a0ea5368d2ba71436525a6e Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Wed, 4 Sep 2024 14:12:49 -0700 Subject: [PATCH 23/32] Fix merge from main --- pip/src/interpreter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pip/src/interpreter.rs b/pip/src/interpreter.rs index 56538f59a8..17daa5ae1e 100644 --- a/pip/src/interpreter.rs +++ b/pip/src/interpreter.rs @@ -533,7 +533,7 @@ impl Receiver for OptionalCallbackReceiver<'_> { callback .call1( self.py, - PyTuple::new( + PyTuple::new_bound( self.py, &[Py::new(self.py, Output(out)).expect("should be able to create output")], ), From 9c797d5766c6ee90b8103875d8c90f4753e1c231 Mon Sep 17 00:00:00 2001 From: DmitryVasilevsky <60718360+DmitryVasilevsky@users.noreply.github.com> Date: Thu, 19 Sep 2024 00:46:00 -0700 Subject: [PATCH 24/32] Added write_latex_for_complex_number (#1924) * Updated general code so that 0 is recognized in Cartesian form. Also propagated parameter to render 1 explicitly out of inner functions. * Added function write_latex_for_complex_number for rendering a standalone complex number. It differs from write_latex_for_term in handling of special cases such as 0, 1, -1. * Changed get_matrix_latex to call this new function. This reduced a number of special cases, so I removed them from get_latex_for_simple_term. Eventually all these special cases should go away. * Added tests for write_latex_for_complex_number. Also added test for get_matrix_latex to check that rendering hasn't changed in the process. * The following can still be improved in general case: * i could be used directly in a fraction. Example: $\frac{1}{2}i$ could be rendered as $\frac{i}{2}$. * Brackets are not needed for standalone rendering. Example: $-\left(1+i\right)$ could be rendered as $-1-i$ * No need to rationalize the denominator. Example: $\frac{\sqrt{2}}{2}$ could be rendered as $\frac{1}{\sqrt{2}}$ Co-authored-by: Dmitry Vasilevsky --- compiler/qsc_eval/src/state.rs | 69 ++++++--------- compiler/qsc_eval/src/state/tests.rs | 124 +++++++++++++++++++++++++-- 2 files changed, 145 insertions(+), 48 deletions(-) diff --git a/compiler/qsc_eval/src/state.rs b/compiler/qsc_eval/src/state.rs index facc496212..d1474b4586 100644 --- a/compiler/qsc_eval/src/state.rs +++ b/compiler/qsc_eval/src/state.rs @@ -209,7 +209,7 @@ impl RealNumber { } } -/// Represents a non-zero complex numbers in the polar form: ``coefficient·𝒆^(π…Β·π’ŠΒ·phase_multiplier)`` +/// Represents a non-zero complex numbers in the polar form: ``magnitude·𝒆^(π…Β·π’ŠΒ·phase_multiplier)`` /// Sign of the number is separated for easier composition and rendering #[derive(Debug)] struct PolarForm { @@ -248,6 +248,10 @@ impl PolarForm { /// Try to recognize a complex number and represent it in the polar form. fn recognize(re: f64, im: f64) -> Option { + if !is_significant(re.abs()) && !is_significant(im.abs()) { + // 0 is better represented in Cartesian form, not polar. + return None; + } for (pi_num, pi_den) in Self::PI_FRACTIONS { #[allow(clippy::cast_precision_loss)] // We only use fixes set of fractions let angle: f64 = std::f64::consts::PI * (pi_num as f64) / (pi_den as f64); @@ -387,23 +391,8 @@ fn is_close_enough(val: &Complex64, target: &Complex64) -> bool { // Quick and dirty matching for the most common matrix terms we care about rendering // LaTeX for, e.g. 1/sqrt(2), -i/sqrt(2), etc. -// Anything not in this list gets a floating point form. +// Anything not in this list gets a standard rendering. fn get_latex_for_simple_term(val: &Complex64) -> Option { - if is_close_enough(val, &Complex64::new(0.0, 0.0)) { - return Some("0".to_string()); - } - if is_close_enough(val, &Complex64::new(1.0, 0.0)) { - return Some("1".to_string()); - } - if is_close_enough(val, &Complex64::new(0.0, 1.0)) { - return Some("i".to_string()); - } - if is_close_enough(val, &Complex64::new(-1.0, 0.0)) { - return Some("-1".to_string()); - } - if is_close_enough(val, &Complex64::new(0.0, -1.0)) { - return Some("-i".to_string()); - } if is_close_enough(val, &Complex64::new(FRAC_1_SQRT_2, 0.0)) { return Some("\\frac{1}{\\sqrt{2}}".to_string()); } @@ -416,15 +405,9 @@ fn get_latex_for_simple_term(val: &Complex64) -> Option { if is_close_enough(val, &Complex64::new(0.0, -FRAC_1_SQRT_2)) { return Some("-\\frac{i}{\\sqrt{2}}".to_string()); } - if is_close_enough(val, &Complex64::new(0.5, 0.0)) { - return Some("\\frac{1}{2}".to_string()); - } if is_close_enough(val, &Complex64::new(0.0, 0.5)) { return Some("\\frac{i}{2}".to_string()); } - if is_close_enough(val, &Complex64::new(-0.5, 0.0)) { - return Some("-\\frac{1}{2}".to_string()); - } if is_close_enough(val, &Complex64::new(0.0, -0.5)) { return Some("-\\frac{i}{2}".to_string()); } @@ -453,26 +436,15 @@ pub fn get_matrix_latex(matrix: &Vec>) -> String { if !is_first { latex.push_str(" & "); } + is_first = false; if let Some(simple_latex) = get_latex_for_simple_term(element) { latex.push_str(&simple_latex); - is_first = false; continue; } let cpl = ComplexNumber::recognize(element.re, element.im); - - match &cpl { - ComplexNumber::Cartesian(cartesian_form) => { - write_latex_for_cartesian_form(&mut latex, cartesian_form, false); - } - ComplexNumber::Polar(polar_form) => { - write_latex_for_polar_form(&mut latex, polar_form, false); - } - } - - // write!(latex, "{}", fmt_complex(element)).expect("Expected to write complex number."); - is_first = false; + write_latex_for_complex_number(&mut latex, &cpl); } latex.push_str(" \\\\ "); } @@ -481,13 +453,26 @@ pub fn get_matrix_latex(matrix: &Vec>) -> String { latex } +/// Write latex for a standalone complex number +/// '-', 0 and 1 are always rendered, but '+' is not. +fn write_latex_for_complex_number(latex: &mut String, number: &ComplexNumber) { + match number { + ComplexNumber::Cartesian(cartesian_form) => { + write_latex_for_cartesian_form(latex, cartesian_form, false, true); + } + ComplexNumber::Polar(polar_form) => { + write_latex_for_polar_form(latex, polar_form, false); + } + } +} + /// Write latex for one term of quantum state. /// Latex is rendered for coefficient only (not for basis vector). /// + is rendered only if ``render_plus`` is true. fn write_latex_for_term(latex: &mut String, term: &Term, render_plus: bool) { match &term.coordinate { ComplexNumber::Cartesian(cartesian_form) => { - write_latex_for_cartesian_form(latex, cartesian_form, render_plus); + write_latex_for_cartesian_form(latex, cartesian_form, render_plus, false); } ComplexNumber::Polar(polar_form) => { write_latex_for_polar_form(latex, polar_form, render_plus); @@ -525,11 +510,13 @@ fn write_latex_for_polar_form(latex: &mut String, polar_form: &PolarForm, render /// Brackets are used if both real and imaginary parts are present. /// If only one part is present, its sign is used as common. /// If both components are present, real part sign is used as common. -/// 1 is not rendered, but + is rendered if ``render_plus`` is true. +/// 1 is rendered if ``render_one`` is true +/// + is rendered if ``render_plus`` is true. fn write_latex_for_cartesian_form( latex: &mut String, cartesian_form: &CartesianForm, render_plus: bool, + render_one: bool, ) { if cartesian_form.sign < 0 { latex.push('-'); @@ -538,7 +525,6 @@ fn write_latex_for_cartesian_form( } if let RealNumber::Zero = cartesian_form.real_part { if let RealNumber::Zero = cartesian_form.imaginary_part { - // NOTE: This branch is never used. latex.push('0'); } else { // Only imaginary part present @@ -547,7 +533,7 @@ fn write_latex_for_cartesian_form( } } else if let RealNumber::Zero = cartesian_form.imaginary_part { // Only real part present - write_latex_for_real_number(latex, &cartesian_form.real_part, false); + write_latex_for_real_number(latex, &cartesian_form.real_part, render_one); } else { // Both real and imaginary parts present latex.push_str("\\left( "); @@ -563,7 +549,7 @@ fn write_latex_for_cartesian_form( } /// Write latex for real number. Note that the sign is not rendered. -/// 1 is only rendered if ``render_one`` is true. 0 is rendered, but not used in current code. +/// 1 is only rendered if ``render_one`` is true. fn write_latex_for_real_number(latex: &mut String, number: &RealNumber, render_one: bool) { match number { RealNumber::Algebraic(algebraic_number) => { @@ -573,7 +559,6 @@ fn write_latex_for_real_number(latex: &mut String, number: &RealNumber, render_o write_latex_for_decimal_number(latex, decimal_number, render_one); } RealNumber::Zero => { - // Note: this arm is not used. latex.push('0'); } } diff --git a/compiler/qsc_eval/src/state/tests.rs b/compiler/qsc_eval/src/state/tests.rs index 1f742f74fe..27111dd161 100644 --- a/compiler/qsc_eval/src/state/tests.rs +++ b/compiler/qsc_eval/src/state/tests.rs @@ -4,15 +4,18 @@ #![allow(clippy::needless_raw_string_hashes)] use super::{ - get_latex, write_latex_for_algebraic_number, write_latex_for_cartesian_form, - write_latex_for_decimal_number, write_latex_for_polar_form, write_latex_for_real_number, - write_latex_for_term, AlgebraicNumber, CartesianForm, ComplexNumber, DecimalNumber, PolarForm, - RationalNumber, RealNumber, Term, + get_latex, get_matrix_latex, write_latex_for_algebraic_number, write_latex_for_cartesian_form, + write_latex_for_complex_number, write_latex_for_decimal_number, write_latex_for_polar_form, + write_latex_for_real_number, write_latex_for_term, AlgebraicNumber, CartesianForm, + ComplexNumber, DecimalNumber, PolarForm, RationalNumber, RealNumber, Term, }; use crate::state::{is_fractional_part_significant, is_significant}; use expect_test::{expect, Expect}; use num_complex::Complex64; -use std::{f64::consts::PI, time::Instant}; +use std::{ + f64::consts::{FRAC_1_SQRT_2, PI}, + time::Instant, +}; #[test] fn check_is_significant() { @@ -511,7 +514,7 @@ fn check_get_latex_for_real() { fn assert_latex_for_cartesian(expected: &Expect, re: f64, im: f64, render_plus: bool) { let number = CartesianForm::recognize(re, im); let mut latex = String::with_capacity(50); - write_latex_for_cartesian_form(&mut latex, &number, render_plus); + write_latex_for_cartesian_form(&mut latex, &number, render_plus, false); expected.assert_eq(&latex); } @@ -638,6 +641,69 @@ fn check_get_latex_for_term() { ); } +fn assert_latex_for_complex_number(expected: &Expect, re: f64, im: f64) { + let n: ComplexNumber = ComplexNumber::recognize(re, im); + let mut latex = String::with_capacity(50); + write_latex_for_complex_number(&mut latex, &n); + expected.assert_eq(&latex); +} + +#[test] +fn check_get_latex_for_complex_number() { + // While rendering is correct, a better way may be the following: + // TODO: -(1-i) -> -1+i for standalone number remove brackets + // TODO: 1/2 i -> i/2 + // TODO: 2/r2 -> r2/2 + assert_latex_for_complex_number(&expect!([r"0"]), 0.0, 0.0); + + assert_latex_for_complex_number(&expect!([r"1"]), 1.0, 0.0); + assert_latex_for_complex_number(&expect!([r"-1"]), -1.0, 0.0); + assert_latex_for_complex_number(&expect!([r"i"]), 0.0, 1.0); + assert_latex_for_complex_number(&expect!([r"-i"]), 0.0, -1.0); + + assert_latex_for_complex_number(&expect!([r"\frac{1}{2}"]), 0.5, 0.0); + assert_latex_for_complex_number(&expect!([r"-\frac{1}{2}"]), -0.5, 0.0); + assert_latex_for_complex_number(&expect!([r"\frac{1}{2}i"]), 0.0, 0.5); + assert_latex_for_complex_number(&expect!([r"-\frac{1}{2}i"]), 0.0, -0.5); + + assert_latex_for_complex_number( + &expect!([r#"\left( \frac{1}{2}+\frac{1}{2}i \right)"#]), + 0.5, + 0.5, + ); + assert_latex_for_complex_number( + &expect!([r#"-\left( \frac{1}{2}-\frac{1}{2}i \right)"#]), + -0.5, + 0.5, + ); + assert_latex_for_complex_number( + &expect!([r#"\left( \frac{1}{2}-\frac{1}{2}i \right)"#]), + 0.5, + -0.5, + ); + assert_latex_for_complex_number( + &expect!([r#"-\left( \frac{1}{2}+\frac{1}{2}i \right)"#]), + -0.5, + -0.5, + ); + + assert_latex_for_complex_number(&expect!([r#"\frac{\sqrt{2}}{2}"#]), FRAC_1_SQRT_2, 0.0); + assert_latex_for_complex_number(&expect!([r#"-\frac{\sqrt{2}}{2}"#]), -FRAC_1_SQRT_2, 0.0); + assert_latex_for_complex_number(&expect!([r#"\frac{\sqrt{2}}{2}i"#]), 0.0, FRAC_1_SQRT_2); + assert_latex_for_complex_number(&expect!([r#"-\frac{\sqrt{2}}{2}i"#]), 0.0, -FRAC_1_SQRT_2); + + assert_latex_for_complex_number( + &expect!([r"\frac{1}{2} e^{ i \pi / 3}"]), + 1.0 / 2.0 * (PI / 3.0).cos(), + 1.0 / 2.0 * (PI / 3.0).sin(), + ); + assert_latex_for_complex_number( + &expect!([r#"\left( \frac{1}{2}+\frac{1}{2}i \right)"#]), + 1.0 / 2.0, + 1.0 / 2.0, + ); +} + #[test] fn check_get_latex() { expect!([r"$|\psi\rangle = \left( \frac{1}{2}+\frac{1}{2}i \right)|00\rangle$"]).assert_eq( @@ -672,6 +738,52 @@ fn check_get_latex() { ).expect("expected valid latex")); } +#[test] +fn check_get_matrix_latex() { + expect!([r#"$ \begin{bmatrix} 0 & 1 \\ i & \left( 1+i \right) \\ \end{bmatrix} $"#]).assert_eq( + &get_matrix_latex(&vec![ + vec![Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)], + vec![Complex64::new(0.0, 1.0), Complex64::new(1.0, 1.0)], + ]), + ); + expect!([r#"$ \begin{bmatrix} -\left( 1-i \right) & -1 \\ -i & -\left( 1+i \right) \\ \end{bmatrix} $"#]).assert_eq( + &get_matrix_latex(&vec![ + vec![Complex64::new(-1.0, 1.0), Complex64::new(-1.0, 0.0)], + vec![Complex64::new(0.0, -1.0), Complex64::new(-1.0, -1.0)], + ]), + ); + expect!([r#"$ \begin{bmatrix} \frac{1}{\sqrt{2}} & \frac{i}{\sqrt{2}} \\ -\frac{1}{\sqrt{2}} & -\frac{i}{\sqrt{2}} \\ \end{bmatrix} $"#]).assert_eq(&get_matrix_latex(&vec![ + vec![ + Complex64::new(FRAC_1_SQRT_2, 0.0), + Complex64::new(0.0, FRAC_1_SQRT_2), + ], + vec![ + Complex64::new(-FRAC_1_SQRT_2, 0.0), + Complex64::new(0.0, -FRAC_1_SQRT_2), + ], + ])); + expect!([r#"$ \begin{bmatrix} \frac{1}{2} & \frac{i}{2} \\ -\frac{1}{2} & -\frac{i}{2} \\ \end{bmatrix} $"#]).assert_eq(&get_matrix_latex(&vec![ + vec![ + Complex64::new(0.5, 0.0), + Complex64::new(0.0, 0.5), + ], + vec![ + Complex64::new(-0.5, 0.0), + Complex64::new(0.0, -0.5), + ], + ])); + expect!([r#"$ \begin{bmatrix} \frac{1}{2} + \frac{i}{2} & -\frac{1}{2} - \frac{i}{2} \\ -\frac{1}{2} + \frac{i}{2} & \frac{1}{2} - \frac{i}{2} \\ \end{bmatrix} $"#]).assert_eq(&get_matrix_latex(&vec![ + vec![ + Complex64::new(0.5, 0.5), + Complex64::new(-0.5, -0.5), + ], + vec![ + Complex64::new(-0.5, 0.5), + Complex64::new(0.5, -0.5), + ], + ])); +} + #[test] fn check_get_latex_perf() { // This is not a CI gate for performance, just prints out data. From 4e54f042121aa7de07cd78d547354865bc25cf52 Mon Sep 17 00:00:00 2001 From: Bill Ticehurst Date: Thu, 19 Sep 2024 10:07:37 -0700 Subject: [PATCH 25/32] Updating for feedback --- compiler/qsc_eval/src/state.rs | 2 +- playground/src/results.tsx | 13 +------------ 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/compiler/qsc_eval/src/state.rs b/compiler/qsc_eval/src/state.rs index d1474b4586..94bd203618 100644 --- a/compiler/qsc_eval/src/state.rs +++ b/compiler/qsc_eval/src/state.rs @@ -389,7 +389,7 @@ fn is_close_enough(val: &Complex64, target: &Complex64) -> bool { (val.re - target.re).abs() < 1e-9 && (val.im - target.im).abs() < 1e-9 } -// Quick and dirty matching for the most common matrix terms we care about rendering +// Quick and dirty matching for the most common matrix elements we care about rendering // LaTeX for, e.g. 1/sqrt(2), -i/sqrt(2), etc. // Anything not in this list gets a standard rendering. fn get_latex_for_simple_term(val: &Complex64) -> Option { diff --git a/playground/src/results.tsx b/playground/src/results.tsx index 791b5dae4a..42a9feac01 100644 --- a/playground/src/results.tsx +++ b/playground/src/results.tsx @@ -53,17 +53,6 @@ function resultIsSame(a: ShotResult, b: ShotResult): boolean { return true; } -function MatrixLatex(props: { latex: string }) { - return ( - <> -
- DumpOperation -
- - - ); -} - export function ResultsTab(props: { evtTarget: QscEventTarget; onShotError?: (err?: VSDiagnostic) => void; @@ -259,7 +248,7 @@ export function ResultsTab(props: { > ) : ( - + ); })} From 27054f182e4d7ee1926ca58e3ac3e838afa2bb82 Mon Sep 17 00:00:00 2001 From: Bill Ticehurst Date: Thu, 19 Sep 2024 10:17:08 -0700 Subject: [PATCH 26/32] Fix merge export --- library/std/src/Std/Diagnostics.qs | 41 ++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/library/std/src/Std/Diagnostics.qs b/library/std/src/Std/Diagnostics.qs index 2b9d40b5c0..85cad3162e 100644 --- a/library/std/src/Std/Diagnostics.qs +++ b/library/std/src/Std/Diagnostics.qs @@ -64,6 +64,46 @@ function DumpRegister(register : Qubit[]) : Unit { body intrinsic; } +/// # Summary +/// Given an operation, dumps the matrix representation of the operation actiong on the given +/// number of qubits. +/// +/// # Input +/// ## nQubits +/// The number of qubits on which the given operation acts. +/// ## op +/// The operation that is to be diagnosed. +/// +/// # Remarks +/// When run on the sparse-state simulator, the following snippet +/// will output the matrix +/// $\left(\begin{matrix} 0.0 & 0.707 \\\\ 0.707 & 0.0\end{matrix}\right)$: +/// +/// ```qsharp +/// operation DumpH() : Unit { +/// DumpOperation(1, qs => H(qs[0])); +/// } +/// ``` +/// Calling this operation has no observable effect from within Q#. +/// Note that if `DumpOperation` is called when there are other qubits allocated, +/// the matrix displayed may reflect any global phase that has accumulated from operations +/// on those other qubits. +@SimulatableIntrinsic() +operation DumpOperation(nQubits : Int, op : Qubit[] => Unit is Adj) : Unit { + use (targets, extra) = (Qubit[nQubits], Qubit[nQubits]); + for i in 0..nQubits - 1 { + H(targets[i]); + CNOT(targets[i], extra[i]); + } + op(targets); + DumpMatrix(targets + extra); + ResetAll(targets + extra); +} + +function DumpMatrix(qs : Qubit[]) : Unit { + body intrinsic; +} + /// Checks whether a qubit is in the |0⟩ state, returning true if it is. /// /// # Description @@ -320,6 +360,7 @@ operation StopCountingQubits() : Int { export DumpMachine, DumpRegister, + DumpOperation, CheckZero, CheckAllZero, Fact, From 76ba20c26513af0033cfa76fccac99857c4fd707 Mon Sep 17 00:00:00 2001 From: Bill Ticehurst Date: Thu, 19 Sep 2024 10:40:23 -0700 Subject: [PATCH 27/32] Fix tests --- compiler/qsc_eval/src/tests.rs | 4 ++-- wasm/src/tests.rs | 11 +++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/compiler/qsc_eval/src/tests.rs b/compiler/qsc_eval/src/tests.rs index 56aadd3261..f819f5b7e6 100644 --- a/compiler/qsc_eval/src/tests.rs +++ b/compiler/qsc_eval/src/tests.rs @@ -3724,7 +3724,7 @@ fn controlled_operation_with_duplicate_controls_fails() { 1, ), item: LocalItemId( - 132, + 134, ), }, caller: PackageId( @@ -3774,7 +3774,7 @@ fn controlled_operation_with_target_in_controls_fails() { 1, ), item: LocalItemId( - 132, + 134, ), }, caller: PackageId( diff --git a/wasm/src/tests.rs b/wasm/src/tests.rs index e834dd401f..9c82679782 100644 --- a/wasm/src/tests.rs +++ b/wasm/src/tests.rs @@ -144,15 +144,22 @@ fn test_matrix() { } }"; let expr = "Test.Main()"; + let count = std::cell::Cell::new(0); let result = run_internal( SourceMap::new([("test.qs".into(), code.into())], Some(expr.into())), |msg| { - // TODO: Validate the matrix output - assert!(msg.contains("Matrix") || msg.contains("result")); + if msg.contains("Matrix") { + count.set(count.get() + 1); + // Check the start and end of the matrix LaTeX is formatted as expected + let startLaTeX = r#"$ \\begin{bmatrix} \\frac{1}{\\sqrt{2}} & 0 & \\frac{1}{\\sqrt{2}} & 0 \\\\"#; + assert!(msg.contains(startLaTeX)); + assert!(msg.contains(r#"\\frac{1}{\\sqrt{2}} & 0 & -\\frac{1}{\\sqrt{2}} & 0 \\\\ \\end{bmatrix} $"#)); + } }, 1, ); assert!(result.is_ok()); + assert!(count.get() == 1); } #[test] From e7d73fceeb1eac0f7b040761493b1ded5c9eea70 Mon Sep 17 00:00:00 2001 From: Bill Ticehurst Date: Thu, 19 Sep 2024 10:43:14 -0700 Subject: [PATCH 28/32] Make clippy happy --- wasm/src/tests.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/wasm/src/tests.rs b/wasm/src/tests.rs index 9c82679782..19243be6d5 100644 --- a/wasm/src/tests.rs +++ b/wasm/src/tests.rs @@ -151,9 +151,12 @@ fn test_matrix() { if msg.contains("Matrix") { count.set(count.get() + 1); // Check the start and end of the matrix LaTeX is formatted as expected - let startLaTeX = r#"$ \\begin{bmatrix} \\frac{1}{\\sqrt{2}} & 0 & \\frac{1}{\\sqrt{2}} & 0 \\\\"#; - assert!(msg.contains(startLaTeX)); - assert!(msg.contains(r#"\\frac{1}{\\sqrt{2}} & 0 & -\\frac{1}{\\sqrt{2}} & 0 \\\\ \\end{bmatrix} $"#)); + assert!(msg.contains( + r"$ \\begin{bmatrix} \\frac{1}{\\sqrt{2}} & 0 & \\frac{1}{\\sqrt{2}} & 0 \\\\" + )); + assert!(msg.contains( + r"\\frac{1}{\\sqrt{2}} & 0 & -\\frac{1}{\\sqrt{2}} & 0 \\\\ \\end{bmatrix} $" + )); } }, 1, From aa8628364ee1f0b04635126df3ac22f38065ace7 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Thu, 19 Sep 2024 11:27:02 -0700 Subject: [PATCH 29/32] Removed TODO from comments --- compiler/qsc_eval/src/state/tests.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/compiler/qsc_eval/src/state/tests.rs b/compiler/qsc_eval/src/state/tests.rs index 27111dd161..6191d5d76a 100644 --- a/compiler/qsc_eval/src/state/tests.rs +++ b/compiler/qsc_eval/src/state/tests.rs @@ -650,10 +650,11 @@ fn assert_latex_for_complex_number(expected: &Expect, re: f64, im: f64) { #[test] fn check_get_latex_for_complex_number() { + // Future work: // While rendering is correct, a better way may be the following: - // TODO: -(1-i) -> -1+i for standalone number remove brackets - // TODO: 1/2 i -> i/2 - // TODO: 2/r2 -> r2/2 + // -(1-i) -> -1+i remove brackets for standalone number + // 1/2 i -> i/2 + // √2/2 -> 1/√2 assert_latex_for_complex_number(&expect!([r"0"]), 0.0, 0.0); assert_latex_for_complex_number(&expect!([r"1"]), 1.0, 0.0); From 658a61e59d56b95e235bcd276357f3b1c55b1f8a Mon Sep 17 00:00:00 2001 From: Bill Ticehurst Date: Mon, 30 Sep 2024 09:11:53 -0700 Subject: [PATCH 30/32] Update fn name --- compiler/qsc/src/lib.rs | 3 ++- compiler/qsc_eval/src/state.rs | 2 +- compiler/qsc_eval/src/state/tests.rs | 16 ++++++++-------- pip/src/displayable_output.rs | 4 ++-- wasm/src/lib.rs | 4 ++-- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/compiler/qsc/src/lib.rs b/compiler/qsc/src/lib.rs index 9a4596d0d3..db9ee9ff4c 100644 --- a/compiler/qsc/src/lib.rs +++ b/compiler/qsc/src/lib.rs @@ -50,7 +50,8 @@ pub mod line_column { pub use qsc_eval::{ backend::{Backend, SparseSim}, state::{ - fmt_basis_state_label, fmt_complex, format_state_id, get_latex, get_matrix_latex, get_phase, + fmt_basis_state_label, fmt_complex, format_state_id, get_matrix_latex, get_phase, + get_state_latex, }, }; diff --git a/compiler/qsc_eval/src/state.rs b/compiler/qsc_eval/src/state.rs index 94bd203618..85dc2f524c 100644 --- a/compiler/qsc_eval/src/state.rs +++ b/compiler/qsc_eval/src/state.rs @@ -349,7 +349,7 @@ fn get_terms_for_state(state: &Vec<(BigUint, Complex64)>) -> Vec { /// `None` is returned if the resulting formula is not nice, i.e. /// if the formula consists of more than 16 terms or if more than two coefficients are not recognized. #[must_use] -pub fn get_latex(state: &Vec<(BigUint, Complex64)>, qubit_count: usize) -> Option { +pub fn get_state_latex(state: &Vec<(BigUint, Complex64)>, qubit_count: usize) -> Option { if state.len() > 16 { return None; } diff --git a/compiler/qsc_eval/src/state/tests.rs b/compiler/qsc_eval/src/state/tests.rs index 6191d5d76a..feccdcd660 100644 --- a/compiler/qsc_eval/src/state/tests.rs +++ b/compiler/qsc_eval/src/state/tests.rs @@ -4,7 +4,7 @@ #![allow(clippy::needless_raw_string_hashes)] use super::{ - get_latex, get_matrix_latex, write_latex_for_algebraic_number, write_latex_for_cartesian_form, + get_state_latex, get_matrix_latex, write_latex_for_algebraic_number, write_latex_for_cartesian_form, write_latex_for_complex_number, write_latex_for_decimal_number, write_latex_for_polar_form, write_latex_for_real_number, write_latex_for_term, AlgebraicNumber, CartesianForm, ComplexNumber, DecimalNumber, PolarForm, RationalNumber, RealNumber, Term, @@ -708,19 +708,19 @@ fn check_get_latex_for_complex_number() { #[test] fn check_get_latex() { expect!([r"$|\psi\rangle = \left( \frac{1}{2}+\frac{1}{2}i \right)|00\rangle$"]).assert_eq( - &get_latex(&vec![(0_u8.into(), Complex64::new(0.5, 0.5))], 2) + &get_state_latex(&vec![(0_u8.into(), Complex64::new(0.5, 0.5))], 2) .expect("expected valid latex"), ); expect!([r"$|\psi\rangle = -|00\rangle$"]).assert_eq( - &get_latex(&vec![(0_u8.into(), Complex64::new(-1.0, 0.0))], 2) + &get_state_latex(&vec![(0_u8.into(), Complex64::new(-1.0, 0.0))], 2) .expect("expected valid latex"), ); expect!([r"$|\psi\rangle = -i|00\rangle$"]).assert_eq( - &get_latex(&vec![(0_u8.into(), Complex64::new(0.0, -1.0))], 2) + &get_state_latex(&vec![(0_u8.into(), Complex64::new(0.0, -1.0))], 2) .expect("expected valid latex"), ); expect!([r"$|\psi\rangle = e^{-2 i \pi / 3}|00\rangle$"]).assert_eq( - &get_latex( + &get_state_latex( &vec![( 0_u8.into(), Complex64::new((-2.0 * PI / 3.0).cos(), (-2.0 * PI / 3.0).sin()), @@ -730,7 +730,7 @@ fn check_get_latex() { .expect("expected valid latex"), ); expect!([r"$|\psi\rangle = \left( 1+\frac{\sqrt{2}}{2}i \right)|00\rangle+\left( 1+\frac{\sqrt{2}}{2}i \right)|10\rangle$"]) - .assert_eq(&get_latex( + .assert_eq(&get_state_latex( &vec![ (0_u8.into(), Complex64::new(1.0, 1.0 / 2.0_f64.sqrt())), (2_u8.into(), Complex64::new(1.0, 1.0 / 2.0_f64.sqrt())), @@ -802,7 +802,7 @@ fn check_get_latex_perf() { ]; expect!([r"$|\psi\rangle = \frac{1}{2}|00\rangle+\frac{1}{2} e^{ i \pi / 4}|01\rangle+\frac{1}{2}i|10\rangle+\frac{1}{2} e^{3 i \pi / 4}|11\rangle$"]) - .assert_eq(&get_latex( + .assert_eq(&get_state_latex( &state, 2, ).expect("expected valid latex")); @@ -811,7 +811,7 @@ fn check_get_latex_perf() { let start = Instant::now(); let mut l: usize = 0; for _ in 0..1_000 { - let s = get_latex(&state, 2); + let s = get_state_latex(&state, 2); l += s.map_or(0, |s| s.len()); } println!( diff --git a/pip/src/displayable_output.rs b/pip/src/displayable_output.rs index 9458cc7ced..7d303a3bbf 100644 --- a/pip/src/displayable_output.rs +++ b/pip/src/displayable_output.rs @@ -7,7 +7,7 @@ mod tests; use num_bigint::BigUint; use num_complex::{Complex64, ComplexFloat}; use qsc::{ - fmt_basis_state_label, fmt_complex, format_state_id, get_latex, get_matrix_latex, get_phase, + fmt_basis_state_label, fmt_complex, format_state_id, get_state_latex, get_matrix_latex, get_phase, }; use std::fmt::Write; @@ -56,7 +56,7 @@ impl DisplayableState { } pub fn to_latex(&self) -> Option { - get_latex(&self.0, self.1) + get_state_latex(&self.0, self.1) } } diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs index f20265712c..61be7c099c 100644 --- a/wasm/src/lib.rs +++ b/wasm/src/lib.rs @@ -12,7 +12,7 @@ use num_complex::Complex64; use project_system::{into_qsc_args, ProgramConfig}; use qsc::{ compile::{self, Dependencies}, - format_state_id, get_latex, get_matrix_latex, + format_state_id, get_state_latex, get_matrix_latex, hir::PackageId, interpret::{ self, @@ -261,7 +261,7 @@ where ) .expect("writing to string should succeed"); - let json_latex = serde_json::to_string(&get_latex(&state, qubit_count)) + let json_latex = serde_json::to_string(&get_state_latex(&state, qubit_count)) .expect("serialization should succeed"); write!(dump_json, r#" "stateLatex": {json_latex} }} "#) .expect("writing to string should succeed"); From ded3b114c05aa889d9e24b4fdcb051bffdaf0b5b Mon Sep 17 00:00:00 2001 From: Bill Ticehurst Date: Mon, 30 Sep 2024 09:14:58 -0700 Subject: [PATCH 31/32] Fix formatting --- compiler/qsc_eval/src/state/tests.rs | 8 ++++---- pip/src/displayable_output.rs | 3 ++- wasm/src/lib.rs | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/compiler/qsc_eval/src/state/tests.rs b/compiler/qsc_eval/src/state/tests.rs index feccdcd660..bcc5005d85 100644 --- a/compiler/qsc_eval/src/state/tests.rs +++ b/compiler/qsc_eval/src/state/tests.rs @@ -4,10 +4,10 @@ #![allow(clippy::needless_raw_string_hashes)] use super::{ - get_state_latex, get_matrix_latex, write_latex_for_algebraic_number, write_latex_for_cartesian_form, - write_latex_for_complex_number, write_latex_for_decimal_number, write_latex_for_polar_form, - write_latex_for_real_number, write_latex_for_term, AlgebraicNumber, CartesianForm, - ComplexNumber, DecimalNumber, PolarForm, RationalNumber, RealNumber, Term, + get_matrix_latex, get_state_latex, write_latex_for_algebraic_number, + write_latex_for_cartesian_form, write_latex_for_complex_number, write_latex_for_decimal_number, + write_latex_for_polar_form, write_latex_for_real_number, write_latex_for_term, AlgebraicNumber, + CartesianForm, ComplexNumber, DecimalNumber, PolarForm, RationalNumber, RealNumber, Term, }; use crate::state::{is_fractional_part_significant, is_significant}; use expect_test::{expect, Expect}; diff --git a/pip/src/displayable_output.rs b/pip/src/displayable_output.rs index 7d303a3bbf..81456fb611 100644 --- a/pip/src/displayable_output.rs +++ b/pip/src/displayable_output.rs @@ -7,7 +7,8 @@ mod tests; use num_bigint::BigUint; use num_complex::{Complex64, ComplexFloat}; use qsc::{ - fmt_basis_state_label, fmt_complex, format_state_id, get_state_latex, get_matrix_latex, get_phase, + fmt_basis_state_label, fmt_complex, format_state_id, get_matrix_latex, get_phase, + get_state_latex, }; use std::fmt::Write; diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs index 61be7c099c..04b425b903 100644 --- a/wasm/src/lib.rs +++ b/wasm/src/lib.rs @@ -12,7 +12,7 @@ use num_complex::Complex64; use project_system::{into_qsc_args, ProgramConfig}; use qsc::{ compile::{self, Dependencies}, - format_state_id, get_state_latex, get_matrix_latex, + format_state_id, get_matrix_latex, get_state_latex, hir::PackageId, interpret::{ self, From 8d7804f0e7599636ac0242a335ea522e2783a475 Mon Sep 17 00:00:00 2001 From: Bill Ticehurst Date: Mon, 30 Sep 2024 09:17:04 -0700 Subject: [PATCH 32/32] Update library/std/src/Std/Diagnostics.qs Co-authored-by: Stefan J. Wernli --- library/std/src/Std/Diagnostics.qs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/std/src/Std/Diagnostics.qs b/library/std/src/Std/Diagnostics.qs index 55aab93977..d71e87fe62 100644 --- a/library/std/src/Std/Diagnostics.qs +++ b/library/std/src/Std/Diagnostics.qs @@ -65,7 +65,7 @@ function DumpRegister(register : Qubit[]) : Unit { } /// # Summary -/// Given an operation, dumps the matrix representation of the operation actiong on the given +/// Given an operation, dumps the matrix representation of the operation action on the given /// number of qubits. /// /// # Input