diff --git a/src/Simulation/Simulators/CommonNativeSimulator/CommonNativeSimulator.cs b/src/Simulation/Simulators/CommonNativeSimulator/CommonNativeSimulator.cs index dda46fc992a..e444e7bc203 100644 --- a/src/Simulation/Simulators/CommonNativeSimulator/CommonNativeSimulator.cs +++ b/src/Simulation/Simulators/CommonNativeSimulator/CommonNativeSimulator.cs @@ -35,6 +35,8 @@ private protected CommonNativeSimulator( public uint Id { get; protected set; } + public abstract uint[] QubitIds { get; } + public override string Name { get diff --git a/src/Simulation/Simulators/CommonNativeSimulator/DisplayableState.cs b/src/Simulation/Simulators/CommonNativeSimulator/DisplayableState.cs new file mode 100644 index 00000000000..0cdf4fd272c --- /dev/null +++ b/src/Simulation/Simulators/CommonNativeSimulator/DisplayableState.cs @@ -0,0 +1,242 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Microsoft.Quantum.Simulation.Simulators +{ + public partial class CommonNativeSimulator + { + /// + /// The convention to be used in labeling computational basis states + /// given their representations as strings of classical bits. + /// + [JsonConverter(typeof(StringEnumConverter))] + public enum BasisStateLabelingConvention + { + /// + /// Label computational states directly by their bit strings. + /// + /// + /// Following this convention, the state |0⟩ ⊗ |1⟩ ⊗ |1⟩ is labeled + /// by |011⟩. + /// + Bitstring, + + /// + /// Label computational states directly by interpreting their bit + /// strings as little-endian encoded integers. + /// + /// + /// Following this convention, the state |0⟩ ⊗ |1⟩ ⊗ |1⟩ is labeled + /// by |6⟩. + /// + LittleEndian, + + /// + /// Label computational states directly by interpreting their bit + /// strings as big-endian encoded integers. + /// + /// + /// Following this convention, the state |0⟩ ⊗ |1⟩ ⊗ |1⟩ is labeled + /// by |3⟩. + /// + BigEndian + } + + /// + /// Represents a quantum state vector and all metadata needed to display + /// that state vector. + /// + public class DisplayableState + { + private static readonly IComparer ToIntComparer = + Comparer.Create((label1, label2) => + Comparer.Default.Compare( + Int32.Parse(label1), Int32.Parse(label2) + ) + ); + + /// + /// Metadata to be used when serializing to JSON, allowing code + /// in other languages to determine what representation is used + /// for this state. + /// + [JsonProperty("diagnostic_kind")] + private string DiagnosticKind => "state-vector"; + + /// + /// The indexes of each qubit on which this state is defined, or + /// null if these indexes are not known. + /// + [JsonProperty("qubit_ids")] + public IEnumerable? QubitIds { get; set; } + + /// + /// The number of qubits on which this state is defined. + /// + [JsonProperty("n_qubits")] + public int NQubits { get; set; } + + /// + /// These amplitudes represent the computational basis states + /// labeled in little-endian order, as per the behavior of + /// . + /// + [JsonProperty("amplitudes")] + public IDictionary? Amplitudes { get; set; } + + /// + /// An enumerable source of the significant amplitudes of this state + /// vector and their labels. + /// + /// + /// The convention to be used in labeling each computational basis state. + /// + /// + /// Whether to truncate small amplitudes. + /// + /// + /// If is true, + /// then amplitudes whose absolute value squared are below this + /// threshold are suppressed. + /// + public IEnumerable<(Complex, string)> SignificantAmplitudes( + BasisStateLabelingConvention convention, + bool truncateSmallAmplitudes, double truncationThreshold + ) => + ( + truncateSmallAmplitudes + ? Amplitudes + .Where(item => + System.Math.Pow(item.Value.Magnitude, 2.0) >= truncationThreshold + ) + : Amplitudes + ) + .Select( + item => (item.Value, BasisStateLabel(convention, item.Key)) + ) + .OrderBy( + item => item.Item2, + // If a basis state label is numeric, we want to compare + // numerically rather than lexographically. + convention switch { + BasisStateLabelingConvention.BigEndian => ToIntComparer, + BasisStateLabelingConvention.LittleEndian => ToIntComparer, + _ => Comparer.Default + } + ); + + /// + /// Using the given labeling convention, returns the label for a + /// computational basis state described by its bit string as encoded + /// into an integer index in the little-endian encoding. + /// + public string BasisStateLabel( + BasisStateLabelingConvention convention, int index + ) => convention switch + { + BasisStateLabelingConvention.Bitstring => + String.Concat( + System + .Convert + .ToString(index, 2) + .PadLeft(NQubits, '0') + .Reverse() + ), + BasisStateLabelingConvention.BigEndian => + System.Convert.ToInt64( + String.Concat( + System.Convert.ToString(index, 2).PadLeft(NQubits, '0').Reverse() + ), + fromBase: 2 + ) + .ToString(), + BasisStateLabelingConvention.LittleEndian => + index.ToString(), + _ => throw new ArgumentException($"Invalid basis state labeling convention {convention}.") + }; + + /// + /// Returns a string that represents the magnitude of the amplitude. + /// + public virtual string FormatMagnitude(double magnitude, double phase) => + (new String('*', (int)System.Math.Ceiling(20.0 * magnitude))).PadRight(20) + $" [ {magnitude:F6} ]"; + + /// + /// Returns a string that represents the phase of the amplitude. + /// + public virtual string FormatAngle(double magnitude, double angle) + { + var PI = System.Math.PI; + var offset = PI / 16.0; + if (magnitude == 0.0) + { + return " "; + } + + var chart = " ---"; + if (angle > 0) + { + if (angle >= (0 * PI / 8) + offset && angle < ((1 * PI / 8) + offset)) { chart = " /-"; } + if (angle >= (1 * PI / 8) + offset && angle < ((2 * PI / 8) + offset)) { chart = " / "; } + if (angle >= (2 * PI / 8) + offset && angle < ((3 * PI / 8) + offset)) { chart = " +/ "; } + if (angle >= (3 * PI / 8) + offset && angle < ((4 * PI / 8) + offset)) { chart = " ↑ "; } + if (angle >= (4 * PI / 8) + offset && angle < ((5 * PI / 8) + offset)) { chart = " \\- "; } + if (angle >= (5 * PI / 8) + offset && angle < ((6 * PI / 8) + offset)) { chart = " \\ "; } + if (angle >= (6 * PI / 8) + offset && angle < ((7 * PI / 8) + offset)) { chart = "+\\ "; } + if (angle >= (7 * PI / 8) + offset) { chart = "--- "; } + } + else if (angle < 0) + { + var abs_angle = System.Math.Abs(angle); + if (abs_angle >= (0 * PI / 8) + offset && abs_angle < ((1 * PI / 8) + offset)) { chart = " \\+"; } + if (abs_angle >= (1 * PI / 8) + offset && abs_angle < ((2 * PI / 8) + offset)) { chart = " \\ "; } + if (abs_angle >= (2 * PI / 8) + offset && abs_angle < ((3 * PI / 8) + offset)) { chart = " -\\ "; } + if (abs_angle >= (3 * PI / 8) + offset && abs_angle < ((4 * PI / 8) + offset)) { chart = " ↓ "; } + if (abs_angle >= (4 * PI / 8) + offset && abs_angle < ((5 * PI / 8) + offset)) { chart = " /+ "; } + if (abs_angle >= (5 * PI / 8) + offset && abs_angle < ((6 * PI / 8) + offset)) { chart = " / "; } + if (abs_angle >= (6 * PI / 8) + offset && abs_angle < ((7 * PI / 8) + offset)) { chart = "-/ "; } + } + + return $" {chart} [ {angle,8:F5} rad ]"; + } + + /// + /// Returns a string for the amplitude's polar representation (magnitude/angle). + /// + public virtual string FormatPolar(double magnitude, double angle) => + $"{FormatMagnitude(magnitude, angle)}{FormatAngle(magnitude, angle)}"; + + /// + /// Returns a string for the amplitude's cartesian representation (real + imagnary). + /// + public virtual string FormatCartesian(double real, double img) => + $"{real,9:F6} + {img,9:F6} i"; + + public string ToString(BasisStateLabelingConvention convention, // Non-override. Parameterized. + bool truncateSmallAmplitudes, + double truncationThreshold) + { + return string.Join('\n', + SignificantAmplitudes(convention, truncateSmallAmplitudes, truncationThreshold) + .Select( + item => + { + var (cmplx, basisLabel) = item; + var amplitude = (cmplx.Real * cmplx.Real) + (cmplx.Imaginary * cmplx.Imaginary); + var angle = System.Math.Atan2(cmplx.Imaginary, cmplx.Real); + return $"|{basisLabel}⟩\t{FormatCartesian(cmplx.Real, cmplx.Imaginary)}\t == \t" + + $"{FormatPolar(amplitude, angle)}"; + })); + } + + public override string ToString() => // An override of the `object.ToString()`. + ToString(BasisStateLabelingConvention.LittleEndian, false, 0.0); + + } + + } +} diff --git a/src/Simulation/Simulators/CommonNativeSimulator/Dump.cs b/src/Simulation/Simulators/CommonNativeSimulator/Dump.cs index 51fa1c153fa..ce3bcf38f63 100644 --- a/src/Simulation/Simulators/CommonNativeSimulator/Dump.cs +++ b/src/Simulation/Simulators/CommonNativeSimulator/Dump.cs @@ -2,19 +2,18 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.IO; using System.Linq; +using System.Numerics; using Microsoft.Quantum.Simulation.Core; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; namespace Microsoft.Quantum.Simulation.Simulators { public partial class CommonNativeSimulator { - protected virtual QVoid process(Action channel, IQArray? qubits) - { - return QVoid.Instance; - } - /// /// Dumps the wave function for the given qubits into the given target. /// If the target is QVoid or an empty string, it dumps it to the console @@ -26,30 +25,27 @@ protected virtual QVoid process(Action channel, IQArray? qubits) /// protected virtual QVoid Dump(T target, IQArray? qubits = null) { - var filename = (target is QVoid) ? "" : target.ToString(); - var logMessage = this.Get, Microsoft.Quantum.Intrinsic.Message>(); + var filename = ((target == null) || (target is QVoid)) ? "" : target.ToString(); - // If no file provided, use `Message` to generate the message into the console; + // If no file provided, output to the console; if (string.IsNullOrWhiteSpace(filename)) { - var op = this.Get, Microsoft.Quantum.Intrinsic.Message>(); - return process((msg) => op.Apply(msg), qubits); + new DisplayableStateDumper(this).Dump(qubits); } else { try { - using (var file = new StreamWriter(filename)) - { - return process(file.WriteLine, qubits); - } + using var file = new StreamWriter(filename); + new DisplayableStateDumper(this, file.WriteLine).Dump(qubits); } catch (Exception e) { + var logMessage = this.Get, Microsoft.Quantum.Intrinsic.Message>(); logMessage.Apply($"[warning] Unable to write state to '{filename}' ({e.Message})"); - return QVoid.Instance; } } + return QVoid.Instance; } // `QsimDumpMachine` makes an impression that it is never used, @@ -96,5 +92,135 @@ public QSimDumpRegister(CommonNativeSimulator m) : base(m) return Simulator.Dump(location, qubits); }; } + + /// + /// This class allows you to dump the state (wave function) + /// of the native simulator into a callback function. + /// The callback function is triggered for every state basis + /// vector in the wavefunction. + /// + public abstract class StateDumper + { + /// + /// Basic constructor. Takes the simulator to probe. + /// + public StateDumper(CommonNativeSimulator qsim) + { + this.Simulator = qsim; + } + + /// + /// The callback method that will be used to report the amplitude + /// of each basis vector of the wave function. + /// The method should return 'true' if the simulator should + /// continue reporting the state of the remaining basis vectors. + /// + /// The index of the basis state vector being reported. + /// The real portion of the amplitude of the given basis state vector. + /// The imaginary portion of the amplitude of the given basis state vector. + /// true if dumping should continue, false to stop dumping. + public abstract bool Callback(uint idx, double real, double img); + + /// + /// The Simulator being reported. + /// + public CommonNativeSimulator Simulator { get; } + + /// + /// Entry method to get the dump of the wave function. + /// + public virtual bool Dump(IQArray? qubits = null) + { + if (qubits == null) + { + this.Simulator.sim_Dump(Callback); + return true; + } + else + { + var ids = qubits.GetIds(); + return this.Simulator.sim_DumpQubits((uint)ids.Length, ids, Callback); + } + } + } + + /// + /// A state dumper that encodes dumped states into displayable + /// objects. + /// + public class DisplayableStateDumper : StateDumper + { + private long _count = -1; + private IDictionary? _data = null; + + /// + /// A method to call to output a string representation. + /// + public virtual Action? FileWriter { get; } + + /// + /// Constructs a new display dumper for a given simulator. + /// + public DisplayableStateDumper(CommonNativeSimulator sim, Action? fileWriter = null) : base(sim) + { + this.FileWriter = fileWriter; + } + + /// + /// Used by the simulator to provide states when dumping. + /// Not intended to be called directly. + /// + public override bool Callback(uint idx, double real, double img) + { + if (_data == null) throw new Exception("Expected data buffer to be initialized before callback, but it was null."); + _data[(int)idx] = new Complex(real, img); + return true; + } + + /// + /// Dumps the state of a register of qubits as a displayable object. + /// + public override bool Dump(IQArray? qubits = null) + { + System.Diagnostics.Debug.Assert(this.Simulator.QubitManager != null, + "Internal logic error, QubitManager must be assigned"); + + _count = qubits == null + ? this.Simulator.QubitManager.AllocatedQubitsCount + : qubits.Length; + _data = new Dictionary(); // If 0 qubits are allocated then the array has + // a single element. The Hilbert space of the system is + // ℂ¹ (that is, complex-valued scalars). + var result = base.Dump(qubits); + + // At this point, _data should be filled with the full state + // vector, so let's display it, counting on the right display + // encoder to be there to pack it into a table. + + var state = new DisplayableState + { + // We cast here as we don't support a large enough number + // of qubits to saturate an int. + QubitIds = qubits?.Select(q => q.Id) ?? Simulator.QubitIds.Select(q => (int)q) ?? Enumerable.Empty(), + NQubits = (int)_count, + Amplitudes = _data, + }; + + if (this.FileWriter != null) + { + this.FileWriter(state.ToString()); + } + else + { + Simulator.MaybeDisplayDiagnostic(state); + } + + // Clean up the state vector buffer. + _data = null; + + return result; + } + + } } } diff --git a/src/Simulation/Simulators/CommonNativeSimulator/NativeWrappers.cs b/src/Simulation/Simulators/CommonNativeSimulator/NativeWrappers.cs index 7e33cea1441..42bfca58c8d 100644 --- a/src/Simulation/Simulators/CommonNativeSimulator/NativeWrappers.cs +++ b/src/Simulation/Simulators/CommonNativeSimulator/NativeWrappers.cs @@ -29,5 +29,9 @@ public partial class CommonNativeSimulator protected abstract void MCY(uint count, uint[] ctrls, uint qubit); protected abstract void Z(uint qubit); protected abstract void MCZ(uint count, uint[] ctrls, uint qubit); + + protected delegate bool DumpCallback(uint idx, double real, double img); + protected abstract void sim_Dump(DumpCallback callback); + protected abstract bool sim_DumpQubits(uint count, uint[] ids, DumpCallback callback); } } diff --git a/src/Simulation/Simulators/QuantumSimulator/Dump.cs b/src/Simulation/Simulators/QuantumSimulator/Dump.cs index 3e414204905..d89a153c50f 100644 --- a/src/Simulation/Simulators/QuantumSimulator/Dump.cs +++ b/src/Simulation/Simulators/QuantumSimulator/Dump.cs @@ -14,7 +14,7 @@ public partial class QuantumSimulator /// /// Returns the list of the qubits' ids currently allocated in the simulator. /// - public uint[] QubitIds + public override uint[] QubitIds { get { @@ -26,19 +26,5 @@ public uint[] QubitIds } } - protected override QVoid process(Action channel, IQArray? qubits) - { - var ids = qubits?.Select(q => (uint)q.Id).ToArray() ?? QubitIds; - - var dumper = new SimpleDumper(this, channel); - channel($"# wave function for qubits with ids (least to most significant): {string.Join(";", ids)}"); - - if (!dumper.Dump(qubits)) - { - channel("## Qubits were entangled with an external qubit. Cannot dump corresponding wave function. ##"); - } - - return QVoid.Instance; - } } } diff --git a/src/Simulation/Simulators/QuantumSimulator/NativeWrappers.cs b/src/Simulation/Simulators/QuantumSimulator/NativeWrappers.cs index b185f287771..4ad08dc2a3d 100644 --- a/src/Simulation/Simulators/QuantumSimulator/NativeWrappers.cs +++ b/src/Simulation/Simulators/QuantumSimulator/NativeWrappers.cs @@ -107,12 +107,12 @@ protected override void MCAdjS(uint count, uint[] ctrls, uint qubit) MCAdjSNative(this.Id, count, ctrls, qubit); } - protected virtual void sim_Dump(DumpCallback callback) + protected override void sim_Dump(DumpCallback callback) { sim_DumpNative(this.Id, callback); } - protected virtual bool sim_DumpQubits(uint count, uint[] ids, DumpCallback callback) + protected override bool sim_DumpQubits(uint count, uint[] ids, DumpCallback callback) { return sim_DumpQubitsNative(this.Id, count, ids, callback); } diff --git a/src/Simulation/Simulators/QuantumSimulator/StateDumper.cs b/src/Simulation/Simulators/QuantumSimulator/StateDumper.cs deleted file mode 100644 index 22c42a05f6e..00000000000 --- a/src/Simulation/Simulators/QuantumSimulator/StateDumper.cs +++ /dev/null @@ -1,184 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Diagnostics; -using Microsoft.Quantum.Simulation.Core; - -namespace Microsoft.Quantum.Simulation.Simulators -{ - public partial class QuantumSimulator - { - protected delegate bool DumpCallback(uint idx, double real, double img); - - /// - /// This class allows you to dump the state (wave function) - /// of the QuantumSimulator into a callback function. - /// The callback function is triggered for every state basis - /// vector in the wavefunction. - /// - public abstract class StateDumper // Is used by "iqsharp\src\Jupyter\Visualization\StateDisplayOperations.cs". - { - /// - /// Basic constructor. Takes the simulator to probe. - /// - public StateDumper(QuantumSimulator qsim) - { - this.Simulator = qsim; - } - - /// - /// The callback method that will be used to report the amplitude - /// of each basis vector of the wave function. - /// The method should return 'true' if the QuantumSimulator should - /// continue reporting the state of the remaining basis vectors. - /// - /// The index of the basis state vector being reported. - /// The real portion of the amplitude of the given basis state vector. - /// The imaginary portion of the amplitude of the given basis state vector. - /// true if dumping should continue, false to stop dumping. - public abstract bool Callback(uint idx, double real, double img); - - /// - /// The QuantumSimulator being reported. - /// - public QuantumSimulator Simulator { get; } - - /// - /// Entry method to get the dump of the wave function. - /// - public virtual bool Dump(IQArray? qubits = null) - { - if (qubits == null) - { - this.Simulator.sim_Dump(Callback); - return true; - } - else - { - var ids = qubits.GetIds(); - return this.Simulator.sim_DumpQubits((uint)ids.Length, ids, Callback); - } - } - } - - /// - /// A simple implementation of a . It outputs the - /// a string representation of the state to the given channel. - /// - public class SimpleDumper : StateDumper - { - private int _maxCharsStateId; - - public SimpleDumper(QuantumSimulator qsim, Action channel) : base(qsim) - { - this.Channel = channel; - } - - /// - /// A method to call to output a string representation of the amplitude of each - /// state basis vector. - /// - public virtual Action Channel { get; } - - /// - /// Returns a string that represents the label for the given base state. - /// - public virtual string FormatBaseState(uint idx) => - $"∣{idx.ToString().PadLeft(_maxCharsStateId, ' ')}❭"; - - /// - /// Returns a string that represents the magnitude of the amplitude. - /// - public virtual string FormatMagnitude(double magnitude, double phase) => - (new String('*', (int)System.Math.Ceiling(20.0 * magnitude))).PadRight(20) + $" [ {magnitude:F6} ]"; - - /// - /// Returns a string that represents the phase of the amplitude. - /// - public virtual string FormatAngle(double magnitude, double angle) - { - var PI = System.Math.PI; - var offset = PI / 16.0; - if (magnitude == 0.0) - { - return " "; - } - - var chart = " ---"; - if (angle > 0) - { - if (angle >= (0 * PI / 8) + offset && angle < ((1 * PI / 8) + offset)) { chart = " /-"; } - if (angle >= (1 * PI / 8) + offset && angle < ((2 * PI / 8) + offset)) { chart = " / "; } - if (angle >= (2 * PI / 8) + offset && angle < ((3 * PI / 8) + offset)) { chart = " +/ "; } - if (angle >= (3 * PI / 8) + offset && angle < ((4 * PI / 8) + offset)) { chart = " ↑ "; } - if (angle >= (4 * PI / 8) + offset && angle < ((5 * PI / 8) + offset)) { chart = " \\- "; } - if (angle >= (5 * PI / 8) + offset && angle < ((6 * PI / 8) + offset)) { chart = " \\ "; } - if (angle >= (6 * PI / 8) + offset && angle < ((7 * PI / 8) + offset)) { chart = "+\\ "; } - if (angle >= (7 * PI / 8) + offset) { chart = "--- "; } - } - else if (angle < 0) - { - var abs_angle = System.Math.Abs(angle); - if (abs_angle >= (0 * PI / 8) + offset && abs_angle < ((1 * PI / 8) + offset)) { chart = " \\+"; } - if (abs_angle >= (1 * PI / 8) + offset && abs_angle < ((2 * PI / 8) + offset)) { chart = " \\ "; } - if (abs_angle >= (2 * PI / 8) + offset && abs_angle < ((3 * PI / 8) + offset)) { chart = " -\\ "; } - if (abs_angle >= (3 * PI / 8) + offset && abs_angle < ((4 * PI / 8) + offset)) { chart = " ↓ "; } - if (abs_angle >= (4 * PI / 8) + offset && abs_angle < ((5 * PI / 8) + offset)) { chart = " /+ "; } - if (abs_angle >= (5 * PI / 8) + offset && abs_angle < ((6 * PI / 8) + offset)) { chart = " / "; } - if (abs_angle >= (6 * PI / 8) + offset && abs_angle < ((7 * PI / 8) + offset)) { chart = "-/ "; } - } - - return $" {chart} [ {angle,8:F5} rad ]"; - } - - /// - /// Returns a string for the amplitude's polar representation (magnitude/angle). - /// - public virtual string FormatPolar(double magnitude, double angle) => - $"{FormatMagnitude(magnitude, angle)}{FormatAngle(magnitude, angle)}"; - - /// - /// Returns a string for the amplitude's cartesian representation (real + imagnary). - /// - public virtual string FormatCartesian(double real, double img) => - $"{real,9:F6} + {img,9:F6} i"; - - /// - /// The method to use to format the amplitude into a string. - /// - public virtual string Format(uint idx, double real, double img) - { - var amplitude = (real * real) + (img * img); - var angle = System.Math.Atan2(img, real); - - return $"{FormatBaseState(idx)}:\t" + - $"{FormatCartesian(real, img)}\t == \t" + - $"{FormatPolar(amplitude, angle)}"; - - } - - /// - /// The callback method. Formats the given state and invokes the - /// - /// True, so the entire wave function is dumped. - public override bool Callback(uint idx, double real, double img) - { - Channel(Format(idx, real, img)); - return true; - } - - public override bool Dump(IQArray? qubits = null) - { - Debug.Assert(this.Simulator.QubitManager != null); - - var count = qubits == null - ? this.Simulator.QubitManager.AllocatedQubitsCount - : qubits.Length; - this._maxCharsStateId = ((1 << (int)count) - 1).ToString().Length; - - return base.Dump(qubits); - } - } - } -}