import proveit
theory = proveit.Theory() # the theorem's theory
from proveit import a, b, e, f, i, l, x, defaults, Lambda
from proveit.logic import Equals, InSet, NotEquals
from proveit.numbers import (
zero, one, two, four, Add, Exp, frac, greater, greater_eq,
Less, Mult, NaturalPos, Neg, Real, RealPos, subtract)
from proveit.numbers.functions import MonDecFuncs
from proveit.physics.quantum.QPE import (
_t_in_natural_pos, _two_pow_t, _two_pow_t_minus_one_is_nat_pos, _phase_is_real,
_mod_add_def, _b_floor, _best_floor_is_int, _delta_b_floor, _delta_b_is_real,
_scaled_delta_b_floor_in_interval, _diff_l_scaled_delta_floor)
from proveit.physics.quantum.QPE.phase_est_ops import Psuccess, Pfail, ModAdd
%proving _failure_upper_bound
defaults.assumptions = _failure_upper_bound.conditions
_failure_upper_bound_lemma
¶from proveit.physics.quantum.QPE import _failure_upper_bound_lemma
_failure_upper_bound_lemma
_failure_upper_bound_lemma_inst = (
_failure_upper_bound_lemma.instantiate(preserve_expr=Neg(Add(e, one))))
first_sum = _failure_upper_bound_lemma_inst.rhs.operands[1].operands[0]
second_sum = _failure_upper_bound_lemma_inst.rhs.operands[1].operands[1]
the_summand = first_sum.summand
first_sum_conditions = first_sum.conditions[0]
second_sum_conditions = second_sum.conditions[0]
_scaled_delta_b_floor_in_interval
_delta_b_is_real.instantiate({b:_b_floor})
# Automatic incidental derivation, but want to label the result for later use
scaled_delta_lower_bound = _scaled_delta_b_floor_in_interval.derive_element_lower_bound()
# Automatic incidental derivation, but want to label the result for later use
scaled_delta_upper_bound = _scaled_delta_b_floor_in_interval.derive_element_upper_bound()
neg_scaled_delta_lower_bound = scaled_delta_upper_bound.left_mult_both_sides(Neg(one))
neg_scaled_delta_upper_bound = scaled_delta_lower_bound.left_mult_both_sides(Neg(one))
_diff_l_scaled_delta_floor.deduce_bound(neg_scaled_delta_lower_bound,
assumptions=defaults.assumptions + (first_sum_conditions,))
# For the 1st Summation
diff_l_scaled_delta_upper_bound = _diff_l_scaled_delta_floor.deduce_bound(
neg_scaled_delta_upper_bound.reversed(),
assumptions=defaults.assumptions + (first_sum_conditions,))
# For the 2nd Summation
diff_l_scaled_delta_upper_bound_2 = _diff_l_scaled_delta_floor.deduce_bound(
neg_scaled_delta_lower_bound,
assumptions=defaults.assumptions + (second_sum_conditions,))
We can find an $\ell$-dependent upper bound on the first summand, then an upper bound on the first summation.
first_summand_bound_02 = the_summand.deduce_bound(scaled_delta_lower_bound.reversed(),
assumptions=defaults.assumptions + (first_sum_conditions,))
first_summand_bound_generalized = first_summand_bound_02.generalize((l), conditions=[first_sum_conditions])
first_sum_bound = first_sum.deduce_bound(first_summand_bound_generalized)
We can find an $\ell$-dependent upper bound on the second summand, then an upper bound on the second summation.
second_summand_bound_02 = the_summand.deduce_bound(diff_l_scaled_delta_upper_bound_2,
assumptions=defaults.assumptions + (second_sum_conditions,))
second_summand_bound_02_relaxed = second_summand_bound_02.derive_relaxed()
second_summand_bound_generalized = second_summand_bound_02.generalize([l], conditions=[second_sum_conditions])
second_summand_bound_relaxed_generalized = second_summand_bound_02_relaxed.generalize([l], conditions=[second_sum_conditions])
second_sum_bound = second_sum.deduce_bound(second_summand_bound_relaxed_generalized)
Now we're ready to invoke our deduce_bound()
method utilizing the bounds we've established on each separate summation.
The following three steps correspond to the transition from (5.30) to (5.31) in Nielsen & Chuang (pg 224):
_failure_upper_bound_lemma_inst_bound_01 = _failure_upper_bound_lemma_inst.rhs.deduce_bound(first_sum_bound)
_failure_upper_bound_lemma_inst_bound_02 = _failure_upper_bound_lemma_inst_bound_01.rhs.deduce_bound(second_sum_bound)
_failure_upper_bound_lemma_inst_bound_03 = (
_failure_upper_bound_lemma_inst_bound_01.apply_transitivity(_failure_upper_bound_lemma_inst_bound_02))
Next, shift the index of the second summation to create identical summands in each of the summations:
_failure_upper_bound_lemma_inst_bound_04 = (
_failure_upper_bound_lemma_inst_bound_03.inner_expr().rhs.operands[1].
operands[1].shift(Neg(one)))
Then negate and flip the order of indices on the first summation (possible because the summand is an even function).
_failure_upper_bound_lemma_inst_bound_05 = (
_failure_upper_bound_lemma_inst_bound_04.inner_expr().rhs.operands[1].
operands[0].negate_index())
Now we're ready to split off the first element of the second summation.
Nielsen & Chuang originally split off the first element of the first summation, eventually leading to a bound requiring $e \ge 2$.
Instead, we split off the first term of the second summation, allowing us to eventually derive a bound valid for $e \ge 1$.
In doing so, the resulting expression is auto-simplified to combine the two resulting like-summations:
Mult.change_simplification_directives(distribute_numeric_rational=True)
bound_07 = _failure_upper_bound_lemma_inst_bound_05.inner_expr().rhs.factors[1].operands[1].split_first()
sum_bound_as_integral = bound_07.rhs.terms[0].factors[1].upper_bound_as_integral()
integral_bound = sum_bound_as_integral.rhs.bound_via_upper_limit_bound()
bound_07.rhs.deduce_bound(sum_bound_as_integral.apply_transitivity(integral_bound))
%qed