Repository: microsoft/qdk (Rust)

Issue:
`DumpMachine` output in Python and console should be empty with no qubits allocated
Following up on #1922, we should change the output of `DumpMachine` in Python/Jupyter as well as console to show a message when no qubits are allocated rather than treating it the same as a single qubit in the zero state.


Discussion:

Patch:
diff --git a/compiler/qsc_eval/src/output.rs b/compiler/qsc_eval/src/output.rs
index cb4a9cced7..342b96ff31 100644
--- a/compiler/qsc_eval/src/output.rs
+++ b/compiler/qsc_eval/src/output.rs
@@ -40,14 +40,18 @@ impl<'a> GenericReceiver<'a> {
 impl<'a> Receiver for GenericReceiver<'a> {
     fn state(&mut self, state: Vec<(BigUint, Complex64)>, qubit_count: usize) -> Result<(), Error> {
         writeln!(self.writer, "STATE:").map_err(|_| Error)?;
-        for (id, state) in state {
-            writeln!(
-                self.writer,
-                "{}: {}",
-                format_state_id(&id, qubit_count),
-                fmt_complex(&state),
-            )
-            .map_err(|_| Error)?;
+        if qubit_count > 0 {
+            for (id, state) in state {
+                writeln!(
+                    self.writer,
+                    "{}: {}",
+                    format_state_id(&id, qubit_count),
+                    fmt_complex(&state),
+                )
+                .map_err(|_| Error)?;
+            }
+        } else {
+            writeln!(self.writer, "No qubits allocated").map_err(|_| Error)?;
         }
         Ok(())
     }
@@ -88,14 +92,18 @@ impl<'a> CursorReceiver<'a> {
 impl<'a> Receiver for CursorReceiver<'a> {
     fn state(&mut self, state: Vec<(BigUint, Complex64)>, qubit_count: usize) -> Result<(), Error> {
         writeln!(self.cursor, "STATE:").map_err(|_| Error)?;
-        for (id, state) in state {
-            writeln!(
-                self.cursor,
-                "{}: {}",
-                format_state_id(&id, qubit_count),
-                state
-            )
-            .map_err(|_| Error)?;
+        if qubit_count > 0 {
+            for (id, state) in state {
+                writeln!(
+                    self.cursor,
+                    "{}: {}",
+                    format_state_id(&id, qubit_count),
+                    state
+                )
+                .map_err(|_| Error)?;
+            }
+        } else {
+            writeln!(self.cursor, "No qubits allocated").map_err(|_| Error)?;
         }
         Ok(())
     }
diff --git a/compiler/qsc_eval/src/state.rs b/compiler/qsc_eval/src/state.rs
index 85dc2f524c..2e3145179d 100644
--- a/compiler/qsc_eval/src/state.rs
+++ b/compiler/qsc_eval/src/state.rs
@@ -350,7 +350,7 @@ fn get_terms_for_state(state: &Vec<(BigUint, Complex64)>) -> Vec<Term> {
 /// if the formula consists of more than 16 terms or if more than two coefficients are not recognized.
 #[must_use]
 pub fn get_state_latex(state: &Vec<(BigUint, Complex64)>, qubit_count: usize) -> Option<String> {
-    if state.len() > 16 {
+    if state.len() > 16 || qubit_count == 0 {
         return None;
     }
 
diff --git a/npm/qsharp/src/compiler/common.ts b/npm/qsharp/src/compiler/common.ts
index 9522c1c7ab..dc3e528736 100644
--- a/npm/qsharp/src/compiler/common.ts
+++ b/npm/qsharp/src/compiler/common.ts
@@ -17,6 +17,7 @@ interface DumpMsg {
   type: "DumpMachine";
   state: Dump;
   stateLatex: string | null;
+  qubitCount: number;
 }
 
 interface MatrixMsg {
diff --git a/npm/qsharp/src/compiler/compiler.ts b/npm/qsharp/src/compiler/compiler.ts
index 6048e84d1f..67345326c3 100644
--- a/npm/qsharp/src/compiler/compiler.ts
+++ b/npm/qsharp/src/compiler/compiler.ts
@@ -292,6 +292,7 @@ export function onCompilerEvent(msg: string, eventTarget: IQscEventTarget) {
       qscEvent = makeEvent("DumpMachine", {
         state: qscMsg.state,
         stateLatex: qscMsg.stateLatex,
+        qubitCount: qscMsg.qubitCount,
       });
       break;
     case "Result":
diff --git a/npm/qsharp/src/compiler/events.ts b/npm/qsharp/src/compiler/events.ts
index 7b65dec6e0..09c5f432a4 100644
--- a/npm/qsharp/src/compiler/events.ts
+++ b/npm/qsharp/src/compiler/events.ts
@@ -8,7 +8,10 @@ import { IServiceEventTarget } from "../workers/common.js";
 // Create strongly typed compiler events
 export type QscEventData =
   | { type: "Message"; detail: string }
-  | { type: "DumpMachine"; detail: { state: Dump; stateLatex: string | null } }
+  | {
+      type: "DumpMachine";
+      detail: { state: Dump; stateLatex: string | null; qubitCount: number };
+    }
   | { type: "Matrix"; detail: { matrix: number[][][]; matrixLatex: string } }
   | { type: "Result"; detail: Result };
 
@@ -108,7 +111,11 @@ export class QscEventTarget implements IQscEventTarget {
     this.queueUiRefresh();
   }
 
-  private onDumpMachine(detail: { state: Dump; stateLatex: string | null }) {
+  private onDumpMachine(detail: {
+    state: Dump;
+    stateLatex: string | null;
+    qubitCount: number;
+  }) {
     this.ensureActiveShot();
 
     const shotIdx = this.results.length - 1;
@@ -116,6 +123,7 @@ export class QscEventTarget implements IQscEventTarget {
       type: "DumpMachine",
       state: detail.state,
       stateLatex: detail.stateLatex,
+      qubitCount: detail.qubitCount,
     });
 
     this.queueUiRefresh();
diff --git a/npm/qsharp/src/debug-service/debug-service.ts b/npm/qsharp/src/debug-service/debug-service.ts
index 1f40daf0d5..151d7132e0 100644
--- a/npm/qsharp/src/debug-service/debug-service.ts
+++ b/npm/qsharp/src/debug-service/debug-service.ts
@@ -158,6 +158,7 @@ export function onCompilerEvent(msg: string, eventTarget: IQscEventTarget) {
       qscEvent = makeEvent("DumpMachine", {
         state: qscMsg.state,
         stateLatex: qscMsg.stateLatex,
+        qubitCount: qscMsg.qubitCount,
       });
       break;
     case "Result":
diff --git a/pip/src/displayable_output.rs b/pip/src/displayable_output.rs
index 81456fb611..79ccf21545 100644
--- a/pip/src/displayable_output.rs
+++ b/pip/src/displayable_output.rs
@@ -18,42 +18,53 @@ pub struct DisplayableMatrix(pub Vec<Vec<Complex64>>);
 
 impl DisplayableState {
     pub fn to_plain(&self) -> String {
-        format!(
-            "STATE:{}",
-            self.0
-                .iter()
-                .fold(String::new(), |mut output, (id, state)| {
-                    let _ = write!(
-                        output,
-                        "\n{}: {}",
-                        format_state_id(id, self.1),
-                        fmt_complex(state)
-                    );
-                    output
-                })
-        )
+        if self.1 > 0 {
+            format!(
+                "STATE:{}",
+                self.0
+                    .iter()
+                    .fold(String::new(), |mut output, (id, state)| {
+                        let _ = write!(
+                            output,
+                            "\n{}: {}",
+                            format_state_id(id, self.1),
+                            fmt_complex(state)
+                        );
+                        output
+                    })
+            )
+        } else {
+            "STATE:\nNo qubits allocated".to_string()
+        }
     }
 
     pub fn to_html(&self) -> String {
-        format!(
-            include_str!("state_header_template.html"),
-            self.0
-                .iter()
-                .fold(String::new(), |mut output, (id, state)| {
-                    let amplitude = state.abs().powi(2) * 100.0;
-                    let _ = write!(
-                        output,
-                        include_str!("state_row_template.html"),
-                        fmt_basis_state_label(id, self.1),
-                        fmt_complex(state),
-                        amplitude,
-                        amplitude,
-                        get_phase(state),
-                        get_phase(state)
-                    );
-                    output
-                })
-        )
+        if self.1 > 0 {
+            format!(
+                include_str!("state_header_template.html"),
+                self.0
+                    .iter()
+                    .fold(String::new(), |mut output, (id, state)| {
+                        let amplitude = state.abs().powi(2) * 100.0;
+                        let _ = write!(
+                            output,
+                            include_str!("state_row_template.html"),
+                            fmt_basis_state_label(id, self.1),
+                            fmt_complex(state),
+                            amplitude,
+                            amplitude,
+                            get_phase(state),
+                            get_phase(state)
+                        );
+                        output
+                    })
+            )
+        } else {
+            format!(
+                include_str!("state_header_template.html"),
+                "<tr><td>No qubits allocated</td></tr>".to_string()
+            )
+        }
     }
 
     pub fn to_latex(&self) -> Option<String> {
diff --git a/vscode/src/debugger/output.ts b/vscode/src/debugger/output.ts
index 0cb478f54c..00dbb0da74 100644
--- a/vscode/src/debugger/output.ts
+++ b/vscode/src/debugger/output.ts
@@ -31,6 +31,7 @@ export function createDebugConsoleEventTarget(out: (message: string) => void) {
     }
 
     const stateTable = evt.detail.state;
+    const qubitCount = evt.detail.qubitCount;
     const basisStates = Object.keys(stateTable);
     const basisColumnWidth = Math.max(
       basisStates[0]?.length ?? 0,
@@ -44,16 +45,19 @@ export function createDebugConsoleEventTarget(out: (message: string) => void) {
       " ".padEnd(basisColumnWidth, "-") +
       "-------------------------------------------\n";
 
-    for (const row of basisStates) {
-      const [real, imag] = stateTable[row];
-      const basis = row.padStart(basisColumnWidth);
-      const amplitude = formatComplex(real, imag).padStart(16);
-      const probability = formatProbabilityPercent(real, imag).padStart(11);
-      const phase = formatPhase(real, imag).padStart(8);
+    if (qubitCount === 0) {
+      out_str += " No qubits allocated\n";
+    } else {
+      for (const row of basisStates) {
+        const [real, imag] = stateTable[row];
+        const basis = row.padStart(basisColumnWidth);
+        const amplitude = formatComplex(real, imag).padStart(16);
+        const probability = formatProbabilityPercent(real, imag).padStart(11);
+        const phase = formatPhase(real, imag).padStart(8);
 
-      out_str += ` ${basis} | ${amplitude} | ${probability} | ${phase}\n`;
+        out_str += ` ${basis} | ${amplitude} | ${probability} | ${phase}\n`;
+      }
     }
-
     out(out_str);
   });
 
diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs
index 56ef5a53a0..ad09a451e2 100644
--- a/wasm/src/lib.rs
+++ b/wasm/src/lib.rs
@@ -263,8 +263,11 @@ where
 
         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");
+        write!(
+            dump_json,
+            r#" "stateLatex": {json_latex}, "qubitCount": {qubit_count} }} "#
+        )
+        .expect("writing to string should succeed");
         (self.event_cb)(&dump_json);
         Ok(())
     }


Test patch:
diff --git a/compiler/qsc/src/interpret/tests.rs b/compiler/qsc/src/interpret/tests.rs
index 1de1aea3ad..ce37254484 100644
--- a/compiler/qsc/src/interpret/tests.rs
+++ b/compiler/qsc/src/interpret/tests.rs
@@ -267,7 +267,7 @@ mod given_interpreter {
             let (result, output) = line(&mut interpreter, "import Std.Diagnostics.*;");
             is_only_value(&result, &output, &Value::unit());
             let (result, output) = line(&mut interpreter, "DumpMachine()");
-            is_unit_with_output(&result, &output, "STATE:\n|0⟩: 1+0i");
+            is_unit_with_output(&result, &output, "STATE:\nNo qubits allocated");
         }
 
         #[test]
@@ -277,7 +277,7 @@ mod given_interpreter {
                 &mut interpreter,
                 "open Microsoft.Quantum.Diagnostics; DumpMachine()",
             );
-            is_unit_with_output(&result, &output, "STATE:\n|0⟩: 1+0i");
+            is_unit_with_output(&result, &output, "STATE:\nNo qubits allocated");
         }
 
         #[test]
@@ -332,7 +332,7 @@ mod given_interpreter {
             let (result, output) = line(&mut interpreter, "import Std.Diagnostics.*;");
             is_only_value(&result, &output, &Value::unit());
             let (result, output) = line(&mut interpreter, "DumpMachine()");
-            is_unit_with_output(&result, &output, "STATE:\n|0⟩: 1+0i");
+            is_unit_with_output(&result, &output, "STATE:\nNo qubits allocated");
             let (result, output) = line(&mut interpreter, "use (q0, qs) = (Qubit(), Qubit[3]);");
             is_only_value(&result, &output, &Value::unit());
             let (result, output) = line(&mut interpreter, "DumpMachine()");
diff --git a/compiler/qsc_eval/src/intrinsic/tests.rs b/compiler/qsc_eval/src/intrinsic/tests.rs
index 8da9898e50..6ab588d929 100644
--- a/compiler/qsc_eval/src/intrinsic/tests.rs
+++ b/compiler/qsc_eval/src/intrinsic/tests.rs
@@ -294,7 +294,7 @@ fn dump_machine() {
         "Microsoft.Quantum.Diagnostics.DumpMachine()",
         &expect![[r#"
             STATE:
-            |0⟩: 1.0000+0.0000𝑖
+            No qubits allocated
         "#]],
     );
 }
diff --git a/samples_test/src/tests/language.rs b/samples_test/src/tests/language.rs
index 3c39d70b4f..6a4b80470a 100644
--- a/samples_test/src/tests/language.rs
+++ b/samples_test/src/tests/language.rs
@@ -214,11 +214,11 @@ pub const LOGICALOPERATORS_EXPECT: Expect = expect!["()"];
 pub const LOGICALOPERATORS_EXPECT_DEBUG: Expect = expect!["()"];
 pub const NAMESPACES_EXPECT: Expect = expect![[r#"
     STATE:
-    |0⟩: 1.0000+0.0000𝑖
+    No qubits allocated
     []"#]];
 pub const NAMESPACES_EXPECT_DEBUG: Expect = expect![[r#"
     STATE:
-    |0⟩: 1.0000+0.0000𝑖
+    No qubits allocated
     []"#]];
 pub const OPERATIONS_EXPECT: Expect = expect![[r#"
     Measurement result: Zero