@@ -63,8 +63,35 @@ import
6363/// # See Also
6464/// - Std.StatePreparation.ApproximatelyPreparePureStateCP
6565operation PreparePureStateD (coefficients : Double [], qubits : Qubit []) : Unit is Adj + Ctl {
66- let coefficientsAsComplexPolar = Mapped (a -> ComplexAsComplexPolar (Complex (a , 0.0 )), coefficients );
67- ApproximatelyPreparePureStateCP (0.0 , coefficientsAsComplexPolar , qubits );
66+ let nQubits = Length (qubits );
67+ // Pad coefficients at tail length to a power of 2.
68+ let coefficientsPadded = Padded (- 2 ^ nQubits , 0.0 , coefficients );
69+ let idxTarget = 0 ;
70+
71+ // Note we use the reversed qubits array to get the endianness ordering that we expect
72+ // when corresponding qubit state to state vector index.
73+ let qubits = Reversed (qubits );
74+
75+ // Since we know the coefficients are real, we can optimize the first round of adjoint approximate unpreparation by directly
76+ // computing the disentangling angles and the new coefficients on those doubles without producing intermediate complex numbers.
77+
78+ // For each 2D block, compute disentangling single-qubit rotation parameters
79+ let (disentanglingY , disentanglingZ , newCoefficients ) = StatePreparationSBMComputeCoefficientsD (coefficientsPadded );
80+
81+ if nQubits == 1 {
82+ let (abs , arg ) = newCoefficients [0 ]! ;
83+ if (AbsD (arg ) > 0.0 ) {
84+ Adjoint Exp ([PauliI ], - 1.0 * arg , [qubits [idxTarget ]]);
85+ }
86+ } elif (Any (c -> AbsComplexPolar (c ) > 0.0 , newCoefficients )) {
87+ // Some coefficients are outside tolerance
88+ let newControl = 2 .. (nQubits - 1 );
89+ let newTarget = 1 ;
90+ Adjoint ApproximatelyUnprepareArbitraryState (0.0 , newCoefficients , newControl , newTarget , qubits );
91+ }
92+
93+ Adjoint ApproximatelyMultiplexPauli (0.0 , disentanglingY , PauliY , qubits [1 ... ], qubits [0 ]);
94+ Adjoint ApproximatelyMultiplexPauli (0.0 , disentanglingZ , PauliZ , qubits [1 ... ], qubits [0 ]);
6895}
6996
7097/// # Summary
@@ -121,7 +148,7 @@ operation ApproximatelyPreparePureStateCP(
121148) : Unit is Adj + Ctl {
122149
123150 let nQubits = Length (qubits );
124- // pad coefficients at tail length to a power of 2.
151+ // Pad coefficients at tail length to a power of 2.
125152 let coefficientsPadded = Padded (- 2 ^ nQubits , ComplexPolar (0.0 , 0.0 ), coefficients );
126153 let idxTarget = 0 ;
127154 // Determine what controls to apply
@@ -148,10 +175,9 @@ operation ApproximatelyUnprepareArbitraryState(
148175) : Unit is Adj + Ctl {
149176
150177 // For each 2D block, compute disentangling single-qubit rotation parameters
151- let (disentanglingY , disentanglingZ , newCoefficients ) = StatePreparationSBMComputeCoefficients (coefficients );
178+ let (disentanglingY , disentanglingZ , newCoefficients ) = StatePreparationSBMComputeCoefficientsCP (coefficients );
152179 if (AnyOutsideToleranceD (tolerance , disentanglingZ )) {
153180 ApproximatelyMultiplexPauli (tolerance , disentanglingZ , PauliZ , register [rngControl ], register [idxTarget ]);
154-
155181 }
156182 if (AnyOutsideToleranceD (tolerance , disentanglingY )) {
157183 ApproximatelyMultiplexPauli (tolerance , disentanglingY , PauliY , register [rngControl ], register [idxTarget ]);
@@ -226,7 +252,27 @@ operation ApproximatelyMultiplexPauli(
226252
227253/// # Summary
228254/// Implementation step of arbitrary state preparation procedure.
229- function StatePreparationSBMComputeCoefficients (
255+ /// This version optimized for purely real coefficients represented by an array of doubles.
256+ function StatePreparationSBMComputeCoefficientsD (
257+ coefficients : Double []
258+ ) : (Double [], Double [], ComplexPolar []) {
259+ mutable disentanglingZ = [];
260+ mutable disentanglingY = [];
261+ mutable newCoefficients = [];
262+
263+ for idxCoeff in 0 .. 2 .. Length (coefficients ) - 1 {
264+ let (rt , phi , theta ) = BlockSphereCoordinatesD (coefficients [idxCoeff ], coefficients [idxCoeff + 1 ]);
265+ set disentanglingZ += [0.5 * phi ];
266+ set disentanglingY += [0.5 * theta ];
267+ set newCoefficients += [rt ];
268+ }
269+
270+ return (disentanglingY , disentanglingZ , newCoefficients );
271+ }
272+
273+ /// # Summary
274+ /// Implementation step of arbitrary state preparation procedure.
275+ function StatePreparationSBMComputeCoefficientsCP (
230276 coefficients : ComplexPolar []
231277) : (Double [], Double [], ComplexPolar []) {
232278
@@ -235,7 +281,7 @@ function StatePreparationSBMComputeCoefficients(
235281 mutable newCoefficients = [];
236282
237283 for idxCoeff in 0 .. 2 .. Length (coefficients ) - 1 {
238- let (rt , phi , theta ) = BlochSphereCoordinates (coefficients [idxCoeff ], coefficients [idxCoeff + 1 ]);
284+ let (rt , phi , theta ) = BlochSphereCoordinatesCP (coefficients [idxCoeff ], coefficients [idxCoeff + 1 ]);
239285 set disentanglingZ += [0.5 * phi ];
240286 set disentanglingY += [0.5 * theta ];
241287 set newCoefficients += [rt ];
@@ -244,6 +290,36 @@ function StatePreparationSBMComputeCoefficients(
244290 return (disentanglingY , disentanglingZ , newCoefficients );
245291}
246292
293+ /// # Summary
294+ /// Computes the Bloch sphere coordinates for a single-qubit state.
295+ ///
296+ /// Given two doubles $a0, a1$ that represent the qubit state, computes coordinates
297+ /// on the Bloch sphere such that
298+ /// $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})$.
299+ ///
300+ /// # Input
301+ /// ## a0
302+ /// Double coefficient of state $\ket{0}$.
303+ /// ## a1
304+ /// Double coefficient of state $\ket{1}$.
305+ ///
306+ /// # Output
307+ /// A tuple containing `(ComplexPolar(r, t), phi, theta)`.
308+ function BlockSphereCoordinatesD (
309+ a0 : Double ,
310+ a1 : Double
311+ ) : (ComplexPolar , Double , Double ) {
312+ let abs0 = AbsD (a0 );
313+ let abs1 = AbsD (a1 );
314+ let arg0 = a0 < 0.0 ? PI () | 0.0 ;
315+ let arg1 = a1 < 0.0 ? PI () | 0.0 ;
316+ let r = Sqrt (abs0 * abs0 + abs1 * abs1 );
317+ let t = 0.5 * (arg0 + arg1 );
318+ let phi = arg1 - arg0 ;
319+ let theta = 2.0 * ArcTan2 (abs1 , abs0 );
320+ return (ComplexPolar (r , t ), phi , theta );
321+ }
322+
247323/// # Summary
248324/// Computes the Bloch sphere coordinates for a single-qubit state.
249325///
@@ -259,7 +335,7 @@ function StatePreparationSBMComputeCoefficients(
259335///
260336/// # Output
261337/// A tuple containing `(ComplexPolar(r, t), phi, theta)`.
262- function BlochSphereCoordinates (
338+ function BlochSphereCoordinatesCP (
263339 a0 : ComplexPolar ,
264340 a1 : ComplexPolar
265341) : (ComplexPolar , Double , Double ) {
@@ -321,31 +397,18 @@ operation ApproximatelyMultiplexZ(
321397 target : Qubit
322398) : Unit is Adj + Ctl {
323399
324- body (... ) {
325- // pad coefficients length at tail to a power of 2.
326- let coefficientsPadded = Padded (- 2 ^ Length (control ), 0.0 , coefficients );
400+ body ... {
401+ let multiplexZParams = GenerateMultiplexZParams (coefficients , control , target );
402+ ApplyMultiplexZParams (multiplexZParams );
403+ }
327404
328- if Length (coefficientsPadded ) == 1 {
329- // Termination case
330- if AbsD (coefficientsPadded [0 ]) > tolerance {
331- Exp ([PauliZ ], coefficientsPadded [0 ], [target ]);
332- }
333- } else {
334- // Compute new coefficients.
335- let (coefficients0 , coefficients1 ) = MultiplexZCoefficients (coefficientsPadded );
336- ApproximatelyMultiplexZ (tolerance , coefficients0 , Most (control ), target );
337- if AnyOutsideToleranceD (tolerance , coefficients1 ) {
338- within {
339- CNOT (Tail (control ), target );
340- } apply {
341- ApproximatelyMultiplexZ (tolerance , coefficients1 , Most (control ), target );
342- }
343- }
344- }
405+ adjoint ... {
406+ let adjMultiplexZParams = GenerateAdjMultiplexZParams (coefficients , control , target );
407+ Adjoint ApplyMultiplexZParams (adjMultiplexZParams );
345408 }
346409
347410 controlled (controlRegister , ... ) {
348- // pad coefficients length to a power of 2.
411+ // Pad coefficients length to a power of 2.
349412 let coefficientsPadded = Padded (2 ^ (Length (control ) + 1 ), 0.0 , Padded (- 2 ^ Length (control ), 0.0 , coefficients ));
350413 let (coefficients0 , coefficients1 ) = MultiplexZCoefficients (coefficientsPadded );
351414 ApproximatelyMultiplexZ (tolerance , coefficients0 , control , target );
@@ -357,8 +420,71 @@ operation ApproximatelyMultiplexZ(
357420 }
358421 }
359422 }
423+
424+ controlled adjoint invert ;
425+ }
426+
427+ operation ApplyMultiplexZParams (params : (Double , Qubit [])[]) : Unit is Adj {
428+ for (angle , qs ) in params {
429+ if Length (qs ) == 2 {
430+ CNOT (qs [0 ], qs [1 ]);
431+ } elif AbsD (angle ) > 0.0 {
432+ Exp ([PauliZ ], angle , qs );
433+ }
434+ }
435+ }
436+ // Provides the sequence of angles or entangling CNOTs to apply for the multiplex Z step of the state preparation procedure,
437+ // given a set of coefficients and control and target qubits.
438+ // In these sequences, there are always one or two qubits in the qubit array. If there is one qubit, it is the target of a Z rotation by the given angle.
439+ // If there are two qubits, they represent a CNOT with the first qubit as control and the second as target, and the angle is ignored.
440+ function GenerateMultiplexZParams (
441+ coefficients : Double [],
442+ control : Qubit [],
443+ target : Qubit
444+ ) : (Double , Qubit [])[] {
445+ // Pad coefficients length at tail to a power of 2.
446+ let coefficientsPadded = Padded (- 2 ^ Length (control ), 0.0 , coefficients );
447+
448+ if Length (coefficientsPadded ) == 1 {
449+ // Termination case
450+ [(coefficientsPadded [0 ], [target ])]
451+ } else {
452+ // Compute new coefficients.
453+ let (coefficients0 , coefficients1 ) = MultiplexZCoefficients (coefficientsPadded );
454+ mutable params = GenerateMultiplexZParams (coefficients0 , Most (control ), target );
455+ params += [(0.0 , [Tail (control ), target ])] + GenerateMultiplexZParams (coefficients1 , Most (control ), target );
456+ params += [(0.0 , [Tail (control ), target ])];
457+ params
458+ }
360459}
361460
461+ // Provides the sequence of angles or entangling CNOTs to apply for the adjoint of the multiplex Z step of the state preparation procedure,
462+ // given a set of coefficients and control and target qubits.
463+ // Note that the adjoint sequence is NOT the reverse of the forward sequence due to the structure of the multiplex Z step, which applies
464+ // the disentangling rotations in between the two recursive calls.
465+ // See `GenerateMultiplexZParams` for more details on the structure of these sequences.
466+ function GenerateAdjMultiplexZParams (
467+ coefficients : Double [],
468+ control : Qubit [],
469+ target : Qubit
470+ ) : (Double , Qubit [])[] {
471+ // Pad coefficients length at tail to a power of 2.
472+ let coefficientsPadded = Padded (- 2 ^ Length (control ), 0.0 , coefficients );
473+
474+ if Length (coefficientsPadded ) == 1 {
475+ // Termination case
476+ [(coefficientsPadded [0 ], [target ])]
477+ } else {
478+ // Compute new coefficients.
479+ let (coefficients0 , coefficients1 ) = MultiplexZCoefficients (coefficientsPadded );
480+ mutable params = [(0.0 , [Tail (control ), target ])] + GenerateAdjMultiplexZParams (coefficients1 , Most (control ), target );
481+ params += [(0.0 , [Tail (control ), target ])];
482+ params += GenerateAdjMultiplexZParams (coefficients0 , Most (control ), target );
483+ params
484+ }
485+ }
486+
487+
362488/// # Summary
363489/// Implementation step of multiply-controlled Z rotations.
364490function MultiplexZCoefficients (coefficients : Double []) : (Double [], Double []) {
0 commit comments