Skip to content

Commit c815077

Browse files
DmitryVasilevskyDmitry Vasilevsky
andauthored
Added ApplyQPE and ApplyOperationPowerCA to Canon namespace (#2473)
- Added ApplyQPE and ApplyOperationPowerCA to Canon namespace. - Added doc comments - Added tests ApplyQPE along with the previously implemented ApplyQFT are useful primitives for quantum algorithms. To keep these implementations simple and avoid performance penalty no swaps are performed on arguments or results. In most cases such results can be used without problems. Note, that ApplyQFT and ApplyQPE work with little-endian registers. --------- Co-authored-by: Dmitry Vasilevsky <[email protected]>
1 parent 0ee0241 commit c815077

File tree

4 files changed

+172
-1
lines changed

4 files changed

+172
-1
lines changed

compiler/qsc_partial_eval/src/tests/qubits.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,6 @@ fn qubit_relabel_in_dynamic_block_triggers_capability_error() {
377377

378378
assert_error(
379379
&error,
380-
&expect!["CapabilityError(UseOfDynamicQubit(Span { lo: 60173, hi: 60186 }))"],
380+
&expect!["CapabilityError(UseOfDynamicQubit(Span { lo: 62782, hi: 62795 }))"],
381381
);
382382
}

library/src/tests/canon.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,44 @@ fn check_qft_le_sample_4() {
504504
);
505505
}
506506

507+
const QPE_TEST_LIB: &str = include_str!("resources/src/qpe.qs");
508+
509+
#[test]
510+
fn check_qpe_z() {
511+
test_expression_with_lib(
512+
"Test.TestQPE_Z()",
513+
QPE_TEST_LIB,
514+
&Value::Tuple(vec![].into()),
515+
);
516+
}
517+
518+
#[test]
519+
fn check_qpe_s() {
520+
test_expression_with_lib(
521+
"Test.TestQPE_S()",
522+
QPE_TEST_LIB,
523+
&Value::Tuple(vec![].into()),
524+
);
525+
}
526+
527+
#[test]
528+
fn check_qpe_t() {
529+
test_expression_with_lib(
530+
"Test.TestQPE_T()",
531+
QPE_TEST_LIB,
532+
&Value::Tuple(vec![].into()),
533+
);
534+
}
535+
536+
#[test]
537+
fn check_qpe_p() {
538+
test_expression_with_lib(
539+
"Test.TestQPE_P()",
540+
QPE_TEST_LIB,
541+
&Value::Tuple(vec![].into()),
542+
);
543+
}
544+
507545
#[test]
508546
fn check_swap_reverse_register() {
509547
test_expression(
@@ -571,3 +609,18 @@ fn check_apply_operation_power_a() {
571609
&Value::RESULT_ZERO,
572610
);
573611
}
612+
613+
#[test]
614+
fn check_apply_operation_power_ca() {
615+
test_expression(
616+
{
617+
"{
618+
use q = Qubit();
619+
ApplyOperationPowerCA(12, Rx(Std.Math.PI()/16.0, _), q);
620+
ApplyOperationPowerCA(-3, Rx(Std.Math.PI()/4.0, _), q);
621+
M(q)
622+
}"
623+
},
624+
&Value::RESULT_ZERO,
625+
);
626+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
namespace Test {
2+
import Std.Diagnostics.*;
3+
4+
operation TestQPE_Phase(phaseGate : Qubit => Unit is Ctl + Adj, power : Int) : Unit {
5+
use state = Qubit();
6+
use phase = Qubit[4];
7+
8+
// Estimate eigenvalue of a phase gate on eigenvector of |0⟩
9+
ApplyQPE(ApplyOperationPowerCA(_, qs => phaseGate(qs[0]), _), [state], phase);
10+
// Eigenvalue should be 1 = exp(i * 2π * 0.0000), so the estimaped phase
11+
// should be 0.0000 fraction of 2π.
12+
Fact(CheckAllZero(phase), "Expected state |0000⟩");
13+
14+
// Estimate eigenvalue of a phase gate on eigenvector of |1⟩
15+
X(state);
16+
ApplyQPE(ApplyOperationPowerCA(_, qs => phaseGate(qs[0]), _), [state], phase);
17+
// The eigenvalue of a phase gate on eigenvector |1⟩ is exp(i * 2π / 2^power),
18+
// so the eigenvalue phase is the binary fraction 0.0…01 of 2π,
19+
// where the 1 is at the position `power` after the point (counting from 1).
20+
// So the a little-endian register `phase` should be
21+
// phase[0] = 0, phase[1] = 0, …, phase[N-power] = 1, phase[N-power+1] = 0, …
22+
X(phase[Length(phase)-power]);
23+
Fact(CheckAllZero(phase), $"Incorrect phase for exp(i * 2π / 2^{power}).");
24+
25+
Reset(state);
26+
}
27+
28+
operation TestQPE_Z() : Unit {
29+
TestQPE_Phase(Z, 1); // eigenvalue = exp(i * 2π / 2^1)
30+
}
31+
32+
operation TestQPE_S() : Unit {
33+
TestQPE_Phase(S, 2); // eigenvalue = exp(i * 2π / 2^2)
34+
35+
}
36+
37+
operation TestQPE_T() : Unit {
38+
TestQPE_Phase(T, 3); // eigenvalue = exp(i * 2π / 2^3)
39+
}
40+
41+
// Phase gate is a rotation around the Z axis and an ajustment for the global phase.
42+
operation P(phase : Double, q : Qubit) : Unit is Ctl + Adj {
43+
Rz(phase, q);
44+
Exp([], phase / 2.0, []);
45+
}
46+
47+
operation TestQPE_P() : Unit {
48+
TestQPE_Phase(P(Std.Math.PI() / 8.0, _), 4); // eigenvalue = exp(i * 2π / 2^4)
49+
}
50+
}

library/std/src/Std/Canon.qs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,72 @@ operation ApplyOperationPowerA<'T>(
599599
}
600600
}
601601

602+
/// # Summary
603+
/// Applies operation `op` to the `target` `power` times.
604+
/// If `power` is negative, the adjoint of `op` is used.
605+
/// If `power` is 0, the operation `op` is not applied.
606+
operation ApplyOperationPowerCA<'T>(
607+
power : Int,
608+
op : 'T => Unit is Ctl + Adj,
609+
target : 'T
610+
) : Unit is Ctl + Adj {
611+
let u = if power >= 0 { op } else { Adjoint op };
612+
for _ in 1..AbsI(power) {
613+
u(target);
614+
}
615+
}
616+
617+
/// # Summary
618+
/// Performs the quantum phase estimation algorithm for a unitary U represented
619+
/// by `applyPowerOfU`, and a `targetState`. Returns the phase estimation
620+
/// in the range of [0, 2π) as a fraction of 2π in the little-endian register `phase`.
621+
///
622+
/// # Input
623+
/// ## applyPowerOfU
624+
/// An oracle implementing Uᵐ for a unitary U and a given integer power m.
625+
/// `ApplyOperationPowerCA(_, U, _)` can be used to implement this oracle,
626+
/// if no better performing implementation is available.
627+
/// ## targetState
628+
/// A quantum register acted on by U. When this `targetState` is the eigenstate
629+
/// of the operator U, the corresponding eigenvalue is estimated.
630+
/// ## phase
631+
/// A little-endian quantum register containing the phase estimation result. The phase is
632+
/// a fraction of 2π represented in binary with q[0] containing 2π/2=π bit, q[1] containing 2π/4=π/2 bit,
633+
/// q[2] containing 2π/8=π/4 bit, and so on. The length of the register indicates the desired precision.
634+
/// The phase register is assumed to be in the state |0...0⟩ when the operation is invoked.
635+
///
636+
/// # Remarks
637+
/// All eigenvalues of a unitary operator are of magnitude 1 and therefore can be represented as
638+
/// $e^{i\phi}$ for some $\phi \in [0, 2\pi)$. Finding the phase of an eigenvalue is therefore
639+
/// equivalent to finding the eigenvalue itself. Passing the eigenvector as `targetState`
640+
/// to the phase estimation operation allows finding the eigenvalue to the desired precision
641+
/// determined by the length of the `phase` register.
642+
///
643+
/// # Reference
644+
/// - [Quantum phase estimation algorithm](https://wikipedia.org/wiki/Quantum_phase_estimation_algorithm)
645+
///
646+
/// # See Also
647+
/// - [Std.Canon.ApplyOperationPowerCA](xref:Qdk.Std.Canon.ApplyOperationPowerCA)
648+
operation ApplyQPE(
649+
applyPowerOfU : (Int, Qubit[]) => Unit is Adj + Ctl,
650+
targetState : Qubit[],
651+
phase : Qubit[]
652+
) : Unit is Adj + Ctl {
653+
654+
let nQubits = Length(phase);
655+
ApplyToEachCA(H, phase);
656+
657+
for i in 0..nQubits - 1 {
658+
let power = 2^((nQubits - i) - 1);
659+
Controlled applyPowerOfU(
660+
phase[i..i],
661+
(power, targetState)
662+
);
663+
}
664+
665+
Adjoint ApplyQFT(phase);
666+
}
667+
602668
/// # Summary
603669
/// Relabels the qubits in the `current` array with the qubits in the `updated` array. The `updated` array
604670
/// must be a valid permutation of the `current` array.
@@ -660,8 +726,10 @@ export
660726
ApplyControlledOnInt,
661727
ApplyControlledOnBitString,
662728
ApplyQFT,
729+
ApplyQPE,
663730
SwapReverseRegister,
664731
ApplyXorInPlace,
665732
ApplyXorInPlaceL,
666733
ApplyOperationPowerA,
734+
ApplyOperationPowerCA,
667735
Relabel;

0 commit comments

Comments
 (0)