Example #1
0
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)
Example #2
0
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
Example #3
0
File: grover.py Project: qcc4cp/qcc
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')
Example #4
0
File: grover.py Project: xbe/qcc
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')
Example #5
0
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))