#!/usr/bin/env python # coding: utf-8 # # Classical bits in Deutcsh Jozsa algorithm # # *** #
# # ## Preliminarys # # *** # In[1]: # Returning functions. def power(e): """Returns a single argument function that raises the argument to e.""" # Define the function f. It is local to the function power. def f(x): # This is f's return value. Note it uses power's e variable. return x**e # This is power's return value. It's the function f. # Note that in some sense f will live longer than power even though it's "local". return f # In[2]: # Call the power function, square is a function. square = power(2) # In[3]: # Call square. square(5) #
# # ## Constant Boolean functions # # *** # In[4]: # A constant function. def bitstobit(x0, x1, x2): return 1 # In[5]: # Always returns 1. bitstobit(1, 0, 1) # In[6]: # Always returns 1. bitstobit(0, 0, 0) # In[7]: # Print all possible inputs/outputs. print(" x0 x1 x2 f") for x0 in [0, 1]: for x1 in [0, 1]: for x2 in [0, 1]: print(f"{x0:4d} {x1:4d} {x2:4d} {bitstobit(x0, x1, x2):4d}") #
# # ## Balanced Boolean functions # # *** # $ # 000 \rightarrow (0 \times 2^0) + (0 \times 2^1) + (0 \times 2^2) = 0 \\ # 001 \rightarrow (0 \times 2^0) + (0 \times 2^1) + (1 \times 2^2) = 1 \\ # 010 \rightarrow (0 \times 2^0) + (1 \times 2^1) + (0 \times 2^2) = 2 \\ # 011 \rightarrow (0 \times 2^0) + (1 \times 2^1) + (1 \times 2^2) = 3 \\ # 100 \rightarrow (1 \times 2^0) + (0 \times 2^1) + (0 \times 2^2) = 4 \\ # 101 \rightarrow (1 \times 2^0) + (0 \times 2^1) + (1 \times 2^2) = 5 \\ # 110 \rightarrow (1 \times 2^0) + (1 \times 2^1) + (0 \times 2^2) = 6 \\ # 111 \rightarrow (1 \times 2^0) + (1 \times 2^1) + (1 \times 2^2) = 7 \\ # $ # In[8]: # A balanced function. def bitstobit(x0, x1, x2): # Return values. vals = [0, 1, 0, 1, 0, 1, 0, 1] # Get the retun value's index in vals. # e.g. 010 -> (0 x 2^0) + (1 x 2^1) + (0 x 2^2) i = x0 * 2**0 + x1 * 2**1 + x2 * 2**2 # Return the correct value. return vals[i] # In[9]: bitstobit(1, 0, 0) # In[10]: bitstobit(1, 1, 1) # In[11]: # Print all possible inputs/outputs. print(" x0 x1 x2 f") for x0 in [0, 1]: for x1 in [0, 1]: for x2 in [0, 1]: print(f"{x0:4d} {x1:4d} {x2:4d} {bitstobit(x0, x1, x2):4d}") #
# # ## All the functions # # *** # In[12]: import itertools as it # No of bits in input. nobits = 2 # Generate all length four binary tuples, then transpose. funcs = list(zip(*it.product([0,1], repeat=2**nobits))) # Generate all possible values of two bits. bits = list(it.product([0,1], repeat=nobits)) # Print a header. th = [f"x{i}" for i in range(nobits)] + [f"f{i}" for i in range(2**2**nobits)] print(" ".join([(" " + h)[-5:] for h in th] )) # Print the functions. for i in range(len(bits)): print(" ".join([f"{j:5d}" for j in bits[i]] + [f"{j:>5d}" for j in funcs[i]])) #
# # ## Random function # # *** # In[13]: import random def f_bitstobit(): """Creates a function mapping three bits to 1. The function is randomly either constant or balanced.""" # Randomly decide if we're balanced or constant. if random.random() < 0.5: # List of eight bits, four 0's and four 1's. vals = [0, 1] * 4 else: # Pick 0 half the time, 1 half the time. if random.random() < 0.5: # Eight 1 bits. vals = [1] * 8 else: # Eight 0 bits. vals = [0] * 8 # Randiomly shuffle the values. random.shuffle(vals) # Construct the function. Note this is a function within a function. def f(x0, x1, x2): # Get the retun value's index in vals. i = x0 * 2**0 + x1 * 2**1 + x2 * 2**2 # Return the correct value. return vals[i] # Return the function f. Note it will live on beyond this function. return f # In[14]: # Create the function. We don't know if its constant of balanced. bitstobit = f_bitstobit() # In[15]: # Do a test run of the function. bitstobit(1, 0, 1) #
# # #### Questions # # 1. In the best case scenario, what is the minimum number of times we need to call `bitstobit` to determine if it is constant or balanced? # 2. In the worst case scenario, what is the minimum number of times we need to call `bitstobit` to determine if it is constant or balanced? # 3. If we get the same return value from four different calls to `bitsttobit`, what is the probability it is balanced? # # #### Exercise 1 # # Write a function called `isbalanced` that takes a `bitstobit` function as an input and returns True if it is definitely balanced and False otherwise. # In[16]: def isbalanced(bitstobit): """Returns True if bitstobit is balanced, False otherwise.""" # Make this function work here. return True # In[17]: # This is how the above function will be called. # Create a new random bitstobit function. bitstobit = f_bitstobit() # Check if it's balanced. isbalanced(bitstobit) # In[18]: # This should check that it works. # Print all possible inputs/outputs. print(" x0 x1 x2 f") for x0 in [0, 1]: for x1 in [0, 1]: for x2 in [0, 1]: print(f"{x0:4d} {x1:4d} {x2:4d} {bitstobit(x0, x1, x2):4d}") #
# # ## General number of bits # # *** # In[19]: def f_bitstobit(k=3): """Creates a function mapping k bits to 1. The function is randomly either constant or balanced.""" # Randomly decide if we're constant or balanced. if random.random() < 0.5: # List of 2^k bits, 2^(k-1) 0's and 2^(k-1) 1's. vals = [0, 1] * (2**(k-1)) else: # Pick 0 half the time, 1 half the time. if random.random() < 0.5: # 2^k 1's. vals = [1] * (2**k) else: # 2^k 0's. vals = [0] * (2**k) # Shuffle the values. random.shuffle(vals) # Construct the function. def f(xs): # Get the retun value's index in vals. i = sum([(xs[i] * 2**i) for i in range(len(xs))]) # Return the correct value. return vals[i] # Return f. return f # In[20]: # Number of inputs bits. k = 4 # In[21]: bitstobit = f_bitstobit(k) # In[22]: # Make sure the bits are in a list or tuple. t = [random.choice([0,1]) for i in range(k)] t # In[23]: # Example of calling bitstobit. bitstobit(t) # In[24]: import itertools print("".join([f" x{i}" for i in range(k)]) + " f") for x in itertools.product([0,1], repeat=k): print("".join([f"{i:4d}" for i in x]) + f" {bitstobit(x)}") #
# # #### Questions # # 1. In the worst case, if there are 100 input bits, how many times does bitstobit have to be called to determine if the function if balanced? # 2. How long will that take on the machine you're running this on? # 3. How many input bits will make the number of calls required greater than the number of known atoms in the universe? # # *** # In[25]: import timeit # In[26]: timeit.timeit(lambda: bitstobit([random.choice([0,1]) for i in range(k)])) # *** # # ## End