def projectq_expectation_value(circuit: Circuit, hamiltonian: QubitOperator) -> float: ProjectQback = Simulator() fwd = ForwarderEngine(ProjectQback) eng = MainEngine(backend=ProjectQback, engine_list=[fwd]) qureg = eng.allocate_qureg(circuit.n_qubits) tk_to_projectq(eng, qureg, circuit) eng.flush() energy = eng.backend.get_expectation_value(hamiltonian, qureg) All(Measure) | qureg return energy
def get_state(self,circuit:Circuit, fit_to_constraints=True) -> list: c = circuit.copy() if fit_to_constraints : Transform.RebaseToProjectQ().apply(c) fwd = ForwarderEngine(self._backend) eng = MainEngine(backend=self._backend,engine_list=[fwd]) qureg = eng.allocate_qureg(c.n_qubits) tk_to_projectq(eng,qureg,c) eng.flush() state = self._backend.cheat()[1] #`cheat()` returns tuple:(a dictionary of qubit indices, statevector) All(Measure) | qureg return state #list of complex numbers
def process_circuits( self, circuits: Iterable[Circuit], n_shots: Optional[int] = None, valid_check: bool = True, **kwargs: KwargTypes, ) -> List[ResultHandle]: """ See :py:meth:`pytket.backends.Backend.process_circuits`. Supported kwargs: `seed`. """ circuit_list = list(circuits) if valid_check: self._check_all_circuits(circuit_list) handle_list = [] for circuit in circuit_list: sim = Simulator(rnd_seed=kwargs.get("seed")) fwd = ForwarderEngine(sim) eng = MainEngine(backend=sim, engine_list=[fwd]) qureg = eng.allocate_qureg(circuit.n_qubits) tk_to_projectq(eng, qureg, circuit, True) eng.flush() state = np.array( eng.backend.cheat()[1], dtype=complex ) # `cheat()` returns tuple:(a dictionary of qubit indices, statevector) handle = ResultHandle(str(uuid4())) try: phase = float(circuit.phase) coeff = np.exp(phase * np.pi * 1j) state *= coeff except ValueError: warning( "Global phase is dependent on a symbolic parameter, so cannot " "adjust for phase") implicit_perm = circuit.implicit_qubit_permutation() # reverse qubits as projectq state is dlo res_qubits = [ implicit_perm[qb] for qb in sorted(circuit.qubits, reverse=True) ] measures = circuit.n_gates_of_type(OpType.Measure) if measures == 0 and n_shots is not None: backres = self.empty_result(circuit, n_shots=n_shots) else: backres = BackendResult(q_bits=res_qubits, state=state) self._cache[handle] = {"result": backres} handle_list.append(handle) return handle_list
def run(self, circuit:Circuit, shots:int, fit_to_constraints=True) -> np.ndarray: state = self.get_state(circuit, fit_to_constraints) fwd = ForwarderEngine(self._backend) eng = MainEngine(backend=self._backend,engine_list=[fwd]) qb_results = [] qureg = eng.allocate_qureg(circuit.n_qubits) for _ in range(shots): self._backend.set_wavefunction(state,qureg) All(Measure) | qureg eng.flush() results = (list(map(int,qureg))) qb_results.append(results) return np.asarray(qb_results)
def _expectation_value( self, circuit: Circuit, hamiltonian: projectq.ops.QubitOperator, valid_check: bool = True, ) -> complex: if valid_check and not self.valid_circuit(circuit): raise ValueError( "Circuits do not satisfy all required predicates for this backend" ) sim = Simulator() fwd = ForwarderEngine(sim) eng = MainEngine(backend=sim, engine_list=[fwd]) qureg = eng.allocate_qureg(circuit.n_qubits) tk_to_projectq(eng, qureg, circuit) eng.flush() energy = eng.backend.get_expectation_value(hamiltonian, qureg) return complex(energy)
def _send_cnot(self, cmd, control, target, flip=False): def cmd_mod(command): command.tags = deepcopy(cmd.tags) + command.tags command.engine = self.main_engine return command # We'll have to add all meta tags before sending on cmd_mod_eng = CommandModifier(cmd_mod) cmd_mod_eng.next_engine = self.next_engine cmd_mod_eng.main_engine = self.main_engine # forward everything to the command modifier forwarder_eng = ForwarderEngine(cmd_mod_eng) target[0].engine = forwarder_eng control[0].engine = forwarder_eng if flip: # flip the CNOT using Hadamard gates: All(H) | (control + target) CNOT | (target, control) All(H) | (control + target) else: CNOT | (control, target)
def _run(self): """ Runs all stored gates. Raises: Exception: If the mapping to the IBM backend cannot be performed or if the mapping was already determined but more CNOTs get sent down the pipeline. """ mapping = [] if len(self._interactions) > 0: ids_and_interactions = [] qubits_to_map = set() for qubit_id, interactions in self._interactions.items(): if len(interactions) > 2: num_interactions = len(interactions) else: num_interactions = 2 ids_and_interactions += [[ qubit_id, num_interactions, self._num_cnot_target[qubit_id] ]] qubits_to_map.add(qubit_id) # sort by #interactions (and #times it is the target qubit # for optimization purposes): ids_and_interactions.sort(key=lambda t: (-t[1], -t[2])) mapped_id = ids_and_interactions[0][0] corrected_counts = deepcopy(self._num_cnot_target) # correct CNOT target counts (the first ID is now fixed): for cmd in self._cmds: if (self._is_cnot(cmd) and cmd.control_qubits[0].id == mapped_id): corrected_counts[cmd.qubits[0][0].id] -= 1 # update interaction counts for i in range(1, len(ids_and_interactions)): ids_and_interactions[i][-1] = ( corrected_counts[ids_and_interactions[i][0]]) ids_and_interactions.sort(key=lambda t: (-t[1], -t[2])) # map remaining interactions to connectivity graph # where necessary. Else: map according to # of times the qubit # is a target qubit in a CNOT. interactions = deepcopy(self._interactions) places = list(range(5)) i = 0 place_idx = 0 mapping = [] while len(qubits_to_map) > 0: last_mapped_id = ids_and_interactions[i][0] ids_and_interactions = (ids_and_interactions[0:i] + ids_and_interactions[i + 1:]) current_place = places[place_idx] mapping.append((last_mapped_id, current_place)) qubits_to_map.remove(last_mapped_id) places = places[0:place_idx] + places[place_idx + 1:] if not len(places) == 4: # currently mapping non-center qubit # map (potential) interaction partner num_partners = len(interactions[last_mapped_id]) if num_partners > 1: self._reset() raise Exception("Mapping without SWAPs failed! " "Sorry...") elif num_partners == 1: partner_id = interactions[last_mapped_id].pop() idx = [ j for j in range(len(ids_and_interactions)) if ids_and_interactions[j][0] == partner_id ] i = idx[0] place_idx = [ pidx for pidx in range(len(places)) if places[pidx] == current_place + 2 ][0] else: i = 0 place_idx = 0 for idx in interactions: if last_mapped_id in interactions[idx]: interactions[idx].remove(last_mapped_id) self._interactions = dict() target_indices = {mp[0]: mp[1] for mp in mapping if mp[1] <= 2} all_indices = {mp[0]: mp[1] for mp in mapping} for cmd in self._cmds: if self._needs_flipping(cmd, all_indices): # To have nicer syntax when flipping CNOTs, we'll use a # forwarder engine and a command modifier to get the tags # right. (If the CNOT is an 'uncompute', then so must be the # remapped CNOT) def cmd_mod(command): command.tags = cmd.tags[:] + command.tags command.engine = self.main_engine return command # We'll have to add all meta tags before sending on cmd_mod_eng = CommandModifier(cmd_mod) cmd_mod_eng.next_engine = self.next_engine cmd_mod_eng.main_engine = self.main_engine # forward everything to the command modifier forwarder_eng = ForwarderEngine(cmd_mod_eng) cmd.engine = forwarder_eng qubit = cmd.qubits[0] ctrl = cmd.control_qubits # flip the CNOT using Hadamard gates: All(H) | (ctrl + qubit) CNOT | (qubit, ctrl) All(H) | (ctrl + qubit) elif cmd.gate == Allocate: ibm_order = [2, 1, 4, 0, 3] cmd.tags += [ QubitPlacementTag( ibm_order[all_indices[cmd.qubits[0][0].id]]) ] self.next_engine.receive([cmd]) else: self.next_engine.receive([cmd]) self._cmds = []
def _process_command(self, cmd): """ Check whether a command cmd can be handled by further engines and, if not, replace it using the decomposition rules loaded with the setup (e.g., setups.default). Args: cmd (Command): Command to process. Raises: Exception if no replacement is available in the loaded setup. """ if self.is_available(cmd): self.send([cmd]) else: # check for decomposition rules decomp_list = [] potential_decomps = [] # First check for a decomposition rules of the gate class, then # the gate class of the inverse gate. If nothing is found, do the # same for the first parent class, etc. gate_mro = type(cmd.gate).mro()[:-1] # If gate does not have an inverse it's parent classes are # DaggeredGate, BasicGate, object. Hence don't check the last two inverse_mro = type(get_inverse(cmd.gate)).mro()[:-2] rules = self.decompositionRuleSet.decompositions for level in range(max(len(gate_mro), len(inverse_mro))): # Check for forward rules if level < len(gate_mro): class_name = gate_mro[level].__name__ try: potential_decomps = [d for d in rules[class_name]] except KeyError: pass # throw out the ones which don't recognize the command for d in potential_decomps: if d.check(cmd): decomp_list.append(d) if len(decomp_list) != 0: break # Check for rules implementing the inverse gate # and run them in reverse if level < len(inverse_mro): inv_class_name = inverse_mro[level].__name__ try: potential_decomps += [ d.get_inverse_decomposition() for d in rules[inv_class_name] ] except KeyError: pass # throw out the ones which don't recognize the command for d in potential_decomps: if d.check(cmd): decomp_list.append(d) if len(decomp_list) != 0: break if len(decomp_list) == 0: raise NoGateDecompositionError("\nNo replacement found for " + str(cmd) + "!") # use decomposition chooser to determine the best decomposition chosen_decomp = self._decomp_chooser(cmd, decomp_list) # the decomposed command must have the same tags # (plus the ones it gets from meta-statements inside the # decomposition rule). # --> use a CommandModifier with a ForwarderEngine to achieve this. old_tags = cmd.tags[:] def cmd_mod_fun(cmd): # Adds the tags cmd.tags = old_tags[:] + cmd.tags cmd.engine = self.main_engine return cmd # the CommandModifier calls cmd_mod_fun for each command # --> commands get the right tags. cmod_eng = CommandModifier(cmd_mod_fun) cmod_eng.next_engine = self # send modified commands back here cmod_eng.main_engine = self.main_engine # forward everything to cmod_eng using the ForwarderEngine # which behaves just like MainEngine # (--> meta functions still work) forwarder_eng = ForwarderEngine(cmod_eng) cmd.engine = forwarder_eng # send gates directly to forwarder # (and not to main engine, which would screw up the ordering). chosen_decomp.decompose(cmd) # run the decomposition
def _run(self): """ Runs all stored gates. Raises: Exception: If the mapping to the IBM backend cannot be performed or if the mapping was already determined but more CNOTs get sent down the pipeline. """ cnot_id = self._cnot_id if cnot_id == -1 and len(self._cnot_ids) > 0: cnot_id = self._cnot_ids[0] if len(self._cnot_ids ) == 2: # we can optimize (at least a little bit)! count1 = 0 count2 = 0 for cmd in self._cmds: if self._is_cnot(cmd): qb = cmd.qubits[0][0] ctrl = cmd.control_qubits[0] if ctrl.id == self._cnot_ids[0]: count1 += 1 elif ctrl.id == self._cnot_ids[1]: count2 += 1 if count1 > count2: cnot_id = self._cnot_ids[1] for cmd in self._cmds: if self._is_cnot(cmd) and not cmd.qubits[0][0].id == cnot_id: # we have to flip it around. To have nice syntax, we'll use a # forwarder engine and a command modifier to get the tags right. # (If the CNOT is an 'uncompute', then so must be the remapped CNOT) def cmd_mod(command): command.tags = cmd.tags[:] + command.tags command.engine = self.main_engine return command cmd_mod_eng = CommandModifier( cmd_mod) # will add potential meta tags cmd_mod_eng.next_engine = self.next_engine # and send on all commands cmd_mod_eng.main_engine = self.main_engine # forward everything to the command modifier forwarder_eng = ForwarderEngine(cmd_mod_eng) cmd.engine = forwarder_eng # and send all gates to forwarder engine. qubit = cmd.qubits[0] ctrl = cmd.control_qubits # flip the CNOT using Hadamard gates: All(H) | (ctrl + qubit) CNOT | (qubit, ctrl) All(H) | (ctrl + qubit) # This cmd would require remapping --> # raise an exception if the CNOT id has already been determined. if self._cnot_id != -1: self._reset() raise Exception( "\nIBM Quantum Experience does not allow " "intermediate measurements / \ndestruction of " "qubits! CNOT mapping may be inconsistent.\n") else: self.next_engine.receive([cmd]) self._cmds = [] self._cnot_id = cnot_id
def _process_command(self, cmd): """ Check whether a command cmd can be handled by further engines and, if not, replace it using the decomposition rules loaded with the setup (e.g., setups.default). Args: cmd (Command): Command to process. Raises: Exception if no replacement is available in the loaded setup. """ if self.is_available(cmd): self.send([cmd]) else: # check for decomposition rules decomp_list = [] potential_decomps = [] inv_list = [] # check for forward rules cls = cmd.gate.__class__.__name__ try: potential_decomps = [ d for d in self.decompositionRuleSet.decompositions[cls] ] except KeyError: pass # check for rules implementing the inverse gate # and run them in reverse inv_cls = get_inverse(cmd.gate).__class__.__name__ try: potential_decomps += [ d.get_inverse_decomposition() for d in self.decompositionRuleSet.decompositions[inv_cls] ] except KeyError: pass # throw out the ones which don't recognize the command for d in potential_decomps: if d.check(cmd): decomp_list.append(d) if len(decomp_list) == 0: raise NoGateDecompositionError("\nNo replacement found for " + str(cmd) + "!") # use decomposition chooser to determine the best decomposition chosen_decomp = self._decomp_chooser(cmd, decomp_list) # the decomposed command must have the same tags # (plus the ones it gets from meta-statements inside the # decomposition rule). # --> use a CommandModifier with a ForwarderEngine to achieve this. old_tags = cmd.tags[:] def cmd_mod_fun(cmd): # Adds the tags cmd.tags = old_tags[:] + cmd.tags cmd.engine = self.main_engine return cmd # the CommandModifier calls cmd_mod_fun for each command # --> commands get the right tags. cmod_eng = CommandModifier(cmd_mod_fun) cmod_eng.next_engine = self # send modified commands back here cmod_eng.main_engine = self.main_engine # forward everything to cmod_eng using the ForwarderEngine # which behaves just like MainEngine # (--> meta functions still work) forwarder_eng = ForwarderEngine(cmod_eng) cmd.engine = forwarder_eng # send gates directly to forwarder # (and not to main engine, which would screw up the ordering). chosen_decomp.decompose(cmd) # run the decomposition