def test_correctness_3(self) -> None: circuit = Circuit(5) wide_gate = IdentityGate(3) circuit.append_gate(HGate(), [1]) circuit.append_gate(CNOTGate(), [2, 3]) circuit.append_gate(wide_gate, [1, 2, 3]) circuit.append_gate(CNOTGate(), [1, 2]) circuit.append_gate(HGate(), [3]) circuit.append_gate(XGate(), [0]) circuit.append_gate(XGate(), [0]) circuit.append_gate(XGate(), [0]) circuit.append_gate(XGate(), [4]) circuit.append_gate(XGate(), [4]) circuit.append_gate(XGate(), [4]) utry = circuit.get_unitary() circuit.fold(circuit.get_region([(0, 2), (1, 1), (2, 1)])) assert circuit.get_num_operations() == 9 assert circuit.get_depth() == 3 assert circuit.count(HGate()) == 2 assert circuit.count(XGate()) == 6 assert isinstance(circuit[1, 1].gate, CircuitGate) test_gate: CircuitGate = circuit[1, 1].gate assert test_gate._circuit[0, 1].gate is CNOTGate() assert test_gate._circuit[0, 2].gate is CNOTGate() assert test_gate._circuit[1, 0].gate is wide_gate assert test_gate._circuit[1, 1].gate is wide_gate assert test_gate._circuit[1, 2].gate is wide_gate check_no_idle_cycles(circuit) assert np.allclose(utry.get_numpy(), circuit.get_unitary().get_numpy())
def check_gradient(circ: Circuit, num_params: int) -> None: totaldiff = [0] * num_params eps = 1e-5 repeats = 100 for _ in range(repeats): v = np.random.rand(num_params) * 2 * np.pi M, Js = circ.get_unitary_and_grad(v) for i in range(num_params): v2 = np.copy(v) v2[i] = v[i] + eps U1 = circ.get_unitary(v2) if isinstance(U1, UnitaryMatrix): utry1 = U1.get_numpy() else: utry1 = U1 v2[i] = v[i] - eps U2 = circ.get_unitary(v2) if isinstance(U2, UnitaryMatrix): utry2 = U2.get_numpy() else: utry2 = U2 FD = (utry1 - utry2) / (2 * eps) diffs = np.sum(np.abs(FD - Js[i])) totaldiff[i] += diffs for i in range(num_params): assert totaldiff[i] < eps
def test_run(self) -> None: """Test run with a linear topology.""" # 0 1 2 3 4 ######### # 0 --o-----o--P--P-- --#-o---o-#-----#######-- # 1 --x--o--x--o----- --#-x-o-x-#######-o---#-- # 2 -----x--o--x--o-- => --#---x---#---o-#-x-o-#-- # 3 --o--P--x--P--x-- --#########-o-x-#---x-#-- # 4 --x-----------P-- ----------#-x---#######-- # ####### num_q = 5 circ = Circuit(num_q) circ.append_gate(CNOTGate(), [0, 1]) circ.append_gate(CNOTGate(), [3, 4]) circ.append_gate(CNOTGate(), [1, 2]) circ.append_gate(CNOTGate(), [0, 1]) circ.append_gate(CNOTGate(), [2, 3]) circ.append_gate(CNOTGate(), [1, 2]) circ.append_gate(CNOTGate(), [2, 3]) utry = circ.get_unitary() ScanPartitioner(3).run(circ, {}) assert len(circ) == 3 assert all(isinstance(op.gate, CircuitGate) for op in circ) placeholder_gate = TaggedGate(IdentityGate(1), '__fold_placeholder__') assert all( op.gate._circuit.count(placeholder_gate) == 0 for op in circ) # type: ignore # noqa assert circ.get_unitary() == utry for cycle_index in range(circ.get_num_cycles()): assert not circ._is_cycle_idle(cycle_index)
def test_corner_case_2(self) -> None: circuit = Circuit(6) circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [0]) circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [1]) circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [5]) circuit.append_gate( ConstantUnitaryGate(UnitaryMatrix.random(2), ), [ 3, 0, ], ) circuit.append_gate( ConstantUnitaryGate(UnitaryMatrix.random(2), ), [ 5, 0, ], ) circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [3]) circuit.append_gate( ConstantUnitaryGate(UnitaryMatrix.random(4), ), [ 4, 0, 1, 2, ], ) circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [5]) circuit.append_gate( ConstantUnitaryGate(UnitaryMatrix.random(3), ), [ 5, 0, 1, ], ) circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [1]) utry = circuit.get_unitary() ScanPartitioner(3).run(circuit, {}) assert all( isinstance(op.gate, CircuitGate) or len(op.location) > 3 for op in circuit) assert all(not isinstance(op.gate, TaggedGate) or not op.gate.tag != '__fold_placeholder__' for op in circuit) assert circuit.get_unitary() == utry for cycle_index in range(circuit.get_num_cycles()): assert not circuit._is_cycle_idle(cycle_index)
def test_run_r6(self, r6_qudit_circuit: Circuit) -> None: utry = r6_qudit_circuit.get_unitary() ClusteringPartitioner(3, 2).run(r6_qudit_circuit, {}) assert any(isinstance(op.gate, CircuitGate) for op in r6_qudit_circuit) assert r6_qudit_circuit.get_unitary() == utry for cycle_index in range(r6_qudit_circuit.get_num_cycles()): assert not r6_qudit_circuit._is_cycle_idle(cycle_index)
def test_with_fold(self, r6_qudit_circuit: Circuit) -> None: cycle = 0 qudit = 0 while True: cycle = np.random.randint(r6_qudit_circuit.get_num_cycles()) qudit = np.random.randint(r6_qudit_circuit.get_size()) if not r6_qudit_circuit.is_point_idle((cycle, qudit)): break utry = r6_qudit_circuit.get_unitary() region = r6_qudit_circuit.surround((cycle, qudit), 4) r6_qudit_circuit.fold(region) assert r6_qudit_circuit.get_unitary() == utry
def test_run_r6(self, r6_qudit_circuit: Circuit) -> None: utry = r6_qudit_circuit.get_unitary() ScanPartitioner(3).run(r6_qudit_circuit, {}) assert all( isinstance(op.gate, CircuitGate) or len(op.location) > 3 for op in r6_qudit_circuit) assert all(not isinstance(op.gate, TaggedGate) or not op.gate.tag != '__fold_placeholder__' for op in r6_qudit_circuit) assert r6_qudit_circuit.get_unitary() == utry for cycle_index in range(r6_qudit_circuit.get_num_cycles()): assert not r6_qudit_circuit._is_cycle_idle(cycle_index)
def test_parameters(self) -> None: circ = Circuit(2) circ.append_gate(CNOTGate(), [1, 0]) circ.append_gate(U3Gate(), [0], [0, 0, 0.23]) circ.append_gate(CNOTGate(), [1, 0]) before_fold = circ.get_unitary() circ.fold(circ.get_region([(0, 0), (1, 0), (2, 0)])) after_fold = circ.get_unitary() assert after_fold == before_fold
def test_no_change(self) -> None: u1 = unitary_group.rvs(8) g1 = VariableUnitaryGate(3) circuit = Circuit(3) circuit.append_gate(g1, [0, 1, 2]) utry_before = circuit.get_unitary() # The following call should not make any changes in circuit QFactor().instantiate(circuit, u1, circuit.get_params()) utry_after = circuit.get_unitary() assert np.allclose( utry_before.get_numpy(), utry_after.get_numpy(), )
def run(self, circuit: Circuit, data: dict[str, Any]) -> None: """Perform the pass's operation, see BasePass for more info.""" # Check data for windows markers if 'window_markers' not in data: _logger.warning('Did not find any window markers.') return window_markers = data['window_markers'] _logger.debug('Found window_markers: %s.' % str(window_markers)) if not is_iterable(window_markers): _logger.debug('Invalid type for window_markers.') return if not all(is_integer(marker) for marker in window_markers): _logger.debug('Invalid type for window_markers.') return # Resynthesis each window index_shift = 0 for marker in window_markers: marker -= index_shift # Slice window begin_cycle = int(marker - self.window_size // 2) end_cycle = int(marker + np.ceil(self.window_size / 2)) if begin_cycle < 0: begin_cycle = 0 if end_cycle > circuit.get_num_cycles(): end_cycle = circuit.get_num_cycles() - 1 window = Circuit(circuit.get_size(), circuit.get_radixes()) window.extend(circuit[begin_cycle:end_cycle]) _logger.info( 'Resynthesizing window from cycle ' f'{begin_cycle} to {end_cycle}.', ) # Synthesize utry = window.get_unitary() new_window = self.synthesispass.synthesize(utry, data) # Replace if self.replace_filter(new_window, window): _logger.debug('Replacing window with synthesized circuit.') actual_window_size = end_cycle - begin_cycle for _ in range(actual_window_size): circuit.pop_cycle(begin_cycle) circuit.insert_circuit( begin_cycle, new_window, list(range(circuit.get_size())), ) index_shift += actual_window_size - new_window.get_num_cycles()
def test_hilbert_schmidt_residuals(r3_qubit_circuit: Circuit) -> None: x0 = np.random.random((r3_qubit_circuit.get_num_params(), )) cost = HilbertSchmidtResidualsGenerator().gen_cost( r3_qubit_circuit, r3_qubit_circuit.get_unitary(x0), ) assert cost.get_cost(x0) < 1e-10
def run(self, circuit: Circuit, data: dict[str, Any]) -> None: """Perform the pass's operation, see BasePass for more info.""" start = 'left' if self.start_from_left else 'right' _logger.debug(f'Starting scan gate removal from {start}.') target = circuit.get_unitary() circuit_copy = circuit.copy() reverse_iter = not self.start_from_left for cycle, op in circuit.operations_with_cycles(reverse=reverse_iter): if not self.collection_filter(op): _logger.debug(f'Skipping operation {op} at cycle {cycle}.') continue _logger.info(f'Attempting removal of operation at cycle {cycle}.') _logger.debug(f'Operation: {op}') working_copy = circuit_copy.copy() # If removing gates from the left, we need to track index changes. if self.start_from_left: idx_shift = circuit.get_num_cycles() idx_shift -= working_copy.get_num_cycles() cycle -= idx_shift working_copy.pop((cycle, op.location[0])) working_copy.instantiate(target, **self.instantiate_options) if self.cost(working_copy, target) < self.success_threshold: _logger.info('Successfully removed operation.') circuit_copy = working_copy circuit.become(circuit_copy)
def test_2_gate(self) -> None: g1 = VariableUnitaryGate(2) g2 = VariableUnitaryGate(3) g3 = RXGate() circuit = Circuit(4) circuit.append_gate(g1, [0, 1]) circuit.append_gate(g2, [1, 2, 3]) circuit.append_gate(g3, [1]) utry = circuit.get_unitary(np.random.random(circuit.get_num_params())) params = QFactor().instantiate(circuit, utry, circuit.get_params()) circuit.set_params(params) assert np.allclose( circuit.get_unitary().get_numpy(), utry.get_numpy(), )
def test_1_gate(self) -> None: u1 = unitary_group.rvs(8) g1 = VariableUnitaryGate(3) circuit = Circuit(3) circuit.append_gate(g1, [0, 1, 2]) params = QFactor().instantiate(circuit, u1, circuit.get_params()) circuit.set_params(params) g1_params = list(np.reshape(u1, (64, ))) g1_params = list(np.real(g1_params)) + list(np.imag(g1_params)) assert np.allclose( circuit.get_unitary().get_numpy(), g1.get_unitary(g1_params).get_numpy(), )
def test_toffoli_simulation( toffoli_unitary: UnitaryMatrix, toffoli_circuit: Circuit, ) -> None: calc_unitary = toffoli_circuit.get_unitary() assert toffoli_unitary.get_distance_from(calc_unitary) < 1e8
def test_correctness_1(self) -> None: circuit = Circuit(4) wide_gate = IdentityGate(4) circuit.append_gate(wide_gate, [0, 1, 2, 3]) circuit.append_gate(wide_gate, [0, 1, 2, 3]) circuit.append_gate(wide_gate, [0, 1, 2, 3]) circuit.append_gate(wide_gate, [0, 1, 2, 3]) assert circuit.get_num_operations() == 4 assert circuit.get_depth() == 4 utry = circuit.get_unitary() circuit.fold(circuit.get_region([(0, 0), (1, 0)])) assert circuit.get_num_operations() == 3 assert circuit.get_depth() == 3 check_no_idle_cycles(circuit) for q in range(4): assert isinstance(circuit[0, q].gate, CircuitGate) for c in range(1, 3, 1): for q in range(4): assert isinstance(circuit[c, q].gate, IdentityGate) assert isinstance(circuit[c, q].gate, IdentityGate) test_gate: CircuitGate = circuit[0, 0].gate # type: ignore assert test_gate._circuit.get_num_operations() == 2 assert test_gate._circuit.get_num_cycles() == 2 for q in range(4): assert isinstance(test_gate._circuit[0, q].gate, IdentityGate) assert isinstance(test_gate._circuit[1, q].gate, IdentityGate) circuit.fold(circuit.get_region([(1, 0), (2, 0)])) assert circuit.get_num_operations() == 2 assert circuit.get_depth() == 2 check_no_idle_cycles(circuit) for c in range(2): for q in range(4): assert isinstance(circuit[c, q].gate, CircuitGate) test_gate: CircuitGate = circuit[0, 0].gate # type: ignore assert test_gate._circuit.get_num_operations() == 2 assert test_gate._circuit.get_num_cycles() == 2 for q in range(4): assert isinstance(test_gate._circuit[0, q].gate, IdentityGate) assert isinstance(test_gate._circuit[1, q].gate, IdentityGate) test_gate: CircuitGate = circuit[1, 0].gate # type: ignore assert test_gate._circuit.get_num_operations() == 2 assert test_gate._circuit.get_num_cycles() == 2 for q in range(4): assert isinstance(test_gate._circuit[0, q].gate, IdentityGate) assert isinstance(test_gate._circuit[1, q].gate, IdentityGate) circuit.fold(circuit.get_region([(0, 0), (1, 0)])) assert circuit.get_num_operations() == 1 assert circuit.get_depth() == 1 check_no_idle_cycles(circuit) for q in range(4): assert isinstance(circuit[0, q].gate, CircuitGate) test_gate: CircuitGate = circuit[0, 0].gate # type: ignore assert test_gate._circuit.get_num_operations() == 2 assert test_gate._circuit.get_num_cycles() == 2 for q in range(4): assert isinstance(test_gate._circuit[0, q].gate, CircuitGate) assert isinstance(test_gate._circuit[1, q].gate, CircuitGate) inner_gate1: CircuitGate = test_gate._circuit[0, 0].gate # type: ignore inner_gate2: CircuitGate = test_gate._circuit[1, 0].gate # type: ignore assert inner_gate1._circuit.get_num_operations() == 2 assert inner_gate1._circuit.get_num_cycles() == 2 for q in range(4): assert isinstance(inner_gate1._circuit[0, q].gate, IdentityGate) assert isinstance(inner_gate1._circuit[1, q].gate, IdentityGate) assert isinstance(inner_gate2._circuit[0, q].gate, IdentityGate) assert isinstance(inner_gate2._circuit[1, q].gate, IdentityGate) check_no_idle_cycles(circuit) assert np.allclose(utry.get_numpy(), circuit.get_unitary().get_numpy())
def synthesize(self, utry: UnitaryMatrix, data: dict[str, Any]) -> Circuit: """Synthesize `utry` into a circuit, see SynthesisPass for more info.""" # 0. Skip any unitaries too small for the configured block. if self.block_size_start > utry.get_size(): _logger.warning( 'Skipping synthesis: block size is larger than input unitary.', ) return Circuit.from_unitary(utry) # 1. Create empty circuit with same size and radixes as `utry`. circuit = Circuit(utry.get_size(), utry.get_radixes()) # 2. Calculate block sizes block_size_end = utry.get_size() - 1 if self.block_size_limit is not None: block_size_end = self.block_size_limit # 3. Calculate relevant coupling_graphs # TODO: Look for topology info in `data`, use all-to-all otherwise. model = MachineModel(utry.get_size()) locations = [ model.get_locations(i) for i in range(self.block_size_start, block_size_end + 1) ] # 3. Bottom-up synthesis: build circuit up one gate at a time layer = 1 last_cost = 1.0 last_loc = None while True: remainder = utry.get_dagger() @ circuit.get_unitary() sorted_locations = self.analyze_remainder(remainder, locations) for loc in sorted_locations: # Never predict the previous location if loc == last_loc: continue _logger.info(f'Trying next predicted location {loc}.') circuit.append_gate(VariableUnitaryGate(len(loc)), loc) circuit.instantiate( utry, **self.instantiate_options, # type: ignore ) cost = self.cost.calc_cost(circuit, utry) _logger.info(f'Instantiated; layers: {layer}, cost: {cost:e}.') if cost < self.success_threshold: _logger.info(f'Circuit found with cost: {cost:e}.') _logger.info('Successful synthesis.') return circuit progress_threshold = self.progress_threshold_a progress_threshold += self.progress_threshold_r * np.abs(cost) if last_cost - cost >= progress_threshold: _logger.info('Progress has been made, depth increasing.') last_loc = loc last_cost = cost layer += 1 break _logger.info('Progress has not been made.') circuit.pop((-1, loc[0]))