class TestOperation(unittest.TestCase): # Runs before all tests @classmethod def setUpClass(self) -> None: op_info = { 'name': "SINGLE", 'qids': ["qubit_1"], 'cids': None, 'gate': Operation.X, 'gate_param': None, 'pre_allocated_qubits': False, 'computing_host_ids': ["QPU_1"] } self.operation = Operation( name=op_info['name'], qids=op_info['qids'], gate=op_info['gate'], computing_host_ids=op_info['computing_host_ids']) self._op_info = op_info # Runs after all tests @classmethod def tearDownClass(self) -> None: pass def test_instantiation(self): self.assertEqual(self.operation.name, "SINGLE") self.assertEqual(self.operation.gate, Operation.X) self.assertEqual(self.operation.computing_host_ids, ["QPU_1"]) self.assertEqual(self.operation.get_dict(), self._op_info)
def setUpClass(self) -> None: op_info = { 'name': "SINGLE", 'qids': ["qubit_1"], 'cids': None, 'gate': Operation.X, 'gate_param': None, 'pre_allocated_qubits': False, 'computing_host_ids': ["QPU_1"] } self.operation = Operation( name=op_info['name'], qids=op_info['qids'], gate=op_info['gate'], computing_host_ids=op_info['computing_host_ids']) self._op_info = op_info
def test_instantiation(self): self.assertEqual(self._circuit.total_qubits(), 3) self.assertEqual(self._circuit.q_map, self._q_map) self.assertEqual(self._circuit.layers, []) self._circuit.add_new_qubit({'QPU_2': ['qubit_4']}) self.assertEqual(self._circuit.total_qubits(), 4) operations = [Operation( name="SINGLE", qids=["qubit_1"], gate=Operation.X, computing_host_ids=["QPU_1"])] layer = Layer(operations) self._circuit.add_layer_to_circuit(layer) self.assertNotEqual(self._circuit.layers, []) self.assertEqual(self._circuit.layers[0].operations[0].name, "SINGLE")
def test_distributed_scheduler(self): self.controller_host.connect_host("QPU_2") q_map = { 'QPU_1': ['qubit_1', 'qubit_2'], 'QPU_2': ['qubit_2', 'qubit_4'] } # Form layer 1 op_1 = Operation(name="SINGLE", qids=["qubit_1"], gate=Operation.H, computing_host_ids=["QPU_1"]) op_2 = Operation(name="SEND_ENT", qids=["qubit_2"], computing_host_ids=["QPU_1", "QPU_2"]) op_3 = Operation(name="REC_ENT", qids=["qubit_2"], computing_host_ids=["QPU_2", "QPU_1"]) layer_1 = Layer([op_1, op_2, op_3]) # Form layer 2 op_1 = Operation(name="TWO_QUBIT", qids=["qubit_2", "qubit_4"], gate=Operation.CNOT, computing_host_ids=["QPU_2"]) layer_2 = Layer([op_1]) # Form layer 3 op_1 = Operation(name="MEASURE", qids=["qubit_2"], cids=["bit_1"], computing_host_ids=["QPU_2"]) layer_3 = Layer([op_1]) # Form layer 4 op_1 = Operation(name="SEND_CLASSICAL", cids=["bit_1"], computing_host_ids=["QPU_2", "QPU_1"]) op_2 = Operation(name="REC_CLASSICAL", cids=["bit_1"], computing_host_ids=["QPU_1", "QPU_2"]) layer_4 = Layer([op_1, op_2]) # Form layer 5 op_1 = Operation(name="CLASSICAL_CTRL_GATE", qids=["qubit_1"], cids=["bit_1"], gate=Operation.X, computing_host_ids=["QPU_1"]) layer_5 = Layer([op_1]) layers = [layer_1, layer_2, layer_3, layer_4, layer_5] circuit = Circuit(q_map, layers) computing_host_schedules, max_execution_time = self.controller_host._create_distributed_schedules( circuit) self.assertEqual(len(computing_host_schedules), 2) self.assertEqual(len(computing_host_schedules['QPU_1']), 4) self.assertEqual(len(computing_host_schedules['QPU_2']), 4) self.assertEqual(max_execution_time, 5) self.assertEqual(computing_host_schedules['QPU_1'][0]['name'], "SINGLE") self.assertEqual(computing_host_schedules['QPU_1'][0]['layer_end'], 0) self.assertEqual(computing_host_schedules['QPU_1'][1]['name'], "SEND_ENT") self.assertEqual(computing_host_schedules['QPU_1'][1]['layer_end'], 0) self.assertEqual(computing_host_schedules['QPU_1'][2]['name'], "REC_CLASSICAL") self.assertEqual(computing_host_schedules['QPU_1'][2]['layer_end'], 3) self.assertEqual(computing_host_schedules['QPU_1'][3]['name'], "CLASSICAL_CTRL_GATE") self.assertEqual(computing_host_schedules['QPU_1'][3]['layer_end'], 4) self.assertEqual(computing_host_schedules['QPU_2'][0]['name'], "REC_ENT") self.assertEqual(computing_host_schedules['QPU_2'][0]['layer_end'], 0) self.assertEqual(computing_host_schedules['QPU_2'][1]['name'], "TWO_QUBIT") self.assertEqual(computing_host_schedules['QPU_2'][1]['layer_end'], 1) self.assertEqual(computing_host_schedules['QPU_2'][2]['name'], "MEASURE") self.assertEqual(computing_host_schedules['QPU_2'][2]['layer_end'], 2) self.assertEqual(computing_host_schedules['QPU_2'][3]['name'], "SEND_CLASSICAL") self.assertEqual(computing_host_schedules['QPU_2'][3]['layer_end'], 3)
def test_monolithic_to_distributed_circuit_algorithm_2(self): self.controller_host.connect_hosts( ["QPU_2", "QPU_3", "QPU_4", "QPU_5"]) q_map = { 'QPU_1': ['qubit_1', 'qubit_3', 'qubit_4'], 'QPU_2': ['qubit_2'], 'QPU_3': ['qubit_5'], 'QPU_4': ['qubit_6'], 'QPU_5': ['qubit_7'] } # Form layer 1 op_1 = Operation(name="SINGLE", qids=["qubit_1"], gate=Operation.H, computing_host_ids=["QPU_1"]) layer_1 = Layer([op_1]) # Form layer 2 op_1 = Operation(name="TWO_QUBIT", qids=["qubit_2", "qubit_1"], gate=Operation.CNOT, computing_host_ids=["QPU_2", "QPU_1"]) op_2 = Operation(name="TWO_QUBIT", qids=["qubit_5", "qubit_6"], gate=Operation.CNOT, computing_host_ids=["QPU_3", "QPU_4"]) op_3 = Operation(name="SINGLE", qids=["qubit_7"], gate=Operation.H, computing_host_ids=["QPU_5"]) layer_2 = Layer([op_1, op_2, op_3]) # Form layer 3 op_1 = Operation(name="TWO_QUBIT", qids=["qubit_2", "qubit_3"], gate=Operation.CNOT, computing_host_ids=["QPU_2", "QPU_1"]) layer_3 = Layer([op_1]) # Form layer 4 op_1 = Operation(name="TWO_QUBIT", qids=["qubit_2", "qubit_4"], gate=Operation.CNOT, computing_host_ids=["QPU_2", "QPU_1"]) layer_4 = Layer([op_1]) # Form layer 5 op_1 = Operation(name="TWO_QUBIT", qids=["qubit_1", "qubit_6"], gate=Operation.CNOT, computing_host_ids=["QPU_1", "QPU_4"]) layer_5 = Layer([op_1]) layers = [layer_1, layer_2, layer_3, layer_4, layer_5] circuit = Circuit(q_map, layers) distributed_circuit = self.controller_host._generate_distributed_circuit( circuit) layers = distributed_circuit.layers self.assertEqual(len(layers), 23) self.assertEqual(layers[0].operations[0].name, "SINGLE") layer_op_names = [i.name for i in layers[1].operations] self.assertEqual( layer_op_names, ['SINGLE', 'SEND_ENT', 'REC_ENT', 'SEND_ENT', 'REC_ENT']) layer_op_names = [i.name for i in layers[2].operations] self.assertEqual(layer_op_names, ["TWO_QUBIT", "TWO_QUBIT"]) self.assertEqual(layers[6].operations[0].name, "TWO_QUBIT") self.assertEqual(layers[6].operations[1].name, "TWO_QUBIT") self.assertEqual(layers[7].operations[0].name, "TWO_QUBIT") self.assertEqual(layers[7].operations[1].name, "SINGLE") self.assertEqual(layers[8].operations[0].name, "TWO_QUBIT") layer_op_names = [i.name for i in layers[11].operations] self.assertEqual(layer_op_names, ['SEND_CLASSICAL', 'REC_CLASSICAL']) layer_op_names = [i.name for i in layers[13].operations] self.assertEqual(layer_op_names, ['SEND_ENT', 'REC_ENT']) self.assertEqual(layers[22].operations[0].name, "CLASSICAL_CTRL_GATE")
def test_monolithic_to_distributed_circuit_algorithm_1(self): self.controller_host.connect_host("QPU_2") q_map = { 'QPU_1': ['qubit_1', 'qubit_2'], 'QPU_2': ['qubit_3', 'qubit_4'], 'QPU_3': ['qubit_5'] } # Form layer 1 op_1 = Operation(name="SINGLE", qids=["qubit_1"], gate=Operation.H, computing_host_ids=["QPU_1"]) op_2 = Operation(name="SINGLE", qids=["qubit_3"], gate=Operation.H, computing_host_ids=["QPU_2"]) op_3 = Operation(name="SINGLE", qids=["qubit_3"], gate=Operation.H, computing_host_ids=["QPU_3"]) layer_1 = Layer([op_1, op_2, op_3]) # Form layer 2 op_1 = Operation(name="TWO_QUBIT", qids=["qubit_3", "qubit_1"], gate=Operation.CNOT, computing_host_ids=["QPU_2", "QPU_1"]) op_2 = Operation(name="SINGLE", qids=["qubit_3"], gate=Operation.X, computing_host_ids=["QPU_3"]) layer_2 = Layer([op_1, op_2]) # Form layer 3 op_1 = Operation(name="MEASURE", qids=["qubit_3"], cids=["bit_1"], computing_host_ids=["QPU_2"]) layer_3 = Layer([op_1]) layers = [layer_1, layer_2, layer_3] circuit = Circuit(q_map, layers) distributed_circuit = self.controller_host._generate_distributed_circuit( circuit) self.assertEqual(len(distributed_circuit.layers), 12) self.assertEqual(len(distributed_circuit.layers[0].operations), 3) self.assertEqual(len(distributed_circuit.layers[1].operations), 3) self.assertEqual(distributed_circuit.layers[1].operations[0].name, "SINGLE") self.assertEqual(distributed_circuit.layers[1].operations[1].name, "SEND_ENT") self.assertEqual(distributed_circuit.layers[1].operations[2].name, "REC_ENT") self.assertEqual(distributed_circuit.layers[2].operations[0].name, "TWO_QUBIT") self.assertEqual(distributed_circuit.layers[10].operations[0].name, "CLASSICAL_CTRL_GATE") self.assertEqual(distributed_circuit.layers[11].operations[0].name, "MEASURE")
def test_control_gate_info(self): self._circuit.add_new_qubit({'QPU_1': ['qubit_4']}) self._circuit.add_new_qubit({'QPU_3': ['qubit_5']}) self._circuit.add_new_qubit({'QPU_4': ['qubit_6']}) self.assertEqual(self._circuit.total_qubits(), 6) op_1 = Operation(name="TWO_QUBIT", qids=["qubit_2", "qubit_1"], gate=Operation.CNOT, computing_host_ids=["QPU_2", "QPU_1"]) op_2 = Operation(name="TWO_QUBIT", qids=["qubit_5", "qubit_6"], gate=Operation.CNOT, computing_host_ids=["QPU_3", "QPU_4"]) layer_1 = Layer([op_1, op_2]) op_1 = Operation(name="TWO_QUBIT", qids=["qubit_2", "qubit_3"], gate=Operation.CNOT, computing_host_ids=["QPU_2", "QPU_1"]) layer_2 = Layer([op_1]) op_1 = Operation(name="TWO_QUBIT", qids=["qubit_2", "qubit_4"], gate=Operation.CNOT, computing_host_ids=["QPU_2", "QPU_1"]) layer_3 = Layer([op_1]) op_1 = Operation(name="TWO_QUBIT", qids=["qubit_6", "qubit_1"], gate=Operation.CNOT, computing_host_ids=["QPU_4", "QPU_1"]) layer_4 = Layer([op_1]) self._circuit.add_layer_to_circuit(layer_1) self._circuit.add_layer_to_circuit(layer_2) self._circuit.add_layer_to_circuit(layer_3) self._circuit.add_layer_to_circuit(layer_4) control_gate_info = self._circuit.control_gate_info() self.assertEqual(len(control_gate_info[0]), 2) self.assertEqual(control_gate_info[0][0]['computing_hosts'], ['QPU_2', 'QPU_1']) self.assertEqual(control_gate_info[1], []) self.assertEqual(control_gate_info[2], []) self.assertEqual(len(control_gate_info[0][1]['operations']), 1) test_operations = control_gate_info[0][0]['operations'] self.assertEqual(len(test_operations), 3) self.assertEqual(test_operations[0].get_target_qubit(), 'qubit_4') self.assertEqual(test_operations[1].get_target_qubit(), 'qubit_3') self.assertEqual(test_operations[2].get_target_qubit(), 'qubit_1') self.assertEqual(control_gate_info[0][0]['control_qubit'], 'qubit_2') self.assertEqual(control_gate_info[3][0]['control_qubit'], 'qubit_6')
def test_cnot_1(self): q_map = { 'QPU_1': ['qubit_1'], 'QPU_2': ['qubit_2']} # TODO: Maybe layer one is always like this # Form layer 1 op_1 = Operation( name="PREPARE_QUBITS", qids=["qubit_1"], computing_host_ids=["QPU_1"]) op_2 = Operation( name="PREPARE_QUBITS", qids=["qubit_2"], computing_host_ids=["QPU_2"]) layer_1 = Layer([op_1, op_2]) # TODO: For operations, can the name and computing host ids be found from # the gate name and qubit id? That would simplify the inputs # We could have an OperationFactory object that takes the topology as input # and then it can generate the operations using those two points # Form layer 2 op_1 = Operation( name="SINGLE", qids=["qubit_1"], gate=Operation.X, computing_host_ids=["QPU_1"]) op_2 = Operation( name="SINGLE", qids=["qubit_2"], gate=Operation.X, computing_host_ids=["QPU_2"]) layer_2 = Layer([op_1, op_2]) # Form layer 3 op_1 = Operation( name="TWO_QUBIT", qids=["qubit_1", "qubit_2"], gate=Operation.CNOT, computing_host_ids=["QPU_1", "QPU_2"]) layer_3 = Layer([op_1]) # Form layer 4 op_1 = Operation( name="MEASURE", qids=["qubit_1"], cids=["qubit_1"], computing_host_ids=["QPU_1"]) op_2 = Operation( name="MEASURE", qids=["qubit_2"], cids=["qubit_2"], computing_host_ids=["QPU_2"]) layer_4 = Layer([op_1, op_2]) layers = [layer_1, layer_2, layer_3, layer_4] circuit = Circuit(q_map, layers) def controller_host_protocol(host): host.generate_and_send_schedules(circuit) host.receive_results() def computing_host_protocol(host): host.receive_schedule() host.send_results() for i in range(1): self.controller_host.run_protocol(controller_host_protocol) self.computing_host_1.run_protocol(computing_host_protocol) self.computing_host_2.run_protocol(computing_host_protocol) time.sleep(12) self.assertEqual(self.clock._maximum_ticks, 13) self.assertEqual(self.computing_host_1._bits['qubit_1'], 1) self.assertEqual(self.computing_host_2._bits['qubit_2'], 0) self.assertEqual(self.controller_host._results['QPU_1']['qubit_1'], 1) self.assertEqual(self.controller_host._results['QPU_2']['qubit_2'], 0)
def test_cnot_3(self): q_map = { 'QPU_1': ['qubit_1'], 'QPU_2': ['qubit_2'], 'QPU_3': ['qubit_3']} # Form layer 1 op_1 = Operation( name="PREPARE_QUBITS", qids=["qubit_1"], computing_host_ids=["QPU_1"]) op_2 = Operation( name="PREPARE_QUBITS", qids=["qubit_2"], computing_host_ids=["QPU_2"]) op_3 = Operation( name="PREPARE_QUBITS", qids=["qubit_3"], computing_host_ids=["QPU_3"]) layer_1 = Layer([op_1, op_2, op_3]) # Form layer 2 op_1 = Operation( name="SINGLE", qids=["qubit_1"], gate=Operation.X, computing_host_ids=["QPU_1"]) layer_2 = Layer([op_1]) # Form layer 3 op_1 = Operation( name="TWO_QUBIT", qids=["qubit_1", "qubit_2"], gate=Operation.CNOT, computing_host_ids=["QPU_1", "QPU_2"]) layer_3 = Layer([op_1]) # Form layer 4 op_1 = Operation( name="TWO_QUBIT", qids=["qubit_2", "qubit_3"], gate=Operation.CNOT, computing_host_ids=["QPU_2", "QPU_3"]) layer_4 = Layer([op_1]) # Form layer 4 op_1 = Operation( name="MEASURE", qids=["qubit_1"], cids=["qubit_1"], computing_host_ids=["QPU_1"]) op_2 = Operation( name="MEASURE", qids=["qubit_2"], cids=["qubit_2"], computing_host_ids=["QPU_2"]) op_3 = Operation( name="MEASURE", qids=["qubit_3"], cids=["qubit_3"], computing_host_ids=["QPU_3"]) layer_5 = Layer([op_1, op_2, op_3]) layers = [layer_1, layer_2, layer_3, layer_4, layer_5] circuit = Circuit(q_map, layers) def controller_host_protocol(host): host.generate_and_send_schedules(circuit) host.receive_results() def computing_host_protocol(host): host.receive_schedule() host.send_results() for i in range(1): self.controller_host.run_protocol(controller_host_protocol) self.computing_host_1.run_protocol(computing_host_protocol) self.computing_host_2.run_protocol(computing_host_protocol) self.computing_host_3.run_protocol(computing_host_protocol) time.sleep(18) self.assertEqual(self.clock._maximum_ticks, 23) self.assertEqual(self.computing_host_1._bits['qubit_1'], 1) self.assertEqual(self.computing_host_2._bits['qubit_2'], 1) self.assertEqual(self.computing_host_3._bits['qubit_3'], 1) results = self.controller_host._results self.assertEqual(results['QPU_1']['type'], 'measurement_result') self.assertEqual(results['QPU_1']['val']['qubit_1'], 1) self.assertEqual(results['QPU_2']['val']['qubit_2'], 1) self.assertEqual(results['QPU_3']['val']['qubit_3'], 1)
def _replace_control_gates(control_gate_info: list, current_layer: Layer): """ Replace control gates with a distributed version of the control gate over the different computing hosts Args: control_gate_info (list): List of information regarding control gates present in one layer current_layer (Layer): Layer object in which the control gates are present """ max_gates = 0 for gate_info in control_gate_info: max_gates = max(len(gate_info['operations']), max_gates) circuit_len = Constants.DISTRIBUTED_CONTROL_CIRCUIT_LEN + max_gates operations = [[] for _ in range(circuit_len)] for gate_info in control_gate_info: control_qubit = gate_info['control_qubit'] control_host = gate_info['computing_hosts'][0] target_host = gate_info['computing_hosts'][1] epr_qubit_id = str(uuid.uuid4()) bit_id_1, bit_id_2 = str(uuid.uuid4()), str(uuid.uuid4()) # Generate new EPR pair (counted in the pre-allocated qubits) for the # two computing hosts op_1 = Operation(name=Constants.SEND_ENT, qids=[epr_qubit_id], computing_host_ids=[control_host, target_host], pre_allocated_qubits=True) op_2 = Operation(name=Constants.REC_ENT, qids=[epr_qubit_id], computing_host_ids=[target_host, control_host], pre_allocated_qubits=True) current_layer.add_operations([op_1, op_2]) # Circuit to implement distributed control gate itr = 0 op_1 = Operation(name=Constants.TWO_QUBIT, qids=[control_qubit, epr_qubit_id], gate=Operation.CNOT, computing_host_ids=[control_host]) operations[itr].extend([op_1]) itr += 1 op_1 = Operation(name=Constants.MEASURE, qids=[epr_qubit_id], cids=[bit_id_1], computing_host_ids=[control_host]) operations[itr].extend([op_1]) itr += 1 op_1 = Operation(name=Constants.SEND_CLASSICAL, cids=[bit_id_1], computing_host_ids=[control_host, target_host]) op_2 = Operation(name=Constants.REC_CLASSICAL, cids=[bit_id_1], computing_host_ids=[target_host, control_host]) operations[itr].extend([op_1, op_2]) itr += 1 op_1 = Operation(name=Constants.CLASSICAL_CTRL_GATE, qids=[epr_qubit_id], cids=[bit_id_1], gate=Operation.X, computing_host_ids=[target_host]) operations[itr].extend([op_1]) # The control gate we are trying to implement for op in gate_info['operations'][::-1]: itr += 1 op_1 = Operation(name=Constants.TWO_QUBIT, qids=[epr_qubit_id, op.get_target_qubit()], gate=op.gate, gate_param=op.gate_param, computing_host_ids=[target_host]) operations[itr].extend([op_1]) itr += 1 op_1 = Operation(name=Constants.SINGLE, qids=[epr_qubit_id], gate=Operation.H, computing_host_ids=[target_host]) operations[itr].extend([op_1]) itr += 1 op_1 = Operation(name=Constants.MEASURE, qids=[epr_qubit_id], cids=[bit_id_2], computing_host_ids=[target_host]) operations[itr].extend([op_1]) itr += 1 op_1 = Operation(name=Constants.SEND_CLASSICAL, cids=[bit_id_2], computing_host_ids=[target_host, control_host]) op_2 = Operation(name=Constants.REC_CLASSICAL, cids=[bit_id_2], computing_host_ids=[control_host, target_host]) operations[itr].extend([op_1, op_2]) itr += 1 op_1 = Operation(name=Constants.CLASSICAL_CTRL_GATE, qids=[control_qubit], cids=[bit_id_2], gate=Operation.Z, computing_host_ids=[control_host]) operations[itr].extend([op_1]) # Make the new layers from the operations distributed_layers = [] if control_gate_info: for ops in operations: layer = Layer(ops) distributed_layers.append(layer) return current_layer, distributed_layers