Repository: microsoft/qsharp
Pull request #2322 (2025-04-21 22:32:55)

Change Editor "Learn More" Link
Change the Learn More link to point to the editor wiki when using the editor.


Patch:
diff --git a/compiler/qsc_circuit/src/builder.rs b/compiler/qsc_circuit/src/builder.rs
index cbd28737..af0685d5 100644
--- a/compiler/qsc_circuit/src/builder.rs
+++ b/compiler/qsc_circuit/src/builder.rs
@@ -88,35 +88,35 @@ impl Backend for Builder {
 
     fn rx(&mut self, theta: f64, q: usize) {
         let q = self.map(q);
-        self.push_gate(rotation_gate("rx", theta, [q]));
+        self.push_gate(rotation_gate("Rx", theta, [q]));
     }
 
     fn rxx(&mut self, theta: f64, q0: usize, q1: usize) {
         let q0 = self.map(q0);
         let q1 = self.map(q1);
-        self.push_gate(rotation_gate("rxx", theta, [q0, q1]));
+        self.push_gate(rotation_gate("Rxx", theta, [q0, q1]));
     }
 
     fn ry(&mut self, theta: f64, q: usize) {
         let q = self.map(q);
-        self.push_gate(rotation_gate("ry", theta, [q]));
+        self.push_gate(rotation_gate("Ry", theta, [q]));
     }
 
     fn ryy(&mut self, theta: f64, q0: usize, q1: usize) {
         let q0 = self.map(q0);
         let q1 = self.map(q1);
-        self.push_gate(rotation_gate("ryy", theta, [q0, q1]));
+        self.push_gate(rotation_gate("Ryy", theta, [q0, q1]));
     }
 
     fn rz(&mut self, theta: f64, q: usize) {
         let q = self.map(q);
-        self.push_gate(rotation_gate("rz", theta, [q]));
+        self.push_gate(rotation_gate("Rz", theta, [q]));
     }
 
     fn rzz(&mut self, theta: f64, q0: usize, q1: usize) {
         let q0 = self.map(q0);
         let q1 = self.map(q1);
-        self.push_gate(rotation_gate("rzz", theta, [q0, q1]));
+        self.push_gate(rotation_gate("Rzz", theta, [q0, q1]));
     }
 
     fn sadj(&mut self, q: usize) {
diff --git a/compiler/qsc_qasm/src/stdlib/QasmStd/src/QasmStd/Angle.qs b/compiler/qsc_qasm/src/stdlib/QasmStd/src/QasmStd/Angle.qs
index d3926410..d7ccc58e 100644
--- a/compiler/qsc_qasm/src/stdlib/QasmStd/src/QasmStd/Angle.qs
+++ b/compiler/qsc_qasm/src/stdlib/QasmStd/src/QasmStd/Angle.qs
@@ -8,6 +8,7 @@ import Std.Convert.IntAsBigInt;
 import Std.Convert.IntAsBoolArray;
 import Std.Convert.IntAsDouble;
 import Std.Diagnostics.Fact;
+import Std.Math.RoundHalfAwayFromZero;
 import Convert.__IntAsResultArrayBE__;
 import Convert.__ResultAsInt__;
 
@@ -231,7 +232,7 @@ function __MultiplyAngleByBigInt__(angle : __Angle__, factor : BigInt) : __Angle
     let (value, size) = angle!;
     let value : BigInt = Std.Convert.IntAsBigInt(value);
     let value = (value * factor) % Std.Convert.IntAsBigInt(1 <<< size);
-    let value = Std.Convert.BoolArrayAsInt(Std.Convert.BigIntAsBoolArray(value, size));
+    let value = Std.Convert.BigIntAsInt(value);
     new __Angle__ { Value = value, Size = size }
 }
 
@@ -254,20 +255,3 @@ function __NegAngle__(angle : __Angle__) : __Angle__ {
     let value = (1 <<< size) - value;
     new __Angle__ { Value = value, Size = size }
 }
-
-// not exported
-function RoundHalfAwayFromZero(value : Double) : Int {
-    let roundedValue = Microsoft.Quantum.Math.Round(value);
-    let EPSILON = 2.2204460492503131e-16;
-    let diff = Std.Math.AbsD(value - Std.Convert.IntAsDouble(roundedValue));
-    if (Std.Math.AbsD(diff - 0.5) < EPSILON) {
-        if (value > 0.0) {
-            return roundedValue + 1;
-        } else {
-            return roundedValue - 1;
-        }
-    } else {
-        return roundedValue;
-    }
-}
-
diff --git a/compiler/qsc_qasm/src/stdlib/QasmStd/src/QasmStd/Convert.qs b/compiler/qsc_qasm/src/stdlib/QasmStd/src/QasmStd/Convert.qs
index 22dfb0c0..eb5eee6a 100644
--- a/compiler/qsc_qasm/src/stdlib/QasmStd/src/QasmStd/Convert.qs
+++ b/compiler/qsc_qasm/src/stdlib/QasmStd/src/QasmStd/Convert.qs
@@ -5,10 +5,7 @@ import Std.Math.AbsI;
 
 /// The POW function is used to implement the `pow` modifier in QASM for integers.
 operation __Pow__<'T>(N: Int, op: ('T => Unit is Adj + Ctl), target : 'T) : Unit {
-    let op = if N > 0 { () => op(target) } else { () => Adjoint op(target) };
-    for _ in 1..AbsI(N) {
-        op()
-    }
+    ApplyOperationPowerA(N, op, target);
 }
 
 /// The ``BARRIER`` function is used to implement the `barrier` statement in QASM.
diff --git a/library/std/src/Std/Canon.qs b/library/std/src/Std/Canon.qs
index 98751cc6..1eccc6fb 100644
--- a/library/std/src/Std/Canon.qs
+++ b/library/std/src/Std/Canon.qs
@@ -583,6 +583,22 @@ operation ApplyXorInPlaceL(value : BigInt, target : Qubit[]) : Unit is Adj + Ctl
     }
     adjoint self;
 }
+
+/// # Summary
+/// Applies operation `op` to the `target` `power` times.
+/// If `power` is negative, the adjoint of `op` is used.
+/// If `power` is 0, the operation `op` is not applied.
+operation ApplyOperationPowerA<'T>(
+    power : Int,
+    op : 'T => Unit is Adj,
+    target : 'T
+) : Unit is Adj {
+    let u = if power >= 0 { op } else { Adjoint op };
+    for _ in 1..AbsI(power) {
+        u(target);
+    }
+}
+
 /// # Summary
 /// Relabels the qubits in the `current` array with the qubits in the `updated` array. The `updated` array
 /// must be a valid permutation of the `current` array.
@@ -647,4 +663,5 @@ export
     SwapReverseRegister,
     ApplyXorInPlace,
     ApplyXorInPlaceL,
+    ApplyOperationPowerA,
     Relabel;
diff --git a/library/std/src/Std/Convert.qs b/library/std/src/Std/Convert.qs
index 58d8953f..721dc94e 100644
--- a/library/std/src/Std/Convert.qs
+++ b/library/std/src/Std/Convert.qs
@@ -162,6 +162,34 @@ function BigIntAsBoolArray(number : BigInt, bits : Int) : Bool[] {
     result
 }
 
+/// # Summary
+/// Converts a BigInt number into Int. Raises an error if the number is too large to fit.
+///
+/// # Input
+/// ## number
+/// A BigInt number to be converted.
+///
+/// # Output
+/// Int representation of a number.
+function BigIntAsInt(number : BigInt) : Int {
+    let max = 9_223_372_036_854_775_807L;
+    let min = -9_223_372_036_854_775_808L;
+    Fact(number >= min and number <= max, $"`number`=number is too big to fit into Int.");
+
+    mutable result = 0;
+    mutable powL = 1L;
+    mutable pow = 1;
+    for _ in 0..63 {
+        if number &&& powL != 0L {
+            result |||= pow;
+        }
+        powL <<<= 1;
+        pow <<<= 1;
+    }
+
+    result
+}
+
 /// # Summary
 /// Produces a non-negative integer from a string of Results in little-endian format.
 ///
@@ -289,9 +317,10 @@ export
     IntAsBoolArray,
     BoolArrayAsBigInt,
     BigIntAsBoolArray,
+    BigIntAsInt,
     ResultArrayAsInt,
     ResultArrayAsBoolArray,
     BoolArrayAsResultArray,
     ComplexAsComplexPolar,
     ComplexPolarAsComplex,
-    DoubleAsStringWithPrecision;
\ No newline at end of file
+    DoubleAsStringWithPrecision;
diff --git a/library/std/src/Std/Math.qs b/library/std/src/Std/Math.qs
index 7e9cdf11..943473f7 100644
--- a/library/std/src/Std/Math.qs
+++ b/library/std/src/Std/Math.qs
@@ -396,16 +396,27 @@ function Floor(value : Double) : Int {
 }
 
 /// # Summary
-/// Returns the nearest integer to the specified number.
-/// For example: Round(3.7) = 4; Round(-3.7) = -4
+/// Returns the nearest integer to the specified number. Half is rounded towards zero.
+/// For example: Round(3.7) = 4; Round(-3.7) = -4; Round(3.5) = 3;
+///
+/// # References
+/// [Wikipedia article - Rounding](https://en.wikipedia.org/wiki/Rounding#Rounding_half_toward_zero)
 function Round(value : Double) : Int {
     let (truncated, remainder, isPositive) = ExtendedTruncation(value);
-    if AbsD(remainder) <= 1e-15 {
-        truncated
-    } else {
-        let abs = AbsD(remainder);
-        truncated + (abs <= 0.5 ? 0 | (isPositive ? 1 | -1))
-    }
+    let abs = AbsD(remainder);
+    truncated + (abs <= 0.5 ? 0 | (isPositive ? 1 | -1))
+}
+
+/// # Summary
+/// Returns the nearest integer to the specified number. Half is rounded away from zero.
+/// For example: RoundHalfAwayFromZero(-3.7) = -4, RoundHalfAwayFromZero(3.5) = 4;
+///
+/// # References
+/// [Wikipedia article - Rounding](https://en.wikipedia.org/wiki/Rounding#Rounding_half_away_from_zero)
+function RoundHalfAwayFromZero(value : Double) : Int {
+    let (truncated, remainder, isPositive) = ExtendedTruncation(value);
+    let abs = AbsD(remainder);
+    truncated + (abs < 0.5 ? 0 | (isPositive ? 1 | -1))
 }
 
 //
@@ -1493,6 +1504,7 @@ export
     Ceiling,
     Floor,
     Round,
+    RoundHalfAwayFromZero,
     DivRemI,
     DivRemL,
     ModulusI,
diff --git a/npm/qsharp/ux/circuit.tsx b/npm/qsharp/ux/circuit.tsx
index 47ca3f09..21d6f2b3 100644
--- a/npm/qsharp/ux/circuit.tsx
+++ b/npm/qsharp/ux/circuit.tsx
@@ -270,7 +270,13 @@ export function CircuitPanel(props: CircuitProps) {
       )}
       <p>
         Learn more at{" "}
-        <a href="https://aka.ms/qdk.circuits">https://aka.ms/qdk.circuits</a>
+        {props.isEditable ? (
+          <a href="https://aka.ms/qdk.circuit-editor">
+            https://aka.ms/qdk.circuit-editor
+          </a>
+        ) : (
+          <a href="https://aka.ms/qdk.circuits">https://aka.ms/qdk.circuits</a>
+        )}
       </p>
       {props.calculating ? (
         <div>


Test patch:
diff --git a/compiler/qsc/src/interpret/circuit_tests.rs b/compiler/qsc/src/interpret/circuit_tests.rs
index a2832ab7..5543bb0a 100644
--- a/compiler/qsc/src/interpret/circuit_tests.rs
+++ b/compiler/qsc/src/interpret/circuit_tests.rs
@@ -148,9 +148,9 @@ fn rotation_gate() {
         .circuit(CircuitEntryPoint::EntryPoint, false)
         .expect("circuit generation should succeed");
 
-    expect![[r"
-        q_0    ─ rx(1.5708) ──
-    "]]
+    expect![[r#"
+        q_0    ─ Rx(1.5708) ──
+    "#]]
     .assert_eq(&circ.to_string());
 }
 
@@ -883,10 +883,10 @@ fn operation_with_long_gates_properly_aligned() {
     expect![[r#"
         q_0    ── H ────────────────────────────────────── ● ──────── M ────────────────────────────────── ● ─────────
                                                            │          ╘════════════════════════════════════╪══════════
-        q_1    ── H ──────── X ─────── ry(1.0000) ──────── X ───────────────────────────── rxx(1.0000) ────┼───── M ──
+        q_1    ── H ──────── X ─────── Ry(1.0000) ──────── X ───────────────────────────── Rxx(1.0000) ────┼───── M ──
                                                                                                 ┆          │      ╘═══
-        q_2    ── H ─── rx(1.0000) ──────── H ─────── rx(1.0000) ──── H ─── rx(1.0000) ─────────┆──────────┼──────────
-        q_3    ─────────────────────────────────────────────────────────────────────────── rxx(1.0000) ─── X ──── M ──
+        q_2    ── H ─── Rx(1.0000) ──────── H ─────── Rx(1.0000) ──── H ─── Rx(1.0000) ─────────┆──────────┼──────────
+        q_3    ─────────────────────────────────────────────────────────────────────────── Rxx(1.0000) ─── X ──── M ──
                                                                                                                   ╘═══
     "#]]
     .assert_eq(&circ.to_string());
@@ -919,12 +919,12 @@ fn operation_with_subsequent_qubits_gets_horizontal_lines() {
         .expect("circuit generation should succeed");
 
     expect![[r#"
-        q_0    ─ rxx(1.0000) ─
+        q_0    ─ Rxx(1.0000) ─
                       ┆
-        q_1    ─ rxx(1.0000) ─
-        q_2    ─ rxx(1.0000) ─
+        q_1    ─ Rxx(1.0000) ─
+        q_2    ─ Rxx(1.0000) ─
                       ┆
-        q_3    ─ rxx(1.0000) ─
+        q_3    ─ Rxx(1.0000) ─
     "#]]
     .assert_eq(&circ.to_string());
 }
@@ -953,9 +953,9 @@ fn operation_with_subsequent_qubits_no_double_rows() {
         .expect("circuit generation should succeed");
 
     expect![[r#"
-        q_0    ─ rxx(1.0000) ── rxx(1.0000) ─
+        q_0    ─ Rxx(1.0000) ── Rxx(1.0000) ─
                       ┆              ┆
-        q_1    ─ rxx(1.0000) ── rxx(1.0000) ─
+        q_1    ─ Rxx(1.0000) ── Rxx(1.0000) ─
     "#]]
     .assert_eq(&circ.to_string());
 }
@@ -989,12 +989,12 @@ fn operation_with_subsequent_qubits_no_added_rows() {
         .expect("circuit generation should succeed");
 
     expect![[r#"
-        q_0    ─ rxx(1.0000) ─── M ──
+        q_0    ─ Rxx(1.0000) ─── M ──
                       ┆          ╘═══
-        q_1    ─ rxx(1.0000) ────────
-        q_2    ─ rxx(1.0000) ─── M ──
+        q_1    ─ Rxx(1.0000) ────────
+        q_2    ─ Rxx(1.0000) ─── M ──
                       ┆          ╘═══
-        q_3    ─ rxx(1.0000) ────────
+        q_3    ─ Rxx(1.0000) ────────
     "#]]
     .assert_eq(&circ.to_string());
 }
diff --git a/compiler/qsc_partial_eval/src/tests/qubits.rs b/compiler/qsc_partial_eval/src/tests/qubits.rs
index cb537f2c..f9985a4c 100644
--- a/compiler/qsc_partial_eval/src/tests/qubits.rs
+++ b/compiler/qsc_partial_eval/src/tests/qubits.rs
@@ -377,6 +377,6 @@ fn qubit_relabel_in_dynamic_block_triggers_capability_error() {
 
     assert_error(
         &error,
-        &expect!["CapabilityError(UseOfDynamicQubit(Span { lo: 59760, hi: 59773 }))"],
+        &expect!["CapabilityError(UseOfDynamicQubit(Span { lo: 60173, hi: 60186 }))"],
     );
 }
diff --git a/library/src/tests/canon.rs b/library/src/tests/canon.rs
index 7133ebf2..a7afda85 100644
--- a/library/src/tests/canon.rs
+++ b/library/src/tests/canon.rs
@@ -556,3 +556,18 @@ fn check_apply_xor_in_place_l() {
         &Value::Int(953),
     );
 }
+
+#[test]
+fn check_apply_operation_power_a() {
+    test_expression(
+        {
+            "{
+            use q = Qubit();
+            ApplyOperationPowerA(12, Rx(Std.Math.PI()/16.0, _), q);
+            ApplyOperationPowerA(-3, Rx(Std.Math.PI()/4.0, _), q);
+            M(q)
+        }"
+        },
+        &Value::RESULT_ZERO,
+    );
+}
diff --git a/library/src/tests/convert.rs b/library/src/tests/convert.rs
index 415dbe0a..b209711e 100644
--- a/library/src/tests/convert.rs
+++ b/library/src/tests/convert.rs
@@ -170,6 +170,37 @@ fn check_bigint_as_bool_array() {
     );
 }
 
+#[test]
+fn check_big_int_as_int_0() {
+    test_expression("Std.Convert.BigIntAsInt(0L)", &Value::Int(0));
+}
+
+#[test]
+fn check_big_int_as_int_1() {
+    test_expression("Std.Convert.BigIntAsInt(1L)", &Value::Int(1));
+}
+
+#[test]
+fn check_big_int_as_int_n1() {
+    test_expression("Std.Convert.BigIntAsInt(-1L)", &Value::Int(-1));
+}
+
+#[test]
+fn check_big_int_as_int_max() {
+    test_expression(
+        "Std.Convert.BigIntAsInt(9_223_372_036_854_775_807L)",
+        &Value::Int(i64::MAX),
+    );
+}
+
+#[test]
+fn check_big_int_as_int_min() {
+    test_expression(
+        "Std.Convert.BigIntAsInt(-9_223_372_036_854_775_808L)",
+        &Value::Int(i64::MIN),
+    );
+}
+
 #[test]
 fn check_bool_array_as_big_int() {
     test_expression(
diff --git a/library/src/tests/math.rs b/library/src/tests/math.rs
index b1d8c85e..dd295bae 100644
--- a/library/src/tests/math.rs
+++ b/library/src/tests/math.rs
@@ -367,8 +367,28 @@ fn check_floor() {
 
 #[test]
 fn check_round() {
-    test_expression("Microsoft.Quantum.Math.Round(3.1)", &Value::Int(3));
-    test_expression("Microsoft.Quantum.Math.Round(-3.7)", &Value::Int(-4));
+    test_expression("Std.Math.Round(0.5-1.0e-16)", &Value::Int(0));
+    test_expression("Std.Math.Round(0.5)", &Value::Int(0));
+    test_expression("Std.Math.Round(0.5+1.0e-16)", &Value::Int(1));
+    test_expression("Std.Math.Round(3.1)", &Value::Int(3));
+    test_expression("Std.Math.Round(-3.7)", &Value::Int(-4));
+    test_expression("Std.Math.Round(-3.5)", &Value::Int(-3));
+}
+
+#[test]
+fn check_round_away_from_zero() {
+    test_expression(
+        "Std.Math.RoundHalfAwayFromZero(0.5-1.0e-16)",
+        &Value::Int(0),
+    );
+    test_expression("Std.Math.RoundHalfAwayFromZero(0.5)", &Value::Int(1));
+    test_expression(
+        "Std.Math.RoundHalfAwayFromZero(0.5+1.0e-16)",
+        &Value::Int(1),
+    );
+    test_expression("Std.Math.RoundHalfAwayFromZero(3.1)", &Value::Int(3));
+    test_expression("Std.Math.RoundHalfAwayFromZero(-3.7)", &Value::Int(-4));
+    test_expression("Std.Math.RoundHalfAwayFromZero(-3.5)", &Value::Int(-4));
 }
 
 //