diff --git a/Chemistry/src/Runtime/JordanWigner/JordanWignerOptimizedBlockEncoding.qs b/Chemistry/src/Runtime/JordanWigner/JordanWignerOptimizedBlockEncoding.qs index 6abc4dde6cd..35923a236d6 100644 --- a/Chemistry/src/Runtime/JordanWigner/JordanWignerOptimizedBlockEncoding.qs +++ b/Chemistry/src/Runtime/JordanWigner/JordanWignerOptimizedBlockEncoding.qs @@ -370,14 +370,14 @@ namespace Microsoft.Quantum.Chemistry.JordanWigner { operation _JordanWignerOptimizedBlockEncodingStatePrep(targetError : Double, optimizedBEGeneratorSystem : OptimizedBEGeneratorSystem, qROMIdxRegister : LittleEndian, qROMGarbage : Qubit[], signQubit : Qubit, selectZControlRegisters : Qubit[], OptimizedBEControlRegisters : Qubit[], pauliBases : Qubit[], pauliBasesIdx : LittleEndian, indexRegisters : LittleEndian[]) : Unit { body (...) { - let (nTerms, oneNorm0, intToGenIdx) = optimizedBEGeneratorSystem!; + let (nTerms, _, _) = optimizedBEGeneratorSystem!; let coefficients = _OptimizedBEGeneratorSystemCoeff_(optimizedBEGeneratorSystem); - let (qROMQubitCount, oneNorm, qROMUnitary) = QuantumROM(targetError, coefficients); + let purifiedState = PurifiedMixedState(targetError, coefficients); let unitaryGenerator = (nTerms, _ToJordanWignerSelectInput_(_, optimizedBEGeneratorSystem)); let pauliBasesUnitaryGenerator = (5, _ToPauliBases_); //let multiplexer = MultiplexerFromGenerator; - qROMUnitary(qROMIdxRegister, qROMGarbage); + purifiedState::Prepare(qROMIdxRegister, new Qubit[0], qROMGarbage); MultiplexOperationsFromGenerator(unitaryGenerator, qROMIdxRegister, (signQubit, selectZControlRegisters, OptimizedBEControlRegisters, pauliBasesIdx, indexRegisters)); MultiplexOperationsFromGenerator(pauliBasesUnitaryGenerator, pauliBasesIdx, pauliBases); } @@ -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 (_, (nIndexRegister, nGarbageQubits)) = QuantumROMQubitCount(targetError, nCoeffs); - let parts = Partitioned([nIndexRegister, nGarbageQubits], ctrlRegister); + let requirements = PurifiedMixedStateRequirements(targetError, nCoeffs); + let parts = Partitioned([requirements::NIndexQubits, requirements::NGarbageQubits], ctrlRegister); let ((qROMIdx, qROMGarbage), rest0) = ((LittleEndian(parts[0]), parts[1]), parts[2]); let ((signQubit, selectZControlRegisters, optimizedBEControlRegisters, pauliBases, indexRegisters, tmp), rest1) = _JordanWignerSelectQubitManager_(nZ, nMaj, nIdxRegQubits, rest0, new Qubit[0]); let registers = Partitioned([3], rest1); @@ -401,7 +401,7 @@ namespace Microsoft.Quantum.Chemistry.JordanWigner { function _JordanWignerOptimizedBlockEncodingQubitCount_ (targetError : Double, nCoeffs : Int, nZ : Int, nMaj : Int, nIdxRegQubits : Int, nTarget : Int) : ((Int, Int), (Int, Int, Int, Int, Int, Int, Int, Int[], Int)) { let (nSelectTotal, (a0, a1, a2, a3, a4)) = _JordanWignerSelectQubitCount_(nZ, nMaj, nIdxRegQubits); - let (nQROMTotal, (b0, b1)) = QuantumROMQubitCount(targetError, nCoeffs); + let (nQROMTotal, (b0, b1)) = (PurifiedMixedStateRequirements(targetError, nCoeffs))!; let pauliBasesIdx = 3; return (((nSelectTotal + nQROMTotal) + pauliBasesIdx, nTarget), (b0, b1, a0, a1, a2, a3, pauliBasesIdx, a4, nTarget)); } diff --git a/Chemistry/tests/SamplesTests/DocsInvokingChemistryLibrary.cs b/Chemistry/tests/SamplesTests/DocsInvokingChemistryLibrary.cs index 37a1b82a232..aa4f770c158 100644 --- a/Chemistry/tests/SamplesTests/DocsInvokingChemistryLibrary.cs +++ b/Chemistry/tests/SamplesTests/DocsInvokingChemistryLibrary.cs @@ -19,6 +19,7 @@ // The System namespace provides a number of useful built-in // types and methods that we'll use throughout this sample. using System; +using System.IO; // We use this for convnience functions for manipulation arrays. using System.Linq; @@ -84,7 +85,7 @@ static void LoadFromBroombridgeFile() var root = @"Molecules"; // This deserializes Broombridge. - var broombridge = Broombridge.Deserializers.DeserializeBroombridge($@"{root}\{filename}"); + var broombridge = Broombridge.Deserializers.DeserializeBroombridge(Path.Combine(root, filename)); // Note that the deserializer returns a list of `ProblemDescriptions` instances // as the file might describe multiple Hamiltonians. In this example, there is @@ -122,7 +123,7 @@ static void LoadFromLiquidFile() var root = @"Molecules"; // Deserialize the LiQuiD format. - var problem = LiQuiD.Deserialize($@"{root}\{filename}").First(); + var problem = LiQuiD.Deserialize(Path.Combine(root, filename)).First(); // This extracts the `OrbitalIntegralHamiltonian` from problem // description format. @@ -135,7 +136,7 @@ static void LoadFromLiquidFile() static void ResourceEstimate() { // Filename of Hamiltonian to be loaded. - var filename = "Molecules/LiH_0.2.yaml"; + var filename = Path.Combine("Molecules", "LiH_0.2.yaml"); // This deserializes Broombridge. var problem = Broombridge.Deserializers.DeserializeBroombridge(filename).ProblemDescriptions.First(); diff --git a/Standard/src/Arithmetic/Asserts.qs b/Standard/src/Arithmetic/Asserts.qs index a0ae67967b4..3673af4cefc 100644 --- a/Standard/src/Arithmetic/Asserts.qs +++ b/Standard/src/Arithmetic/Asserts.qs @@ -7,6 +7,7 @@ namespace Microsoft.Quantum.Arithmetic { open Microsoft.Quantum.Convert; open Microsoft.Quantum.Arrays; open Microsoft.Quantum.Diagnostics; + open Microsoft.Quantum.Math; /// # Summary /// Asserts that the probability of a specific state of a quantum register has the @@ -40,16 +41,23 @@ namespace Microsoft.Quantum.Arithmetic { /// - `AssertProbInt(0,0.125,qubits,10e-10);` /// - `AssertProbInt(6,0.875,qubits,10e-10);` operation AssertProbInt(stateIndex : Int, expected : Double, qubits : LittleEndian, tolerance : Double) : Unit { - let nQubits = Length(qubits!); - let bits = IntAsBoolArray(stateIndex, nQubits); - using (flag = Qubit()) { - (ControlledOnBitString(bits, X))(qubits!, flag); - AssertMeasurementProbability([PauliZ], [flag], One, expected, $"AssertProbInt failed on stateIndex {stateIndex}, expected probability {expected}.", tolerance); + within { + (ControlledOnInt(stateIndex, X))(qubits!, flag); + } apply { + AssertMeasurementProbability([PauliZ], [flag], One, expected, $"AssertProbInt failed on stateIndex {stateIndex}, expected probability {expected}.", tolerance); + } + } + } - // Uncompute flag qubit. - (ControlledOnBitString(bits, X))(qubits!, flag); - Reset(flag); + operation AssertSignedProbInt(stateIndex : Int, expected : Double, sign : Qubit, qubits : LittleEndian, tolerance : Double) : Unit { + using (flag = Qubit()) { + let signOffset = expected < 0.0 ? 1 <<< Length(qubits!) | 0; + within { + (ControlledOnInt(stateIndex + signOffset, X))(qubits! + [sign], flag); + } apply { + AssertMeasurementProbability([PauliZ], [flag], One, AbsD(expected), $"AssertSignedProbInt failed on stateIndex {stateIndex}, expected probability {expected}.", tolerance); + } } } diff --git a/Standard/src/Canon/Multiplexer.qs b/Standard/src/Canon/Multiplexer.qs index eca76e3a365..31f21e0a379 100644 --- a/Standard/src/Canon/Multiplexer.qs +++ b/Standard/src/Canon/Multiplexer.qs @@ -5,6 +5,7 @@ namespace Microsoft.Quantum.Canon { open Microsoft.Quantum.Intrinsic; open Microsoft.Quantum.Arithmetic; open Microsoft.Quantum.Arrays; + open Microsoft.Quantum.Diagnostics; open Microsoft.Quantum.Math; /// # Summary @@ -43,7 +44,7 @@ namespace Microsoft.Quantum.Canon { } if (nUnitaries > 0) { let auxiliary = new Qubit[0]; - Adjoint _MultiplexOperationsFromGenerator(unitaryGeneratorWithOffset, auxiliary, index, target); + Adjoint MultiplexOperationsFromGeneratorImpl(unitaryGeneratorWithOffset, auxiliary, index, target); } } @@ -51,7 +52,7 @@ namespace Microsoft.Quantum.Canon { /// Implementation step of `MultiplexOperationsFromGenerator`. /// # See Also /// - Microsoft.Quantum.Canon.MultiplexOperationsFromGenerator - operation _MultiplexOperationsFromGenerator<'T>(unitaryGenerator : (Int, Int, (Int -> ('T => Unit is Adj + Ctl))), auxiliary: Qubit[], index: LittleEndian, target: 'T) + internal operation MultiplexOperationsFromGeneratorImpl<'T>(unitaryGenerator : (Int, Int, (Int -> ('T => Unit is Adj + Ctl))), auxiliary: Qubit[], index: LittleEndian, target: 'T) : Unit { body (...) { let nIndex = Length(index!); @@ -59,13 +60,13 @@ namespace Microsoft.Quantum.Canon { let (nUnitaries, unitaryOffset, unitaryFunction) = unitaryGenerator; - let nUnitariesLeft = MinI(nUnitaries, nStates/2); + let nUnitariesLeft = MinI(nUnitaries, nStates / 2); let nUnitariesRight = MinI(nUnitaries, nStates); let leftUnitaries = (nUnitariesLeft, unitaryOffset, unitaryFunction); - let rightUnitaries = (nUnitariesRight-nUnitariesLeft, unitaryOffset + nUnitariesLeft, unitaryFunction); + let rightUnitaries = (nUnitariesRight - nUnitariesLeft, unitaryOffset + nUnitariesLeft, unitaryFunction); - let newControls = LittleEndian(index![0..nIndex - 2]); + let newControls = LittleEndian(Most(index!)); if (nUnitaries > 0) { if (Length(auxiliary) == 1 and nIndex == 0) { @@ -74,38 +75,38 @@ namespace Microsoft.Quantum.Canon { (Controlled Adjoint (unitaryFunction(unitaryOffset)))(auxiliary, target); } elif (Length(auxiliary) == 0 and nIndex >= 1) { // Start case - let newauxiliary = [index![Length(index!) - 1]]; - if(nUnitariesRight > 0){ - _MultiplexOperationsFromGenerator(rightUnitaries, newauxiliary, newControls, target); + let newauxiliary = Tail(index!); + if (nUnitariesRight > 0) { + MultiplexOperationsFromGeneratorImpl(rightUnitaries, [newauxiliary], newControls, target); } within { - X(newauxiliary[0]); + X(newauxiliary); } apply { - _MultiplexOperationsFromGenerator(leftUnitaries, newauxiliary, newControls, target); + MultiplexOperationsFromGeneratorImpl(leftUnitaries, [newauxiliary], newControls, target); } } else { // Recursion that reduces nIndex by 1 & sets Length(auxiliary) to 1. - using (newauxiliary = Qubit[1]) { - let op = LogicalANDMeasAndFix(_, _); - // Naive measurement-free approach uses 4x more T gates with - // let op = (Controlled X); - op([index![Length(index!) - 1]] + auxiliary, newauxiliary[0]); - if (nUnitariesRight > 0) { - _MultiplexOperationsFromGenerator(rightUnitaries, newauxiliary, newControls, target); - } + let controls = [Tail(index!)] + auxiliary; + using ((newauxiliary, andauxiliary) = (Qubit(), Qubit[MaxI(0, Length(controls) - 2)])) { within { - (Controlled X)(auxiliary, newauxiliary[0]); + ApplyAndChain(andauxiliary, controls, newauxiliary); } apply { - _MultiplexOperationsFromGenerator(leftUnitaries, newauxiliary, newControls, target); + if (nUnitariesRight > 0) { + MultiplexOperationsFromGeneratorImpl(rightUnitaries, [newauxiliary], newControls, target); + } + within { + (Controlled X)(auxiliary, newauxiliary); + } apply { + MultiplexOperationsFromGeneratorImpl(leftUnitaries, [newauxiliary], newControls, target); + } } - (Adjoint op)([index![Length(index!) - 1]] + auxiliary, newauxiliary[0]); } } } } adjoint auto; controlled (controlRegister, (...)) { - _MultiplexOperationsFromGenerator(unitaryGenerator, auxiliary + controlRegister, index, target); + MultiplexOperationsFromGeneratorImpl(unitaryGenerator, auxiliary + controlRegister, index, target); } adjoint controlled auto; } @@ -191,63 +192,24 @@ namespace Microsoft.Quantum.Canon { } /// # Summary - /// Computes the logical AND of multiple qubits. - /// # Input - /// ## ctrlRegister - /// Qubits input to the multiple-input AND gate. - /// ## target - /// Qubit on which output of AND is written to. This is - /// assumed to always start in the $\ket{0}$ state. - /// # Remarks - /// When `ctrlRegister` has exactly $2$ qubits, - /// this is equivalent to the `CCNOT` operation but phase with a phase - /// $e^{i\Pi/2}$ on $\ket{001}$ and $-e^{i\Pi/2}$ on $\ket{101}$ and $\ket{011}$. - /// The Adjoint is also measure-and-fixup approach that is the inverse - /// of the original operation only in special cases (see references), - /// but uses fewer T-gates. - /// - /// # References - /// - [ *Craig Gidney*, 1709.06648](https://arxiv.org/abs/1709.06648) - internal operation LogicalANDMeasAndFix(ctrlRegister : Qubit[], target : Qubit) - : Unit { - body (...) { - if(Length(ctrlRegister) == 2){ - let c1 = ctrlRegister[0]; - let c2 = ctrlRegister[1]; - H(target); - T(target); - CNOT(c1,target); - CNOT(c2,target); - CNOT(target,c1); - CNOT(target,c2); - (Adjoint T)(c1); - (Adjoint T)(c2); - T(target); - CNOT(target,c2); - CNOT(target,c1); - H(target); - S(target); - } else { - (Controlled X)(ctrlRegister, target); - } - } - adjoint (...) { - if(Length(ctrlRegister) == 2){ - let c1 = ctrlRegister[0]; - let c2 = ctrlRegister[1]; - H(target); - let Meas = M(target); - if (Meas == One) { - within { - H(c2); - } apply { - CNOT(c1,c2); - } - X(target); - } - } else { - (Controlled X)(ctrlRegister, target); - } + /// Computes a chain of AND gates + /// + /// # Description + /// The auxiliary qubits to compute temporary results must be specified explicitly. + /// The length of that register is `Length(ctrlRegister) - 2`, if there are at least + /// two controls, otherwise the length is 0. + internal operation ApplyAndChain(auxRegister : Qubit[], ctrlRegister : Qubit[], target : Qubit) + : Unit is Adj { + if (Length(ctrlRegister) == 0) { + X(target); + } elif (Length(ctrlRegister) == 1) { + CNOT(Head(ctrlRegister), target); + } else { + EqualityFactI(Length(auxRegister), Length(ctrlRegister) - 2, "Unexpected number of auxiliary qubits"); + let controls1 = ctrlRegister[0..0] + auxRegister; + let controls2 = Rest(ctrlRegister); + let targets = auxRegister + [target]; + ApplyToEachA(ApplyAnd, Zip3(controls1, controls2, targets)); } } } diff --git a/Standard/src/Preparation/Deprecated.qs b/Standard/src/Preparation/Deprecated.qs new file mode 100644 index 00000000000..4fdb28a5d35 --- /dev/null +++ b/Standard/src/Preparation/Deprecated.qs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. + // Licensed under the MIT License. + +namespace Microsoft.Quantum.Preparation { + + open Microsoft.Quantum.Arithmetic; + + /// # 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, + /// $$ + /// \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}. + /// \end{align} + /// $$ + /// + /// # Input + /// ## targetError + /// The target error $\epsilon$. + /// ## coefficients + /// 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$. + /// + /// # 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]; + /// 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); + /// } + /// } + /// ``` + /// + /// # References + /// - 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 + @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, + IgnoreDataRegister(preparation::Prepare, _, _) + ); + } + + internal operation IgnoreDataRegister(op : ((LittleEndian, Qubit[], Qubit[]) => Unit is Adj + Ctl), indexRegister : LittleEndian, garbageRegister : Qubit[]) : Unit is Adj + Ctl { + op(indexRegister, new Qubit[0], garbageRegister); + } + + /// # Summary + /// Returns the total number of qubits that must be allocated + /// to the operation returned by `QuantumROM`. + /// + /// # Input + /// ## targetError + /// The target error $\epsilon$. + /// ## nCoeffs + /// Number of coefficients specified in `QuantumROM`. + /// + /// # 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. + @Deprecated("Microsoft.Quantum.Preparation.PurifiedMixedStateRequirements") + function QuantumROMQubitCount(targetError: Double, nCoeffs: Int) + : (Int, (Int, Int)) { + return (PurifiedMixedStateRequirements(targetError, nCoeffs))!; + } +} diff --git a/Standard/src/Preparation/QuantumROM.qs b/Standard/src/Preparation/QuantumROM.qs index 9e49dae3d9d..a5f04748782 100644 --- a/Standard/src/Preparation/QuantumROM.qs +++ b/Standard/src/Preparation/QuantumROM.qs @@ -10,19 +10,46 @@ 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} + /// \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} - /// 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}. + /// \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 @@ -32,27 +59,26 @@ namespace Microsoft.Quantum.Preparation { /// 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 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 error is `1e-3`; - /// ```qsharp - /// let coefficients = [1.0,2.0,3.0,4.0,5.0]; + /// $\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), new Qubit[0], garbageRegister); /// } /// } /// ``` @@ -61,40 +87,101 @@ 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 positiveCoefficients = Mapped(AbsD, coefficients); + let (oneNorm, keepCoeff, altIndex) = _QuantumROMDiscretization(nBitsPrecision, positiveCoefficients); + let nCoeffs = Length(positiveCoefficients); let nBitsIndices = Ceiling(Lg(IntAsDouble(nCoeffs))); - let op = PrepareQuantumROMState(nBitsPrecision, nCoeffs, nBitsIndices, keepCoeff, altIndex, _, _); - let qubitCounts = QuantumROMQubitCount(targetError, nCoeffs); - return (qubitCounts, oneNorm, op); + let op = PrepareQuantumROMState(nBitsPrecision, nCoeffs, nBitsIndices, keepCoeff, altIndex, new Int[0], _, _, _); + let qubitCounts = PurifiedMixedStateRequirements(targetError, nCoeffs); + return MixedStatePreparation(qubitCounts, oneNorm, op); + } + + internal function SplitSign(coefficient : Double) : (Double, Int) { + return (AbsD(coefficient), coefficient < 0.0 ? 1 | 0); } /// # Summary - /// Returns the total number of qubits that must be allocated - /// to the operation returned by `QuantumROM`. + /// Same as @"microsoft.quantum.preparation.purifiedmixedstate" but + /// also prepares sign of the coefficient on an extra qubit. /// /// # Input /// ## targetError /// The target error $\epsilon$. - /// ## nCoeffs - /// Number of coefficients specified in `QuantumROM`. + /// ## coefficients + /// Array of $N$ coefficients specifying the probability of basis states. + /// Negative numbers $-\alpha_j$ will be treated as positive $|\alpha_j|$, + /// but the sign of a negative number will be prepared on a separate data + /// qubit. /// /// # 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; + /// An operation that prepares $\tilde \rho$ as a purification onto a joint + /// index and garbage register. + /// + /// # Remarks + /// 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 purifiedState = PurifiedMixedStateAndSign(targetError, coefficients); + /// using ((indexRegister, sign) = (Qubit[purifiedState::Requirements::NIndexQubits], Qubit())) { + /// using (garbageRegister = Qubit[purifiedState::Requirements::NGarbageQubits]) { + /// purifiedState::Prepare(LittleEndian(indexRegister), [sign], garbageRegister); + /// } + /// } + /// ``` + /// + /// # See Also + /// - PurifiedMixedState + function PurifiedMixedStateAndSign(targetError : Double, coefficients : Double[]) + : MixedStatePreparation { + let nBitsPrecision = -Ceiling(Lg(0.5 * targetError)) + 1; + let (positiveCoefficients, signs) = Unzipped(Mapped(SplitSign, coefficients)); + let (oneNorm, keepCoeff, altIndex) = _QuantumROMDiscretization(nBitsPrecision, positiveCoefficients); + let nCoeffs = Length(positiveCoefficients); let nBitsIndices = Ceiling(Lg(IntAsDouble(nCoeffs))); - let nGarbageQubits = nBitsIndices + 2 * nBitsPrecision + 1; - let nTotal = nGarbageQubits + nBitsIndices; - return (nTotal, (nBitsIndices, nGarbageQubits)); + + let op = PrepareQuantumROMState(nBitsPrecision, nCoeffs, nBitsIndices, keepCoeff, altIndex, signs, _, _, _); + let qubitCounts = PurifiedMixedStateRequirements(targetError, nCoeffs); + return MixedStatePreparation(qubitCounts w/ NGarbageQubits <- qubitCounts::NGarbageQubits + 1, oneNorm, op); + } + + /// # Summary + /// Returns the total number of qubits that must be allocated + /// in order to apply the operation returned by + /// @"microsoft.quantum.preparation.purifiedmixedstate". + /// + /// # Input + /// ## targetError + /// The target error $\epsilon$. + /// ## nCoefficients + /// The number of coefficients to be specified in preparing a mixed state. + /// + /// # Output + /// 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)); } // Classical processing @@ -108,16 +195,19 @@ namespace Microsoft.Quantum.Preparation { fail $"Bits of precision {bitsPrecision} unsupported. Max is 31."; } if (nCoefficients <= 1) { - fail "Cannot prepare state with less than 2 coefficients."; + fail $"Cannot prepare state with less than 2 coefficients."; } if (oneNorm == 0.0) { - fail "State must have at least one coefficient > 0"; + 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(RoundedDiscretizationCoefficients(_, oneNorm, nCoefficients, barHeight), coefficients); + mutable keepCoeff = Mapped( + RoundedDiscretizationCoefficients(_, oneNorm, nCoefficients, barHeight), + coefficients + ); // Calculate difference between number of discretized bars vs. maximum mutable bars = 0; @@ -127,49 +217,38 @@ namespace Microsoft.Quantum.Preparation { // 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; + set barSink += [idxSource]; + } elif (keepCoeff[idxSource] > barHeight) { + set barSource += [idxSource]; } - } - elif (nBarSource > 0) { - let idxSource = barSource[nBarSource - 1]; - set nBarSource = nBarSource - 1; + } elif (Length(barSource) > 0) { + let idxSource = Tail(barSource); + set barSource = Most(barSource); set keepCoeff w/= idxSource <- barHeight; } else { return (oneNorm, keepCoeff, altIndex); @@ -186,9 +265,8 @@ namespace Microsoft.Quantum.Preparation { } // Used in QuantumROM implementation. - internal operation PrepareQuantumROMState(nBitsPrecision: Int, nCoeffs: Int, nBitsIndices: Int, keepCoeff: Int[], altIndex: Int[], indexRegister: LittleEndian, garbageRegister: Qubit[]) + internal operation PrepareQuantumROMState(nBitsPrecision: Int, nCoeffs: Int, nBitsIndices: Int, keepCoeff: Int[], altIndex: Int[], data : Int[], indexRegister: LittleEndian, dataQubits : Qubit[], garbageRegister: Qubit[]) : Unit is Adj + Ctl { - let unitaryGenerator = (nCoeffs, QuantumROMBitStringWriterByIndex(_, keepCoeff, altIndex)); let garbageIdx0 = nBitsIndices; let garbageIdx1 = garbageIdx0 + nBitsPrecision; let garbageIdx2 = garbageIdx1 + nBitsPrecision; @@ -198,13 +276,16 @@ namespace Microsoft.Quantum.Preparation { let keepCoeffRegister = LittleEndian(garbageRegister[garbageIdx0..garbageIdx1 - 1]); let uniformKeepCoeffRegister = LittleEndian(garbageRegister[garbageIdx1..garbageIdx2 - 1]); let flagQubit = garbageRegister[garbageIdx3 - 1]; + let dataRegister = LittleEndian(dataQubits); + let altDataRegister = LittleEndian(garbageRegister[garbageIdx3...]); // 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)); + let unitaryGenerator = (nCoeffs, QuantumROMBitStringWriterByIndex(_, keepCoeff, altIndex, data)); + MultiplexOperationsFromGenerator(unitaryGenerator, indexRegister, (keepCoeffRegister, altIndexRegister, dataRegister, altDataRegister)); // Perform comparison CompareUsingRippleCarry(uniformKeepCoeffRegister, keepCoeffRegister, flagQubit); @@ -212,20 +293,24 @@ namespace Microsoft.Quantum.Preparation { let indexRegisterSize = Length(indexRegister!); // Swap in register based on comparison - ApplyToEachCA((Controlled SWAP)([flagQubit], _), Zip(indexRegister!, altIndexRegister!)); + ApplyToEachCA((Controlled SWAP)([flagQubit], _), Zip(indexRegister! + dataRegister!, altIndexRegister! + altDataRegister!)); } // Used in QuantumROM implementation. - internal function QuantumROMBitStringWriterByIndex(idx : Int, keepCoeff : Int[], altIndex : Int[]) - : ((LittleEndian, LittleEndian) => Unit is Adj + Ctl) { - return WriteQuantumROMBitString(idx, keepCoeff, altIndex, _, _); + internal function QuantumROMBitStringWriterByIndex(idx : Int, keepCoeff : Int[], altIndex : Int[], data : Int[]) + : ((LittleEndian, LittleEndian, LittleEndian, LittleEndian) => Unit is Adj + Ctl) { + return WriteQuantumROMBitString(idx, keepCoeff, altIndex, data, _, _, _, _); } // Used in QuantumROM implementation. - internal operation WriteQuantumROMBitString(idx: Int, keepCoeff: Int[], altIndex: Int[], keepCoeffRegister: LittleEndian, altIndexRegister: LittleEndian) + internal operation WriteQuantumROMBitString(idx: Int, keepCoeff: Int[], altIndex: Int[], data : Int[], keepCoeffRegister: LittleEndian, altIndexRegister: LittleEndian, dataRegister : LittleEndian, altDataRegister : LittleEndian) : Unit is Adj + Ctl { ApplyXorInPlace(keepCoeff[idx], keepCoeffRegister); ApplyXorInPlace(altIndex[idx], altIndexRegister); + if (Length(dataRegister!) > 0) { + ApplyXorInPlace(data[idx], dataRegister); + ApplyXorInPlace(data[altIndex[idx]], altDataRegister); + } } } diff --git a/Standard/src/Preparation/Types.qs b/Standard/src/Preparation/Types.qs new file mode 100644 index 00000000000..12a8cf2b7fc --- /dev/null +++ b/Standard/src/Preparation/Types.qs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. +// 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, a data register, and a + /// garbage register initially in the $\ket{0}$, $\let{00\dots 0}$, and + /// $\ket{00\dots 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[], 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/tests/ArrayTests.qs b/Standard/tests/ArrayTests.qs index 71f5ecc8777..644151da768 100644 --- a/Standard/tests/ArrayTests.qs +++ b/Standard/tests/ArrayTests.qs @@ -28,7 +28,7 @@ namespace Microsoft.Quantum.Tests { } @Test("QuantumSimulator") - function TestUnzipped() : Unit { + function UnzippedTest() : Unit { let first = [6, 5, 5, 3, 2, 1]; let second = [true, false, false, false, true, false]; diff --git a/Standard/tests/QuantumROMTests.qs b/Standard/tests/QuantumROMTests.qs index 301b25ea71d..2de16c86543 100644 --- a/Standard/tests/QuantumROMTests.qs +++ b/Standard/tests/QuantumROMTests.qs @@ -68,26 +68,40 @@ namespace Microsoft.Quantum.Tests { for(nBitsPrecision in -1..-1..-2){ let targetError = PowD(2.0, IntAsDouble(nBitsPrecision)); let probtargetError = targetError / IntAsDouble(coeffs); - let coefficients = DrawMany(DrawRandomDouble, coeffs, (0.0, 1.0)); - let ((nTotal, (nCoeffQubits, nGarbageQubits)), oneNorm, op) = QuantumROM(targetError, coefficients); + let coefficients = DrawMany(DrawRandomDouble, coeffs, (-1.0, 1.0)); - Message($"Test case coeffs {coeffs}, bitsPrecision {nCoeffQubits}, global targetError {targetError}, probability error {probtargetError}."); - for ((idx, coefficient) in Enumerated(coefficients)) { - let tmp = AbsD(coefficient) / oneNorm; - Message($"{idx} expected prob = {tmp}."); + if (true) { // quantum ROM without sign + let purifiedState = PurifiedMixedState(targetError, coefficients); + + using ((coeffRegister, garbageQubits) = (Qubit[purifiedState::Requirements::NIndexQubits], Qubit[purifiedState::Requirements::NGarbageQubits])) { + let coeffQubits = LittleEndian(coeffRegister); + + // Check that probability of each number state in nCoeffQubits is as expected. + within { + purifiedState::Prepare(coeffQubits, new Qubit[0], garbageQubits); + } apply { + for (stateIndex in 0..coeffs - 1) { + let prob = AbsD(coefficients[stateIndex]) / purifiedState::Norm; + AssertProbInt(stateIndex, prob, coeffQubits, probtargetError); + } + } + } } - Message($"Qubits used: {nGarbageQubits} + {nCoeffQubits}"); - using ((coeffRegister, garbageQubits) = (Qubit[nCoeffQubits], Qubit[nGarbageQubits])) { - let coeffQubits = LittleEndian(coeffRegister); + if (true) { // quantum ROM with sign + let purifiedState = PurifiedMixedStateAndSign(targetError, coefficients); + + using ((coeffRegister, signQubit, garbageQubits) = (Qubit[purifiedState::Requirements::NIndexQubits], Qubit(), Qubit[purifiedState::Requirements::NGarbageQubits])) { + let coeffQubits = LittleEndian(coeffRegister); - // Check that probability of each number state in nCoeffQubits is as expected. - within { - op(coeffQubits, garbageQubits); - } apply { - for (stateIndex in 0..coeffs - 1) { - let prob = AbsD(coefficients[stateIndex]) / oneNorm; - AssertProbInt(stateIndex, prob, coeffQubits, probtargetError); + // Check that probability of each number state in nCoeffQubits is as expected. + within { + purifiedState::Prepare(coeffQubits, [signQubit], garbageQubits); + } apply { + for (stateIndex in 0..coeffs - 1) { + let prob = coefficients[stateIndex] / purifiedState::Norm; + AssertSignedProbInt(stateIndex, prob, signQubit, coeffQubits, probtargetError); + } } } }