def run_oracle_experiment(nbits: int) -> None: """Run full experiment for a given number of bits.""" c = make_c(nbits - 1) f = make_oracle_f(c) u = ops.OracleUf(nbits, f) psi = state.zeros(nbits - 1) * state.ones(1) psi = ops.Hadamard(nbits)(psi) psi = u(psi) psi = ops.Hadamard(nbits)(psi) check_result(nbits, c, psi)
def run_experiment(nbits, flavor): """Run full experiment for a given flavor of f().""" f = make_f(nbits - 1, flavor) u = ops.OracleUf(nbits, f) psi = (ops.Hadamard(nbits - 1)(state.zeros(nbits - 1)) * ops.Hadamard()(state.ones(1))) psi = u(psi) psi = (ops.Hadamard(nbits - 1) * ops.Identity(1))(psi) # Measure all of |0>. If all close to 1.0, f() is constant. for idx in range(nbits - 1): p0, _ = ops.Measure(psi, idx, tostate=0, collapse=False) if not math.isclose(p0, 1.0, abs_tol=1e-5): return exp_balanced return exp_constant
def run_experiment(nbits, solutions) -> None: """Run full experiment for a given flavor of f().""" # Note that op_zero multiplies the diagonal elements of the operator by -1, # except for element [0][0]. This can be interpreted as "rotating around # the |00..)>" state. More pragmatically, multiplying this op_zero with # a Hadamard from the left and right gives a matrix of this form: # # 2/N-1 2/N 2/N ... 2/N # 2/N 2N-1 2/N ... 2/N # 2/N 2/N 2/N-1 ... 2/N # 2/N 2/N 2/N ... 2/N-1 # # Multiplying this matrix with a state vector computes exactly: # 2u - c_x # for every vector element c_x, with u being the mean over the # state vector. This is the defintion of inversion about the mean. # zero_projector = np.zeros((2**nbits, 2**nbits)) zero_projector[0, 0] = 1 op_zero = ops.Operator(zero_projector) # Make f and Uf. Note: # We reserve space for an ancilla 'y', which is unused in # Grover's algorithm. This allows reuse of the Deutsch Uf builder. # # We use the Oracle construction for convenience. It is rather # slow (full matrix) for larger qubit counts. Once can construct # a 'regular' function for the grover search algorithms, but this # function is different for each bitstring and that quickly gets # confusing. # f = make_f(nbits, solutions) uf = ops.OracleUf(nbits+1, f) # Build state with 1 ancilla of |1>. # psi = state.zeros(nbits) * state.ones(1) for i in range(nbits + 1): psi.apply1(ops.Hadamard(), i) # Build Grover operator, note Id() for the ancilla. # The Grover operator is the combination of: # - phase inversion via the u unitary # - inversion about the mean (see matrix above) # hn = ops.Hadamard(nbits) reflection = op_zero * 2.0 - ops.Identity(nbits) inversion = hn(reflection(hn)) * ops.Identity() grover = inversion(uf) # Number of Grover iterations # # There are at least 2 ways to compute the required number of iterations. # # 1) Most text books, eg., page 157 of Kaye, Laflamme and Mosca, find # that the highest probability of finding the right results occurs # after pi/4 sqrt(n) rotations. # # 2) For grover specifically, with 1 solution the iteration count is: # int(math.pi / 4 * math.sqrt(n)) # # 3) For grover with multiple solutions: # int(math.pi / 4 * math.sqrt(n / solutions)) # # 4) For amplitude amplification, it's the probability of good # solutions, which is trivial with the Grover equal # superposition here: # int(math.sqrt(n / solutions)) # iterations = int(math.pi / 4 * math.sqrt(2**nbits / solutions)) for _ in range(iterations): psi = grover(psi) # Measurement - pick element with higher probability. # # Note: We constructed the Oracle with n+1 qubits, to allow # for the 'xor-ancillary'. To check the result, we need to # ignore this ancilla. # maxbits, maxprob = psi.maxprob() result = f(maxbits[:-1]) print('Got f({}) = {}, want: 1, #: {:2d}, p: {:6.4f}' .format(maxbits[:-1], result, solutions, maxprob)) if result != 1: raise AssertionError('something went wrong, measured invalid state')
def run_experiment(nbits) -> None: """Run full experiment for a given flavor of f().""" hn = ops.Hadamard(nbits) h = ops.Hadamard() n = 2**nbits # Note that op_zero multiplies the diagonal elements of the operator by -1, # except for element [0][0]. This can be interpreted as "rotating around # the |00..)>" state. More pragmatically, multiplying this op_zero with # a Hadamard from the left and right gives a matrix of this form: # # 2/N-1 2/N 2/N ... 2/N # 2/N 2N-1 2/N ... 2/N # 2/N 2/N 2/N-1 ... 2/N # 2/N 2/N 2/N ... 2/N-1 # # Multiplying this matrix with a state vector computes exactly: # 2u - c_x # for every vector element c_x, with u being the mean over the # state vector. This is the defintion of inversion about the mean. # zero_projector = np.zeros((n, n)) zero_projector[0, 0] = 1 op_zero = ops.Operator(zero_projector) # Make f and Uf. Note: We reserve space for an ancilla 'y', which is unused # in Grover's algorithm. This allows reuse of the Deutsch Uf builder. # # We use the Oracle construction for convenience. It is rather slow (full # matrix) for larger qubit counts. Once can construct a 'regular' function # for the grover search algorithms, but this function is different for # each bitstring and that quickly gets quite confusing. # # Good examples for 2-qubit and 3-qubit functions can be found here: # https://qiskit.org/textbook/ch-algorithms/grover.html#3qubits # f = make_f(nbits) u = ops.OracleUf(nbits + 1, f) # Build state with 1 ancilla of |1>. # psi = state.zeros(nbits) * state.ones(1) for i in range(nbits + 1): psi.apply(h, i) # Build Grover operator, note Id() for the ancilla. # The Grover operator is the combination of: # - phase inversion via the u unitary # - inversion about the mean (see matrix above) # reflection = op_zero * 2.0 - ops.Identity(nbits) inversion = hn(reflection(hn)) * ops.Identity() grover = inversion(u) # Number of Grover iterations # # There are at least 2 ways to compute the required number of iterations. # # 1) Most text books, eg., page 157 of Kaye, Laflamme and Mosca, find # that the highest probability of finding the right results occurs # after pi/4 sqrt(n) rotations. # # 2) I found this computation in qcircuits: # angle_to_rotate = np.arccos(np.sqrt(1 / n)) # rotation_angle = 2 * np.arcsin(np.sqrt(1 / n)) # iterations = int(round(angle_to_rotate / rotation_angle)) # # Both produce identical results. # iterations = int(math.pi / 4 * math.sqrt(n)) for _ in range(iterations): psi = grover(psi) # Measurement - pick element with higher probability. # # Note: We constructed the Oracle with n+1 qubits, to allow # for the 'xor-ancillary'. To check the result, we need to # ignore this ancilla. # maxbits, maxprobs = psi.maxprob() result = f(maxbits[:-1]) print(f'Got f({maxbits[:-1]}) = {result}, want: 1') if result != 1: raise AssertionError('something went wrong, measured invalid state')
def run_experiment(nbits_phase, nbits_grover, solutions) -> None: """Run full experiment for a given number of solutions.""" # Building the Grover Operator, see grover.py n = 2**nbits_grover zero_projector = np.zeros((n, n)) zero_projector[0, 0] = 1 op_zero = ops.Operator(zero_projector) f = make_f(nbits_grover, solutions) u = ops.OracleUf(nbits_grover + 1, f) # The state for the counting algorithm. # We reserve nbits for the phase estimation. # We also reserve nbits for the Oracle. # These numbers could be adjusted to achieve better # accuracy. Yet, this keeps the code a little bit simpler, # while trading off a few off-by-1 estimation errors. # # We also add the |1> for the Oracle. # psi = (state.zeros(nbits_phase) * state.zeros(nbits_grover) * state.ones(1)) # Apply Hadamard to all the qubits. for i in range(nbits_phase + nbits_grover + 1): psi.apply(ops.Hadamard(), i) # Construct the Grover Operator. reflection = op_zero * 2.0 - ops.Identity(nbits_grover) hn = ops.Hadamard(nbits_grover) inversion = hn(reflection(hn)) * ops.Identity() grover = inversion(u) # Now that we have the Grover Operator, we have to perform # phase estimation. This loop is a copy from phase_estimation.py # with more comments there. # for idx, inv in enumerate(range(nbits_phase - 1, -1, -1)): u2 = grover for _ in range(idx): u2 = u2(u2) psi = ops.ControlledU(inv, nbits_phase, u2)(psi, inv) # Reverse QFT gives us the phase as a fraction of 2*Pi psi = ops.Qft(nbits_phase).adjoint()(psi) # Get the state with highest probability and compute the phase # as a binary fraction. Note that the probability increases # as M, the number of solutions, gets closer and closer to N, # the total mnumber of states. maxbits, maxprob = psi.maxprob() phi_estimate = sum(maxbits[i] * 2**(-i - 1) for i in range(nbits_phase)) # We know that after phase estimation, this holds: # # sin(phi/2) = sqrt(M/N) # M = N * sin(phi/2)^2 # # Hence we can compute M. We keep the result to 2 digit to visualize # the errors. Note that the phi_estimate is a fraction of 2*PI, hence # the 1/2 in above formula cancels out against the 2 and we compute: M = round(n * math.sin(phi_estimate * math.pi)**2, 2) print('Estimate: {:.4f} prob: {:5.2f}% --> M: {:5.2f}, want: {:2d}'.format( phi_estimate, maxprob * 100.0, M, solutions))