def get_engine_list(token=None, device=None): # Access to the hardware properties via show_devices # Can also be extended to take into account gate fidelities, new available # gate, etc.. devices = show_devices(token) aqt_setup = [] if device not in devices: raise DeviceOfflineError('Error when configuring engine list: device ' 'requested for Backend not connected') if device == 'aqt_simulator': # The 11 qubit online simulator doesn't need a specific mapping for # gates. Can also run wider gateset but this setup keep the # restrictedgateset setup for coherence mapper = BasicMapperEngine() # Note: Manual Mapper doesn't work, because its map is updated only if # gates are applied if gates in the register are not used, then it # will lead to state errors res = dict() for i in range(devices[device]['nq']): res[i] = i mapper.current_mapping = res aqt_setup = [mapper] else: # If there is an online device not handled into ProjectQ it's not too # bad, the engine_list can be constructed manually with the # appropriate mapper and the 'coupling_map' parameter raise DeviceNotHandledError('Device not yet fully handled by ProjectQ') # Most gates need to be decomposed into a subset that is manually converted # in the backend (until the implementation of the U1,U2,U3) setup = restrictedgateset.get_engine_list(one_qubit_gates=(Rx, Ry), two_qubit_gates=(Rxx, ), other_gates=(Barrier, )) setup.extend(aqt_setup) return setup
def test_runtime_error(): sim = ClassicalSimulator() mapper = BasicMapperEngine() mapper.current_mapping = {} eng = MainEngine(sim, [mapper]) with pytest.raises(RuntimeError): eng.backend.read_bit(WeakQubitRef(None, 1))
def test_aqt_backend_functional_test(monkeypatch): correct_info = { 'circuit': '[["Y", 0.5, [1]], ["X", 0.5, [1]], ["X", 0.5, [1]], ' '["Y", 0.5, [1]], ["MS", 0.5, [1, 2]], ["X", 3.5, [1]], ' '["Y", 3.5, [1]], ["X", 3.5, [2]]]', 'nq': 3, 'shots': 10, 'backend': {'name': 'simulator'}, } def mock_send(*args, **kwargs): assert args[0] == correct_info return [0, 6, 0, 6, 0, 0, 0, 6, 0, 6] monkeypatch.setattr(_aqt, "send", mock_send) backend = _aqt.AQTBackend(verbose=True, num_runs=10) # no circuit has been executed -> raises exception with pytest.raises(RuntimeError): backend.get_probabilities([]) mapper = BasicMapperEngine() res = {} for i in range(4): res[i] = i mapper.current_mapping = res eng = MainEngine(backend=backend, engine_list=[mapper]) unused_qubit = eng.allocate_qubit() qureg = eng.allocate_qureg(2) # entangle the qureg Ry(math.pi / 2) | qureg[0] Rx(math.pi / 2) | qureg[0] Rx(math.pi / 2) | qureg[0] Ry(math.pi / 2) | qureg[0] Rxx(math.pi / 2) | (qureg[0], qureg[1]) Rx(7 * math.pi / 2) | qureg[0] Ry(7 * math.pi / 2) | qureg[0] Rx(7 * math.pi / 2) | qureg[1] All(Barrier) | qureg del unused_qubit # measure; should be all-0 or all-1 All(Measure) | qureg # run the circuit eng.flush() prob_dict = eng.backend.get_probabilities([qureg[0], qureg[1]]) assert prob_dict['11'] == pytest.approx(0.4) assert prob_dict['00'] == pytest.approx(0.6) # Unknown qubit and no mapper invalid_qubit = [WeakQubitRef(eng, 10)] with pytest.raises(RuntimeError): eng.backend.get_probabilities(invalid_qubit) with pytest.raises(RuntimeError): eng.backend.get_probabilities(eng.allocate_qubit())
def __init__(self): """ Initialize an IBM 5-qubit mapper compiler engine. Resets the mapping. """ BasicMapperEngine.__init__(self) self.current_mapping = dict() self._reset()
def __init__(self, map_fun=lambda x: x): """ Initialize the mapper to a given mapping. If no mapping function is provided, the qubit id is used as the location. Args: map_fun (function): Function which, given the qubit id, returns an integer describing the physical location (must be constant). """ BasicMapperEngine.__init__(self) self.map = map_fun self.current_mapping = dict()
def test_ibm_sent_error_2(monkeypatch): backend = _ibm.IBMBackend(verbose=True) mapper = BasicMapperEngine() res = dict() for i in range(4): res[i] = i mapper.current_mapping = res eng = MainEngine(backend=backend, engine_list=[mapper]) qubit = eng.allocate_qubit() Rx(math.pi) | qubit with pytest.raises(Exception): S | qubit # no setup to decompose S gate, so not accepted by the backend dummy = DummyEngine() dummy.is_last_engine = True eng.next_engine = dummy
def test_simulator_convert_logical_to_mapped_qubits(sim): mapper = BasicMapperEngine() def receive(command_list): pass mapper.receive = receive eng = MainEngine(sim, [mapper]) qubit0 = eng.allocate_qubit() qubit1 = eng.allocate_qubit() mapper.current_mapping = { qubit0[0].id: qubit1[0].id, qubit1[0].id: qubit0[0].id } assert (sim._convert_logical_to_mapped_qureg(qubit0 + qubit1) == qubit1 + qubit0)
def test_ibm_errors(): backend = _ibm.IBMBackend(verbose=True, num_runs=1000) mapper = BasicMapperEngine() mapper.current_mapping = {0: 0} eng = MainEngine(backend=backend, engine_list=[mapper]) qb0 = WeakQubitRef(engine=None, idx=0) # No LogicalQubitIDTag with pytest.raises(RuntimeError): eng.backend._store(Command(engine=eng, gate=Measure, qubits=([qb0],))) eng = MainEngine(backend=backend, engine_list=[]) # No mapper with pytest.raises(RuntimeError): eng.backend._store(Command(engine=eng, gate=Measure, qubits=([qb0],), tags=(LogicalQubitIDTag(1),)))
def __init__(self, connections=None): """ Initialize an IBM 5-qubit mapper compiler engine. Resets the mapping. """ BasicMapperEngine.__init__(self) self.current_mapping = dict() self._reset() self._cmds = [] self._interactions = dict() if connections is None: #general connectivity easier for testing functions self.connections = set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), (2, 1), (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) else: self.connections = connections
def test_ibm_sent_error(monkeypatch): # patch send def mock_send(*args, **kwargs): raise TypeError monkeypatch.setattr(_ibm, "send", mock_send) backend = _ibm.IBMBackend(verbose=True) mapper = BasicMapperEngine() res = dict() for i in range(4): res[i] = i mapper.current_mapping = res eng = MainEngine(backend=backend, engine_list=[mapper]) qubit = eng.allocate_qubit() Rx(math.pi) | qubit with pytest.raises(Exception): qubit[0].__del__() eng.flush() # atexit sends another FlushGate, therefore we remove the backend: dummy = DummyEngine() dummy.is_last_engine = True eng.next_engine = dummy
def __init__(self, num_qubits, cyclic=False, storage=1000): """ Initialize a LinearMapper compiler engine. Args: num_qubits(int): Number of physical qubits in the linear chain cyclic(bool): If 1D chain is a cycle. Default is False. storage(int): Number of gates to temporarily store, default is 1000 """ BasicMapperEngine.__init__(self) self.num_qubits = num_qubits self.cyclic = cyclic self.storage = storage # Storing commands self._stored_commands = list() # Logical qubit ids for which the Allocate gate has already been # processed and sent to the next engine but which are not yet # deallocated: self._currently_allocated_ids = set() # Statistics: self.num_mappings = 0 self.depth_of_swaps = dict() self.num_of_swaps_per_mapping = dict()
def test_main_engine_init_mapper(): class LinearMapper(BasicMapperEngine): pass mapper1 = LinearMapper() mapper2 = BasicMapperEngine() engine_list1 = [mapper1] eng1 = _main.MainEngine(engine_list=engine_list1) assert eng1.mapper == mapper1 engine_list2 = [mapper2] eng2 = _main.MainEngine(engine_list=engine_list2) assert eng2.mapper == mapper2 engine_list3 = [mapper1, mapper2] with pytest.raises(_main.UnsupportedEngineError): _main.MainEngine(engine_list=engine_list3)
def test_ibm_backend_functional_test(monkeypatch): correct_info = { 'json': [{ 'qubits': [1], 'name': 'u2', 'params': [0, 3.141592653589793] }, { 'qubits': [1, 2], 'name': 'cx' }, { 'qubits': [1, 3], 'name': 'cx' }, { 'qubits': [1], 'name': 'u3', 'params': [6.28318530718, 0, 0] }, { 'qubits': [1], 'name': 'u1', 'params': [11.780972450962] }, { 'qubits': [1], 'name': 'u3', 'params': [6.28318530718, 0, 0] }, { 'qubits': [1], 'name': 'u1', 'params': [10.995574287564] }, { 'qubits': [1, 2, 3], 'name': 'barrier' }, { 'qubits': [1], 'name': 'u3', 'params': [0.2, -1.5707963267948966, 1.5707963267948966] }, { 'qubits': [1], 'name': 'measure', 'memory': [1] }, { 'qubits': [2], 'name': 'measure', 'memory': [2] }, { 'qubits': [3], 'name': 'measure', 'memory': [3] }], 'nq': 4, 'shots': 1000, 'maxCredits': 10, 'backend': { 'name': 'ibmq_qasm_simulator' } } # {'qasms': [{'qasm': '\ninclude "qelib1.inc";\nqreg q[4];\ncreg c[4];\nu2(0,pi/2) q[1];\ncx q[1], q[2];\ncx q[1], q[3];\nu3(6.28318530718, 0, 0) q[1];\nu1(11.780972450962) q[1];\nu3(6.28318530718, 0, 0) q[1];\nu1(10.995574287564) q[1];\nu3(0.2, -pi/2, pi/2) q[1];\nmeasure q[1] -> c[1];\nmeasure q[2] -> c[2];\nmeasure q[3] -> c[3];'}], 'json': [{'qubits': [1], 'name': 'u2', 'params': [0, 3.141592653589793]}, {'qubits': [1, 2], 'name': 'cx'}, {'qubits': [1, 3], 'name': 'cx'}, {'qubits': [1], 'name': 'u3', 'params': [6.28318530718, 0, 0]}, {'qubits': [1], 'name': 'u1', 'params': [11.780972450962]}, {'qubits': [1], 'name': 'u3', 'params': [6.28318530718, 0, 0]}, {'qubits': [1], 'name': 'u1', 'params': [10.995574287564]}, {'qubits': [1], 'name': 'u3', 'params': [0.2, -1.5707963267948966, 1.5707963267948966]}, {'qubits': [1], 'name': 'measure', 'memory': [1]}, {'qubits': [2], 'name': 'measure', 'memory': [2]}, {'qubits': [3], 'name': 'measure', 'memory': [3]}], 'nq': 4, 'shots': 1000, 'maxCredits': 10, 'backend': {'name': 'ibmq_qasm_simulator'}} def mock_send(*args, **kwargs): assert args[0] == correct_info return { 'data': { 'counts': { '0x0': 504, '0x2': 8, '0xc': 6, '0xe': 482 } }, 'header': { 'clbit_labels': [['c', 0], ['c', 1], ['c', 2], ['c', 3]], 'creg_sizes': [['c', 4]], 'memory_slots': 4, 'n_qubits': 32, 'name': 'circuit0', 'qreg_sizes': [['q', 32]], 'qubit_labels': [['q', 0], ['q', 1], ['q', 2], ['q', 3], ['q', 4], ['q', 5], ['q', 6], ['q', 7], ['q', 8], ['q', 9], ['q', 10], ['q', 11], ['q', 12], ['q', 13], ['q', 14], ['q', 15], ['q', 16], ['q', 17], ['q', 18], ['q', 19], ['q', 20], ['q', 21], ['q', 22], ['q', 23], ['q', 24], ['q', 25], ['q', 26], ['q', 27], ['q', 28], ['q', 29], ['q', 30], ['q', 31]] }, 'metadata': { 'measure_sampling': True, 'method': 'statevector', 'parallel_shots': 1, 'parallel_state_update': 16 }, 'seed_simulator': 465435780, 'shots': 1000, 'status': 'DONE', 'success': True, 'time_taken': 0.0045786460000000005 } monkeypatch.setattr(_ibm, "send", mock_send) backend = _ibm.IBMBackend(verbose=True, num_runs=1000) import sys # no circuit has been executed -> raises exception with pytest.raises(RuntimeError): backend.get_probabilities([]) mapper = BasicMapperEngine() res = dict() for i in range(4): res[i] = i mapper.current_mapping = res ibm_setup = [mapper] setup = restrictedgateset.get_engine_list(one_qubit_gates=(Rx, Ry, Rz, H), two_qubit_gates=(CNOT, ), other_gates=(Barrier, )) setup.extend(ibm_setup) eng = MainEngine(backend=backend, engine_list=setup) # 4 qubits circuit is run, but first is unused to test ability for # get_probability to return the correct values for a subset of the total # register unused_qubit = eng.allocate_qubit() qureg = eng.allocate_qureg(3) # entangle the qureg Entangle | qureg Tdag | qureg[0] Sdag | qureg[0] Barrier | qureg Rx(0.2) | qureg[0] del unused_qubit # measure; should be all-0 or all-1 All(Measure) | qureg # run the circuit eng.flush() prob_dict = eng.backend.get_probabilities([qureg[2], qureg[1]]) assert prob_dict['00'] == pytest.approx(0.512) assert prob_dict['11'] == pytest.approx(0.488) result = "\nu2(0,pi/2) q[1];\ncx q[1], q[2];\ncx q[1], q[3];" if sys.version_info.major == 3: result += "\nu3(6.28318530718, 0, 0) q[1];\nu1(11.780972450962) q[1];" result += "\nu3(6.28318530718, 0, 0) q[1];\nu1(10.995574287564) q[1];" else: result += "\nu3(6.28318530718, 0, 0) q[1];\nu1(11.780972451) q[1];" result += "\nu3(6.28318530718, 0, 0) q[1];\nu1(10.9955742876) q[1];" result += "\nbarrier q[1], q[2], q[3];" result += "\nu3(0.2, -pi/2, pi/2) q[1];\nmeasure q[1] -> c[1];" result += "\nmeasure q[2] -> c[2];\nmeasure q[3] -> c[3];" assert eng.backend.get_qasm() == result with pytest.raises(RuntimeError): eng.backend.get_probabilities(eng.allocate_qubit())
def test_ibm_retrieve(monkeypatch): # patch send def mock_retrieve(*args, **kwargs): return { 'data': { 'counts': { '0x0': 504, '0x2': 8, '0xc': 6, '0xe': 482 } }, 'header': { 'clbit_labels': [['c', 0], ['c', 1], ['c', 2], ['c', 3]], 'creg_sizes': [['c', 4]], 'memory_slots': 4, 'n_qubits': 32, 'name': 'circuit0', 'qreg_sizes': [['q', 32]], 'qubit_labels': [['q', 0], ['q', 1], ['q', 2], ['q', 3], ['q', 4], ['q', 5], ['q', 6], ['q', 7], ['q', 8], ['q', 9], ['q', 10], ['q', 11], ['q', 12], ['q', 13], ['q', 14], ['q', 15], ['q', 16], ['q', 17], ['q', 18], ['q', 19], ['q', 20], ['q', 21], ['q', 22], ['q', 23], ['q', 24], ['q', 25], ['q', 26], ['q', 27], ['q', 28], ['q', 29], ['q', 30], ['q', 31]] }, 'metadata': { 'measure_sampling': True, 'method': 'statevector', 'parallel_shots': 1, 'parallel_state_update': 16 }, 'seed_simulator': 465435780, 'shots': 1000, 'status': 'DONE', 'success': True, 'time_taken': 0.0045786460000000005 } monkeypatch.setattr(_ibm, "retrieve", mock_retrieve) backend = _ibm.IBMBackend(retrieve_execution="ab1s2", num_runs=1000) mapper = BasicMapperEngine() res = dict() for i in range(4): res[i] = i mapper.current_mapping = res ibm_setup = [mapper] setup = restrictedgateset.get_engine_list(one_qubit_gates=(Rx, Ry, Rz, H), two_qubit_gates=(CNOT, )) setup.extend(ibm_setup) eng = MainEngine(backend=backend, engine_list=setup) unused_qubit = eng.allocate_qubit() qureg = eng.allocate_qureg(3) # entangle the qureg Entangle | qureg Tdag | qureg[0] Sdag | qureg[0] Barrier | qureg Rx(0.2) | qureg[0] del unused_qubit # measure; should be all-0 or all-1 All(Measure) | qureg # run the circuit eng.flush() prob_dict = eng.backend.get_probabilities([qureg[0], qureg[2], qureg[1]]) assert prob_dict['000'] == pytest.approx(0.504) assert prob_dict['111'] == pytest.approx(0.482) assert prob_dict['011'] == pytest.approx(0.006)
def get_engine_list(token=None, device=None): """Return the default list of compiler engine for the IBM QE platform.""" # Access to the hardware properties via show_devices # Can also be extended to take into account gate fidelities, new available # gate, etc.. devices = show_devices(token) ibm_setup = [] if device not in devices: raise DeviceOfflineError('Error when configuring engine list: device requested for Backend not connected') if devices[device]['nq'] == 5: # The requested device is a 5 qubit processor # Obtain the coupling map specific to the device coupling_map = devices[device]['coupling_map'] coupling_map = list2set(coupling_map) mapper = IBM5QubitMapper(coupling_map) ibm_setup = [mapper, SwapAndCNOTFlipper(coupling_map), LocalOptimizer(10)] elif device == 'ibmq_qasm_simulator': # The 32 qubit online simulator doesn't need a specific mapping for # gates. Can also run wider gateset but this setup keep the # restrictedgateset setup for coherence mapper = BasicMapperEngine() # Note: Manual Mapper doesn't work, because its map is updated only if # gates are applied if gates in the register are not used, then it # will lead to state errors res = {} for i in range(devices[device]['nq']): res[i] = i mapper.current_mapping = res ibm_setup = [mapper] elif device == 'ibmq_16_melbourne': # Only 15 qubits available on this ibmqx2 unit(in particular qubit 7 # on the grid), therefore need custom grid mapping grid_to_physical = { 0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 15, 8: 14, 9: 13, 10: 12, 11: 11, 12: 10, 13: 9, 14: 8, 15: 7, } coupling_map = devices[device]['coupling_map'] coupling_map = list2set(coupling_map) ibm_setup = [ GridMapper(2, 8, grid_to_physical), LocalOptimizer(5), SwapAndCNOTFlipper(coupling_map), LocalOptimizer(5), ] else: # If there is an online device not handled into ProjectQ it's not too # bad, the engine_list can be constructed manually with the # appropriate mapper and the 'coupling_map' parameter raise DeviceNotHandledError('Device not yet fully handled by ProjectQ') # Most IBM devices accept U1,U2,U3,CX gates. # Most gates need to be decomposed into a subset that is manually converted # in the backend (until the implementation of the U1,U2,U3) # available gates decomposable now for U1,U2,U3: Rx,Ry,Rz and H setup = restrictedgateset.get_engine_list( one_qubit_gates=(Rx, Ry, Rz, H), two_qubit_gates=(CNOT,), other_gates=(Barrier,) ) setup.extend(ibm_setup) return setup
def __init__(self, num_rows, num_columns, mapped_ids_to_backend_ids=None, storage=1000, optimization_function=lambda x: return_swap_depth(x), num_optimization_steps=50): """ Initialize a GridMapper compiler engine. Args: num_rows(int): Number of rows in the grid num_columns(int): Number of columns in the grid. mapped_ids_to_backend_ids(dict): Stores a mapping from mapped ids which are 0,...,self.num_qubits-1 in row-major order on the grid to the corresponding qubit ids of the backend. Key: mapped id. Value: corresponding backend id. Default is None which means backend ids are identical to mapped ids. storage: Number of gates to temporarily store optimization_function: Function which takes a list of swaps and returns a cost value. Mapper chooses a permutation which minimizes this cost. Default optimizes for circuit depth. num_optimization_steps(int): Number of different permutations to of the matching to try and minimize the cost. Raises: RuntimeError: if incorrect `mapped_ids_to_backend_ids` parameter """ BasicMapperEngine.__init__(self) self.num_rows = num_rows self.num_columns = num_columns self.num_qubits = num_rows * num_columns # Internally we use the mapped ids until sending a command. # Before sending we use this map to translate to backend ids: self._mapped_ids_to_backend_ids = mapped_ids_to_backend_ids if self._mapped_ids_to_backend_ids is None: self._mapped_ids_to_backend_ids = dict() for i in range(self.num_qubits): self._mapped_ids_to_backend_ids[i] = i if (not (set(self._mapped_ids_to_backend_ids.keys()) == set( list(range(self.num_qubits)))) or not (len(set(self._mapped_ids_to_backend_ids.values())) == self.num_qubits)): raise RuntimeError("Incorrect mapped_ids_to_backend_ids parameter") self._backend_ids_to_mapped_ids = dict() for mapped_id, backend_id in self._mapped_ids_to_backend_ids.items(): self._backend_ids_to_mapped_ids[backend_id] = mapped_id # As we use internally the mapped ids which are in row-major order, # we have an internal current mapping which maps from logical ids to # these mapped ids: self._current_row_major_mapping = deepcopy(self.current_mapping) self.storage = storage self.optimization_function = optimization_function self.num_optimization_steps = num_optimization_steps # Randomness to pick permutations if there are too many. # This creates an own instance of Random in order to not influence # the bound methods of the random module which might be used in other # places. self._rng = random.Random(11) # Storing commands self._stored_commands = list() # Logical qubit ids for which the Allocate gate has already been # processed and sent to the next engine but which are not yet # deallocated: self._currently_allocated_ids = set() # Change between 2D and 1D mappings (2D is a snake like 1D chain) # Note it translates to our mapped ids in row major order and not # backend ids which might be different. self._map_2d_to_1d = dict() self._map_1d_to_2d = dict() for row_index in range(self.num_rows): for column_index in range(self.num_columns): if row_index % 2 == 0: mapped_id = row_index * self.num_columns + column_index self._map_2d_to_1d[mapped_id] = mapped_id self._map_1d_to_2d[mapped_id] = mapped_id else: mapped_id_2d = row_index * self.num_columns + column_index mapped_id_1d = ((row_index + 1) * self.num_columns - column_index - 1) self._map_2d_to_1d[mapped_id_2d] = mapped_id_1d self._map_1d_to_2d[mapped_id_1d] = mapped_id_2d # Statistics: self.num_mappings = 0 self.depth_of_swaps = dict() self.num_of_swaps_per_mapping = dict()