From 9aebf8421013dbbf4ba74af15a1c89f9ff5961af Mon Sep 17 00:00:00 2001 From: Mathias Soeken Date: Tue, 15 Mar 2022 15:21:54 +0100 Subject: [PATCH 1/3] Fixes #367. --- Standard/src/Diagnostics/Allows.qs | 20 ++++++----- .../Emulation/AllowOperationCalls.cs | 11 ++++++- Standard/tests/Diagnostics/AllowTests.qs | 33 +++++++++++++++++++ 3 files changed, 55 insertions(+), 9 deletions(-) diff --git a/Standard/src/Diagnostics/Allows.qs b/Standard/src/Diagnostics/Allows.qs index 1b5bccf8f11..420999a713a 100644 --- a/Standard/src/Diagnostics/Allows.qs +++ b/Standard/src/Diagnostics/Allows.qs @@ -7,6 +7,11 @@ namespace Microsoft.Quantum.Diagnostics { /// Between a call to this operation and its adjoint, asserts that /// a given operation is called at most a certain number of times. /// + /// Operation calls are considered, if they contain the the specified + /// variant. For example, if `op` is `X` also `Adjoint X` or `Controlled X` + /// are counted, but if `op` is `Controlled X`, only `Controlled X` + /// or `Controlled Adjoint X` are counted. + /// /// # Input /// ## nTimes /// The maximum number of times that `op` may be called. @@ -19,14 +24,13 @@ namespace Microsoft.Quantum.Diagnostics { /// The following snippet will fail when executed on machines which /// support this diagnostic: /// ```qsharp - /// using (register = Qubit[4]) { - /// within { - /// AllowAtMostNCallsCA(3, H, "Too many calls to H."); - /// } apply { - /// // Fails since this calls H four times, rather than the - /// // allowed maximum of three. - /// ApplyToEach(H, register); - /// } + /// within { + /// AllowAtMostNCallsCA(3, H, "Too many calls to H."); + /// } apply { + /// use register = Qubit[4]; + /// // Fails since this calls H four times, rather than the + /// // allowed maximum of three. + /// ApplyToEach(H, register); /// } /// ``` /// diff --git a/Standard/src/Diagnostics/Emulation/AllowOperationCalls.cs b/Standard/src/Diagnostics/Emulation/AllowOperationCalls.cs index e8907d71bb6..1004b84a8fd 100644 --- a/Standard/src/Diagnostics/Emulation/AllowOperationCalls.cs +++ b/Standard/src/Diagnostics/Emulation/AllowOperationCalls.cs @@ -55,6 +55,15 @@ public Native(IOperationFactory m) : base(m) bool IsSelf(ICallable callable) => callable.FullName == "Microsoft.Quantum.Diagnostics.AllowAtMostNCallsCA"; + // Partial ordering on two variants; returns true, if `lhs` is included in `rhs`. + // For example, Body <= Body, and Body <= Adjoint, but Controlled is not less than or equal Adjoint. + bool LessThanOrEqual(OperationFunctor lhs, OperationFunctor rhs) => lhs switch { + OperationFunctor.ControlledAdjoint => rhs == OperationFunctor.ControlledAdjoint, + OperationFunctor.Controlled => rhs == OperationFunctor.ControlledAdjoint || rhs == OperationFunctor.Controlled, + OperationFunctor.Adjoint => rhs == OperationFunctor.ControlledAdjoint || rhs == OperationFunctor.Adjoint, + _ => true // OperationFunctor.Body + }; + // Record whether or not the condition checked by this allow // has failed, so that we can property unwind in the endOperation // handler below. @@ -65,7 +74,7 @@ bool IsSelf(ICallable callable) => { if (IsSelf(callable)) return; callStack = callStack.Push(callable.FullName); - if (callable.FullName == op.FullName) + if (callable.FullName == op.FullName && LessThanOrEqual(op.Variant, callable.Variant)) { callSites = callSites.Add(callStack); if (callSites.Count > nTimes) diff --git a/Standard/tests/Diagnostics/AllowTests.qs b/Standard/tests/Diagnostics/AllowTests.qs index 43385c0b749..dfe47d80548 100644 --- a/Standard/tests/Diagnostics/AllowTests.qs +++ b/Standard/tests/Diagnostics/AllowTests.qs @@ -64,6 +64,39 @@ namespace Microsoft.Quantum.Tests { } } + @Diag.Test("QuantumSimulator") + operation CheckAllowNCallsDistinguishControlled() : Unit { + within { + Diag.AllowAtMostNCallsCA(1, Controlled X, "Too many calls to Controlled X."); + } apply { + use (q1, q2) = (Qubit(), Qubit()); + // Should use one Controlled X, exactly as many times as allowed. + // X will not be accounted for. + X(q2); + Controlled X([q1], q2); + X(q2); + } + } + + @Diag.Test("QuantumSimulator") + operation CheckAllowNCallsDistinguishAdjoint() : Unit { + within { + Diag.AllowAtMostNCallsCA(2, Adjoint S, "Too many calls to Adjoint S."); + Diag.AllowAtMostNCallsCA(1, Adjoint Controlled S, "Too many calls to Adjoint Controlled S."); + } apply { + use (q1, q2) = (Qubit(), Qubit()); + // Should use two Adjoint S (one via Adjoint Controlled S), but exactly + // one Adjoint Controlled S. + S(q1); + Controlled S([q1], q2); + Adjoint Controlled S([q2], q1); + Adjoint S(q1); + + Reset(q1); + Reset(q2); + } + } + @Diag.Test("ToffoliSimulator") operation CheckAllowNQubitsWithNestedCalls() : Unit { // Here, the total number of allocated qubits exceeds our policy, From 2d3469b8f12c7e126f1a39907e9f3ec2b6adfc83 Mon Sep 17 00:00:00 2001 From: Mathias Soeken Date: Fri, 18 Mar 2022 06:52:46 -0700 Subject: [PATCH 2/3] Apply suggestions from code review Co-authored-by: Mariia Mykhailova --- Standard/src/Diagnostics/Allows.qs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Standard/src/Diagnostics/Allows.qs b/Standard/src/Diagnostics/Allows.qs index 420999a713a..ce7c6dfeef5 100644 --- a/Standard/src/Diagnostics/Allows.qs +++ b/Standard/src/Diagnostics/Allows.qs @@ -8,8 +8,8 @@ namespace Microsoft.Quantum.Diagnostics { /// a given operation is called at most a certain number of times. /// /// Operation calls are considered, if they contain the the specified - /// variant. For example, if `op` is `X` also `Adjoint X` or `Controlled X` - /// are counted, but if `op` is `Controlled X`, only `Controlled X` + /// variant. For example, if `op` is `X`, `Adjoint X` or `Controlled X` + /// are also counted, but if `op` is `Controlled X`, only `Controlled X` /// or `Controlled Adjoint X` are counted. /// /// # Input From 46ab2d6fcce8a2469daf8f61eadc8f0f5cefde26 Mon Sep 17 00:00:00 2001 From: Mathias Soeken Date: Fri, 18 Mar 2022 15:58:28 +0100 Subject: [PATCH 3/3] Addressing Mariia's comments. --- Standard/src/Diagnostics/Allows.qs | 14 ++++++++++++++ .../Diagnostics/Emulation/AllowOperationCalls.cs | 4 ++++ Standard/tests/Diagnostics/AllowTests.qs | 2 ++ 3 files changed, 20 insertions(+) diff --git a/Standard/src/Diagnostics/Allows.qs b/Standard/src/Diagnostics/Allows.qs index ce7c6dfeef5..766aff73457 100644 --- a/Standard/src/Diagnostics/Allows.qs +++ b/Standard/src/Diagnostics/Allows.qs @@ -34,6 +34,20 @@ namespace Microsoft.Quantum.Diagnostics { /// } /// ``` /// + /// Another example illustrates how restricted calls are handled. + /// ```qsharp + /// within { + /// // Both tests will pass in this case + /// AllowAtMostNCallsCA(1, Controlled H, "Too many calls to Controlled H."); + /// AllowAtMostNCallsCA(2, H, "Too many calls to H or Controlled H."); + /// } apply { + /// use (a, b) = (Qubit(), Qubit()); + /// H(a); + /// Controlled H([a], b); + /// ResetAll([a, b]); + /// } + /// ``` + /// /// # Remarks /// This operation may be replaced by a no-op on targets which do not /// support it. diff --git a/Standard/src/Diagnostics/Emulation/AllowOperationCalls.cs b/Standard/src/Diagnostics/Emulation/AllowOperationCalls.cs index 1004b84a8fd..5d2a784e983 100644 --- a/Standard/src/Diagnostics/Emulation/AllowOperationCalls.cs +++ b/Standard/src/Diagnostics/Emulation/AllowOperationCalls.cs @@ -74,6 +74,10 @@ bool IsSelf(ICallable callable) => { if (IsSelf(callable)) return; callStack = callStack.Push(callable.FullName); + // `callable` is callable we just entered on the call stack, `op` is the callable + // that we are monitoring with AllowAtMostNCallsCA. We only increment the counter, + // if both callables have the same fully qualified name and if the variant of `op` + // is less restrictive than the one of `callable`. if (callable.FullName == op.FullName && LessThanOrEqual(op.Variant, callable.Variant)) { callSites = callSites.Add(callStack); diff --git a/Standard/tests/Diagnostics/AllowTests.qs b/Standard/tests/Diagnostics/AllowTests.qs index dfe47d80548..f728cd371ce 100644 --- a/Standard/tests/Diagnostics/AllowTests.qs +++ b/Standard/tests/Diagnostics/AllowTests.qs @@ -81,7 +81,9 @@ namespace Microsoft.Quantum.Tests { @Diag.Test("QuantumSimulator") operation CheckAllowNCallsDistinguishAdjoint() : Unit { within { + Diag.AllowAtMostNCallsCA(4, S, "Too many calls to S."); Diag.AllowAtMostNCallsCA(2, Adjoint S, "Too many calls to Adjoint S."); + Diag.AllowAtMostNCallsCA(2, Controlled S, "Too many calls to Controlled S."); Diag.AllowAtMostNCallsCA(1, Adjoint Controlled S, "Too many calls to Adjoint Controlled S."); } apply { use (q1, q2) = (Qubit(), Qubit());