diff --git a/Build/check_indents.py b/Build/check_indents.py index 3e3d6202172..540db399215 100644 --- a/Build/check_indents.py +++ b/Build/check_indents.py @@ -24,7 +24,7 @@ def check_file(filename : str) -> bool: found_spaces = False found_tabs = False - with open(filename, 'r') as f: + with open(filename, 'r', encoding='utf8') as f: contents = list(f.readlines()) for line in contents: diff --git a/Chemistry/src/Runtime/JordanWigner/JordanWignerOptimizedBlockEncoding.qs b/Chemistry/src/Runtime/JordanWigner/JordanWignerOptimizedBlockEncoding.qs index 887398988d2..e47834899ff 100644 --- a/Chemistry/src/Runtime/JordanWigner/JordanWignerOptimizedBlockEncoding.qs +++ b/Chemistry/src/Runtime/JordanWigner/JordanWignerOptimizedBlockEncoding.qs @@ -389,8 +389,8 @@ namespace Microsoft.Quantum.Chemistry.JordanWigner { function _JordanWignerOptimizedBlockEncodingQubitManager_ (targetError : Double, nCoeffs : Int, nZ : Int, nMaj : Int, nIdxRegQubits : Int, ctrlRegister : Qubit[]) : ((LittleEndian, Qubit[], Qubit, Qubit[], Qubit[], Qubit[], LittleEndian, LittleEndian[]), (Qubit, Qubit[], Qubit[], Qubit[], LittleEndian[]), Qubit[]) { - - let ((qROMIdx, qROMGarbage), rest0) = _QuantumROMQubitManager(targetError, nCoeffs, ctrlRegister); + // FIXME: This is a dependency on a private function from a different assembly! + let ((qROMIdx, qROMGarbage), rest0) = _PartitionedForQuantumROM(targetError, nCoeffs, ctrlRegister); let ((signQubit, selectZControlRegisters, optimizedBEControlRegisters, pauliBases, indexRegisters, tmp), rest1) = _JordanWignerSelectQubitManager_(nZ, nMaj, nIdxRegQubits, rest0, new Qubit[0]); let registers = Partitioned([3], rest1); let pauliBasesIdx = LittleEndian(registers[0]); diff --git a/Chemistry/src/Runtime/JordanWigner/StatePreparation.qs b/Chemistry/src/Runtime/JordanWigner/StatePreparation.qs index 76269dad4f2..8c8e4911661 100644 --- a/Chemistry/src/Runtime/JordanWigner/StatePreparation.qs +++ b/Chemistry/src/Runtime/JordanWigner/StatePreparation.qs @@ -38,12 +38,12 @@ namespace Microsoft.Quantum.Chemistry.JordanWigner { // The last term is the reference state. let referenceState = PrepareTrialState((2, [terms[nTerms-1]]), _); - + PrepareUnitaryCoupledClusterState(referenceState, terms[0..nTerms-2], trotterStepSize, qubits); } } - - + + /// # Summary /// Simple state preparation of trial state by occupying /// spin-orbitals @@ -54,23 +54,23 @@ namespace Microsoft.Quantum.Chemistry.JordanWigner { /// ## qubits /// Qubits of Hamiltonian. operation PrepareSingleConfigurationalStateSingleSiteOccupation (qubitIndices : Int[], qubits : Qubit[]) : Unit { - + body (...) { ApplyToEachCA(X, Subarray(qubitIndices, qubits)); } - + adjoint invert; controlled distribute; controlled adjoint distribute; } - - + + function _PrepareSingleConfigurationalStateSingleSiteOccupation (qubitIndices : Int[]) : (Qubit[] => Unit is Adj + Ctl) { - + return PrepareSingleConfigurationalStateSingleSiteOccupation(qubitIndices, _); } - - + + /// # Summary /// Sparse multi-configurational state preparation of trial state by adding excitations /// to initial trial state. @@ -85,34 +85,34 @@ namespace Microsoft.Quantum.Chemistry.JordanWigner { /// ## qubits /// Qubits of Hamiltonian. operation PrepareSparseMultiConfigurationalState (initialStatePreparation : (Qubit[] => Unit), excitations : JordanWignerInputState[], qubits : Qubit[]) : Unit { - + let nExcitations = Length(excitations); - + //FIXME compile error let coefficientsSqrtAbs = Mapped(Compose(Compose(Sqrt, Fst),Fst), excitations); mutable coefficientsSqrtAbs = new Double[nExcitations]; mutable coefficientsNewComplexPolar = new ComplexPolar[nExcitations]; mutable applyFlips = new Int[][nExcitations]; - + for (idx in 0 .. nExcitations - 1) { let (x, excitation) = excitations[idx]!; set coefficientsSqrtAbs w/= idx <- Sqrt(AbsComplexPolar(ComplexAsComplexPolar(Complex(x)))); set coefficientsNewComplexPolar w/= idx <- ComplexPolar(coefficientsSqrtAbs[idx], ArgComplexPolar(ComplexAsComplexPolar(Complex(x)))); set applyFlips w/= idx <- excitation; } - + let nBitsIndices = Ceiling(Lg(IntAsDouble(nExcitations))); - + repeat { mutable success = false; - + using (auxillary = Qubit[nBitsIndices + 1]) { using (flag = Qubit[1]) { let multiplexer = MultiplexerBruteForceFromGenerator(nExcitations, LookupFunction(Mapped(_PrepareSingleConfigurationalStateSingleSiteOccupation, applyFlips))); - (StatePreparationComplexCoefficients(coefficientsNewComplexPolar))(LittleEndian(auxillary)); + PrepareArbitraryStateCP(coefficientsNewComplexPolar, LittleEndian(auxillary)); multiplexer(LittleEndian(auxillary), qubits); - (Adjoint (StatePreparationPositiveCoefficients(coefficientsSqrtAbs)))(LittleEndian(auxillary)); + Adjoint PrepareArbitraryStateD(coefficientsSqrtAbs, LittleEndian(auxillary)); (ControlledOnInt(0, X))(auxillary, flag[0]); - + // if measurement outcome one we prepared required state let outcome = Measure([PauliZ], flag); set success = outcome == One; @@ -126,9 +126,9 @@ namespace Microsoft.Quantum.Chemistry.JordanWigner { ResetAll(qubits); } } - + /// # Summary - /// Unitary coupled-cluster state preparation of trial state + /// Unitary coupled-cluster state preparation of trial state /// /// # Input /// ## initialStatePreparation diff --git a/Standard/src/Math/Convert.qs b/Standard/src/Math/Convert.qs index 947601cdc75..8353345dd79 100644 --- a/Standard/src/Math/Convert.qs +++ b/Standard/src/Math/Convert.qs @@ -3,6 +3,24 @@ namespace Microsoft.Quantum.Math { + /// # Summary + /// Converts a real floating-point number to a complex number in its polar + /// representation. + /// + /// # Input + /// ## input + /// The real number to be represented as a complex number. + /// + /// # Output + /// A complex number representing the given input in terms of polar + /// coordinates. + function DoubleAsComplexPolar(input : Double) : ComplexPolar { + return ComplexPolar( + AbsD(input), + input < 0.0 ? PI() | 0.0 + ); + } + /// # Summary /// Converts a complex number of type `ComplexPolar` to a complex /// number of type `Complex`. diff --git a/Standard/src/Preparation/Arbitrary.qs b/Standard/src/Preparation/Arbitrary.qs new file mode 100644 index 00000000000..1e04d4fa5cd --- /dev/null +++ b/Standard/src/Preparation/Arbitrary.qs @@ -0,0 +1,269 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Preparation { + open Microsoft.Quantum.Canon; + open Microsoft.Quantum.Arithmetic; + open Microsoft.Quantum.Intrinsic; + open Microsoft.Quantum.Math; + open Microsoft.Quantum.Arrays; + + /// # Summary + /// Given an expansion of an abitrary state in the computational basis, + /// prepares that state on a register of qubits. + /// + /// # Description + /// Given a register of qubits initially in the $n$-qubit + /// the $n$-qubit number state $\ket{0}$ (using a little-endian encoding), + /// prepares that register in the state + /// $$ + /// \begin{align} + /// \ket{\psi} & = \frac{ + /// \sum_{j = 0}^{2^n - 1} r_j e^{i t_j} \ket{j} + /// }{ + /// \sqrt{\sum_{j = 0}^{2^n - 1} |r_j|^2} + /// }, + /// \end{align} + /// $$ + /// where $\{r_j e^{i t_j}\}_{j = 0}^{2^n - 1}$ is a list of complex + /// coefficients representing the state to be prepared. + /// + /// # Input + /// ## coefficients + /// An array of up to $2^n$ complex coefficients represented by their + /// magnitude and phase $(r_j, t_j)$. The $j$th coefficient + /// indexes the number state $\ket{j}$ encoded in little-endian format. + /// + /// ## qubits + /// Qubit register encoding number states in little-endian format. This is + /// expected to be initialized in the number state $\ket{0}$. + /// + /// # Remarks + /// Negative input coefficients $r_j < 0$ will be treated as though + /// positive with value $|r_j|$. If `coefficients` is shorter than $2^n$ + /// elements, this input will be padded with elements + /// $(r_j, t_j) = (0.0, 0.0)$ (that is, elements representing the coefficient + /// $0$). + /// + /// # Example + /// The following snippet prepares a new three-qubit register in the state + /// $\frac{1}{\sqrt{3}}\left( \sqrt{2} \ket{0} + e^{i \pi / 3} \ket{2} \right)$: + /// + /// ```Q# + /// // Represent 1 / √3 (√2 |0⟩ + e^{𝑖 π / 3} |2⟩) as an array of complex + /// // coefficients. + /// let coefficients = [ + /// ComplexPolar(Sqrt(2.0) / Sqrt(3.0), 0.0), + /// ComplexPolar(0.0, 0.0), + /// ComplexPolar(1.0 / Sqrt(3.0), PI() / 3.0) + /// ]; + /// + /// // Allocate a bare register of three qubits. + /// using (qs = Qubit[3]) { + /// // Use the bare register to create a new little-endian register. + /// // Note that in a little-endian encoding, the computational basis + /// // state |000⟩ encodes the number state |0⟩. + /// let register = LittleEndian(qs); + /// + /// // We can prepare the state represented by the coefficients array + /// // by calling PrepareArbitraryState. + /// PrepareArbitraryStateCP(coefficients, register); + /// // ... + /// } + /// ``` + /// + /// # References + /// - Synthesis of Quantum Logic Circuits + /// Vivek V. Shende, Stephen S. Bullock, Igor L. Markov + /// https://arxiv.org/abs/quant-ph/0406176 + operation PrepareArbitraryStateCP(coefficients : ComplexPolar[], qubits : LittleEndian) + : Unit is Adj + Ctl { + // pad coefficients at tail length to a power of 2. + let paddedCoefficients = Padded(-2 ^ Length(qubits!), ComplexPolar(0.0, 0.0), coefficients); + let target = (qubits!)[0]; + let op = (Adjoint _UnprepareArbitraryState(paddedCoefficients, _, _))(_, target); + + op( + // Determine what controls to apply to `op`. + Length(qubits!) > 1 + ? LittleEndian((qubits!)[1 .. Length(qubits!) - 1]) + | LittleEndian(new Qubit[0]) + ); + } + + /// # Summary + /// Given an expansion of an abitrary state in the computational basis, + /// prepares that state on a register of qubits. + /// + /// # Description + /// Given a register of qubits initially in the $n$-qubit + /// the $n$-qubit number state $\ket{0}$ (using a little-endian encoding), + /// prepares that register in the state + /// $$ + /// \begin{align} + /// \ket{\psi} & = \frac{ + /// \sum_{j = 0}^{2^n - 1} r_j e^{i t_j} \ket{j} + /// }{ + /// \sqrt{\sum_{j = 0}^{2^n - 1} |r_j|^2} + /// }, + /// \end{align} + /// $$ + /// where $\{r_j e^{i t_j}\}_{j = 0}^{2^n - 1}$ is a list of complex + /// coefficients representing the state to be prepared. + /// + /// # Input + /// ## coefficients + /// An array of up to $2^n$ coefficients. The $j$th coefficient + /// indexes the number state $\ket{j}$ encoded in little-endian format. + /// + /// ## qubits + /// Qubit register encoding number states in little-endian format. This is + /// expected to be initialized in the number state $\ket{0}$. + /// + /// # Remarks + /// If `coefficients` is shorter than $2^n$ elements, this input will be + /// padded with `0.0`. + /// + /// # Example + /// The following snippet prepares a new three-qubit register in the state + /// $\frac{1}{\sqrt{3}}\left( \sqrt{2} \ket{0} + \ket{2} \right)$: + /// + /// ```Q# + /// // Represent 1 / √3 (√2 |0⟩ + e^{𝑖 π / 3} |2⟩) as an array of complex + /// // coefficients. + /// let coefficients = [ + /// Sqrt(2.0) / Sqrt(3.0), + /// 0.0, + /// 1.0 / Sqrt(3.0) + /// ]; + /// + /// // Allocate a bare register of three qubits. + /// using (qs = Qubit[3]) { + /// // Use the bare register to create a new little-endian register. + /// // Note that in a little-endian encoding, the computational basis + /// // state |000⟩ encodes the number state |0⟩. + /// let register = LittleEndian(qs); + /// + /// // We can prepare the state represented by the coefficients array + /// // by calling PrepareArbitraryState. + /// PrepareArbitraryStateD(coefficients, register); + /// // ... + /// } + /// ``` + /// + /// # References + /// - Synthesis of Quantum Logic Circuits + /// Vivek V. Shende, Stephen S. Bullock, Igor L. Markov + /// https://arxiv.org/abs/quant-ph/0406176 + operation PrepareArbitraryStateD(coefficients : Double[], qubits : LittleEndian) + : Unit is Adj + Ctl { + PrepareArbitraryStateCP( + Mapped(DoubleAsComplexPolar, coefficients), + qubits + ); + } + + /// # Summary + /// Implementation step of arbitrary state preparation procedure. + /// As it is easier to implement an operation that maps the given state + /// to $\ket{0}$, we do that here in an adjointable manner, then take the + /// adjoint to get the desired preparation. + /// + /// # See Also + /// - PrepareArbitraryState + /// - Microsoft.Quantum.Canon.MultiplexPauli + operation _UnprepareArbitraryState(coefficients : ComplexPolar[], control : LittleEndian, target : Qubit) + : Unit is Adj + Ctl { + // For each 2D block, compute disentangling single-qubit rotation + // parameters. + let (disentanglingY, disentanglingZ, newCoefficients) = _StatePreparationSBMComputeCoefficients(coefficients); + MultiplexPauli(disentanglingZ, PauliZ, control, target); + MultiplexPauli(disentanglingY, PauliY, control, target); + + // At this point, by having applied the disentanglers above, the target + // qubit is guaranteed to be in the |0⟩ state, up to a global phase + // given by the argument of newCoefficients[0]. + // Since this operation may be called in a controlled fashion, we need + // to correct that phase before recursing. + + // Continue recursion while there are control qubits. + if (Length(control!) == 0) { + Exp([PauliI], -(Head(newCoefficients))::Argument, [target]); + } else { + _UnprepareArbitraryState( + newCoefficients, + LittleEndian(Rest(control!)), + Head(control!) + ); + } + } + + + /// # Summary + /// Computes the Bloch sphere coordinates for a single-qubit state. + /// + /// # Description + /// Given two complex numbers $a_0$ and $a_1$ represent the single-qubit state, + /// $a_0 \ket{0} + a_1 \ket{1}$, returns coordinates $r$, $t$, $phi$, and + /// $\theta$ on the Bloch sphere such that + /// $$ + /// \begin{align} + /// a_0 \ket{0} + a_1 \ket{1} & = r e^_{i t} \left( + /// e^{-i \phi / 2} \cos(\theta / 2) \ket{0} + + /// e^{ i \phi / 2} \sin(\theta / 2) \ket{1} + /// \right). + /// \end{align} + /// + /// # Input + /// ## a0 + /// Complex coefficient of state $\ket{0}$. + /// ## a1 + /// Complex coefficient of state $\ket{1}$. + /// + /// # Output + /// A tuple containing `(ComplexPolar(r, t), phi, theta)`. + /// + /// # Example + /// ```Q# + /// let coefficients = ( + /// ComplexPolar(Sqrt(2.0) / Sqrt(3.0), 0.0), + /// ComplexPolar(1.0 / Sqrt(3.0), PI() / 3.0) + /// ); + /// let blochSphereCoordinates = BlochSphereCoordinates(coefficients); + /// Message($"{blochSphereCoordinates}"); + /// // Output: + /// // (ComplexPolar((1, 0.5235987755982988)), 1.0471975511965976, 1.9106332362490186) + /// ``` + function BlochSphereCoordinates(a0 : ComplexPolar, a1 : ComplexPolar) + : (ComplexPolar, Double, Double) { + let r = Sqrt(PowD(a0::Magnitude, 2.0) + PowD(a1::Magnitude, 2.0)); + let t = 0.5 * (a0::Argument + a1::Argument); + let phi = a1::Argument - a0::Argument; + let theta = 2.0 * ArcTan2(a1::Magnitude, a0::Magnitude); + return (ComplexPolar(r, t), phi, theta); + } + + /// # Summary + /// Implementation step of arbitrary state preparation procedure. + /// + /// # See Also + /// - Microsoft.Quantum.Preparation.PrepareArbitraryState + function _StatePreparationSBMComputeCoefficients(coefficients : ComplexPolar[]) + : (Double[], Double[], ComplexPolar[]) { + mutable disentanglingZ = new Double[Length(coefficients) / 2]; + mutable disentanglingY = new Double[Length(coefficients) / 2]; + mutable newCoefficients = new ComplexPolar[Length(coefficients) / 2]; + + for (idxCoeff in 0 .. 2 .. Length(coefficients) - 1) { + let (rt, phi, theta) = BlochSphereCoordinates(coefficients[idxCoeff], coefficients[idxCoeff + 1]); + set disentanglingZ w/= idxCoeff / 2 <- 0.5 * phi; + set disentanglingY w/= idxCoeff / 2 <- 0.5 * theta; + set newCoefficients w/= idxCoeff / 2 <- rt; + } + + return (disentanglingY, disentanglingZ, newCoefficients); + } + +} + + diff --git a/Standard/src/Preparation/Deprecated.qs b/Standard/src/Preparation/Deprecated.qs new file mode 100644 index 00000000000..90640d77e02 --- /dev/null +++ b/Standard/src/Preparation/Deprecated.qs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Preparation { + open Microsoft.Quantum.Arithmetic; + open Microsoft.Quantum.Math; + + @Deprecated("Microsoft.Quantum.Preparation.PrepareSingleQubitPositivePauliEigenstate") + operation PrepareQubit(basis : Pauli, qubit : Qubit) : Unit { + PrepareSingleQubitPositivePauliEigenstate(basis, qubit); + } + + @Deprecated("Microsoft.Quantum.Preparation.PurifiedMixedState") + function QuantumROM(targetError: Double, coefficients: Double[]) + : ((Int, (Int, Int)), Double, ((LittleEndian, Qubit[]) => Unit is Adj + Ctl)) { + let preparation = PurifiedMixedState(targetError, coefficients); + return ( + preparation::Requirements!, + preparation::Norm, + preparation::Prepare + ); + } + + @Deprecated("Microsoft.Quantum.Preparation.PurifiedMixedStateRequirements") + function QuantumROMQubitCount(targetError: Double, nCoeffs: Int) + : (Int, (Int, Int)) { + return (PurifiedMixedStateRequirements(targetError, nCoeffs))!; + } + + @Deprecated("Microsoft.Quantum.Preparation.PrepareArbitraryStateCP") + operation PrepareArbitraryState(coefficients : ComplexPolar[], qubits : LittleEndian) + : Unit is Adj + Ctl { + PrepareArbitraryStateCP(coefficients, qubits); + } + + @Deprecated("Microsoft.Quantum.Preparation.PrepareArbitraryStateCP") + function StatePreparationComplexCoefficients (coefficients : ComplexPolar[]) : (LittleEndian => Unit is Adj + Ctl) { + return PrepareArbitraryStateCP(coefficients, _); + } + + @Deprecated("Microsoft.Quantum.Preparation.PrepareArbitraryStateD") + function StatePreparationPositiveCoefficients (coefficients : Double[]) + : (LittleEndian => Unit is Adj + Ctl) { + let nCoefficients = Length(coefficients); + mutable coefficientsComplexPolar = new ComplexPolar[nCoefficients]; + + for (idx in 0 .. nCoefficients - 1) { + set coefficientsComplexPolar w/= idx <- ComplexPolar(AbsD(coefficients[idx]), 0.0); + } + + return PrepareArbitraryStateCP(coefficientsComplexPolar, _); + } +} diff --git a/Standard/src/Preparation/Mixed.qs b/Standard/src/Preparation/Mixed.qs index c4bd4252788..aaf46f69a8c 100644 --- a/Standard/src/Preparation/Mixed.qs +++ b/Standard/src/Preparation/Mixed.qs @@ -7,12 +7,15 @@ namespace Microsoft.Quantum.Preparation { open Microsoft.Quantum.Math; /// # Summary - /// Prepares a qubit in the maximally mixed state. - /// - /// It prepares the given qubit in the $\boldone / 2$ state by applying the depolarizing channel + /// Prepares a qubit in the maximally mixed state. + /// + /// # Description + /// This operation prepares the given qubit in the $\boldone / 2$ state by applying the depolarizing channel /// $$ /// \begin{align} - /// \Omega(\rho) \mathrel{:=} \frac{1}{4} \sum_{\mu \in \{0, 1, 2, 3\}} \sigma\_{\mu} \rho \sigma\_{\mu}^{\dagger}, + /// \Omega(\rho) & \mathrel{:=} + /// \frac{1}{4} \sum_{\mu \in \{0, 1, 2, 3\}} + /// \sigma\_{\mu} \rho \sigma\_{\mu}^{\dagger}, /// \end{align} /// $$ /// where $\sigma\_i$ is the $i$th Pauli operator, and where @@ -31,60 +34,93 @@ namespace Microsoft.Quantum.Preparation { /// to pure states, but it acts as described in expectation. /// In particular, this operation can be used in process tomography /// to measure the *non-unital* components of a channel. + /// + /// As the mapping from an arbitrary density operator to the $\openone / 2$ + /// state is non-unitary, this operation is neither adjointable nor + /// controllable. operation PrepareSingleQubitIdentity(qubit : Qubit) : Unit { ApplyPauli([RandomSingleQubitPauli()], [qubit]); } /// # Summary /// Given a register, prepares that register in the maximally mixed state. - /// - /// The register is prepared in the $\boldone / 2^N$ state by applying the - /// complete depolarizing - /// channel to each qubit, where $N$ is the length of the register. + /// + /// # Description + /// The register is prepared in the $\boldone / 2^N$ state by applying the + /// complete depolarizing channel to each qubit, where $N$ is the length of + /// the register. /// /// # Input /// ## register /// A register whose state is to be depolarized in the manner /// described above. /// + /// # Remarks + /// The mixed state $\boldone / 2^N$ describing the result of + /// applying this operation to a state implicitly describes + /// an expectation value over random choices made in this operation. + /// Thus, for any single application, this operation maps pure states + /// to pure states, but it acts as described in expectation. + /// In particular, this operation can be used in process tomography + /// to measure the *non-unital* components of a channel. + /// + /// As the mapping from an arbitrary density operator to the $\openone / 2^N$ + /// state is non-unitary, this operation is neither adjointable nor + /// controllable. + /// /// # See Also - /// - @"microsoft.quantum.canon.preparesinglequbitidentity" + /// - Microsoft.Quantum.Preparation.PrepareSingleQubitIdentity operation PrepareIdentity(register : Qubit[]) : Unit { ApplyToEach(PrepareSingleQubitIdentity, register); } /// # Summary - /// Prepares a qubit in the +1 (`Zero`) eigenstate of the given Pauli operator. - /// If the identity operator is given, then the qubit is prepared in the maximally - /// mixed state. - /// + /// Prepares a qubit in the positive eigenstate of the given Pauli operator. + /// If the identity operator is given, then the qubit is prepared in the maximally + /// mixed state. + /// + /// # Descrition /// If the qubit was initially in the $\ket{0}$ state, this operation prepares the /// qubit in the $+1$ eigenstate of a given Pauli operator, or, for `PauliI`, - /// in the maximally mixed state instead (see ). - /// - /// If the qubit was in a state other than $\ket{0}$, this operation applies the following gates: - /// $H$ for `PauliX`, $HS$ for `PauliY`, $I$ for `PauliZ` and - /// for `PauliI`. + /// in the maximally mixed state instead + /// (see ). /// /// # Input /// ## basis /// A Pauli operator $P$. /// ## qubit - /// A qubit to be prepared. - operation PrepareQubit (basis : Pauli, qubit : Qubit) : Unit { - if (basis == PauliI) - { + /// A qubit to be prepared in the given eigenstate. + /// + /// # Remarks + /// As an invariant of this operation, measuring the input qubit in the basis + /// given by `basis` will result in a `Zero` (corresponding to the $+1$ + /// eigenspace of that basis) immediately following preparation. + /// + /// For example: + /// ```Q# + /// PrepareSingleQubitPositivePauliEigenstate(PauliY, q); + /// EqualityFactR(Measure([PauliY], [q]), Zero, "Error in preparation."); + /// ``` + /// + /// # Example + /// To prepare the $\ket{+}$ state on a qubit `q` initially in the $\ket{0}$ + /// state: + /// ```Q# + /// PrepareSingleQubitPositivePauliEigenstate(PauliX, q); + /// ``` + operation PrepareSingleQubitPositivePauliEigenstate(basis : Pauli, qubit : Qubit) + : Unit { + if (basis == PauliI) { PrepareSingleQubitIdentity(qubit); - } - elif (basis == PauliX) - { + } elif (basis == PauliX) { H(qubit); - } - elif (basis == PauliY) - { + } elif (basis == PauliY) { H(qubit); S(qubit); } + // NB: Since the input qubit starts off in the |0⟩ state as a precondition, + // the preparation for PauliZ is a no-op, so we fall through in that + // case. } } diff --git a/Standard/src/Preparation/Properties/NamespaceInfo.qs b/Standard/src/Preparation/Properties/NamespaceInfo.qs index e858cf88da3..7c15a5f056b 100644 --- a/Standard/src/Preparation/Properties/NamespaceInfo.qs +++ b/Standard/src/Preparation/Properties/NamespaceInfo.qs @@ -3,5 +3,21 @@ /// # Summary /// This namespace contains functions and operations for preparing qubits into -/// arbitrary initial states. +/// different states, given registers in specific initial states. +/// +/// # Description +/// This namespace provides operations for preparing different states, such as +/// entangled states, uniform superposition states, or arbitrary states +/// described by their coefficients in some basis. +/// +/// By contrast with reset operations, the state preparation +/// operations provided by this namespace in general assume that quantum +/// registers provided as input are in fixed initial states, such as +/// the all-zeros state $\ket{00 \cdots 0}$, and prepare desired states +/// accordingly. Thus, preparation operations are more likely to be adjointable, +/// representing "unpreparation," useful for returning returning qubits +/// to their initial state without using intermediate measurements. +/// +/// # See Also +/// - Microsoft.Quantum.Measurement namespace Microsoft.Quantum.Preparation {} diff --git a/Standard/src/Preparation/QuantumROM.qs b/Standard/src/Preparation/QuantumROM.qs index d2c984ba8fe..0a81c8cf268 100644 --- a/Standard/src/Preparation/QuantumROM.qs +++ b/Standard/src/Preparation/QuantumROM.qs @@ -10,49 +10,75 @@ namespace Microsoft.Quantum.Preparation { open Microsoft.Quantum.Arrays; /// # Summary - /// Uses the Quantum ROM technique to represent a given density matrix. - /// - /// Given a list of $N$ coefficients $\alpha_j$, this returns a unitary $U$ that uses the Quantum-ROM - /// technique to prepare - /// an approximation $\tilde\rho\sum_{j=0}^{N-1}p_j\ket{j}\bra{j}$ of the purification of the density matrix - /// $\rho=\sum_{j=0}^{N-1}\frac{|alpha_j|}{\sum_k |\alpha_k|}\ket{j}\bra{j}$. In this approximation, the - /// error $\epsilon$ is such that $|p_j-\frac{|alpha_j|}{\sum_k |\alpha_k|}|\le \epsilon / N$ and - /// $\|\tilde\rho - \rho\| \le \epsilon$. In other words, + /// Returns an operation that prepares a a purification of a given mixed + /// state. + /// + /// # Description + /// Uses the Quantum ROM technique to represent a given density matrix, + /// returning that representation as a state preparation operation. + /// + /// In particular, given a list of $N$ coefficients $\alpha_j$, this + /// function returns an operation that uses the Quantum ROM technique to + /// prepare an approximation /// $$ /// \begin{align} - /// U\ket{0}^{\lceil\log_2 N\rceil}\ket{0}^{m}=\sum_{j=0}^{N-1}\sqrt{p_j} \ket{j}\ket{\text{garbage}_j}. + /// \tilde\rho = \sum_{j = 0}^{N - 1} p_j \ket{j}\bra{j} /// \end{align} /// $$ + /// of the mixed state + /// $$ + /// \begin{align} + /// \rho = \sum_{j = 0}^{N-1}\ frac{|alpha_j|}{\sum_k |\alpha_k|} \ket{j}\bra{j}, + /// \end{align} + /// $$ + /// where each $p_j$ is an approximation to the given coefficient $\alpha_j$ + /// such that + /// $$ + /// \begin{align} + /// \left| p_j - \frac{ |\alpha_j| }{ \sum_k |\alpha_k| } \le \frac{\epsilon}{N} + /// \end{align} + /// $$ + /// for each $j$. + /// + /// When passed an index register and a register of garbage qubits, + /// initially in the state $\ket{0} \ket{00\cdots 0}, the returned operation + /// prepares both registers into the purification of $\tilde \rho$, + /// $$ + /// \begin{align} + /// \sum_{j=0}^{N-1} \sqrt{p_j} \ket{j}\ket{\text{garbage}_j}, + /// \end{align} + /// $$ + /// such that resetting and deallocating the garbage register enacts the + /// desired preparation to within the target error $\epsilon$. /// /// # Input /// ## targetError /// The target error $\epsilon$. /// ## coefficients - /// Array of $N$ coefficients specifying the probability of basis states. + /// Array of $N$ coefficients specifying the probability of basis states. /// Negative numbers $-\alpha_j$ will be treated as positive $|\alpha_j|$. /// /// # Output - /// ## First parameter - /// A tuple `(x,(y,z))` where `x = y + z` is the total number of qubits allocated, - /// `y` is the number of qubits for the `LittleEndian` register, and `z` is the Number - /// of garbage qubits. - /// ## Second parameter - /// The one-norm $\sum_j |\alpha_j|$ of the coefficient array. - /// ## Third parameter - /// The unitary $U$. + /// An operation that prepares $\tilde \rho$ as a purification onto a joint + /// index and garbage register. /// /// # Remarks - /// ## Example - /// The following code snippet prepares an purification of the $3$-qubit state - /// $\rho=\sum_{j=0}^{4}\frac{|alpha_j|}{\sum_k |\alpha_k|}\ket{j}\bra{j}$, where - /// $\vec\alpha=(1.0,2.0,3.0,4.0,5.0)$, and the error is `1e-3`; - /// ```qsharp - /// let coefficients = [1.0,2.0,3.0,4.0,5.0]; + /// The coefficients provided to this operation are normalized following the + /// 1-norm, such that the coefficients are always considered to describe a + /// valid categorical probability distribution. + /// + /// # Example + /// The following code snippet prepares an purification of the $3$-qubit state + /// $\rho=\sum_{j=0}^{4}\frac{|alpha_j|}{\sum_k |\alpha_k|}\ket{j}\bra{j}$, where + /// $\vec\alpha=(1.0, 2.0, 3.0, 4.0, 5.0)$, and the target error is + /// $10^{-3}$: + /// ```Q# + /// let coefficients = [1.0, 2.0, 3.0, 4.0, 5.0]; /// let targetError = 1e-3; - /// let ((nTotalQubits, (nIndexQubits, nGarbageQubits)), oneNorm, op) = QuantumROM(targetError, coefficients); - /// using (indexRegister = Qubit[nIndexQubits]) { - /// using (garbageRegister = Qubit[nGarbageQubits]) { - /// op(LittleEndian(indexRegister), garbageRegister); + /// let purifiedState = PurifiedMixedState(targetError, coefficients); + /// using (indexRegister = Qubit[purifiedState::Requirements::NIndexQubits]) { + /// using (garbageRegister = Qubit[purifiedState::Requirements::NGarbageQubits]) { + /// purifiedState::Prepare(LittleEndian(indexRegister), garbageRegister); /// } /// } /// ``` @@ -61,53 +87,62 @@ namespace Microsoft.Quantum.Preparation { /// - Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity /// Ryan Babbush, Craig Gidney, Dominic W. Berry, Nathan Wiebe, Jarrod McClean, Alexandru Paler, Austin Fowler, Hartmut Neven /// https://arxiv.org/abs/1805.03662 - function QuantumROM(targetError: Double, coefficients: Double[]) : ((Int, (Int, Int)), Double, ((LittleEndian, Qubit[]) => Unit is Adj + Ctl)) { + function PurifiedMixedState(targetError : Double, coefficients : Double[]) + : MixedStatePreparation { let nBitsPrecision = -Ceiling(Lg(0.5 * targetError)) + 1; let (oneNorm, keepCoeff, altIndex) = _QuantumROMDiscretization(nBitsPrecision, coefficients); let nCoeffs = Length(coefficients); let nBitsIndices = Ceiling(Lg(IntAsDouble(nCoeffs))); let op = _QuantumROMImpl(nBitsPrecision, nCoeffs, nBitsIndices, keepCoeff, altIndex, _, _); - let qubitCounts = QuantumROMQubitCount(targetError, nCoeffs); - return (qubitCounts, oneNorm, op); + let qubitCounts = PurifiedMixedStateRequirements(targetError, nCoeffs); + return MixedStatePreparation(qubitCounts, oneNorm, op); } /// # Summary /// Returns the total number of qubits that must be allocated - /// to the operation returned by `QuantumROM`. + /// in order to apply the operation returned by + /// @"microsoft.quantum.preparation.purifiedmixedstate". /// /// # Input /// ## targetError /// The target error $\epsilon$. - /// ## nCoeffs - /// Number of coefficients specified in `QuantumROM`. + /// ## nCoefficients + /// The number of coefficients to be specified in preparing a mixed state. /// /// # Output - /// ## First parameter - /// A tuple `(x,(y,z))` where `x = y + z` is the total number of qubits allocated, - /// `y` is the number of qubits for the `LittleEndian` register, and `z` is the Number - /// of garbage qubits. - function QuantumROMQubitCount(targetError: Double, nCoeffs: Int) : (Int, (Int, Int)) - { - let nBitsPrecision = -Ceiling(Lg(0.5*targetError))+1; - let nBitsIndices = Ceiling(Lg(IntAsDouble(nCoeffs))); - let nGarbageQubits = nBitsIndices + 2 * nBitsPrecision + 1; - let nTotal = nGarbageQubits + nBitsIndices; - return (nTotal, (nBitsIndices, nGarbageQubits)); + /// A description of how many qubits are required in total, and for each of + /// the index and garbage registers used by the + /// @"microsoft.quantum.preparation.purifiedmixedstate" function. + /// + /// # See Also + /// - Microsoft.Quantum.Preparation.PurifiedMixedState + function PurifiedMixedStateRequirements(targetError : Double, nCoefficients : Int) + : MixedStatePreparationRequirements { + let nBitsPrecision = -Ceiling(Lg(0.5*targetError)) + 1; + let nIndexQubits = Ceiling(Lg(IntAsDouble(nCoefficients))); + let nGarbageQubits = nIndexQubits + 2 * nBitsPrecision + 1; + let nTotal = nGarbageQubits + nIndexQubits; + return MixedStatePreparationRequirements(nTotal, (nIndexQubits, nGarbageQubits)); } // Implementation step of `QuantumROM`. This splits a single // qubit array into the subarrays required by the operation. - function _QuantumROMQubitManager(targetError: Double, nCoeffs: Int, qubits: Qubit[]) : ((LittleEndian, Qubit[]), Qubit[]) { - let (nTotal, (nIndexRegister, nGarbageQubits)) = QuantumROMQubitCount(targetError, nCoeffs); - let registers = Partitioned([nIndexRegister, nGarbageQubits], qubits); - return((LittleEndian(registers[0]), registers[1]), registers[2]); + function _PartitionedForQuantumROM(targetError: Double, nCoeffs: Int, qubits: Qubit[]) + : ((LittleEndian, Qubit[]), Qubit[]) { + let requirements = PurifiedMixedStateRequirements(targetError, nCoeffs); + let registers = Partitioned( + [requirements::NIndexQubits, requirements::NGarbageQubits], + qubits + ); + return ((LittleEndian(registers[0]), registers[1]), registers[2]); } // Classical processing // This discretizes the coefficients such that - // |coefficient[i] * oneNorm - discretizedCoefficient[i] * discreizedOneNorm| * nCoeffs <= 2^{1-bitsPrecision}. - function _QuantumROMDiscretization(bitsPrecision: Int, coefficients: Double[]) : (Double, Int[], Int[]) { + // |coefficient[i] * oneNorm - discretizedCoefficient[i] * discreizedOneNorm| * nCoeffs <= 2^{1-bitsPrecision}. + function _QuantumROMDiscretization(bitsPrecision: Int, coefficients: Double[]) + : (Double, Int[], Int[]) { let oneNorm = PNorm(1.0, coefficients); let nCoefficients = Length(coefficients); if (bitsPrecision > 31) { @@ -120,10 +155,13 @@ namespace Microsoft.Quantum.Preparation { fail $"State must have at least one coefficient > 0"; } - let barHeight = 2^bitsPrecision - 1; + let barHeight = 2 ^ bitsPrecision - 1; - mutable altIndex = RangeAsIntArray(0..nCoefficients-1); - mutable keepCoeff = Mapped(_QuantumROMDiscretizationRoundCoefficients(_, oneNorm, nCoefficients, barHeight), coefficients); + mutable altIndex = RangeAsIntArray(0..nCoefficients - 1); + mutable keepCoeff = Mapped( + _QuantumROMDiscretizationRoundCoefficients(_, oneNorm, nCoefficients, barHeight), + coefficients + ); // Calculate difference between number of discretized bars vs. maximum mutable bars = 0; @@ -133,117 +171,100 @@ namespace Microsoft.Quantum.Preparation { //Message($"Excess bars {bars}."); // Uniformly distribute excess bars across coefficients. for (idx in 0..AbsI(bars) - 1) { - if (bars > 0) { - set keepCoeff w/= idx <- keepCoeff[idx] - 1; - } else { - set keepCoeff w/= idx <- keepCoeff[idx] + 1; - } + set keepCoeff w/= idx <- keepCoeff[idx] + (bars > 0 ? -1 | +1); } - mutable barSink = new Int[nCoefficients]; - mutable barSource = new Int[nCoefficients]; - mutable nBarSink = 0; - mutable nBarSource = 0; + mutable barSink = new Int[0]; + mutable barSource = new Int[0]; for (idxCoeff in IndexRange(keepCoeff)) { if (keepCoeff[idxCoeff] > barHeight) { - set barSource w/= nBarSource <- idxCoeff; - set nBarSource = nBarSource + 1; + set barSource += [idxCoeff]; } elif (keepCoeff[idxCoeff] < barHeight) { - set barSink w/= nBarSink <- idxCoeff; - set nBarSink = nBarSink + 1; + set barSink += [idxCoeff]; } } for (rep in 0..nCoefficients * 10) { - if (nBarSource > 0 and nBarSink > 0) { - let idxSink = barSink[nBarSink-1]; - let idxSource = barSource[nBarSource-1]; - set nBarSink = nBarSink - 1; - set nBarSource = nBarSource - 1; + if (Length(barSink) > 0 and Length(barSource) > 0) { + let idxSink = Tail(barSink); + let idxSource = Tail(barSource); + set barSink = Most(barSink); + set barSource = Most(barSource); set keepCoeff w/= idxSource <- keepCoeff[idxSource] - barHeight + keepCoeff[idxSink]; set altIndex w/= idxSink <- idxSource; - if (keepCoeff[idxSource] < barHeight) - { - set barSink w/= nBarSink <- idxSource; - set nBarSink = nBarSink + 1; - } - elif(keepCoeff[idxSource] > barHeight) - { - set barSource w/= nBarSource <- idxSource; - set nBarSource = nBarSource + 1; + if (keepCoeff[idxSource] < barHeight) { + set barSink += [idxSource]; + } elif (keepCoeff[idxSource] > barHeight) { + set barSource += [idxSource]; } - } - elif (nBarSource > 0) { + } elif (Length(barSource) > 0) { //Message($"rep: {rep}, nBarSource {nBarSource}."); - let idxSource = barSource[nBarSource-1]; - set nBarSource = nBarSource - 1; + let idxSource = Tail(barSource); + set barSource = Most(barSource); set keepCoeff w/= idxSource <- barHeight; } else { return (oneNorm, keepCoeff, altIndex); } } - + return (oneNorm, keepCoeff, altIndex); } - + // Used in QuantumROM implementation. function _QuantumROMDiscretizationRoundCoefficients(coefficient: Double, oneNorm: Double, nCoefficients: Int, barHeight: Int) : Int { return Round((AbsD(coefficient) / oneNorm) * IntAsDouble(nCoefficients) * IntAsDouble(barHeight)); } // Used in QuantumROM implementation. - operation _QuantumROMImpl(nBitsPrecision: Int, nCoeffs: Int, nBitsIndices: Int, keepCoeff: Int[], altIndex: Int[], indexRegister: LittleEndian, garbageRegister: Qubit[]) : Unit { - body (...) { - let unitaryGenerator = (nCoeffs, _QuantumROMWriteBitStringUnitary(_, keepCoeff, altIndex)); - let garbageIdx0 = nBitsIndices; - let garbageIdx1 = garbageIdx0 + nBitsPrecision; - let garbageIdx2 = garbageIdx1 + nBitsPrecision; - let garbageIdx3 = garbageIdx2 + 1; - - let altIndexRegister = LittleEndian(garbageRegister[0..garbageIdx0-1]); - let keepCoeffRegister = LittleEndian(garbageRegister[garbageIdx0..garbageIdx1 - 1]); - let uniformKeepCoeffRegister = LittleEndian(garbageRegister[garbageIdx1..garbageIdx2 - 1]); - let flagQubit = garbageRegister[garbageIdx3 - 1]; - - // Create uniform superposition over index and alt coeff register. - PrepareUniformSuperposition(nCoeffs, indexRegister); - ApplyToEachCA(H, uniformKeepCoeffRegister!); - - // Write bitstrings to altIndex and keepCoeff register. - MultiplexOperationsFromGenerator(unitaryGenerator, indexRegister, (keepCoeffRegister, altIndexRegister)); - - // Perform comparison - CompareUsingRippleCarry(uniformKeepCoeffRegister, keepCoeffRegister, flagQubit); - - let indexRegisterSize = Length(indexRegister!); - - // Swap in register based on comparison - for(idx in 0..nBitsIndices-1){ - (Controlled SWAP)([flagQubit], (indexRegister![nBitsIndices - idx - 1], altIndexRegister![idx])); - } - } - adjoint auto; - controlled auto; - adjoint controlled auto; - } + operation _QuantumROMImpl( + nBitsPrecision: Int, nCoeffs: Int, nBitsIndices: Int, keepCoeff: Int[], + altIndex: Int[], indexRegister: LittleEndian, garbageRegister: Qubit[] + ) + : Unit is Adj + Ctl { + let unitaryGenerator = (nCoeffs, _QuantumROMWriteBitStringUnitary(_, keepCoeff, altIndex)); + let partitionedRegisters = Partitioned([nBitsIndices, nBitsPrecision, nBitsPrecision, 1], garbageRegister); + + let altIndexRegister = LittleEndian(partitionedRegisters[0]); + let keepCoeffRegister = LittleEndian(partitionedRegisters[1]); + let uniformKeepCoeffRegister = LittleEndian(partitionedRegisters[2]); + let flagQubit = partitionedRegisters[3][0]; + + // Create uniform superposition over index and alt coeff register. + PrepareUniformSuperposition(nCoeffs, indexRegister); + ApplyToEachCA(H, uniformKeepCoeffRegister!); + // Write bitstrings to altIndex and keepCoeff register. + MultiplexOperationsFromGenerator(unitaryGenerator, indexRegister, (keepCoeffRegister, altIndexRegister)); + + // Perform comparison + CompareUsingRippleCarry(uniformKeepCoeffRegister, keepCoeffRegister, flagQubit); + + // Swap in register based on comparison + Controlled ApplyToEachCA( + [flagQubit], + ( + SWAP, + Zip(Reversed(indexRegister!), altIndexRegister!) + ) + ); + } // Used in QuantumROM implementation. - function _QuantumROMWriteBitStringUnitary(idx: Int, keepCoeff: Int[], altIndex: Int[]) : ((LittleEndian, LittleEndian) => Unit is Adj + Ctl) { + function _QuantumROMWriteBitStringUnitary(idx : Int, keepCoeff : Int[], altIndex : Int[]) + : ((LittleEndian, LittleEndian) => Unit is Adj + Ctl) { return _QuantumROMWriteBitString(idx, keepCoeff, altIndex, _, _); } // Used in QuantumROM implementation. - operation _QuantumROMWriteBitString(idx: Int, keepCoeff: Int[], altIndex: Int[], keepCoeffRegister: LittleEndian, altIndexRegister: LittleEndian) : Unit { - body (...) { - ApplyXorInPlace(keepCoeff[idx], keepCoeffRegister); - ApplyXorInPlace(altIndex[idx], altIndexRegister); - } - adjoint auto; - controlled auto; - adjoint controlled auto; + operation _QuantumROMWriteBitString( + idx: Int, keepCoeff: Int[], altIndex: Int[], + keepCoeffRegister: LittleEndian, altIndexRegister: LittleEndian + ) + : Unit is Adj + Ctl { + ApplyXorInPlace(keepCoeff[idx], keepCoeffRegister); + ApplyXorInPlace(altIndex[idx], altIndexRegister); } diff --git a/Standard/src/Preparation/Reference.qs b/Standard/src/Preparation/Reference.qs index 9203f0439ec..170fccdc557 100644 --- a/Standard/src/Preparation/Reference.qs +++ b/Standard/src/Preparation/Reference.qs @@ -63,7 +63,7 @@ namespace Microsoft.Quantum.Preparation { /// - PrepareChoiStateA /// - PrepareChoiStateC /// - PrepareChoiStateCA - operation PrepareChoiState (op : (Qubit[] => Unit), reference : Qubit[], target : Qubit[]) : Unit { + operation PrepareChoiState(op : (Qubit[] => Unit), reference : Qubit[], target : Qubit[]) : Unit { PrepareEntangledState(reference, target); op(target); } @@ -87,7 +87,7 @@ namespace Microsoft.Quantum.Preparation { /// /// # See Also /// - PrepareChoiState - operation PrepareChoiStateA (op : (Qubit[] => Unit is Adj), reference : Qubit[], target : Qubit[]) : Unit is Adj { + operation PrepareChoiStateA(op : (Qubit[] => Unit is Adj), reference : Qubit[], target : Qubit[]) : Unit is Adj { PrepareEntangledState(reference, target); op(target); } diff --git a/Standard/src/Preparation/StatePreparation.qs b/Standard/src/Preparation/StatePreparation.qs deleted file mode 100644 index ec3f9a6d845..00000000000 --- a/Standard/src/Preparation/StatePreparation.qs +++ /dev/null @@ -1,241 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -namespace Microsoft.Quantum.Preparation { - open Microsoft.Quantum.Canon; - open Microsoft.Quantum.Arithmetic; - open Microsoft.Quantum.Intrinsic; - open Microsoft.Quantum.Math; - open Microsoft.Quantum.Arrays; - - // This library returns operations that prepare a specified quantum state - // from the computational basis state $\ket{0...0}$. - - /// # Summary - /// Returns an operation that prepares the given quantum state. - /// - /// The returned operation $U$ prepares an arbitrary quantum - /// state $\ket{\psi}$ with positive coefficients $\alpha_j\ge 0$ from - /// the $n$-qubit computational basis state $\ket{0...0}$. - /// - /// The action of U on a newly-allocated register is given by - /// $$ - /// \begin{align} - /// U \ket{0\cdots 0} = \ket{\psi} = \frac{\sum_{j=0}^{2^n-1}\alpha_j \ket{j}}{\sqrt{\sum_{j=0}^{2^n-1}|\alpha_j|^2}}. - /// \end{align} - /// $$ - /// - /// # Input - /// ## coefficients - /// Array of up to $2^n$ coefficients $\alpha_j$. The $j$th coefficient - /// indexes the number state $\ket{j}$ encoded in little-endian format. - /// - /// # Output - /// A state-preparation unitary operation $U$. - /// - /// # Remarks - /// Negative input coefficients $\alpha_j < 0$ will be treated as though - /// positive with value $|\alpha_j|$. `coefficients` will be padded with - /// elements $\alpha_j = 0.0$ if fewer than $2^n$ are specified. - /// - /// ## Example - /// The following snippet prepares the quantum state $\ket{\psi}=\sqrt{1/8}\ket{0}+\sqrt{7/8}\ket{2}$ - /// in the qubit register `qubitsLE`. - /// ```qsharp - /// let amplitudes = [Sqrt(0.125), 0.0, Sqrt(0.875), 0.0]; - /// let op = StatePreparationPositiveCoefficients(amplitudes); - /// using (qubits = Qubit[2]) { - /// let qubitsLE = LittleEndian(qubits); - /// op(qubitsLE); - /// } - /// ``` - function StatePreparationPositiveCoefficients (coefficients : Double[]) : (LittleEndian => Unit is Adj + Ctl) { - let nCoefficients = Length(coefficients); - mutable coefficientsComplexPolar = new ComplexPolar[nCoefficients]; - - for (idx in 0 .. nCoefficients - 1) { - set coefficientsComplexPolar w/= idx <- ComplexPolar(AbsD(coefficients[idx]), 0.0); - } - - return PrepareArbitraryState(coefficientsComplexPolar, _); - } - - /// # Summary - /// Returns an operation that prepares a specific quantum state. - /// - /// The returned operation $U$ prepares an arbitrary quantum - /// state $\ket{\psi}$ with complex coefficients $r_j e^{i t_j}$ from - /// the $n$-qubit computational basis state $\ket{0...0}$. - /// - /// The action of U on a newly-allocated register is given by - /// $$ - /// \begin{align} - /// U\ket{0...0}=\ket{\psi}=\frac{\sum_{j=0}^{2^n-1}r_j e^{i t_j}\ket{j}}{\sqrt{\sum_{j=0}^{2^n-1}|r_j|^2}}. - /// \end{align} - /// $$ - /// - /// # Input - /// ## coefficients - /// Array of up to $2^n$ complex coefficients represented by their - /// absolute value and phase $(r_j, t_j)$. The $j$th coefficient - /// indexes the number state $\ket{j}$ encoded in little-endian format. - /// - /// # Output - /// A state-preparation unitary operation $U$. - /// - /// # Remarks - /// Negative input coefficients $r_j < 0$ will be treated as though - /// positive with value $|r_j|$. `coefficients` will be padded with - /// elements $(r_j, t_j) = (0.0, 0.0)$ if fewer than $2^n$ are - /// specified. - /// - /// ## Example - /// The following snippet prepares the quantum state $\ket{\psi}=e^{i 0.1}\sqrt{1/8}\ket{0}+\sqrt{7/8}\ket{2}$ - /// in the qubit register `qubitsLE`. - /// ```qsharp - /// let amplitudes = [Sqrt(0.125), 0.0, Sqrt(0.875), 0.0]; - /// let phases = [0.1, 0.0, 0.0, 0.0]; - /// mutable complexNumbers = new ComplexPolar[4]; - /// for (idx in 0..3) { - /// set complexNumbers[idx] = ComplexPolar(amplitudes[idx], phases[idx]); - /// } - /// let op = StatePreparationComplexCoefficients(complexNumbers); - /// using (qubits = Qubit[2]) { - /// let qubitsLE = LittleEndian(qubits); - /// op(qubitsLE); - /// } - /// ``` - function StatePreparationComplexCoefficients (coefficients : ComplexPolar[]) : (LittleEndian => Unit is Adj + Ctl) { - return PrepareArbitraryState(coefficients, _); - } - - - /// # Summary - /// Returns an operation that prepares a given quantum state. - /// - /// The returned operation $U$ prepares an arbitrary quantum - /// state $\ket{\psi}$ with complex coefficients $r_j e^{i t_j}$ from - /// the $n$-qubit computational basis state $\ket{0...0}$. - /// - /// $$ - /// \begin{align} - /// U\ket{0...0}=\ket{\psi}=\frac{\sum_{j=0}^{2^n-1}r_j e^{i t_j}\ket{j}}{\sqrt{\sum_{j=0}^{2^n-1}|r_j|^2}}. - /// \end{align} - /// $$ - /// - /// # Input - /// ## coefficients - /// Array of up to $2^n$ complex coefficients represented by their - /// absolute value and phase $(r_j, t_j)$. The $j$th coefficient - /// indexes the number state $\ket{j}$ encoded in little-endian format. - /// - /// ## qubits - /// Qubit register encoding number states in little-endian format. This is - /// expected to be initialized in the computational basis state - /// $\ket{0...0}$. - /// - /// # Remarks - /// Negative input coefficients $r_j < 0$ will be treated as though - /// positive with value $|r_j|$. `coefficients` will be padded with - /// elements $(r_j, t_j) = (0.0, 0.0)$ if fewer than $2^n$ are - /// specified. - /// - /// # References - /// - Synthesis of Quantum Logic Circuits - /// Vivek V. Shende, Stephen S. Bullock, Igor L. Markov - /// https://arxiv.org/abs/quant-ph/0406176 - operation PrepareArbitraryState (coefficients : ComplexPolar[], qubits : LittleEndian) : Unit is Adj + Ctl { - // pad coefficients at tail length to a power of 2. - let coefficientsPadded = Padded(-2 ^ Length(qubits!), ComplexPolar(0.0, 0.0), coefficients); - let target = (qubits!)[0]; - let op = (Adjoint _PrepareArbitraryState(coefficientsPadded, _, _))(_, target); - - op( - // Determine what controls to apply to `op`. - Length(qubits!) > 1 - ? LittleEndian((qubits!)[1 .. Length(qubits!) - 1]) - | LittleEndian(new Qubit[0]) - ); - } - - - /// # Summary - /// Implementation step of arbitrary state preparation procedure. - /// - /// # See Also - /// - PrepareArbitraryState - /// - Microsoft.Quantum.Canon.MultiplexPauli - operation _PrepareArbitraryState(coefficients : ComplexPolar[], control : LittleEndian, target : Qubit) : Unit is Adj + Ctl - { - // For each 2D block, compute disentangling single-qubit rotation parameters - let (disentanglingY, disentanglingZ, newCoefficients) = _StatePreparationSBMComputeCoefficients(coefficients); - MultiplexPauli(disentanglingZ, PauliZ, control, target); - MultiplexPauli(disentanglingY, PauliY, control, target); - - // target is now in |0> state up to the phase given by arg of newCoefficients. - - // Continue recursion while there are control qubits. - if (Length(control!) == 0) - { - let (abs, arg) = newCoefficients[0]!; - Exp([PauliI], -1.0 * arg, [target]); - } - else - { - let newControl = LittleEndian((control!)[1 .. Length(control!) - 1]); - let newTarget = (control!)[0]; - _PrepareArbitraryState(newCoefficients, newControl, newTarget); - } - } - - - /// # Summary - /// Computes the Bloch sphere coordinates for a single-qubit state. - /// - /// Given two complex numbers $a0, a1$ that represent the qubit state, computes coordinates - /// on the Bloch sphere such that - /// $a0 \ket{0} + a1 \ket{1} = r e^{it}(e^{-i \phi /2}\cos{(\theta/2)}\ket{0}+e^{i \phi /2}\sin{(\theta/2)}\ket{1})$. - /// - /// # Input - /// ## a0 - /// Complex coefficient of state $\ket{0}$. - /// ## a1 - /// Complex coefficient of state $\ket{1}$. - /// - /// # Output - /// A tuple containing `(ComplexPolar(r, t), phi, theta)`. - function BlochSphereCoordinates (a0 : ComplexPolar, a1 : ComplexPolar) : (ComplexPolar, Double, Double) - { - let abs0 = AbsComplexPolar(a0); - let abs1 = AbsComplexPolar(a1); - let arg0 = ArgComplexPolar(a0); - let arg1 = ArgComplexPolar(a1); - let r = Sqrt(abs0 * abs0 + abs1 * abs1); - let t = 0.5 * (arg0 + arg1); - let phi = arg1 - arg0; - let theta = 2.0 * ArcTan2(abs1, abs0); - return (ComplexPolar(r, t), phi, theta); - } - - /// # Summary - /// Implementation step of arbitrary state preparation procedure. - /// # See Also - /// - Microsoft.Quantum.Canon.PrepareArbitraryState - function _StatePreparationSBMComputeCoefficients (coefficients : ComplexPolar[]) : (Double[], Double[], ComplexPolar[]) { - mutable disentanglingZ = new Double[Length(coefficients) / 2]; - mutable disentanglingY = new Double[Length(coefficients) / 2]; - mutable newCoefficients = new ComplexPolar[Length(coefficients) / 2]; - - for (idxCoeff in 0 .. 2 .. Length(coefficients) - 1) { - let (rt, phi, theta) = BlochSphereCoordinates(coefficients[idxCoeff], coefficients[idxCoeff + 1]); - set disentanglingZ w/= idxCoeff / 2 <- 0.5 * phi; - set disentanglingY w/= idxCoeff / 2 <- 0.5 * theta; - set newCoefficients w/= idxCoeff / 2 <- rt; - } - - return (disentanglingY, disentanglingZ, newCoefficients); - } - -} - - diff --git a/Standard/src/Preparation/Types.qs b/Standard/src/Preparation/Types.qs new file mode 100644 index 00000000000..70c8392f605 --- /dev/null +++ b/Standard/src/Preparation/Types.qs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Preparation { + open Microsoft.Quantum.Arithmetic; + + /// # Summary + /// Represents a particular mixed state that can be prepared on an index + /// and a garbage register. + /// + /// # Input + /// ## Requirements + /// Specifies the size of the qubit registers required to prepare the + /// mixed state represented by this UDT value. + /// ## Norm + /// Specifies the 1-norm of the coefficients used to define this mixed + /// state. + /// ## Prepare + /// An operation that, given an index register and a garbage register initially + /// in the $\ket{0}$ and $\ket{00\cdots 0}$ states (respectively), + /// prepares the state represented by this UDT value on those registers. + /// + /// # See Also + /// - Microsoft.Quantum.PurifiedMixedState + newtype MixedStatePreparation = ( + Requirements: MixedStatePreparationRequirements, + Norm: Double, + Prepare: ((LittleEndian, Qubit[]) => Unit is Adj + Ctl) + ); + + /// # Summary + /// Represents the number of qubits required in order to prepare a given + /// mixed state. + /// + /// # Input + /// ## NTotalQubits + /// The total number of qubits required by the represented state preparation + /// operation. + /// ## NIndexQubits + /// The number of qubits required for the index register used by the + /// represented state preparation operation. + /// ## NGarbageQubits + /// The number of qubits required for the garbage register used by the + /// represented state preparation operation. + /// + /// # See Also + /// - Microsoft.Quantum.PurifiedMixedState + newtype MixedStatePreparationRequirements = ( + NTotalQubits: Int, + ( + NIndexQubits: Int, + NGarbageQubits: Int + ) + ); + +} diff --git a/Standard/src/Preparation/UniformSuperposition.qs b/Standard/src/Preparation/UniformSuperposition.qs index 177c43dcef9..727ccb4a954 100644 --- a/Standard/src/Preparation/UniformSuperposition.qs +++ b/Standard/src/Preparation/UniformSuperposition.qs @@ -11,32 +11,37 @@ namespace Microsoft.Quantum.Preparation { open Microsoft.Quantum.Math; /// # Summary - /// Creates a uniform superposition over states that encode 0 through `nIndices`. + /// Creates a uniform superposition over all integers less than a given + /// index. /// - /// That is, this unitary $U$ creates a uniform superposition over all number states - /// $0$ to $M-1$, given an input state $\ket{0\cdots 0}$. In other words, + /// # Description + /// Given a register of qubits representing a little-endian encoded integer + /// and initially in the state $\ket{0}$, this operation prepares the + /// register in the uniform superposition /// $$ /// \begin{align} - /// U\ket{0}=\frac{1}{\sqrt{M}}\sum_{j=0}^{M-1}\ket{j}. + /// \frac{1}{\sqrt{M}} \sum_{j = 0}^{M - 1} \ket{j} /// \end{align} - /// $$. + /// $$ + /// for a given integer $M$. /// /// # Input /// ## nIndices /// The desired number of states $M$ in the uniform superposition. /// ## indexRegister /// The qubit register that stores the number states in `LittleEndian` format. - /// This register must be able to store the number $M-1$, and is assumed to be - /// initialized in the state $\ket{0\cdots 0}$. + /// This register must be able to store the number $M - 1$, and is assumed to be + /// initialized in the state $\ket{0}$ (represented by the computational + /// basis state $\ket{0\cdots 0}). /// - /// # Remarks - /// ## Example + /// # Example /// The following example prepares the state $\frac{1}{\sqrt{6}}\sum_{j=0}^{5}\ket{j}$ - /// on $3$ qubits. + /// on $3$ qubits: /// ``` Q# /// let nIndices = 6; - /// using(indexRegister = Qubit[3]) { + /// using (indexRegister = Qubit[3]) { /// PrepareUniformSuperposition(nIndices, LittleEndian(indexRegister)); + /// // ... /// } /// ``` operation PrepareUniformSuperposition(nIndices: Int, indexRegister: LittleEndian) : Unit is Adj + Ctl { @@ -44,49 +49,49 @@ namespace Microsoft.Quantum.Preparation { fail $"Cannot prepare uniform superposition over {nIndices} state."; } elif (nIndices == 1) { // Superposition over one state, so do nothing. - } elif (nIndices == 2){ + } elif (nIndices == 2) { H(indexRegister![0]); } else { let nQubits = Ceiling(Lg(IntAsDouble(nIndices))); - if (nQubits > Length(indexRegister!)){ + if (nQubits > Length(indexRegister!)) { fail $"Cannot prepare uniform superposition over {nIndices} states as it is larger than the qubit register."; } - using (flagQubit = Qubit[3]) { + using (flagQubits = Qubit[3]) { let targetQubits = indexRegister![0..nQubits - 1]; - let qubits = flagQubit + targetQubits; - let stateOracle = StateOracle(PrepareUniformSuperposition_(nIndices, nQubits, _, _)); + let qubits = flagQubits + targetQubits; + let stateOracle = StateOracle(_PrepareUniformSuperposition(nIndices, nQubits, _, _)); (StandardAmplitudeAmplification(1, stateOracle, 0))(qubits); - ApplyToEachCA(X, flagQubit); + ApplyToEachCA(X, flagQubits); } } } /// # Summary /// Implementation step of - operation PrepareUniformSuperposition_(nIndices: Int, nQubits: Int, idxFlag: Int, qubits: Qubit[]) : Unit { - body (...) { - let targetQubits = qubits[3..3 + nQubits-1]; - let flagQubit = qubits[0]; - let auxillaryQubits = qubits[1..2]; - let theta = ArcSin(Sqrt(IntAsDouble(2^nQubits)/IntAsDouble(nIndices)) * Sin(PI() / 6.0)); - //let theta = PI() * 0.5; + operation _PrepareUniformSuperposition(nIndices : Int, nQubits : Int, idxFlag : Int, qubits : Qubit[]) + : Unit is Adj + Ctl { + let targetQubits = qubits[3..3 + nQubits - 1]; + let flagQubit = qubits[0]; + let auxillaryQubits = qubits[1..2]; + let theta = ArcSin( + Sqrt(IntAsDouble(2 ^ nQubits) / IntAsDouble(nIndices)) * + Sin(PI() / 6.0) + ); - ApplyToEachCA(H, targetQubits); - using(compareQubits = Qubit[nQubits]) { + ApplyToEachCA(H, targetQubits); + using (compareQubits = Qubit[nQubits]) { + within { ApplyXorInPlace(nIndices - 1, LittleEndian(compareQubits)); + } apply { CompareUsingRippleCarry(LittleEndian(targetQubits), LittleEndian(compareQubits), auxillaryQubits[0]); X(auxillaryQubits[0]); - ApplyXorInPlace(nIndices - 1, LittleEndian(compareQubits)); } - Exp([PauliY], -theta, [auxillaryQubits[1]]); - (Controlled X)(auxillaryQubits, flagQubit); } - adjoint auto; - controlled auto; - adjoint controlled auto; + Exp([PauliY], -theta, [auxillaryQubits[1]]); + (Controlled X)(auxillaryQubits, flagQubit); } } diff --git a/Standard/tests/Math/ConvertTests.qs b/Standard/tests/Math/ConvertTests.qs new file mode 100644 index 00000000000..2b790e24220 --- /dev/null +++ b/Standard/tests/Math/ConvertTests.qs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Math.Tests { + open Microsoft.Quantum.Math; + open Microsoft.Quantum.Diagnostics; + + @Test("QuantumSimulator") + function DoubleAsComplexPolarTest() : Unit { + EqualityFactCP(ComplexPolar(1.0, 0.0), DoubleAsComplexPolar(1.0), "Expected 1.0 == 1.0 exp(0)."); + EqualityFactCP(ComplexPolar(3.0, PI()), DoubleAsComplexPolar(-3.0), "Expected -3.0 == 3.0 exp(𝑖π)."); + } +} diff --git a/Standard/tests/Preparation/ArbitraryTests.qs b/Standard/tests/Preparation/ArbitraryTests.qs new file mode 100644 index 00000000000..1ddc35ffade --- /dev/null +++ b/Standard/tests/Preparation/ArbitraryTests.qs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +namespace Microsoft.Quantum.Preparation.Tests { + open Microsoft.Quantum.Preparation; + open Microsoft.Quantum.Diagnostics; + open Microsoft.Quantum.Math; + + @Test("QuantumSimulator") + function BlochSphereCoordinatesFact() : Unit { + let coefficients = ( + ComplexPolar(Sqrt(2.0) / Sqrt(3.0), 0.0), + ComplexPolar(1.0 / Sqrt(3.0), PI() / 3.0) + ); + let (prefactor, phi, theta) = BlochSphereCoordinates(coefficients); + NearEqualityFactCP(prefactor, ComplexPolar(1.0, 0.5235987755982988)); + NearEqualityFactD(phi, 1.0471975511965976); + NearEqualityFactD(theta, 1.9106332362490186); + } +} diff --git a/Standard/tests/StatePreparationTests.qs b/Standard/tests/Preparation/StatePreparationTests.qs similarity index 57% rename from Standard/tests/StatePreparationTests.qs rename to Standard/tests/Preparation/StatePreparationTests.qs index b0d03a1295f..f8b1b0f284d 100644 --- a/Standard/tests/StatePreparationTests.qs +++ b/Standard/tests/Preparation/StatePreparationTests.qs @@ -9,100 +9,88 @@ namespace Microsoft.Quantum.Tests { open Microsoft.Quantum.Math; open Microsoft.Quantum.Measurement; open Microsoft.Quantum.Arrays; + open Microsoft.Quantum.Diagnostics; // number of qubits, abs(amplitude), phase - newtype StatePreparationTestCase = (Int, Double[], Double[]); - + newtype StatePreparationTestCase = ( + NQubits: Int, + Magnitudes: Double[], + Phases: Double[] + ); + @Test("QuantumSimulator") operation StatePreparationPositiveCoefficientsTest () : Unit { - let tolerance = 1E-09; - mutable testCases = new StatePreparationTestCase[100]; - mutable nTests = 0; - - // Test positive coefficients. - set testCases w/= nTests <- StatePreparationTestCase(1, [0.773761, 0.633478], [0.0, 0.0]); - set nTests = nTests + 1; - set testCases w/= nTests <- StatePreparationTestCase(2, [0.183017, 0.406973, 0.604925, 0.659502], [0.0, 0.0, 0.0, 0.0]); - set nTests = nTests + 1; - set testCases w/= nTests <- StatePreparationTestCase(3, [0.0986553, 0.359005, 0.465689, 0.467395, 0.419893, 0.118445, 0.461883, 0.149609], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); - set nTests = nTests + 1; - set testCases w/= nTests <- StatePreparationTestCase(4, [0.271471, 0.0583654, 0.11639, 0.36112, 0.307383, 0.193371, 0.274151, 0.332542, 0.130172, 0.222546, 0.314879, 0.210704, 0.212429, 0.245518, 0.30666, 0.22773], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); - set nTests = nTests + 1; - - // Test negative coefficients. Should give same probabilities as positive coefficients. - set testCases w/= nTests <- StatePreparationTestCase(1, [-0.773761, 0.633478], [0.0, 0.0]); - set nTests = nTests + 1; - set testCases w/= nTests <- StatePreparationTestCase(2, [0.183017, -0.406973, 0.604925, 0.659502], [0.0, 0.0, 0.0, 0.0]); - set nTests = nTests + 1; - set testCases w/= nTests <- StatePreparationTestCase(3, [0.0986553, -0.359005, 0.465689, -0.467395, 0.419893, 0.118445, -0.461883, 0.149609], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); - set nTests = nTests + 1; - set testCases w/= nTests <- StatePreparationTestCase(4, [-0.271471, 0.0583654, 0.11639, 0.36112, -0.307383, 0.193371, -0.274151, 0.332542, 0.130172, 0.222546, 0.314879, -0.210704, 0.212429, 0.245518, -0.30666, -0.22773], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); - set nTests = nTests + 1; - - // Test unnormalized coefficients - set testCases w/= nTests <- StatePreparationTestCase(3, [1.0986553, 0.359005, 0.465689, -0.467395, 0.419893, 0.118445, 0.461883, 0.149609], new Double[0]); - set nTests = nTests + 1; - - // Test missing coefficients - set testCases w/= nTests <- StatePreparationTestCase(3, [1.0986553, 0.359005, 0.465689, -0.467395, 0.419893, 0.118445], new Double[0]); - set nTests = nTests + 1; - + let testCases = [ + // Test positive coefficients. + StatePreparationTestCase(1, [0.773761, 0.633478], [0.0, 0.0]), + StatePreparationTestCase(2, [0.183017, 0.406973, 0.604925, 0.659502], [0.0, 0.0, 0.0, 0.0]), + StatePreparationTestCase(3, [0.0986553, 0.359005, 0.465689, 0.467395, 0.419893, 0.118445, 0.461883, 0.149609], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), + StatePreparationTestCase(4, [0.271471, 0.0583654, 0.11639, 0.36112, 0.307383, 0.193371, 0.274151, 0.332542, 0.130172, 0.222546, 0.314879, 0.210704, 0.212429, 0.245518, 0.30666, 0.22773], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), + + // Test negative coefficients. Should give same probabilities as positive coefficients. + StatePreparationTestCase(1, [-0.773761, 0.633478], [0.0, 0.0]), + StatePreparationTestCase(2, [0.183017, -0.406973, 0.604925, 0.659502], [0.0, 0.0, 0.0, 0.0]), + StatePreparationTestCase(3, [0.0986553, -0.359005, 0.465689, -0.467395, 0.419893, 0.118445, -0.461883, 0.149609], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), + StatePreparationTestCase(4, [-0.271471, 0.0583654, 0.11639, 0.36112, -0.307383, 0.193371, -0.274151, 0.332542, 0.130172, 0.222546, 0.314879, -0.210704, 0.212429, 0.245518, -0.30666, -0.22773], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), + + // Test unnormalized coefficients + StatePreparationTestCase(3, [1.0986553, 0.359005, 0.465689, -0.467395, 0.419893, 0.118445, 0.461883, 0.149609], new Double[0]), + + // Test missing coefficients + StatePreparationTestCase(3, [1.0986553, 0.359005, 0.465689, -0.467395, 0.419893, 0.118445], new Double[0]) + ]; + // Loop over multiple qubit tests - for (idxTest in 0 .. nTests - 1) { - let (nQubits, coefficientsAmplitude, coefficientsPhase) = testCases[idxTest]!; - let nCoefficients = Length(coefficientsAmplitude); - - // Test negative coefficients. Should give same results as positive coefficients. - using (qubits = Qubit[nQubits]) { + for ((idxTestCase, testCase) in Enumerated(testCases)) { + using (qubits = Qubit[testCase::NQubits]) { let qubitsLE = LittleEndian(qubits); - let op = StatePreparationPositiveCoefficients(coefficientsAmplitude); + let op = StatePreparationPositiveCoefficients(testCase::Magnitudes); op(qubitsLE); - let normalizedCoefficients = PNormalized(2.0, coefficientsAmplitude); - - for (idxCoeff in 0 .. nCoefficients - 1) { - let amp = normalizedCoefficients[idxCoeff]; - let prob = amp * amp; - AssertProbInt(idxCoeff, prob, qubitsLE, tolerance); + let normalizedCoefficients = PNormalized(2.0, testCase::Magnitudes); + + for ((idxCoefficient, coefficient) in Enumerated(normalizedCoefficients)) { + AssertProbInt(idxCoefficient, coefficient * coefficient, qubitsLE, tolerance); } - + ResetAll(qubits); } } } - - + + // Test phase factor on 1-qubit uniform superposition. + @Test("QuantumSimulator") operation StatePreparationComplexCoefficientsQubitPhaseTest () : Unit { - + let tolerance = 1E-09; mutable testCases = new StatePreparationTestCase[10]; mutable nTests = 0; - + // Test phase factor on uniform superposition. set testCases w/= nTests <- StatePreparationTestCase(1, [1.0, 1.0], [0.01, -0.01]); set nTests = nTests + 1; set testCases w/= nTests <- StatePreparationTestCase(1, [1.0, 1.0], [0.01, -0.05]); set nTests = nTests + 1; - + // Loop over tests for (idxTest in 0 .. nTests - 1) { let (nQubits, coefficientsAmplitude, coefficientsPhase) = testCases[idxTest]!; Message($"Test case {idxTest}"); let nCoefficients = Length(coefficientsAmplitude); - + using (qubits = Qubit[nQubits]) { let qubitsLE = LittleEndian(qubits); mutable coefficients = new ComplexPolar[nCoefficients]; mutable coefficientsPositive = new Double[nCoefficients]; - + for (idxCoeff in 0 .. nCoefficients - 1) { set coefficients w/= idxCoeff <- ComplexPolar(coefficientsAmplitude[idxCoeff], coefficientsPhase[idxCoeff]); set coefficientsPositive w/= idxCoeff <- coefficientsAmplitude[idxCoeff]; } - + let normalizedCoefficients = PNormalized(2.0, coefficientsAmplitude); - + // Test phase factor on uniform superposition let phase = 0.5 * (coefficientsPhase[0] - coefficientsPhase[1]); let amp = normalizedCoefficients[0]; @@ -116,15 +104,16 @@ namespace Microsoft.Quantum.Tests { } } } - - + + // Test probabilities and phases factor of multi-qubit uniform superposition. + @Test("QuantumSimulator") operation StatePreparationComplexCoefficientsMultiQubitPhaseTest () : Unit { - + let tolerance = 1E-09; mutable testCases = new StatePreparationTestCase[10]; mutable nTests = 0; - + // Test probability and phases of uniform superposition. set testCases w/= nTests <- StatePreparationTestCase(1, [1.0, 1.0], [0.01, -0.01]); set nTests = nTests + 1; @@ -136,46 +125,46 @@ namespace Microsoft.Quantum.Tests { set nTests = nTests + 1; set testCases w/= nTests <- StatePreparationTestCase(3, [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], [1.0986553, 0.359005, 0.465689, -0.467395, 0.419893, 0.118445, 0.461883, 0.149609]); set nTests = nTests + 1; - + // Loop over tests for (idxTest in 0 .. nTests - 1) { let (nQubits, coefficientsAmplitude, coefficientsPhase) = testCases[idxTest]!; Message($"Test case {idxTest}"); let nCoefficients = Length(coefficientsAmplitude); - + using (qubits = Qubit[nQubits]) { let qubitsLE = LittleEndian(qubits); mutable coefficients = new ComplexPolar[nCoefficients]; mutable coefficientsPositive = new Double[nCoefficients]; - + for (idxCoeff in 0 .. nCoefficients - 1) { set coefficients w/= idxCoeff <- ComplexPolar(coefficientsAmplitude[idxCoeff], coefficientsPhase[idxCoeff]); set coefficientsPositive w/= idxCoeff <- coefficientsAmplitude[idxCoeff]; } - + let normalizedCoefficients = PNormalized(2.0, coefficientsAmplitude); - + // Test probability and phases of uniform superposition let op = StatePreparationComplexCoefficients(coefficients); - + using (control = Qubit[1]) { - + // Test probability H(control[0]); Controlled op(control, qubitsLE); X(control[0]); Controlled (ApplyToEachCA(H, _))(control, qubitsLE!); X(control[0]); - + for (idxCoeff in 0 .. nCoefficients - 1) { let amp = normalizedCoefficients[idxCoeff]; let prob = amp * amp; AssertProbInt(idxCoeff, prob, qubitsLE, tolerance); } - + ResetAll(control); ResetAll(qubits); - + //Test phase for (repeats in 0 .. nCoefficients / 2) { H(control[0]); @@ -194,73 +183,67 @@ namespace Microsoft.Quantum.Tests { } } } - - + + // Test probabilities and phases of arbitrary multi-qubit superposition. + @Test("QuantumSimulator") operation StatePreparationComplexCoefficientsArbitraryMultiQubitPhaseTest () : Unit { - + let tolerance = 1E-09; - mutable testCases = new StatePreparationTestCase[10]; - mutable nTests = 0; - set testCases w/= nTests <- StatePreparationTestCase(1, [1.0986553, 0.359005], [0.419893, 0.118445]); - set nTests = nTests + 1; - set testCases w/= nTests <- StatePreparationTestCase(2, [1.0986553, 0.359005, -0.123, 9.238], [0.419893, 0.118445, -0.467395, 0.419893]); - set nTests = nTests + 1; - set testCases w/= nTests <- StatePreparationTestCase(3, [1.0986553, 0.359005, 0.465689, 0.467395, 0.419893, 0.118445, 0.123, 9.238], [1.0986553, 0.359005, 0.465689, -0.467395, 0.419893, 0.118445, 0.461883, 0.149609]); - set nTests = nTests + 1; - + let testCases = [ + StatePreparationTestCase(1, [1.0986553, 0.359005], [0.419893, 0.118445]), + StatePreparationTestCase(2, [1.0986553, 0.359005, -0.123, 9.238], [0.419893, 0.118445, -0.467395, 0.419893]),StatePreparationTestCase(3, [1.0986553, 0.359005, 0.465689, 0.467395, 0.419893, 0.118445, 0.123, 9.238], [1.0986553, 0.359005, 0.465689, -0.467395, 0.419893, 0.118445, 0.461883, 0.149609]) + ]; + // Loop over tests - for (idxTest in 0 .. nTests - 1) { - let (nQubits, coefficientsAmplitude, coefficientsPhase) = testCases[idxTest]!; - Message($"Test case {idxTest}"); + for ((idxTestCase, testCase) in Enumerated(testCases)) { + let (nQubits, coefficientsAmplitude, coefficientsPhase) = testCase!; + Message($"Test case {idxTestCase}"); let nCoefficients = Length(coefficientsAmplitude); - + using (qubits = Qubit[nQubits]) { let qubitsLE = LittleEndian(qubits); - mutable coefficients = new ComplexPolar[nCoefficients]; mutable coefficientsPositive = new Double[nCoefficients]; - - for (idxCoeff in 0 .. nCoefficients - 1) { - set coefficients w/= idxCoeff <- ComplexPolar(coefficientsAmplitude[idxCoeff], coefficientsPhase[idxCoeff]); - set coefficientsPositive w/= idxCoeff <- coefficientsAmplitude[idxCoeff]; - } - + + let coefficients = Mapped(ComplexPolar, Zip(coefficientsAmplitude, coefficientsPhase)); let normalizedCoefficients = PNormalized(2.0, coefficientsAmplitude); - + // Test probability and phases of arbitrary superposition let opComplex = StatePreparationComplexCoefficients(coefficients); - let opReal = StatePreparationPositiveCoefficients(coefficientsPositive); - - using (control = Qubit[1]) { - + let opReal = StatePreparationPositiveCoefficients(coefficientsAmplitude); + + using (control = Qubit()) { + // Test probability - H(control[0]); - Controlled opComplex(control, qubitsLE); - X(control[0]); - Controlled opReal(control, qubitsLE); - X(control[0]); - - for (idxCoeff in 0 .. nCoefficients - 1) { - let amp = normalizedCoefficients[idxCoeff]; - let prob = amp * amp; - AssertProbInt(idxCoeff, prob, qubitsLE, tolerance); + H(control); + Controlled opComplex([control], qubitsLE); + within { + X(control); + } apply { + Controlled opReal([control], qubitsLE); } - - ResetAll(control); + + for ((idxCoeff, coeff) in Enumerated(normalizedCoefficients)) { + AssertProbInt(idxCoeff, coeff * coeff, qubitsLE, tolerance); + } + + Reset(control); ResetAll(qubits); - + // Test phase for (repeats in 0 .. nCoefficients / 2) { - H(control[0]); - Controlled opComplex(control, qubitsLE); - X(control[0]); - Controlled opReal(control, qubitsLE); - X(control[0]); + H(control); + Controlled opComplex([control], qubitsLE); + within { + X(control); + } apply { + Controlled opReal([control], qubitsLE); + } let indexMeasuredInteger = MeasureInteger(qubitsLE); let phase = coefficientsPhase[indexMeasuredInteger]; Message($"StatePreparationComplexCoefficientsTest: expected phase = {phase}."); - AssertPhase(-0.5 * phase, control[0], tolerance); - ResetAll(control); + AssertPhase(-0.5 * phase, control, tolerance); + Reset(control); ResetAll(qubits); } } diff --git a/Standard/tests/QuantumROMTests.qs b/Standard/tests/QuantumROMTests.qs index 31528fb15ff..fbf273392a4 100644 --- a/Standard/tests/QuantumROMTests.qs +++ b/Standard/tests/QuantumROMTests.qs @@ -3,6 +3,8 @@ namespace Microsoft.Quantum.Tests { + open Microsoft.Quantum.Arrays; + open Microsoft.Quantum.Arithmetic; open Microsoft.Quantum.Intrinsic; open Microsoft.Quantum.Canon; open Microsoft.Quantum.Preparation; @@ -11,93 +13,87 @@ namespace Microsoft.Quantum.Tests { open Microsoft.Quantum.Convert; // Tests the discretization algorithm + @Test("QuantumSimulator") operation _QuantumROMDiscretizationTest() : Unit { - for(rep in 0..20){ - let coeffs = RandomInt(5000)+2; - let bitsPrecision = RandomInt(30)+1; - let barHeight = 2^(bitsPrecision) - 1; - mutable coefficients = new Double[coeffs]; - Message($"Test case coeffs {coeffs}, bitsPrecision {bitsPrecision}"); - for (idx in 0..coeffs-1) - { - set coefficients w/= idx <- 1000.0 * RandomReal(2*bitsPrecision); + for (rep in 0..20) { + let nCoefficients = RandomInt(5000) + 2; + let nBitsPrecision = RandomInt(30) + 1; + let barHeight = 2 ^ nBitsPrecision - 1; + mutable coefficients = new Double[nCoefficients]; + Message($"Test case coeffs {nCoefficients}, bitsPrecision {nBitsPrecision}"); + for (idx in 0..nCoefficients - 1) { + set coefficients w/= idx <- 1000.0 * RandomReal(2 * nBitsPrecision); } // This avoids the case where coefficient are all zeros. - let rnd = RandomInt(coeffs); + let rnd = RandomInt(nCoefficients); set coefficients w/= rnd <- coefficients[rnd] + 1.0; - let (oneNorm, keepCoeff, altIndex) = _QuantumROMDiscretization(bitsPrecision, coefficients); + let (oneNorm, keepCoeff, altIndex) = _QuantumROMDiscretization(nBitsPrecision, coefficients); Message($"One-norm {oneNorm}"); // Reconstruct coefficients - mutable coefficientsOutInt = new Int[coeffs]; - for (idx in 0..coeffs-1) - { + mutable coefficientsOutInt = new Int[nCoefficients]; + for (idx in 0..nCoefficients - 1) { set coefficientsOutInt w/= idx <- coefficientsOutInt[idx] + keepCoeff[idx]; - if (altIndex[idx] >= 0) - { + if (altIndex[idx] >= 0) { set coefficientsOutInt w/= altIndex[idx] <- coefficientsOutInt[altIndex[idx]] + barHeight - keepCoeff[idx]; } } // Reconstruct coefficients - mutable coefficientsOut = new Double[coeffs]; - mutable errors = new Double[coeffs]; + mutable coefficientsOut = new Double[nCoefficients]; + mutable errors = new Double[nCoefficients]; mutable maxError = 0.0; - for (i in 0..coeffs-1) - { - set coefficientsOut w/= i <- oneNorm * IntAsDouble(coefficientsOutInt[i]) / IntAsDouble(barHeight * coeffs); - let error = AbsD(coefficients[i] - coefficientsOut[i]) / oneNorm /( PowD(2.0, IntAsDouble(-bitsPrecision)) / IntAsDouble(coeffs)); + for (i in 0..nCoefficients - 1) { + set coefficientsOut w/= i <- oneNorm * IntAsDouble(coefficientsOutInt[i]) / IntAsDouble(barHeight * nCoefficients); + let error = AbsD(coefficients[i] - coefficientsOut[i]) / oneNorm /( PowD(2.0, IntAsDouble(-nBitsPrecision)) / IntAsDouble(nCoefficients)); set errors w/= i <- error; - if(AbsD(error) > AbsD(maxError)){ - set maxError = error; - } + set maxError = MaxD(AbsD(error), maxError); } - Message($"coeffs {coeffs}, bitsPrecision {bitsPrecision}, maxError {maxError}"); - for(i in 0..coeffs-1){ - if(errors[i] < IntAsDouble(3)){ - // test passes - } - else{ + Message($"coeffs {nCoefficients}, bitsPrecision {nBitsPrecision}, maxError {maxError}"); + for (i in 0..nCoefficients - 1) { + if (errors[i] >= 3.0) { fail $"index {i} reconstructed coefficient incorrect. Error is {errors[i]}"; } } } } + @Test("QuantumSimulator") operation QuantumROMTest() : Unit { - for(coeffs in 2..7){ - for(nBitsPrecision in -1..-1..-2){ + for (coeffs in 2..7) { + for (nBitsPrecision in -1..-1..-2) { let targetError = PowD(2.0, IntAsDouble(nBitsPrecision)); let probtargetError = targetError / IntAsDouble(coeffs); - mutable coefficients = new Double[coeffs]; - for (idx in 0..coeffs-1) - { - set coefficients w/= idx <- RandomReal(2*32); - } - let ((nTotal, (nCoeffQubits, nGarbageQubits)), oneNorm, op) = QuantumROM(targetError, coefficients); - Message($"Test case coeffs {coeffs}, bitsPrecision {nCoeffQubits}, global targetError {targetError}, probability error {probtargetError}."); - for (idx in 0..coeffs-1) - { - let tmp = AbsD(coefficients[idx]) / oneNorm; + let coefficients = ForEach(Delay(RandomReal, 2 * 32, _), ConstantArray(coeffs, ())); + let preparation = PurifiedMixedState(targetError, coefficients); + Message($"Test case coeffs {coeffs}, bitsPrecision {preparation::Requirements::NIndexQubits}, global targetError {targetError}, probability error {probtargetError}."); + for (idx in 0..coeffs - 1) { + let tmp = AbsD(coefficients[idx]) / preparation::Norm; Message($"{idx} expected prob = {tmp}."); } - Message($"Qubits used: {nGarbageQubits} + {nCoeffQubits}"); - using(qubits = Qubit[nTotal]){ - let (register, rest) = _QuantumROMQubitManager(targetError, coeffs, qubits); + Message($"Qubits used: {preparation::Requirements::NGarbageQubits} + {preparation::Requirements::NIndexQubits}"); + using (qubits = Qubit[preparation::Requirements::NTotalQubits]) { + let (register, rest) = _PartitionedForQuantumROM(targetError, coeffs, qubits); let (coeffQubits, garbageQubits) = register; - op(register); - // Now check that probability of each number state in nCoeffQubits is as expected. - for(stateIndex in 0..coeffs-1){ - let prob = AbsD(coefficients[stateIndex]) / oneNorm; - Message($"Testing probability {prob} on index {stateIndex}"); - //BAssertProbIntBE(stateIndex, AbsD(coefficients[stateIndex]) / oneNorm, BigEndian(coeffQubits), targetError / IntAsDouble(coeffs)); + within { + preparation::Prepare(register); + } apply { + // Now check that probability of each number state in nCoeffQubits is as expected. + for (stateIndex in 0..coeffs - 1) { + let prob = AbsD(coefficients[stateIndex]) / preparation::Norm; + Message($"Testing probability {prob} on index {stateIndex}"); + AssertProbInt( + stateIndex, + prob, + coeffQubits, + targetError / IntAsDouble(coeffs) + ); + } } - (Adjoint op)(register); - } } }