Пример #1
0
def run_experiment(nbits, t=8):
    """Run single phase estimation experiment."""

    # Make a unitary and find Eigen value/vector to estimate.
    #
    umat = scipy.stats.unitary_group.rvs(2**nbits)
    eigvals, eigvecs = np.linalg.eig(umat)
    u = ops.Operator(umat)

    # Pick Eigenvalue 'eigen_index' (any Eigenvalue / Eigenvector pair will work).
    eigen_index = 1
    phi = np.real(np.log(eigvals[eigen_index]) / (2j * np.pi))
    if phi < 0:
        phi += 1

    # Make state + circuit to estimate phi.
    # Pick Eigenvector 'eigen_index' to math the Eigenvalue.
    psi = state.zeros(t) * state.State(eigvecs[:, eigen_index])
    psi = phase1(psi, u, t)
    psi = ops.Qft(t).adjoint()(psi)

    # Find state with highest measurement probability and show results.
    #
    maxbits, maxprob = psi.maxprob()
    phi_estimate = sum(maxbits[i] * 2**(-i - 1) for i in range(t))

    delta = abs(phi - phi_estimate)
    print('Phase   : {:.4f}'.format(phi))
    print('Estimate: {:.4f} delta: {:.4f} probability: {:5.2f}%'.format(
        phi_estimate, delta, maxprob * 100.0))
    if delta > 0.02 and phi_estimate < 0.98:
        print('*** Warning: Delta is large')
Пример #2
0
def graph_to_adjacency(n: int, nodes: List[int]) -> ops.Operator:
    """Compute adjacency matrix from graph."""

    op = np.zeros((n, n))
    for node in nodes:
        op[node[0], node[1]] = node[2]
        op[node[1], node[0]] = node[2]
    return ops.Operator(op)
Пример #3
0
def graph_to_hamiltonian(n: int, nodes: List[int]) -> ops.Operator:
    """Compute Hamiltonian matrix from graph."""

    H = np.zeros((2**n, 2**n))
    for node in nodes:
        idx1 = node[0]
        idx2 = node[1]
        if idx1 > idx2:
            idx1, idx2 = idx2, idx1
        op = 1.0
        for _ in range(idx1):
            op = op * ops.Identity()
        op = op * (node[2] * ops.PauliZ())
        for _ in range(idx1 + 1, idx2):
            op = op * ops.Identity()
        op = op * (node[2] * ops.PauliZ())
        for _ in range(idx2 + 1, n):
            op = op * ops.Identity()
        H = H + op
    return ops.Operator(H)
Пример #4
0
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')
Пример #5
0
 def diagonalize(U):
   _, V = np.linalg.eig(U)
   return ops.Operator(V)
Пример #6
0
def spectral_decomp(ndim: int):
    """Implement and verify spectral decomposition theorem."""

    # The spectral theorem says that a Hermitian matrix can be written as
    # the sum over eigenvalues lambda_i and eigenvectors u_i, as:
    #
    #   A = sum_i {lambda_i * |u_i><u_i|}
    #
    # Let's check by example...

    # Create random unitary. It will be unitary, but not Hermitian.
    #
    u = scipy.stats.unitary_group.rvs(ndim)
    umat = ops.Operator(u)

    # Make it Hermitian by computing:
    #    A = 1/2 * (A + A^*)
    #
    # This makes A Hermitian (but no longer unitary).
    #
    hmat = 0.5 * (umat + umat.adjoint())
    if not np.allclose(hmat, hmat.adjoint()):
        raise AssertionError('Something is wrong, created non-Hermitian.')

    # Compute eigenvalues and vectors.
    #
    # Note the Python way to extract column c from
    # a matrix v as v[:, c]:
    #
    #     w       : array of eigenvalues
    #     v[:, i] : eigenvector corresponding to w[i]
    #
    w, v = np.linalg.eig(hmat)

    # Check that the eigenvalues are real.
    #
    for i in range(ndim):
        if not np.allclose(w[i].imag, 0.0):
            raise AssertionError('Found non-real eigenvalue.')

    # Check that the eigenvectors are orthogonal.
    #
    for i in range(ndim):
        for j in range(i + 1, ndim):
            dot = np.dot(v[:, i], v[:, j].adjoint())
            if not np.allclose(dot, 0.0, atol=1e-5):
                raise AssertionError('Invalid, non-orthogonal basis found')

    # Check that eigenvectors are orthonormal.
    for i in range(ndim):
        dot = np.dot(v[:, i], v[:, i].adjoint())
        if not np.allclose(dot, 1.0, atol=1e-5):
            raise AssertionError('Found non-orthonormal basis vectors')

    # Construct a matrix following the spectral theorem and
    # check equivalance.
    #
    #  A = sum_i {lambda_i * |u_i><u_i|}
    #
    x = np.matrix(np.zeros((ndim, ndim)))
    for i in range(ndim):
        x = x + w[i] * np.outer(v[:, i], v[:, i].adjoint())
    if not np.allclose(hmat, x, atol=1e-5):
        raise AssertionError('Spectral decomp doesn\'t seem to work.')

    # Can we use this elegant spectral decomposition to compute the
    # the inverse of a matrix?
    #
    # Let's compute the inverse by using only the inverse of the eigenvalues,
    # as (1 / eigenvalue), but keep the same eigenvectors otherwise:
    #
    x = np.matrix(np.zeros((ndim, ndim)))
    for i in range(ndim):
        x = x + 1 / w[i] * np.outer(v[:, i], v[:, i].adjoint())
    if not np.allclose(np.linalg.inv(hmat), x, atol=1e-5):
        raise AssertionError('Inverse computation doesn\'t seem to work.')
Пример #7
0
Файл: grover.py Проект: 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')
Пример #8
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))