In this section, you will implement an algorithm to try to separate quantum states into the qubits they were constructed from, see that the algorithm is incomplete, discuss ways to improve the algorithm, and introduce the notion that some quantum states cannot be separated in this manner.
With the create_quantum_state function, any time we put in a list of qubits, we get a single state out, representing them together. We could go in the other direction, and write a function called get_qubits_from_state, which can, given a state, return the original qubits from which it was generated.
There are some states that are impossible to separate, no matter how good we make our function that determines whether it is separable. The mathematics works out that for some states, no matter what, we can't describe the parts of the state individually. One example of such a state is the following state, which is composed of two qubits:
non_separable_state_00_plus_11=1/np.sqrt(2)*(create_quantum_state([zero_qubit,zero_qubit])+create_quantum_state([one_qubit,one_qubit]))
The mathematics work out that there's no way to divide the two qubits so that we can talk about them separately. Let's call the two qubits a and b; there is no way to separate a and b into states so that non_separable_state_00_plus_11 is equal to a b. This is called a non-separable state, and these non-separable states are crucial to the power of quantum computing, as we will learn in the next section of this chapter. A non-separable state is called an entangled state.
To make a stab at separating a state, we will assume that the state was created from qubits that are one of the basis states we went over in Chapter 2, Qubits, |"0">, |"1">, |"+">, |"-">, , or .
Now, we can define a function that will return which possible combination of basis states that the state we are interested in is composed of, and, if that doesn't apply, return None:
def get_qubits_from_state(state):
basis_states=[zero_qubit,one_qubit,plus_qubit,minus_qubit,
clockwisearrow_qubit,counterclockwisearrow_qubit]
for separated_state in itertools.product(basis_states,
repeat=get_nqubits_quantum_state(state)):
candidate_state=create_quantum_state(separated_state)
if np.allclose(candidate_state,state):
return separated_state
This function computes all possible permutations of the basis states that are of our desired length. It then computes what the entangled quantum state would be for each of those permutations. Finally, it checks whether that candidate is equal to the state in question. If it is, it returns the permutation. If no such state is found, it returns None. Our simplified code won't catch all possible separations that could ever exist, though.
One example that our get_qubits_from_state would fail to separate, but is in fact possible to separate, would be something as simple as a qubit from the previous chapter, that is, 10% |"0"> and 90% |"1">, defined as ten_ninety_qubit=np.sqrt(0.1)*zero_qubit+np.sqrt(0.9)*one_qubit. Not only would the get_qubits_from_state(ten_ninety_qubit) function return None, but also get_qubits_from_state(create_quantum_state([ten_ninety_qubit,ten_ninety_qubit])) would return None. It's clear that, since get_qubits_from_state is simply supposed to tell us what list went into the create_quantum_state function, it isn't a complete function. It didn't work in this case. We could switch the get_qubits_from_state function to consider the possibility that one of the original states is ten_ninety_qubit. I'll call the function get_qubits_from_state_with_additional_guess as follows:
def get_qubits_from_state_with_additional_guess(state):
basis_states=[zero_qubit,one_qubit,plus_qubit,minus_qubit,
clockwisearrow_qubit,counterclockwisearrow_qubit,
ten_ninety_qubit]
for separated_state in itertools.product(basis_states,
repeat=get_nqubits_quantum_state(state)):
candidate_state=create_quantum_state(separated_state)
if np.allclose(candidate_state,state):
return separated_state
Now things work! get_qubits_from_state_with_additional_guess(ten_ninety_qubit) returns ten_ninety_qubit and get_qubits_from_state_with_additional_guess(create_quantum_state([ten_ninety_qubit,ten_ninety_qubit])) returns [ten_ninety_qubit,ten_ninety_qubit]. But we'd need a lot of guesses to be able to consider all possible scenarios with this simple algorithm. We would have to modify the function to consider all possible permutations of all possible states, not just those permutations of the few basis states we have studied. That would be a tough order! It turns out this problem is something called NP-hard, which essentially means it is a highly difficult problem to solve classically. We won't solve it in this text, and will only separate states that are composed of one of the basis states we have chosen to work with, as in get_qubits_from_state. The important thing to take away is that any state that can in principle be reduced to a of single qubits is called a separable state, although sometimes finding whether this separation is possible and the separation itself can be hard to compute classically.