def hipster_multi(): """Multi-qubit, optimized application.""" nbits = 7 for bits in helper.bitprod(nbits): psi = state.bitstring(*bits) for target in range(1, nbits): # Full matrix (O(n*n). op = (ops.Identity(target - 1) * ops.Cnot(target - 1, target) * ops.Identity(nbits - target - 1)) psi1 = op(psi) # Single Qubit (O(n)) psi = apply_controlled_gate(ops.PauliX(), target - 1, target, psi) if not psi.is_close(psi1): raise AssertionError('Invalid Single Gate Application.') psi = state.bitstring(1, 1, 0, 0, 1) pn = ops.Cnot(1, 4)(psi, 1) if not pn.is_close(apply_controlled_gate(ops.PauliX(), 1, 4, psi)): raise AssertionError('Invalid Cnot') pn = ops.Cnot(4, 1)(psi, 1) if not pn.is_close(apply_controlled_gate(ops.PauliX(), 4, 1, psi)): raise AssertionError('Invalid Cnot') pn = ops.ControlledU(0, 1, ops.ControlledU(1, 4, ops.PauliX()))(psi) psi = state.qubit(alpha=0.6) * state.ones(2) pn = ops.Cnot(0, 2)(psi) if not pn.is_close(apply_controlled_gate(ops.PauliX(), 0, 2, psi)): raise AssertionError('Invalid Cnot')
def simple_walk(): """Simple quantum walk, allowing initial experiments.""" nbits = 8 qc = circuit.qc('simple_walk') x = qc.reg(nbits, 0b10000000) aux = qc.reg(nbits, 0) coin = qc.reg(1, 0) for _ in range(32): # Using a Hadamard coin, others are possible, of course. qc.h(coin[0]) incr(qc, 0, nbits, aux, [coin[0]]) decr(qc, 0, nbits, aux, [[coin[0]]]) # Find and print the non-zero amplitudes for all states for bits in helper.bitprod(nbits): idx_bits = bits for i in range(nbits): idx_bits = idx_bits + (0,) idx_bits0 = idx_bits + (0,) idx_bits1 = idx_bits + (1,) # Printing bits0 only, this can be changed, of course. if qc.psi.ampl(*idx_bits0) != 0.0: print('{:5.1f} {:5.4f}'.format(float(helper.bits2val(bits)), qc.psi.ampl(*idx_bits0).real))
def compute_max_cut(n: int, nodes: List[int]) -> int: """Compute (inefficiently) the max cut, exhaustively.""" max_cut = -1000 for bits in helper.bitprod(n): # Collect in/out sets. iset = [] oset = [] for idx, val in enumerate(bits): iset.append(idx) if val == 0 else oset.append(idx) # Compute costs for this cut, record maximum. cut = 0 for node in nodes: if node[0] in iset and node[1] in oset: cut += node[2] if node[1] in iset and node[0] in oset: cut += node[2] if cut > max_cut: max_cut_in, max_cut_out = iset.copy(), oset.copy() max_cut = cut max_bits = bits state = bin(helper.bits2val(max_bits))[2:].zfill(n) print('Max Cut. N: {}, Max: {:.1f}, {}-{}, |{}>' .format(n, np.real(max_cut), max_cut_in, max_cut_out, state)) return helper.bits2val(max_bits)
def run_experiment(): """Run single, defined experiment for secret 11.""" psi = state.zeros(4) u = make_u() psi = ops.Hadamard(2)(psi) psi = u(psi) psi = ops.Hadamard(2)(psi) # Because of the xor patterns (Yanofski 6.64) # measurement will only find those qubit strings where # the scalar product of z (lower bits) and secret string: # <z, c> = 0 # # This should measure |00> and |11> with equal probability. # If true, than we can derive the secret string as being 11 # because f(00) = f(11) and because f(00) = f(00 ^ c) -> c = 11 # print('Measure likely states (want: pairs of 00 or 11):') for bits in helper.bitprod(4): if psi.prob(*bits) > 0.01: if (bits[0] == 0 and bits[1] == 1) or (bits[0] == 1 and bits[1] == 0): raise AssertionError('Invalid Results') print('|{}{} {}{}> = 0 : {:.2f} dot % 2: {:.2f}'.format( bits[0], bits[1], bits[2], bits[3], psi.prob(*bits), dot2(bits)))
def maxprob(self) -> (List[float], float): """Find state with highest probability.""" maxbits, maxprob = [], 0.0 for bits in helper.bitprod(self.nbits): cur_prob = self.prob(*bits) if cur_prob > maxprob: maxbits, maxprob = bits, cur_prob return maxbits, maxprob
def main(argv): if len(argv) > 1: raise app.UsageError('Too many command-line arguments.') print('Order finding.') number = flags.FLAGS.N a = flags.FLAGS.a # The classical part are handled in 'shor_classic.py' nbits = number.bit_length() print('Shor: N = {}, a = {}, n = {} -> qubits: {}'.format( number, a, nbits, nbits * 4 + 2)) qc = circuit.qc('order_finding') # Aux register for additional and multiplication. aux = qc.reg(nbits + 2, name='q0') # Register for QFT. This reg will hold the resulting x-value. up = qc.reg(nbits * 2, name='q1') # Register for multiplications. down = qc.reg(nbits, name='q2') qc.h(up) qc.x(down[0]) for i in range(nbits * 2): cmultmodn(qc, up[i], down, aux, int(a**(2**i)), number, nbits) inverse_qft(qc, up, 2 * nbits, with_swaps=1) qc.dump_to_file() print('Measurement...') total_prob = 0.0 for bits in helper.bitprod(nbits * 4 + 2): prob = qc.psi.prob(*bits) if prob > 0.01: intval = helper.bits2val(bits[nbits + 2:nbits + 2 + nbits * 2][::-1]) phase = helper.bits2frac(bits[nbits + 2:nbits + 2 + nbits * 2][::-1]) r = fractions.Fraction(phase).limit_denominator(8).denominator guesses = [ math.gcd(a**(r // 2) - 1, number), math.gcd(a**(r // 2) + 1, number) ] print( 'Final x: {:3d} phase: {:3f} prob: {:.3f} factors: {}'.format( intval, phase, prob.real, guesses)) total_prob += qc.psi.prob(*bits) if total_prob > 0.999: break print(qc.stats())
def check_result(nbits: int, c: Tuple[bool], psi: state.State) -> None: """Check expected vs achieved results.""" print(f'Expected: {c}') # The state with the 'flipped' bits will have probability 1.0. # It will be found on the ver first try. # for bits in helper.bitprod(nbits): if psi.prob(*bits) > 0.1: print('Found : {} = {:.1f}'.format(bits[:-1], psi.prob(*bits))) if bits[:-1] != c: raise AssertionError('invalid result')
def compute_partition(num_list: List[int]): """Compute paritions that add up.""" solutions = [] for bits in helper.bitprod(len(num_list)): iset = [] oset = [] for idx, val in enumerate(bits): (iset.append(num_list[idx]) if val == 0 else oset.append(num_list[idx])) if sum(iset) == sum(oset): solutions.append(bits) return solutions
def hipster_single(): """Single-qubit Hipster Technique.""" # This is a nice trick, outlined in this paper on "Hipster": # https://arxiv.org/pdf/1601.07195.pdf # # The observation is that to apply a single-qubit gate to a # gubit with index i, take the binary representation of inidices and # apply the transformation matrix to the elements according # to the power of 2 index. Generally: # "Performing a single-qubit gate on qubit k of n-qubit quantum # register applies G to pairs of amplitudes whose indices differ in # k-th bits of their binary index". # # For example, for a 2-qubit system, to apply a gate to qubit 0: # apply G to # q11, q12 psi[0], psi[1] # q21, q22 psi[2], psi[3] # # To apply to qubit 1: # q11, q12 psi[0], psi[2] # q21, q22 psi[1], psi[3] # # 'Outer loop' jumps by 2**(nbits+1) # 'Inner loop' jumps by 2**k # # To maintain the qubit / index ordering of this infrastructure, # the qubit index in the paper is reversed to the qubit index here. # (Hence the (nbits - qubit - 1) above) # # Make sure that for sample gates and all states the transformations # are identical. # for gate in (ops.PauliX(), ops.PauliZ(), ops.Hadamard(), ops.RotationX(0.5)): nbits = 5 for bits in helper.bitprod(nbits): psi = state.bitstring(*bits) qubit = random.randint(0, nbits - 1) # Full matrix (O(n*n). op = ops.Identity(qubit) * gate * ops.Identity(nbits - qubit - 1) psi1 = op(psi) # Single Qubit (O(n)) psi = apply_single_gate(gate, qubit, psi) if not psi.is_close(psi1): raise AssertionError('Invalid Single Gate Application.')
def create_unitaries(base, limit): """Create all combinations of all base gates, up to length 'limit'.""" # Create bitstrings up to bitstring length limit-1: # 0, 1, 00, 01, 10, 11, 000, 001, 010, ... # # Multiply together the 2 base operators, according to their index. # Note: This can be optimized, by remembering the last 2^x results # and multiplying them with base gets 0, 1. # gate_list = [] for width in range(limit): for bits in helper.bitprod(width): U = ops.Identity() for bit in bits: U = U @ base[bit] gate_list.append(U) return gate_list
def run_experiment(nbits): """Run single, defined experiment for secret 11.""" psi = state.zeros(nbits * 2) c = make_c(nbits) u = make_u(nbits, c) psi = ops.Hadamard(nbits)(psi) psi = u(psi) psi = ops.Hadamard(nbits)(psi) # Because of the xor patterns (Yanofski 6.64) # measurement will only find those qubit strings where # the scalar product of z (lower bits) and secret string: # <z, c> = 0 # print('Measure likely states:') for bits in helper.bitprod(nbits * 2): if psi.prob(*bits) > 0.0 and dot2(bits, nbits) < 1.0: print('|{}> = 0 : {:.2f} dot % 2: {:.2f}'.format( bits, psi.prob(*bits), dot2(bits, nbits)))
def dump_state(psi, desc: Optional[str] = None, prob_only: bool = True) -> None: """Dump probabilities for a state, as well as local qubit state.""" if desc: print('|', end='') for i in range(psi.nbits): print(i % 10, end='') print(f'> \'{desc}\'') state_list: List[str] = [] for bits in helper.bitprod(psi.nbits): if prob_only and (psi.prob(*bits) < 10e-6): continue state_list.append( '{:s}: ampl: {:+.2f} prob: {:.2f} Phase: {:5.1f}'.format( state_to_string(bits), psi.ampl(*bits), psi.prob(*bits), psi.phase(*bits))) state_list.sort() print(*state_list, sep='\n')