Stabilizer simulator#17
Conversation
Seems like we have measurement in correctly now!
Stabilizer simulator can now return the state (which has been tested) and the density matrix (which has not been tested) More tests need to be generated to validate that the stabilizer simulation actually works.
…to stabilizer_simulator
Error was on how we were pulling the qubits from the instruction. NOTE: Do not use get_qubits() as it always return qubit indices in numerical order.
returns wavefunction and density matrix.
| qvm = QVM_Density(gate_set=gate_set, noise_model=noise_model) | ||
|
|
||
| elif type_trans == 'stabilizer': | ||
| qvm = QVM_Stabilizer(gate_set=stabilizer_gate_set) |
| return param_obj.address | ||
| elif isinstance(param_obj, Slot): | ||
| return param_obj.value() | ||
| # elif isinstance(param_obj, Slot): |
|
|
||
| S T A B I L I Z E R | ||
|
|
||
| S T A T E S |
| if invalid is True and self.all_inst is False: | ||
| raise TypeError("In QVM_Unitary, only Gates and DefGates are " | ||
| "supported") | ||
| raise TypeError("Some gates used are not allowed in this QAM") |
There was a problem hiding this comment.
will defgates let the user sneak through invalid programs?
There was a problem hiding this comment.
yes, but that was always a risk of defgates that was weighed in the original quil spec. If users want to use defgate they should know that it takes them outside of the QAM model. Remember QAM requires gate specification in the definition. If the user wants to add something to this list then we are trusting them to know what they're doing.
| if not isinstance(pyquil_program, Program): | ||
| raise TypeError("I can only generate from pyQuil programs") | ||
|
|
||
| if self.all_inst is None: |
There was a problem hiding this comment.
There is a better way to do this in terms of ABC's but that will be a future upgrade. I will add to the issue list.
| else: | ||
| self.tableau = self._n_qubit_tableau(num_qubits) | ||
|
|
||
| def load_program(self, pyquil_program): |
There was a problem hiding this comment.
this whole function is redundant with the superclass
There was a problem hiding this comment.
I make a note that at some point each subclass or QAM should make its own load_program. The reason for this is that if load_program needs to check particular gate sets (like matchgates only...or something like that) each new subclass has to provide this functionality. In the future I think I'd like to remove load_program from QAM.
There was a problem hiding this comment.
then can you remove the obviously-not-appropriate-for-stabalizer-qvm parts of this implementation
| defined_gates[dg.name] = dg.matrix | ||
| self.defgate_set = defined_gates | ||
|
|
||
| # if QVM_Unitary, check if all instructions are valid. |
There was a problem hiding this comment.
it's probably not QVM_Unitary at this point
Slots are no longer in pyquil
| # return HALTED (i.e. program_counter is end of program) | ||
| return self.program_counter == len(self.program) | ||
|
|
||
| def pre(self): |
There was a problem hiding this comment.
Asymmetric Pauli channel model still works in stabilizer simulation. preparing for the future
|
|
||
| elif isinstance(instruction, Jump): | ||
| # unconditional Jump; go directly to Label | ||
| self.program_counter = self.find_label(instruction.target) |
There was a problem hiding this comment.
self.find_label doesn't exist
There was a problem hiding this comment.
moved to QAM superclass from wavefunction. Fixes the same problem in density_qvm that slipped by!!!
| self._apply_cnot(instruction) | ||
| elif instruction.name == 'I': | ||
| pass | ||
| else: |
There was a problem hiding this comment.
Future additions! Right now this is just CHP simulator
| # set up stabilizers | ||
| self.tableau = self._n_qubit_tableau(self.num_qubits) | ||
| self.kernel() | ||
| results.append(list(map(int, self.classical_memory[classical_addresses]))) |
There was a problem hiding this comment.
[int(b) for b in self.classical_memory[classical_addresses]]
| self.tableau = self._n_qubit_tableau(self.num_qubits) | ||
| self.kernel() | ||
| stabilizers = binary_stabilizer_to_pauli_stabilizer(self.stabilizer_tableau()) | ||
| pauli_ops = list(map(lambda x: 0.5 * (sI(0) + x), stabilizers)) |
There was a problem hiding this comment.
write as list comprehension
| self.kernel() | ||
| stabilizers = binary_stabilizer_to_pauli_stabilizer(self.stabilizer_tableau()) | ||
| stabilizer_state = project_stabilized_state(stabilizers) | ||
| stabilizer_state = np.array(stabilizer_state.todense()) # todense() returns a matrixlib type |
There was a problem hiding this comment.
stabalizer_state.toarray() does this in one fell swoop
| """ | ||
| phase_accumulator = 0 | ||
| for j in range(self.num_qubits): | ||
| # self._g_update(x_{hj}, z_{hj}, x_{ij}, z_{ij}) |
There was a problem hiding this comment.
this comment doesn't match the actual indices?
| :param t: target qubit index | ||
| :return: 0/1 phase update for r_{i} | ||
| """ | ||
| return self.tableau[i, -1] ^ (self.tableau[i, c] * self.tableau[i, t + self.num_qubits]) * ( |
There was a problem hiding this comment.
this line is wrapped weirdly
| # We need to check if R(i) anticommutes with Za...which it does if x_{ia} = 1 | ||
| if self.tableau[ii, t_qbit] == 1: # referencing the destabilizers | ||
|
|
||
| # TODO: Remove this for performance? |
There was a problem hiding this comment.
did you want to remove this?
| rindices, cindices = state.nonzero() | ||
| for ridx, cidx in zip(rindices, cindices): | ||
| # this is so gross looking | ||
| bitstring = list(map(int, np.binary_repr(ridx, width=num_qubits)))[::-1] |
| # this is so gross looking | ||
| bitstring = list(map(int, np.binary_repr(ridx, width=num_qubits)))[::-1] | ||
| new_ket, new_coefficient = compute_action(bitstring, pauli_operator, num_qubits) | ||
| new_indices.append(int("".join([str(x) for x in new_ket[::-1]]), 2)) |
There was a problem hiding this comment.
this seems like an odd way of going from bits to an integer. maybe make a function to make it clear what you're doing here. You can avoid converting to string if you do something like
places = 2**np.arange(n)
num = np.sum(places * bits)| state += state_family_generator(state, generator) | ||
| state /= 2 | ||
|
|
||
| normalization = (state.conj().T.dot(state)).todense() |
| if vector1.shape != vector2.shape: | ||
| raise ValueError("vectors must be the same size.") | ||
|
|
||
| # TODO: add a check for binary or integer linear arrays |
|
|
||
| # TODO: add a check for binary or integer linear arrays | ||
|
|
||
| hadamard_product = np.multiply(vector1, vector2) |
There was a problem hiding this comment.
why not vector1 * vector2
Clean up and list comps
Implements the "Improved Simulation of Stabilizer Circuits" from Aaronson and Gottesman. This qvm also contains nice functions for projecting out states starting from stabilizers and returning the density matrix starting from stabilizers.