From ab799a8f60f69317a57c91e8776267eafe4e87d3 Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Thu, 6 Aug 2020 12:33:15 -0400 Subject: [PATCH 1/9] Remove duplicate qubits from RuntimeMetadata.Targets --- src/Simulation/Core/Operations/Operation.cs | 23 ++++++++++------- .../Circuits/RuntimeMetadataTest.qs | 4 +++ .../Simulators.Tests/RuntimeMetadataTests.cs | 25 +++++++++++++++++++ 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/Simulation/Core/Operations/Operation.cs b/src/Simulation/Core/Operations/Operation.cs index aba000d7765..8cd8080abf1 100644 --- a/src/Simulation/Core/Operations/Operation.cs +++ b/src/Simulation/Core/Operations/Operation.cs @@ -16,7 +16,7 @@ public partial interface ICallable : ICallable { O Apply(I args); - ICallable Partial

(Func mapper); + ICallable Partial

(Func mapper); } ///

@@ -34,7 +34,7 @@ public interface IOperationWrapper /// /// Type of input parameters. /// Type of return values. - [DebuggerTypeProxy(typeof(Operation<,>.DebuggerProxy))] + [DebuggerTypeProxy(typeof(Operation<,>.DebuggerProxy))] public abstract class Operation : AbstractCallable, ICallable { private Lazy> _adjoint; @@ -56,7 +56,7 @@ public Operation(IOperationFactory m) : base(m) public virtual IApplyData __dataIn(I data) => new QTuple(data); - + public virtual IApplyData __dataOut(O data) => new QTuple(data); [DebuggerBrowsable(DebuggerBrowsableState.Never)] @@ -78,13 +78,18 @@ public Operation(IOperationFactory m) : base(m) public ControlledOperation Controlled => _controlled.Value; /// - public override RuntimeMetadata? GetRuntimeMetadata(IApplyData args) => - new RuntimeMetadata() + public override RuntimeMetadata? GetRuntimeMetadata(IApplyData args) + { + var targets = args.GetQubits() ?? new List(); + // Remove duplicate qubits + targets = new HashSet(targets).ToList(); + return new RuntimeMetadata() { Label = ((ICallable)this).Name, FormattedNonQubitArgs = args.GetNonQubitArgumentsAsString() ?? "", - Targets = args.GetQubits() ?? new List(), + Targets = targets, }; + } public O Apply(I a) { @@ -95,7 +100,7 @@ public O Apply(I a) this.Factory?.StartOperation(this, __dataIn(a)); __result__ = this.Body(a); } - catch( Exception e) + catch (Exception e) { this.Factory?.Fail(System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(e)); throw; @@ -105,7 +110,7 @@ public O Apply(I a) this.Factory?.EndOperation(this, __dataOut(__result__)); } - return __result__; + return __result__; } public T Partial(object partialInfo) @@ -212,7 +217,7 @@ internal class DebuggerProxy { private Operation op; - public DebuggerProxy(Operation op) + public DebuggerProxy(Operation op) { this.op = op; } diff --git a/src/Simulation/Simulators.Tests/Circuits/RuntimeMetadataTest.qs b/src/Simulation/Simulators.Tests/Circuits/RuntimeMetadataTest.qs index b75e70fb7d5..4e721723832 100644 --- a/src/Simulation/Simulators.Tests/Circuits/RuntimeMetadataTest.qs +++ b/src/Simulation/Simulators.Tests/Circuits/RuntimeMetadataTest.qs @@ -26,5 +26,9 @@ namespace Microsoft.Quantum.Simulation.Simulators.Tests.Circuits { HOp(q); } } + + operation TwoQubitOp (q1 : Qubit, q2 : Qubit) : Unit { + // ... + } } diff --git a/src/Simulation/Simulators.Tests/RuntimeMetadataTests.cs b/src/Simulation/Simulators.Tests/RuntimeMetadataTests.cs index a31849fe169..e9f6e69e626 100644 --- a/src/Simulation/Simulators.Tests/RuntimeMetadataTests.cs +++ b/src/Simulation/Simulators.Tests/RuntimeMetadataTests.cs @@ -409,7 +409,10 @@ public void MResetZ() Assert.Equal(op.GetRuntimeMetadata(args), expected); } + } + public class CustomCircuitTests + { [Fact] public void EmptyOperation() { @@ -475,6 +478,28 @@ public void NestedOperation() Assert.Equal(op.GetRuntimeMetadata(args), expected); } + + [Fact] + public void DuplicateQubitArgs() + { + var q = new FreeQubit(0); + var op = new QuantumSimulator().Get(); + var args = op.__dataIn((q, q)); + var expected = new RuntimeMetadata() + { + Label = "TwoQubitOp", + FormattedNonQubitArgs = "", + IsAdjoint = false, + IsControlled = false, + IsMeasurement = false, + IsComposite = false, + Children = null, + Controls = new List() { }, + Targets = new List() { q }, + }; + + Assert.Equal(op.GetRuntimeMetadata(args), expected); + } } public class UDTTests From 397c3b20fc632e40b12d1012d9db54e992dac2b3 Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Thu, 6 Aug 2020 13:17:11 -0400 Subject: [PATCH 2/9] Use Distinct --- src/Simulation/Core/Operations/Operation.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/Simulation/Core/Operations/Operation.cs b/src/Simulation/Core/Operations/Operation.cs index 8cd8080abf1..d9648cc3c8e 100644 --- a/src/Simulation/Core/Operations/Operation.cs +++ b/src/Simulation/Core/Operations/Operation.cs @@ -78,18 +78,13 @@ public Operation(IOperationFactory m) : base(m) public ControlledOperation Controlled => _controlled.Value; /// - public override RuntimeMetadata? GetRuntimeMetadata(IApplyData args) - { - var targets = args.GetQubits() ?? new List(); - // Remove duplicate qubits - targets = new HashSet(targets).ToList(); - return new RuntimeMetadata() + public override RuntimeMetadata? GetRuntimeMetadata(IApplyData args) => + new RuntimeMetadata() { Label = ((ICallable)this).Name, FormattedNonQubitArgs = args.GetNonQubitArgumentsAsString() ?? "", - Targets = targets, + Targets = args.GetQubits().Distinct() ?? new List(), }; - } public O Apply(I a) { From 4fd98c9002756939886cccc41ebdc0e91b1968a7 Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Thu, 6 Aug 2020 13:35:41 -0400 Subject: [PATCH 3/9] Add null check --- src/Simulation/Core/Operations/Operation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Simulation/Core/Operations/Operation.cs b/src/Simulation/Core/Operations/Operation.cs index d9648cc3c8e..6a20d69af6c 100644 --- a/src/Simulation/Core/Operations/Operation.cs +++ b/src/Simulation/Core/Operations/Operation.cs @@ -83,7 +83,7 @@ public Operation(IOperationFactory m) : base(m) { Label = ((ICallable)this).Name, FormattedNonQubitArgs = args.GetNonQubitArgumentsAsString() ?? "", - Targets = args.GetQubits().Distinct() ?? new List(), + Targets = args.GetQubits()?.Distinct() ?? new List(), }; public O Apply(I a) From 3802bd189b3539a7e6b444af8f9192af0e9a47da Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Tue, 4 Aug 2020 15:27:53 -0400 Subject: [PATCH 4/9] First try at implementing classical controlled ops in runtimemetadata --- src/Simulation/Core/Extensions.cs | 20 ++++-- src/Simulation/Core/Types.cs | 18 ++++++ src/Simulation/QsharpCore/ClassicalControl.cs | 32 ++++++++++ .../Simulators.Tests/RuntimeMetadataTests.cs | 61 +++++++++++++------ .../Simulators/QuantumSimulator/M.cs | 2 +- .../Simulators/QuantumSimulator/Measure.cs | 2 +- 6 files changed, 109 insertions(+), 26 deletions(-) create mode 100644 src/Simulation/QsharpCore/ClassicalControl.cs diff --git a/src/Simulation/Core/Extensions.cs b/src/Simulation/Core/Extensions.cs index ab524f68f8e..5e2c79688ac 100644 --- a/src/Simulation/Core/Extensions.cs +++ b/src/Simulation/Core/Extensions.cs @@ -17,20 +17,28 @@ public static partial class Extensions public static IEnumerable WhereNotNull(this IEnumerable source) where T: class => source.Where(q => q != null).Select(q => q!); - public static Result ToResult(this bool b) + public static Result ToResult(this bool b, uint[]? sourceIds = null) { return b - ? Result.One - : Result.Zero; + ? (sourceIds != null) + ? new ResultMeasured(ResultValue.One, sourceIds) + : Result.One + : (sourceIds != null) + ? new ResultMeasured(ResultValue.Zero, sourceIds) + : Result.Zero; } - public static Result ToResult(this uint b) + public static Result ToResult(this uint b, uint[]? sourceIds = null) { Debug.Assert(b == 0 || b == 1, $"Unexpected result value: {b}"); return b == 0 - ? Result.Zero - : Result.One; + ? (sourceIds != null) + ? new ResultMeasured(ResultValue.Zero, sourceIds) + : Result.Zero + : (sourceIds != null) + ? new ResultMeasured(ResultValue.One, sourceIds) + : Result.One; } public static double Pow(this double x, double y) diff --git a/src/Simulation/Core/Types.cs b/src/Simulation/Core/Types.cs index 4fac9c5e969..8b3f581a37c 100644 --- a/src/Simulation/Core/Types.cs +++ b/src/Simulation/Core/Types.cs @@ -49,6 +49,24 @@ public override ResultValue GetValue() } } + public class ResultMeasured : Result + { + private ResultValue Value; + + public uint[] SourceIds { get; } + + public ResultMeasured(ResultValue value, uint[] sourceIds) + { + Value = value; + SourceIds = sourceIds; + } + + public override ResultValue GetValue() + { + return Value; + } + } + /// /// Represents the Result of a Measurement. Corresponds to Q# type Result. /// diff --git a/src/Simulation/QsharpCore/ClassicalControl.cs b/src/Simulation/QsharpCore/ClassicalControl.cs new file mode 100644 index 00000000000..783e42df9d8 --- /dev/null +++ b/src/Simulation/QsharpCore/ClassicalControl.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#nullable enable + +using System.Diagnostics; +using Microsoft.Quantum.Simulation.Core; + +namespace Microsoft.Quantum.Simulation.QuantumProcessor.Extensions +{ + public partial class ApplyIfElseR<__T__, __U__> + { + /// + public override RuntimeMetadata? GetRuntimeMetadata(IApplyData args) + { + Debug.Assert(args.Value is QTuple<(Result,(ICallable,__T__),(ICallable,__U__))>, + $"Failed to retrieve runtime metadata for {this.ToString()}."); + + if (args.Value is QTuple<(Result,(ICallable,__T__),(ICallable,__U__))> ifArgs) + { + var (result, (onZeroOp, zeroArg), (onOneOp, oneArg)) = ifArgs.Data; + return new RuntimeMetadata() + { + Label = ((ICallable)this).Name, + IsComposite = true, + }; + } + + return null; + } + } +} diff --git a/src/Simulation/Simulators.Tests/RuntimeMetadataTests.cs b/src/Simulation/Simulators.Tests/RuntimeMetadataTests.cs index e9f6e69e626..1d9dd24bec9 100644 --- a/src/Simulation/Simulators.Tests/RuntimeMetadataTests.cs +++ b/src/Simulation/Simulators.Tests/RuntimeMetadataTests.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Quantum.Simulation.Core; +using Microsoft.Quantum.Simulation.Common; using Xunit; namespace Microsoft.Quantum.Simulation.Simulators.Tests @@ -434,28 +435,52 @@ public void EmptyOperation() Assert.Equal(op.GetRuntimeMetadata(args), expected); } - - [Fact] - public void OperationAsArgument() + public class TracerApplyIfElse : Microsoft.Quantum.Simulation.QuantumProcessor.Extensions.ApplyIfElseIntrinsic { - var q = new FreeQubit(0); - var opArg = new QuantumSimulator().Get(); - var op = new QuantumSimulator().Get(); - var args = op.__dataIn((opArg, q)); - var expected = new RuntimeMetadata() + private SimulatorBase Simulator { get; } + + public TracerApplyIfElse(SimulatorBase m) : base(m) { - Label = "WrapperOp", - FormattedNonQubitArgs = "(HOp)", - IsAdjoint = false, - IsControlled = false, - IsMeasurement = false, - IsComposite = false, - Children = null, - Controls = new List() { }, - Targets = new List() { q }, + this.Simulator = m; + } + + public override Func<(Result, ICallable, ICallable), QVoid> Body => (q) => + { + (Result measurementResult, ICallable onZero, ICallable onOne) = q; + onZero.Apply(QVoid.Instance); + onOne.Apply(QVoid.Instance); + + return QVoid.Instance; }; + } - Assert.Equal(op.GetRuntimeMetadata(args), expected); + [Fact] + public void OperationAsArgument() + { + var qsim = new QuantumSimulator(); + qsim.Register(typeof(QuantumProcessor.Extensions.ApplyIfElseIntrinsic), typeof(TracerApplyIfElse)); + var customX = qsim.Get(); + Assert.Equal(typeof(TracerApplyIfElse), customX.GetType()); + + + // var q = new FreeQubit(0); + // var opArg = new QuantumSimulator().Get(); + // var op = new QuantumSimulator().Get(); + // var args = op.__dataIn((opArg, q)); + // var expected = new RuntimeMetadata() + // { + // Label = "WrapperOp", + // FormattedNonQubitArgs = "(HOp)", + // IsAdjoint = false, + // IsControlled = false, + // IsMeasurement = false, + // IsComposite = false, + // Children = null, + // Controls = new List() { }, + // Targets = new List() { q }, + // }; + + // Assert.Equal(op.GetRuntimeMetadata(args), expected); } [Fact] diff --git a/src/Simulation/Simulators/QuantumSimulator/M.cs b/src/Simulation/Simulators/QuantumSimulator/M.cs index 6f2766ea333..98c6621aa81 100644 --- a/src/Simulation/Simulators/QuantumSimulator/M.cs +++ b/src/Simulation/Simulators/QuantumSimulator/M.cs @@ -29,7 +29,7 @@ public QSimM(QuantumSimulator m) : base(m) Simulator.CheckQubit(q); //setting qubit as measured to allow for release q.IsMeasured = true; - return M(Simulator.Id, (uint)q.Id).ToResult(); + return M(Simulator.Id, (uint)q.Id).ToResult(new[] { (uint)q.Id }); }; } } diff --git a/src/Simulation/Simulators/QuantumSimulator/Measure.cs b/src/Simulation/Simulators/QuantumSimulator/Measure.cs index 40661c9b4d9..2e5f083bb6a 100644 --- a/src/Simulation/Simulators/QuantumSimulator/Measure.cs +++ b/src/Simulation/Simulators/QuantumSimulator/Measure.cs @@ -36,7 +36,7 @@ public QSimMeasure(QuantumSimulator m) : base(m) //setting qubit as measured to allow for release q.IsMeasured = true; } - return Measure(Simulator.Id, (uint)paulis.Length, paulis.ToArray(), qubits.GetIds()).ToResult(); + return Measure(Simulator.Id, (uint)paulis.Length, paulis.ToArray(), qubits.GetIds()).ToResult(qubits.GetIds()); }; } } From 71bd7493837c4a21910549444434bcc2bedc8e68 Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Thu, 6 Aug 2020 15:03:15 -0400 Subject: [PATCH 5/9] Implement test for ApplyIfElseR --- src/Simulation/Core/Extensions.cs | 36 ++++-- src/Simulation/Core/RuntimeMetadata.cs | 6 +- src/Simulation/Core/Types.cs | 6 +- src/Simulation/QsharpCore/ClassicalControl.cs | 120 ++++++++++++++++-- .../Simulators.Tests/RuntimeMetadataTests.cs | 91 +++++++------ .../Simulators/QuantumSimulator/M.cs | 2 +- .../Simulators/QuantumSimulator/Measure.cs | 2 +- 7 files changed, 193 insertions(+), 70 deletions(-) diff --git a/src/Simulation/Core/Extensions.cs b/src/Simulation/Core/Extensions.cs index 5e2c79688ac..cb1dea84a8a 100644 --- a/src/Simulation/Core/Extensions.cs +++ b/src/Simulation/Core/Extensions.cs @@ -17,28 +17,36 @@ public static partial class Extensions public static IEnumerable WhereNotNull(this IEnumerable source) where T: class => source.Where(q => q != null).Select(q => q!); - public static Result ToResult(this bool b, uint[]? sourceIds = null) + public static Result ToResult(this bool b) { return b - ? (sourceIds != null) - ? new ResultMeasured(ResultValue.One, sourceIds) - : Result.One - : (sourceIds != null) - ? new ResultMeasured(ResultValue.Zero, sourceIds) - : Result.Zero; + ? Result.One + : Result.Zero; } - public static Result ToResult(this uint b, uint[]? sourceIds = null) + public static Result ToResult(this bool b, Qubit qubit) + { + return b + ? new ResultMeasured(ResultValue.One, qubit) + : new ResultMeasured(ResultValue.Zero, qubit); + } + + public static Result ToResult(this uint b) + { + Debug.Assert(b == 0 || b == 1, $"Unexpected result value: {b}"); + + return b == 0 + ? Result.Zero + : Result.One; + } + + public static Result ToResult(this uint b, Qubit qubit) { Debug.Assert(b == 0 || b == 1, $"Unexpected result value: {b}"); return b == 0 - ? (sourceIds != null) - ? new ResultMeasured(ResultValue.Zero, sourceIds) - : Result.Zero - : (sourceIds != null) - ? new ResultMeasured(ResultValue.One, sourceIds) - : Result.One; + ? new ResultMeasured(ResultValue.Zero, qubit) + : new ResultMeasured(ResultValue.One, qubit); } public static double Pow(this double x, double y) diff --git a/src/Simulation/Core/RuntimeMetadata.cs b/src/Simulation/Core/RuntimeMetadata.cs index d61f63050f9..a462884c6ae 100644 --- a/src/Simulation/Core/RuntimeMetadata.cs +++ b/src/Simulation/Core/RuntimeMetadata.cs @@ -42,12 +42,16 @@ public class RuntimeMetadata /// /// True if operation is composed of multiple operations. /// - /// /// /// This is used in composite operations, such as ApplyToEach. /// public bool IsComposite { get; set; } + /// + /// True if operation is a classically-controlled conditional operation. + /// + public bool IsConditional { get; set; } + /// /// Group of operations for each classical branch (true and false). /// diff --git a/src/Simulation/Core/Types.cs b/src/Simulation/Core/Types.cs index 8b3f581a37c..840668ac775 100644 --- a/src/Simulation/Core/Types.cs +++ b/src/Simulation/Core/Types.cs @@ -53,12 +53,12 @@ public class ResultMeasured : Result { private ResultValue Value; - public uint[] SourceIds { get; } + public Qubit Qubit { get; set; } - public ResultMeasured(ResultValue value, uint[] sourceIds) + public ResultMeasured(ResultValue value, Qubit qubit) { Value = value; - SourceIds = sourceIds; + Qubit = qubit; } public override ResultValue GetValue() diff --git a/src/Simulation/QsharpCore/ClassicalControl.cs b/src/Simulation/QsharpCore/ClassicalControl.cs index 783e42df9d8..bb263d97ee1 100644 --- a/src/Simulation/QsharpCore/ClassicalControl.cs +++ b/src/Simulation/QsharpCore/ClassicalControl.cs @@ -3,6 +3,8 @@ #nullable enable +using System; +using System.Collections.Generic; using System.Diagnostics; using Microsoft.Quantum.Simulation.Core; @@ -10,20 +12,122 @@ namespace Microsoft.Quantum.Simulation.QuantumProcessor.Extensions { public partial class ApplyIfElseR<__T__, __U__> { - /// public override RuntimeMetadata? GetRuntimeMetadata(IApplyData args) { - Debug.Assert(args.Value is QTuple<(Result,(ICallable,__T__),(ICallable,__U__))>, + Debug.Assert(args.Value is ValueTuple, $"Failed to retrieve runtime metadata for {this.ToString()}."); - if (args.Value is QTuple<(Result,(ICallable,__T__),(ICallable,__U__))> ifArgs) + if (args.Value is ValueTuple ifArgs) { - var (result, (onZeroOp, zeroArg), (onOneOp, oneArg)) = ifArgs.Data; - return new RuntimeMetadata() + var (result, (onZeroOp, zeroArg), (onOneOp, oneArg)) = ifArgs; + var metadata = base.GetRuntimeMetadata(args); + + if (metadata == null) return null; + + if (result is ResultMeasured measured) + { + metadata.IsConditional = true; + metadata.Controls = new List() { measured.Qubit }; + } + else + { + metadata.IsComposite = true; + } + + return metadata; + } + + return null; + } + } + + public partial class ApplyIfElseRA<__T__, __U__> + { + public override RuntimeMetadata? GetRuntimeMetadata(IApplyData args) + { + Debug.Assert(args.Value is ValueTuple, + $"Failed to retrieve runtime metadata for {this.ToString()}."); + + if (args.Value is ValueTuple ifArgs) + { + var (result, (onZeroOp, zeroArg), (onOneOp, oneArg)) = ifArgs; + var metadata = base.GetRuntimeMetadata(args); + + if (metadata == null) return null; + + if (result is ResultMeasured measured) + { + metadata.IsConditional = true; + metadata.Controls = new List() { measured.Qubit }; + } + else { - Label = ((ICallable)this).Name, - IsComposite = true, - }; + metadata.IsComposite = true; + } + + return metadata; + } + + return null; + } + } + + public partial class ApplyIfElseRC<__T__, __U__> + { + public override RuntimeMetadata? GetRuntimeMetadata(IApplyData args) + { + Debug.Assert(args.Value is ValueTuple, + $"Failed to retrieve runtime metadata for {this.ToString()}."); + + if (args.Value is ValueTuple ifArgs) + { + var (result, (onZeroOp, zeroArg), (onOneOp, oneArg)) = ifArgs; + var metadata = base.GetRuntimeMetadata(args); + + if (metadata == null) return null; + + if (result is ResultMeasured measured) + { + metadata.IsConditional = true; + metadata.Controls = new List() { measured.Qubit }; + } + else + { + metadata.IsComposite = true; + } + + return metadata; + } + + return null; + } + } + + public partial class ApplyIfElseRCA<__T__, __U__> + { + public override RuntimeMetadata? GetRuntimeMetadata(IApplyData args) + { + Debug.Assert(args.Value is ValueTuple, + $"Failed to retrieve runtime metadata for {this.ToString()}."); + + if (args.Value is ValueTuple ifArgs) + { + var (result, (onZeroOp, zeroArg), (onOneOp, oneArg)) = ifArgs; + var metadata = base.GetRuntimeMetadata(args); + + if (metadata == null) return null; + + if (result is ResultMeasured measured) + { + metadata.IsConditional = true; + metadata.Controls = new List() { measured.Qubit }; + } + else + { + metadata.IsComposite = true; + } + + return metadata; } return null; diff --git a/src/Simulation/Simulators.Tests/RuntimeMetadataTests.cs b/src/Simulation/Simulators.Tests/RuntimeMetadataTests.cs index 1d9dd24bec9..b5c3d22c2e6 100644 --- a/src/Simulation/Simulators.Tests/RuntimeMetadataTests.cs +++ b/src/Simulation/Simulators.Tests/RuntimeMetadataTests.cs @@ -7,7 +7,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Quantum.Simulation.Core; -using Microsoft.Quantum.Simulation.Common; +using Microsoft.Quantum.Simulation.QuantumProcessor.Extensions; using Xunit; namespace Microsoft.Quantum.Simulation.Simulators.Tests @@ -435,52 +435,28 @@ public void EmptyOperation() Assert.Equal(op.GetRuntimeMetadata(args), expected); } - public class TracerApplyIfElse : Microsoft.Quantum.Simulation.QuantumProcessor.Extensions.ApplyIfElseIntrinsic - { - private SimulatorBase Simulator { get; } - - public TracerApplyIfElse(SimulatorBase m) : base(m) - { - this.Simulator = m; - } - - public override Func<(Result, ICallable, ICallable), QVoid> Body => (q) => - { - (Result measurementResult, ICallable onZero, ICallable onOne) = q; - onZero.Apply(QVoid.Instance); - onOne.Apply(QVoid.Instance); - - return QVoid.Instance; - }; - } [Fact] public void OperationAsArgument() { - var qsim = new QuantumSimulator(); - qsim.Register(typeof(QuantumProcessor.Extensions.ApplyIfElseIntrinsic), typeof(TracerApplyIfElse)); - var customX = qsim.Get(); - Assert.Equal(typeof(TracerApplyIfElse), customX.GetType()); - - - // var q = new FreeQubit(0); - // var opArg = new QuantumSimulator().Get(); - // var op = new QuantumSimulator().Get(); - // var args = op.__dataIn((opArg, q)); - // var expected = new RuntimeMetadata() - // { - // Label = "WrapperOp", - // FormattedNonQubitArgs = "(HOp)", - // IsAdjoint = false, - // IsControlled = false, - // IsMeasurement = false, - // IsComposite = false, - // Children = null, - // Controls = new List() { }, - // Targets = new List() { q }, - // }; + var q = new FreeQubit(0); + var opArg = new QuantumSimulator().Get(); + var op = new QuantumSimulator().Get(); + var args = op.__dataIn((opArg, q)); + var expected = new RuntimeMetadata() + { + Label = "WrapperOp", + FormattedNonQubitArgs = "(HOp)", + IsAdjoint = false, + IsControlled = false, + IsMeasurement = false, + IsComposite = false, + Children = null, + Controls = new List() { }, + Targets = new List() { q }, + }; - // Assert.Equal(op.GetRuntimeMetadata(args), expected); + Assert.Equal(op.GetRuntimeMetadata(args), expected); } [Fact] @@ -821,4 +797,35 @@ public void PartialUDT() Assert.Equal(op.GetRuntimeMetadata(args), expected); } } + + public class ClassicallyControlledTests + { + [Fact] + public void ApplyIfElseR() + { + var qsim = new QuantumSimulator(); + var control = new FreeQubit(0); + var target = new FreeQubit(1); + var res = new ResultMeasured(ResultValue.Zero, control); + var zeroOp = qsim.Get(); + var oneOp = qsim.Get(); + var op = qsim.Get>(); + var args = op.__dataIn((res, (zeroOp, target), (oneOp, target))); + var expected = new RuntimeMetadata() + { + Label = "ApplyIfElseR", + FormattedNonQubitArgs = "(Zero, (Z), (X))", + IsAdjoint = false, + IsControlled = false, + IsMeasurement = false, + IsComposite = false, + IsConditional = true, + Children = null, + Controls = new List() { control }, + Targets = new List() { target }, + }; + + Assert.Equal(op.GetRuntimeMetadata(args), expected); + } + } } diff --git a/src/Simulation/Simulators/QuantumSimulator/M.cs b/src/Simulation/Simulators/QuantumSimulator/M.cs index 98c6621aa81..b42e5c965c3 100644 --- a/src/Simulation/Simulators/QuantumSimulator/M.cs +++ b/src/Simulation/Simulators/QuantumSimulator/M.cs @@ -29,7 +29,7 @@ public QSimM(QuantumSimulator m) : base(m) Simulator.CheckQubit(q); //setting qubit as measured to allow for release q.IsMeasured = true; - return M(Simulator.Id, (uint)q.Id).ToResult(new[] { (uint)q.Id }); + return M(Simulator.Id, (uint)q.Id).ToResult(q); }; } } diff --git a/src/Simulation/Simulators/QuantumSimulator/Measure.cs b/src/Simulation/Simulators/QuantumSimulator/Measure.cs index 2e5f083bb6a..40661c9b4d9 100644 --- a/src/Simulation/Simulators/QuantumSimulator/Measure.cs +++ b/src/Simulation/Simulators/QuantumSimulator/Measure.cs @@ -36,7 +36,7 @@ public QSimMeasure(QuantumSimulator m) : base(m) //setting qubit as measured to allow for release q.IsMeasured = true; } - return Measure(Simulator.Id, (uint)paulis.Length, paulis.ToArray(), qubits.GetIds()).ToResult(qubits.GetIds()); + return Measure(Simulator.Id, (uint)paulis.Length, paulis.ToArray(), qubits.GetIds()).ToResult(); }; } } From 5c58a692f13b5c54d16c634ec83d40c93836bcc0 Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Thu, 6 Aug 2020 16:25:33 -0400 Subject: [PATCH 6/9] Add IsConditional to equality check --- src/Simulation/Core/RuntimeMetadata.cs | 3 +- .../Simulators.Tests/RuntimeMetadataTests.cs | 32 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/Simulation/Core/RuntimeMetadata.cs b/src/Simulation/Core/RuntimeMetadata.cs index a462884c6ae..3e74d0ad59b 100644 --- a/src/Simulation/Core/RuntimeMetadata.cs +++ b/src/Simulation/Core/RuntimeMetadata.cs @@ -87,7 +87,8 @@ public override bool Equals(object? obj) if (this.Label != other.Label || this.FormattedNonQubitArgs != other.FormattedNonQubitArgs || this.IsAdjoint != other.IsAdjoint || this.IsControlled != other.IsControlled || - this.IsMeasurement != other.IsMeasurement || this.IsComposite != other.IsComposite) + this.IsMeasurement != other.IsMeasurement || this.IsComposite != other.IsComposite || + this.IsConditional != other.IsConditional) return false; if (!ListEquals(this.Controls, other.Controls)) return false; diff --git a/src/Simulation/Simulators.Tests/RuntimeMetadataTests.cs b/src/Simulation/Simulators.Tests/RuntimeMetadataTests.cs index b5c3d22c2e6..975df2a076f 100644 --- a/src/Simulation/Simulators.Tests/RuntimeMetadataTests.cs +++ b/src/Simulation/Simulators.Tests/RuntimeMetadataTests.cs @@ -42,6 +42,7 @@ public void CheckEquality() IsControlled = false, IsMeasurement = false, IsComposite = false, + IsConditional = false, Children = null, Controls = new List() { }, Targets = new List() { }, @@ -54,6 +55,7 @@ public void CheckEquality() IsControlled = false, IsMeasurement = false, IsComposite = false, + IsConditional = false, Children = null, Controls = new List() { }, Targets = new List() { }, @@ -90,6 +92,11 @@ public void CheckEquality() Assert.NotEqual(a, b); Assert.NotEqual(a.GetHashCode(), b.GetHashCode()); b.IsComposite = false; + + b.IsConditional = true; + Assert.NotEqual(a, b); + Assert.NotEqual(a.GetHashCode(), b.GetHashCode()); + b.IsConditional = false; } [Fact] @@ -199,6 +206,7 @@ public void CNOT() IsControlled = true, IsMeasurement = false, IsComposite = false, + IsConditional = false, Children = null, Controls = new List() { control }, Targets = new List() { target }, @@ -223,6 +231,7 @@ public void CCNOT() IsControlled = true, IsMeasurement = false, IsComposite = false, + IsConditional = false, Children = null, Controls = new List() { control1, control2 }, Targets = new List() { target }, @@ -246,6 +255,7 @@ public void Swap() IsControlled = false, IsMeasurement = false, IsComposite = false, + IsConditional = false, Children = null, Controls = new List() { }, Targets = new List() { q1, q2 }, @@ -268,6 +278,7 @@ public void Ry() IsControlled = false, IsMeasurement = false, IsComposite = false, + IsConditional = false, Children = null, Controls = new List() { }, Targets = new List() { target }, @@ -290,6 +301,7 @@ public void M() IsControlled = false, IsMeasurement = true, IsComposite = false, + IsConditional = false, Children = null, Controls = new List() { }, Targets = new List() { measureQubit }, @@ -312,6 +324,7 @@ public void Reset() IsControlled = false, IsMeasurement = false, IsComposite = false, + IsConditional = false, Children = null, Controls = new List() { }, Targets = new List() { target }, @@ -359,6 +372,7 @@ public void MResetX() IsControlled = false, IsMeasurement = true, IsComposite = false, + IsConditional = false, Children = null, Controls = new List() { }, Targets = new List() { measureQubit }, @@ -381,6 +395,7 @@ public void MResetY() IsControlled = false, IsMeasurement = true, IsComposite = false, + IsConditional = false, Children = null, Controls = new List() { }, Targets = new List() { measureQubit }, @@ -403,6 +418,7 @@ public void MResetZ() IsControlled = false, IsMeasurement = true, IsComposite = false, + IsConditional = false, Children = null, Controls = new List() { }, Targets = new List() { measureQubit }, @@ -428,6 +444,7 @@ public void EmptyOperation() IsControlled = false, IsMeasurement = false, IsComposite = false, + IsConditional = false, Children = null, Controls = new List() { }, Targets = new List() { }, @@ -451,6 +468,7 @@ public void OperationAsArgument() IsControlled = false, IsMeasurement = false, IsComposite = false, + IsConditional = false, Children = null, Controls = new List() { }, Targets = new List() { q }, @@ -472,6 +490,7 @@ public void NestedOperation() IsControlled = false, IsMeasurement = false, IsComposite = false, + IsConditional = false, Children = null, Controls = new List() { }, Targets = new List() { }, @@ -494,6 +513,7 @@ public void DuplicateQubitArgs() IsControlled = false, IsMeasurement = false, IsComposite = false, + IsConditional = false, Children = null, Controls = new List() { }, Targets = new List() { q }, @@ -519,6 +539,7 @@ public void FooUDTOp() IsControlled = false, IsMeasurement = false, IsComposite = false, + IsConditional = false, Children = null, Controls = new List() { }, Targets = new List() { target }, @@ -545,6 +566,7 @@ public void ControlledH() IsControlled = true, IsMeasurement = false, IsComposite = false, + IsConditional = false, Children = null, Controls = controls, Targets = new List() { target }, @@ -568,6 +590,7 @@ public void ControlledX() IsControlled = true, IsMeasurement = false, IsComposite = false, + IsConditional = false, Children = null, Controls = controls, Targets = new List() { target }, @@ -592,6 +615,7 @@ public void ControlledCNOT() IsControlled = true, IsMeasurement = false, IsComposite = false, + IsConditional = false, Children = null, Controls = controls.Append(control), Targets = new List() { target }, @@ -618,6 +642,7 @@ public void ControlledCCNOT() IsControlled = true, IsMeasurement = false, IsComposite = false, + IsConditional = false, Children = null, Controls = new List() { control1, control2, control3 }, Targets = new List() { target }, @@ -643,6 +668,7 @@ public void AdjointH() IsControlled = false, IsMeasurement = false, IsComposite = false, + IsConditional = false, Children = null, Controls = new List() { }, Targets = new List() { target }, @@ -665,6 +691,7 @@ public void AdjointX() IsControlled = false, IsMeasurement = false, IsComposite = false, + IsConditional = false, Children = null, Controls = new List() { }, Targets = new List() { target }, @@ -687,6 +714,7 @@ public void AdjointAdjointH() IsControlled = false, IsMeasurement = false, IsComposite = false, + IsConditional = false, Children = null, Controls = new List() { }, Targets = new List() { target }, @@ -711,6 +739,7 @@ public void ControlledAdjointH() IsControlled = true, IsMeasurement = false, IsComposite = false, + IsConditional = false, Children = null, Controls = controls, Targets = new List() { target }, @@ -737,6 +766,7 @@ public void ControlledAdjointAdjointH() IsControlled = true, IsMeasurement = false, IsComposite = false, + IsConditional = false, Children = null, Controls = controls, Targets = new List() { target }, @@ -766,6 +796,7 @@ public void PartialRy() IsControlled = false, IsMeasurement = false, IsComposite = false, + IsConditional = false, Children = null, Controls = new List() { }, Targets = new List() { target }, @@ -789,6 +820,7 @@ public void PartialUDT() IsControlled = false, IsMeasurement = false, IsComposite = false, + IsConditional = false, Children = null, Controls = new List() { }, Targets = new List() { }, From 6f85645f6bf3a6126f135ecbfe8d31e86727c93a Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Fri, 7 Aug 2020 11:20:57 -0400 Subject: [PATCH 7/9] Add tests for variants --- src/Simulation/QsharpCore/ClassicalControl.cs | 12 +-- .../Simulators.Tests/RuntimeMetadataTests.cs | 84 +++++++++++++++++++ 2 files changed, 90 insertions(+), 6 deletions(-) diff --git a/src/Simulation/QsharpCore/ClassicalControl.cs b/src/Simulation/QsharpCore/ClassicalControl.cs index bb263d97ee1..6ba8394ccef 100644 --- a/src/Simulation/QsharpCore/ClassicalControl.cs +++ b/src/Simulation/QsharpCore/ClassicalControl.cs @@ -45,10 +45,10 @@ public partial class ApplyIfElseRA<__T__, __U__> { public override RuntimeMetadata? GetRuntimeMetadata(IApplyData args) { - Debug.Assert(args.Value is ValueTuple, + Debug.Assert(args.Value is ValueTuple, $"Failed to retrieve runtime metadata for {this.ToString()}."); - if (args.Value is ValueTuple ifArgs) + if (args.Value is ValueTuple ifArgs) { var (result, (onZeroOp, zeroArg), (onOneOp, oneArg)) = ifArgs; var metadata = base.GetRuntimeMetadata(args); @@ -76,10 +76,10 @@ public partial class ApplyIfElseRC<__T__, __U__> { public override RuntimeMetadata? GetRuntimeMetadata(IApplyData args) { - Debug.Assert(args.Value is ValueTuple, + Debug.Assert(args.Value is ValueTuple, $"Failed to retrieve runtime metadata for {this.ToString()}."); - if (args.Value is ValueTuple ifArgs) + if (args.Value is ValueTuple ifArgs) { var (result, (onZeroOp, zeroArg), (onOneOp, oneArg)) = ifArgs; var metadata = base.GetRuntimeMetadata(args); @@ -107,10 +107,10 @@ public partial class ApplyIfElseRCA<__T__, __U__> { public override RuntimeMetadata? GetRuntimeMetadata(IApplyData args) { - Debug.Assert(args.Value is ValueTuple, + Debug.Assert(args.Value is ValueTuple, $"Failed to retrieve runtime metadata for {this.ToString()}."); - if (args.Value is ValueTuple ifArgs) + if (args.Value is ValueTuple ifArgs) { var (result, (onZeroOp, zeroArg), (onOneOp, oneArg)) = ifArgs; var metadata = base.GetRuntimeMetadata(args); diff --git a/src/Simulation/Simulators.Tests/RuntimeMetadataTests.cs b/src/Simulation/Simulators.Tests/RuntimeMetadataTests.cs index 975df2a076f..4f6d0727833 100644 --- a/src/Simulation/Simulators.Tests/RuntimeMetadataTests.cs +++ b/src/Simulation/Simulators.Tests/RuntimeMetadataTests.cs @@ -859,5 +859,89 @@ public void ApplyIfElseR() Assert.Equal(op.GetRuntimeMetadata(args), expected); } + + [Fact] + public void ApplyIfElseRA() + { + var qsim = new QuantumSimulator(); + var control = new FreeQubit(0); + var target = new FreeQubit(1); + var res = new ResultMeasured(ResultValue.Zero, control); + var zeroOp = qsim.Get().Adjoint; + var oneOp = qsim.Get().Adjoint; + var op = qsim.Get>(); + var args = op.__dataIn((res, (zeroOp, target), (oneOp, target))); + var expected = new RuntimeMetadata() + { + Label = "ApplyIfElseRA", + FormattedNonQubitArgs = "(Zero, (Z), (X))", + IsAdjoint = false, + IsControlled = false, + IsMeasurement = false, + IsComposite = false, + IsConditional = true, + Children = null, + Controls = new List() { control }, + Targets = new List() { target }, + }; + + Assert.Equal(op.GetRuntimeMetadata(args), expected); + } + + [Fact] + public void ApplyIfElseRC() + { + var qsim = new QuantumSimulator(); + var control = new FreeQubit(0); + var target = new FreeQubit(1); + var res = new ResultMeasured(ResultValue.Zero, control); + var zeroOp = qsim.Get().Controlled; + var oneOp = qsim.Get().Controlled; + var op = qsim.Get>(); + var args = op.__dataIn((res, (zeroOp, target), (oneOp, target))); + var expected = new RuntimeMetadata() + { + Label = "ApplyIfElseRC", + FormattedNonQubitArgs = "(Zero, (Z), (X))", + IsAdjoint = false, + IsControlled = false, + IsMeasurement = false, + IsComposite = false, + IsConditional = true, + Children = null, + Controls = new List() { control }, + Targets = new List() { target }, + }; + + Assert.Equal(op.GetRuntimeMetadata(args), expected); + } + + [Fact] + public void ApplyIfElseRCA() + { + var qsim = new QuantumSimulator(); + var control = new FreeQubit(0); + var target = new FreeQubit(1); + var res = new ResultMeasured(ResultValue.Zero, control); + var zeroOp = qsim.Get().Controlled.Adjoint; + var oneOp = qsim.Get().Controlled.Adjoint; + var op = qsim.Get>(); + var args = op.__dataIn((res, (zeroOp, target), (oneOp, target))); + var expected = new RuntimeMetadata() + { + Label = "ApplyIfElseRCA", + FormattedNonQubitArgs = "(Zero, (Z), (X))", + IsAdjoint = false, + IsControlled = false, + IsMeasurement = false, + IsComposite = false, + IsConditional = true, + Children = null, + Controls = new List() { control }, + Targets = new List() { target }, + }; + + Assert.Equal(op.GetRuntimeMetadata(args), expected); + } } } From 3537e0a50ecc48b0247d19bcad9087e730825445 Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Fri, 7 Aug 2020 14:45:15 -0400 Subject: [PATCH 8/9] Fix runtimemetadata hashcode generation --- src/Simulation/Core/RuntimeMetadata.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Simulation/Core/RuntimeMetadata.cs b/src/Simulation/Core/RuntimeMetadata.cs index 3e74d0ad59b..86a8067c5c3 100644 --- a/src/Simulation/Core/RuntimeMetadata.cs +++ b/src/Simulation/Core/RuntimeMetadata.cs @@ -127,7 +127,7 @@ public override int GetHashCode() // Combine all other properties and get the resulting hashcode var otherHash = HashCode.Combine(this.Label, this.FormattedNonQubitArgs, this.IsAdjoint, this.IsControlled, - this.IsMeasurement, this.IsComposite); + this.IsMeasurement, this.IsComposite, this.IsConditional); // Combine them all together to get the final hashcode return HashCode.Combine(controlsHash, targetsHash, childrenHash, otherHash); From 1648d5508317f042c8320bff8866b661bf45c4ba Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Tue, 11 Aug 2020 11:28:56 -0400 Subject: [PATCH 9/9] Avoid code duplication across ApplyIfElse variants --- src/Simulation/QsharpCore/ClassicalControl.cs | 127 ++++++------------ 1 file changed, 43 insertions(+), 84 deletions(-) diff --git a/src/Simulation/QsharpCore/ClassicalControl.cs b/src/Simulation/QsharpCore/ClassicalControl.cs index 6ba8394ccef..4645786fa92 100644 --- a/src/Simulation/QsharpCore/ClassicalControl.cs +++ b/src/Simulation/QsharpCore/ClassicalControl.cs @@ -10,48 +10,29 @@ namespace Microsoft.Quantum.Simulation.QuantumProcessor.Extensions { - public partial class ApplyIfElseR<__T__, __U__> + /// + /// Provides interface to access base `GetRuntimeMetadata` method. + /// + public interface IApplyIfElse : ICallable { - public override RuntimeMetadata? GetRuntimeMetadata(IApplyData args) - { - Debug.Assert(args.Value is ValueTuple, - $"Failed to retrieve runtime metadata for {this.ToString()}."); - - if (args.Value is ValueTuple ifArgs) - { - var (result, (onZeroOp, zeroArg), (onOneOp, oneArg)) = ifArgs; - var metadata = base.GetRuntimeMetadata(args); - - if (metadata == null) return null; - - if (result is ResultMeasured measured) - { - metadata.IsConditional = true; - metadata.Controls = new List() { measured.Qubit }; - } - else - { - metadata.IsComposite = true; - } - - return metadata; - } - - return null; - } + RuntimeMetadata? GetBaseRuntimeMetadata(IApplyData args); } - public partial class ApplyIfElseRA<__T__, __U__> + /// + /// Provides static `GetRuntimeMetadata` method for ApplyIfElseR and its variants + /// to avoid code duplication. + /// + public class ApplyIfElseUtils<__C__, __T__, __U__> { - public override RuntimeMetadata? GetRuntimeMetadata(IApplyData args) + public static RuntimeMetadata? GetRuntimeMetadata(IApplyIfElse op, IApplyData args) { - Debug.Assert(args.Value is ValueTuple, - $"Failed to retrieve runtime metadata for {this.ToString()}."); + Debug.Assert(args.Value is ValueTuple, + $"Failed to retrieve runtime metadata for {op.ToString()}."); - if (args.Value is ValueTuple ifArgs) + if (args.Value is ValueTuple ifArgs) { var (result, (onZeroOp, zeroArg), (onOneOp, oneArg)) = ifArgs; - var metadata = base.GetRuntimeMetadata(args); + var metadata = op.GetBaseRuntimeMetadata(args); if (metadata == null) return null; @@ -72,65 +53,43 @@ public partial class ApplyIfElseRA<__T__, __U__> } } - public partial class ApplyIfElseRC<__T__, __U__> + public partial class ApplyIfElseR<__T__, __U__> : IApplyIfElse { - public override RuntimeMetadata? GetRuntimeMetadata(IApplyData args) - { - Debug.Assert(args.Value is ValueTuple, - $"Failed to retrieve runtime metadata for {this.ToString()}."); - - if (args.Value is ValueTuple ifArgs) - { - var (result, (onZeroOp, zeroArg), (onOneOp, oneArg)) = ifArgs; - var metadata = base.GetRuntimeMetadata(args); - - if (metadata == null) return null; - - if (result is ResultMeasured measured) - { - metadata.IsConditional = true; - metadata.Controls = new List() { measured.Qubit }; - } - else - { - metadata.IsComposite = true; - } - - return metadata; - } + public RuntimeMetadata? GetBaseRuntimeMetadata(IApplyData args) => + base.GetRuntimeMetadata(args); - return null; - } + /// + public override RuntimeMetadata? GetRuntimeMetadata(IApplyData args) => + ApplyIfElseUtils.GetRuntimeMetadata(this, args); } - public partial class ApplyIfElseRCA<__T__, __U__> + public partial class ApplyIfElseRA<__T__, __U__> : IApplyIfElse { - public override RuntimeMetadata? GetRuntimeMetadata(IApplyData args) - { - Debug.Assert(args.Value is ValueTuple, - $"Failed to retrieve runtime metadata for {this.ToString()}."); + public RuntimeMetadata? GetBaseRuntimeMetadata(IApplyData args) => + base.GetRuntimeMetadata(args); - if (args.Value is ValueTuple ifArgs) - { - var (result, (onZeroOp, zeroArg), (onOneOp, oneArg)) = ifArgs; - var metadata = base.GetRuntimeMetadata(args); + /// + public override RuntimeMetadata? GetRuntimeMetadata(IApplyData args) => + ApplyIfElseUtils.GetRuntimeMetadata(this, args); + } - if (metadata == null) return null; + public partial class ApplyIfElseRC<__T__, __U__> : IApplyIfElse + { + public RuntimeMetadata? GetBaseRuntimeMetadata(IApplyData args) => + base.GetRuntimeMetadata(args); - if (result is ResultMeasured measured) - { - metadata.IsConditional = true; - metadata.Controls = new List() { measured.Qubit }; - } - else - { - metadata.IsComposite = true; - } + /// + public override RuntimeMetadata? GetRuntimeMetadata(IApplyData args) => + ApplyIfElseUtils.GetRuntimeMetadata(this, args); + } - return metadata; - } + public partial class ApplyIfElseRCA<__T__, __U__> : IApplyIfElse + { + public RuntimeMetadata? GetBaseRuntimeMetadata(IApplyData args) => + base.GetRuntimeMetadata(args); - return null; - } + /// + public override RuntimeMetadata? GetRuntimeMetadata(IApplyData args) => + ApplyIfElseUtils.GetRuntimeMetadata(this, args); } }