diff --git a/src/Simulation/Core/Default.cs b/src/Simulation/Core/Default.cs new file mode 100644 index 00000000000..315c6c0587d --- /dev/null +++ b/src/Simulation/Core/Default.cs @@ -0,0 +1,92 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace Microsoft.Quantum.Simulation.Core +{ + /// + /// Creates default values of Q# types. + /// + public static class Default + { + /// + /// A dictionary from basic types to their default values. + /// + private static readonly IReadOnlyDictionary BasicValues = new Dictionary + { + [typeof(QRange)] = QRange.Empty, + [typeof(QVoid)] = QVoid.Instance, + [typeof(Result)] = Result.Zero, + [typeof(string)] = "" + }; + + /// + /// A list of all generic tuple types. + /// + private static readonly IReadOnlyList Tuples = new List + { + typeof(ValueTuple<>), + typeof(ValueTuple<,>), + typeof(ValueTuple<,,>), + typeof(ValueTuple<,,,>), + typeof(ValueTuple<,,,,>), + typeof(ValueTuple<,,,,,>), + typeof(ValueTuple<,,,,,,>), + typeof(ValueTuple<,,,,,,,>) + }; + + /// + /// Returns the default value of the Q# type. May return null when null is the default value of the type, or if + /// the type is not a valid Q# type. + /// + [return: MaybeNull] + public static T OfType() => OfType(typeof(T)) is T value ? value : default; + + /// + /// Returns the default value of the Q# type. May return null when null is the default value of the type, or if + /// the type is not a valid Q# type. + /// + private static object? OfType(Type type) => OfAnyType(type).FirstOrDefault(value => !(value is null)); + + /// + /// Enumerates the default values of different kinds of types. Yields null if the given type is not the right + /// kind, and yields a non-null value if a default value is found. + /// + private static IEnumerable OfAnyType(Type type) + { + yield return BasicValues.GetValueOrDefault(type); + yield return OfArrayType(type); + yield return OfTupleType(type); + yield return OfUserDefinedType(type); + } + + /// + /// If the given type is a Q# array type, returns the default array of that type, or null otherwise. + /// + private static object? OfArrayType(Type type) => + type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IQArray<>) + ? Activator.CreateInstance(typeof(QArray<>).MakeGenericType(type.GenericTypeArguments)) + : null; + + /// + /// If the given type is a Q# tuple type, returns the default tuple of that type, or null otherwise. + /// + private static object? OfTupleType(Type type) => + type.IsGenericType && Tuples.Contains(type.GetGenericTypeDefinition()) + ? Activator.CreateInstance(type, type.GenericTypeArguments.Select(OfType).ToArray()) + : null; + + /// + /// If the given type is a Q# user-defined type, returns the default value of that type, or null otherwise. + /// + private static object? OfUserDefinedType(Type type) => + !(type.BaseType is null) + && type.BaseType.IsGenericType + && type.BaseType.GetGenericTypeDefinition() == typeof(UDTBase<>) + ? Activator.CreateInstance(type, type.BaseType.GenericTypeArguments.Select(OfType).ToArray()) + : null; + } +} diff --git a/src/Simulation/Core/QArray.cs b/src/Simulation/Core/QArray.cs index de9db576c88..a6e7ac4aac3 100644 --- a/src/Simulation/Core/QArray.cs +++ b/src/Simulation/Core/QArray.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Reflection; using Newtonsoft.Json; namespace Microsoft.Quantum.Simulation.Core @@ -98,17 +97,10 @@ public QArrayInner(params T[] collection) } /// - /// Creates an array of size given by capacity and default-initializes - /// array elements. Uses C# keyword default to initialize array elements. + /// Creates an array of size given by capacity and default-initializes array elements. Uses the default Q# + /// value to initialize array elements. /// - public QArrayInner(long capacity) - { - storage = new List((int)capacity); - for (var i = 0L; i < capacity; ++i) - { - storage.Add(CreateDefault()); - } - } + public QArrayInner(long capacity) => Extend(capacity); public T GetElement(long index) => storage == null @@ -143,20 +135,19 @@ public void UnsafeSetElement(long index, T value) public void Extend(long newLength) { - if (storage == null) + var newLengthInt = Convert.ToInt32(newLength); + if (storage is null) { - storage = new List(); + storage = new List(newLengthInt); } - long oldLength = storage.Count; - for (int i = 0; i < (newLength - oldLength); i++) + else if (storage.Capacity < newLengthInt) { - T obj = CreateDefault(); - storage.Add(obj); + storage.Capacity = newLengthInt; } + storage.AddRange(Enumerable.Repeat(Default.OfType(), newLengthInt - storage.Count)); } } - private QArrayInner storage; private long start = 0; private long step = 1; @@ -174,25 +165,6 @@ private void CopyAndCompress() step = 1; } - // Returns the default value of an object of this type of array. Normally null or 0, but for things like - // ValueTuples, it returns an empty instance of that value tuple. - private static T CreateDefault() - { - if (typeof(T).IsValueType || typeof(T).IsAbstract || typeof(T) == typeof(String) || typeof(T) == typeof(QVoid)) - { - return default(T); - } - else - { - // First look for an empty constructor - ConstructorInfo defaultConstructor = typeof(T).GetConstructor(Type.EmptyTypes); - return defaultConstructor != null - ? (T)(defaultConstructor.Invoke(new object[] { })) - : Activator.CreateInstance(); - } - } - - /// /// Create an array of length 0. /// @@ -508,7 +480,7 @@ public QArrayEnumerator(QArray qArray) currentIndex = -1; } - public T Current => currentIndex >= 0 ? array[currentIndex] : CreateDefault(); + public T Current => currentIndex >= 0 ? array[currentIndex] : Default.OfType(); object IEnumerator.Current => this.Current; diff --git a/src/Simulation/Core/QRange.cs b/src/Simulation/Core/QRange.cs index ff8133ccfac..14f10f86607 100644 --- a/src/Simulation/Core/QRange.cs +++ b/src/Simulation/Core/QRange.cs @@ -14,7 +14,7 @@ namespace Microsoft.Quantum.Simulation.Core /// public class QRange : IEnumerable { - public QRange() : this(0, 1, 0) + public QRange() : this(1, 1, 0) { } @@ -61,8 +61,7 @@ public QRange(long start, long end) : this(start, 1, end) /// /// Returns an empty range. /// - public static QRange Empty => - new QRange(0L, -1L); + public static QRange Empty => new QRange(); /// /// Returns true if the range is empty. diff --git a/src/Simulation/CsharpGeneration.Tests/SimulationCodeTests.fs b/src/Simulation/CsharpGeneration.Tests/SimulationCodeTests.fs index 4d050ca2686..8b28e6cc3d5 100644 --- a/src/Simulation/CsharpGeneration.Tests/SimulationCodeTests.fs +++ b/src/Simulation/CsharpGeneration.Tests/SimulationCodeTests.fs @@ -2924,7 +2924,7 @@ internal partial class EmptyInternalOperation : Operation, ICallab """ public class U : UDTBase, IApplyData { - public U() : base(default(IUnitary)) + public U() : base(global::Microsoft.Quantum.Simulation.Core.Default.OfType()) { } @@ -2950,7 +2950,7 @@ internal partial class EmptyInternalOperation : Operation, ICallab """ public class AA : UDTBase, IApplyData { - public AA() : base(default(A)) + public AA() : base(global::Microsoft.Quantum.Simulation.Core.Default.OfType()) { } @@ -2976,7 +2976,7 @@ internal partial class EmptyInternalOperation : Operation, ICallab """ public class Q : UDTBase, IApplyData { - public Q() : base(default(Qubit)) + public Q() : base(global::Microsoft.Quantum.Simulation.Core.Default.OfType()) { } @@ -3002,7 +3002,7 @@ internal partial class EmptyInternalOperation : Operation, ICallab """ public class QQ : UDTBase, IApplyData { - public QQ() : base(default(Q)) + public QQ() : base(global::Microsoft.Quantum.Simulation.Core.Default.OfType()) { } @@ -3028,7 +3028,7 @@ internal partial class EmptyInternalOperation : Operation, ICallab """ public class Qubits : UDTBase>, IApplyData { - public Qubits() : base(new QArray()) + public Qubits() : base(global::Microsoft.Quantum.Simulation.Core.Default.OfType>()) { } @@ -3054,7 +3054,7 @@ internal partial class EmptyInternalOperation : Operation, ICallab """ public class udt_args1 : UDTBase<(Int64,IQArray)>, IApplyData { - public udt_args1() : base(default((Int64,IQArray))) + public udt_args1() : base(global::Microsoft.Quantum.Simulation.Core.Default.OfType<(Int64,IQArray)>()) { } @@ -3084,7 +3084,7 @@ internal partial class EmptyInternalOperation : Operation, ICallab """ public class udt_Real : UDTBase, IApplyData { - public udt_Real() : base(default(Double)) + public udt_Real() : base(global::Microsoft.Quantum.Simulation.Core.Default.OfType()) { } @@ -3104,7 +3104,7 @@ internal partial class EmptyInternalOperation : Operation, ICallab """ public class udt_Complex : UDTBase<(udt_Real,udt_Real)>, IApplyData { - public udt_Complex() : base(default((udt_Real,udt_Real))) + public udt_Complex() : base(global::Microsoft.Quantum.Simulation.Core.Default.OfType<(udt_Real,udt_Real)>()) { } @@ -3127,7 +3127,7 @@ internal partial class EmptyInternalOperation : Operation, ICallab """ public class udt_TwoDimArray : UDTBase>>, IApplyData { - public udt_TwoDimArray() : base(new QArray>()) + public udt_TwoDimArray() : base(global::Microsoft.Quantum.Simulation.Core.Default.OfType>>()) { } @@ -3149,7 +3149,7 @@ internal partial class EmptyInternalOperation : Operation, ICallab """ internal class InternalType : UDTBase, IApplyData { - public InternalType() : base(default(QVoid)) + public InternalType() : base(global::Microsoft.Quantum.Simulation.Core.Default.OfType()) { } @@ -3171,7 +3171,7 @@ internal class InternalType : UDTBase, IApplyData """ public class NamedTuple : UDTBase<((Int64,Double),Int64)>, IApplyData { - public NamedTuple() : base(default(((Int64,Double),Int64))) + public NamedTuple() : base(global::Microsoft.Quantum.Simulation.Core.Default.OfType<((Int64,Double),Int64)>()) { } @@ -3231,7 +3231,7 @@ namespace Microsoft.Quantum { public class Pair : UDTBase<(Int64,Int64)>, IApplyData { - public Pair() : base(default((Int64,Int64))) + public Pair() : base(global::Microsoft.Quantum.Simulation.Core.Default.OfType<(Int64,Int64)>()) { } @@ -3251,7 +3251,7 @@ namespace Microsoft.Quantum public class Unused : UDTBase<(Int64,Int64)>, IApplyData { - public Unused() : base(default((Int64,Int64))) + public Unused() : base(global::Microsoft.Quantum.Simulation.Core.Default.OfType<(Int64,Int64)>()) { } @@ -3337,7 +3337,7 @@ namespace Microsoft.Quantum { public class Pair : UDTBase<(Int64,Int64)>, IApplyData { - public Pair() : base(default((Int64,Int64))) + public Pair() : base(global::Microsoft.Quantum.Simulation.Core.Default.OfType<(Int64,Int64)>()) { } @@ -3361,7 +3361,7 @@ namespace Microsoft.Quantum public class NestedPair : UDTBase<(Double,((Boolean,String),Int64))>, IApplyData { - public NestedPair() : base(default((Double,((Boolean,String),Int64)))) + public NestedPair() : base(global::Microsoft.Quantum.Simulation.Core.Default.OfType<(Double,((Boolean,String),Int64))>()) { } @@ -3616,7 +3616,7 @@ namespace Microsoft.Quantum.Core { public class Attribute : UDTBase, IApplyData { - public Attribute() : base(default(QVoid)) + public Attribute() : base(global::Microsoft.Quantum.Simulation.Core.Default.OfType()) { } @@ -3636,7 +3636,7 @@ namespace Microsoft.Quantum.Diagnostics { public class Test : UDTBase, IApplyData { - public Test() : base(default(String)) + public Test() : base(global::Microsoft.Quantum.Simulation.Core.Default.OfType()) { } diff --git a/src/Simulation/CsharpGeneration/SimulationCode.fs b/src/Simulation/CsharpGeneration/SimulationCode.fs index ba57ba79c55..b7db959d2a4 100644 --- a/src/Simulation/CsharpGeneration/SimulationCode.fs +++ b/src/Simulation/CsharpGeneration/SimulationCode.fs @@ -1461,15 +1461,12 @@ module SimulationCode = let context = globalContext.setUdt udt let name = userDefinedName None udt.FullName.Name.Value let qsharpType = udt.Type - let buildEmtpyConstructor = - let baseTupleType = - match qsharpType.Resolution with - | ArrayType b -> roslynTypeName context b |> sprintf "QArray<%s>" - | _ -> (roslynTypeName context qsharpType) - let defaultValue = match qsharpType.Resolution with | ArrayType _ -> [ sprintf "new %s()" baseTupleType] | _ -> [ sprintf "default(%s)" baseTupleType ] - let args = [] - ``constructor`` name ``(`` args ``)`` - ``:`` defaultValue + let buildEmptyConstructor = + let defaultValue = + roslynTypeName context qsharpType + |> sprintf "global::Microsoft.Quantum.Simulation.Core.Default.OfType<%s>()" + ``constructor`` name ``(`` [] ``)`` + ``:`` [ defaultValue ] [ ``public`` ] ``{`` [] @@ -1540,7 +1537,7 @@ module SimulationCode = let baseClass = ``simpleBase`` baseClassName let modifiers = [ classAccessModifier udt.Modifiers.Access ] let interfaces = [ ``simpleBase`` "IApplyData" ] - let constructors = [ buildEmtpyConstructor; buildBaseTupleConstructor ] + let constructors = [ buildEmptyConstructor; buildBaseTupleConstructor ] let qubitsField = buildQubitsField context qsharpType let itemFields = buildNamedItemFields @ buildItemFields let allFields = itemFields @ qubitsField diff --git a/src/Simulation/Simulators.Tests/Circuits/Default.qs b/src/Simulation/Simulators.Tests/Circuits/Default.qs new file mode 100644 index 00000000000..55e1853dfc7 --- /dev/null +++ b/src/Simulation/Simulators.Tests/Circuits/Default.qs @@ -0,0 +1,91 @@ +namespace Microsoft.Quantum.Simulation.Simulators.Tests.Circuits.Default { + open Microsoft.Quantum.Diagnostics; + open Microsoft.Quantum.Simulation.Simulators.Tests.Circuits; + + @Test("QuantumSimulator") + function DefaultUnit() : Unit { + AssertEqual((), Default()); + } + + @Test("QuantumSimulator") + function DefaultInt() : Unit { + AssertEqual(0, Default()); + } + + @Test("QuantumSimulator") + function DefaultBigInt() : Unit { + AssertEqual(0L, Default()); + } + + @Test("QuantumSimulator") + function DefaultDouble() : Unit { + AssertEqual(0.0, Default()); + } + + @Test("QuantumSimulator") + function DefaultBool() : Unit { + AssertEqual(false, Default()); + } + + @Test("QuantumSimulator") + function DefaultString() : Unit { + AssertEqual("", Default()); + } + + @Test("QuantumSimulator") + function DefaultQubit() : Unit { + // Creating a default qubit (without using it) should succeed. + let _ = Default(); + } + + @Test("QuantumSimulator") + function DefaultPauli() : Unit { + AssertEqual(PauliI, Default()); + } + + @Test("QuantumSimulator") + function DefaultResult() : Unit { + AssertEqual(Zero, Default()); + } + + @Test("QuantumSimulator") + function DefaultRange() : Unit { + let range = Default(); + AssertEqual(1, RangeStart(range)); + AssertEqual(1, RangeStep(range)); + AssertEqual(0, RangeEnd(range)); + } + + @Test("QuantumSimulator") + function DefaultCallable() : Unit { + // Creating a default callable (without calling it) should succeed. + let _ = Default<(Unit -> Unit)>(); + } + + @Test("QuantumSimulator") + function DefaultArray() : Unit { + AssertEqual(new Unit[0], Default()); + } + + @Test("QuantumSimulator") + function DefaultTuple() : Unit { + AssertEqual((false, 0), Default<(Bool, Int)>()); + AssertEqual((0, Zero, ""), Default<(Int, Result, String)>()); + AssertEqual(("", "", "", ""), Default<(String, String, String, String)>()); + AssertEqual(("", "", "", "", ""), Default<(String, String, String, String, String)>()); + AssertEqual(("", "", "", "", "", ""), Default<(String, String, String, String, String, String)>()); + AssertEqual(("", "", "", "", "", "", ""), Default<(String, String, String, String, String, String, String)>()); + AssertEqual(("", "", "", "", "", "", "", ""), Default<(String, String, String, String, String, String, String, String)>()); + AssertEqual(("", "", "", "", "", "", "", "", ""), Default<(String, String, String, String, String, String, String, String, String)>()); + } + + newtype BoolInt = (Bool, Int); + newtype IntResultString = (Int, Result, String); + + @Test("QuantumSimulator") + function DefaultUserDefinedType() : Unit { + AssertEqual(BoolInt(false, 0), Default()); + AssertEqual(IntResultString(0, Zero, ""), Default()); + AssertEqual((BoolInt(false, 0), IntResultString(0, Zero, "")), Default<(BoolInt, IntResultString)>()); + } +} diff --git a/src/Simulation/Simulators.Tests/CoreTests.cs b/src/Simulation/Simulators.Tests/CoreTests.cs index 88917ff4c24..b087a623f40 100644 --- a/src/Simulation/Simulators.Tests/CoreTests.cs +++ b/src/Simulation/Simulators.Tests/CoreTests.cs @@ -6,6 +6,7 @@ using System.IO; using System.Reflection; using System.Text; +using Microsoft.Quantum.Core; using Microsoft.Quantum.QsCompiler; using Microsoft.Quantum.Simulation.Common; using Microsoft.Quantum.Simulation.Core; @@ -292,6 +293,14 @@ public void BigInts() }); } + [Fact] + public void DefaultQubitIsNull() => OperationsTestHelper.RunWithMultipleSimulators(async simulator => + Assert.Null(await Default.Run(simulator))); + + [Fact] + public void DefaultCallableIsNull() => OperationsTestHelper.RunWithMultipleSimulators(async simulator => + Assert.Null(await Default.Run(simulator))); + [Fact] public void CatchFail() { diff --git a/src/Simulation/Simulators.Tests/QTupleTests.cs b/src/Simulation/Simulators.Tests/QTupleTests.cs index 0f5501b8a3b..ba8c49c1400 100644 --- a/src/Simulation/Simulators.Tests/QTupleTests.cs +++ b/src/Simulation/Simulators.Tests/QTupleTests.cs @@ -37,7 +37,7 @@ public void TupleEmptyConstructor() } { var actual = new TupleC(); - Assert.Equal(default((Qubit, TupleB)), ((IApplyData)actual).Value); + Assert.Equal((null as Qubit, new TupleB()), ((IApplyData)actual).Value); NullOrEmptyQubits(actual); } { @@ -47,7 +47,10 @@ public void TupleEmptyConstructor() } { var actual = new TupleE(); - Assert.Equal(default((Int64, IQArray)), ((IApplyData)actual).Value); + var value = ((IApplyData)actual).Value as (long, IQArray)?; + Assert.True(value.HasValue); + Assert.Equal(0, value.Value.Item1); + Assert.Equal(0, value.Value.Item2?.Length); NullOrEmptyQubits(actual); } { @@ -57,7 +60,15 @@ public void TupleEmptyConstructor() } { var actual = new TupleH(); - Assert.Equal(default((TupleD, TupleG)), ((IApplyData)actual).Value); + var value = ((IApplyData)actual).Value as (TupleD, TupleG)?; + Assert.True(value.HasValue); + Assert.NotNull(value.Value.Item1); + Assert.NotNull(value.Value.Item2); + Assert.Equal(0, value.Value.Item1.Data?.Length); + Assert.Null(value.Value.Item2.Item1); + Assert.Equal(new TupleF(), value.Value.Item2.Item2); + Assert.Equal(new TupleC(), value.Value.Item2.Item3); + Assert.Equal(0, value.Value.Item2.Item4?.Data?.Length); NullOrEmptyQubits(actual); } {