def test_topology_get_locations_2 ( self ): cgraph = [ (0, 1), (1, 2), (2, 3) ] t = Topology( 4, cgraph ) l = t.get_locations( 3 ) self.assertTrue( len( l ) == 2 ) self.assertTrue( (0, 1, 2) in l ) self.assertTrue( (1, 2, 3) in l )
def test_topology_get_locations_3 ( self ): t = Topology( 4 ) l = t.get_locations( 3 ) self.assertTrue( len( l ) == 4 ) self.assertTrue( (0, 1, 2) in l ) self.assertTrue( (0, 1, 3) in l ) self.assertTrue( (0, 2, 3) in l ) self.assertTrue( (1, 2, 3) in l )
def test_decomposer_constructor_valid ( self ): valid_utry = np.identity( 8 ) valid_target_gate_size = 2 valid_model = "PermModel" valid_optimizer = "LBFGSOptimizer" valid_hierarchy_fn = lambda x : 2 valid_topology = Topology( 3, [ (0, 1), (1, 2) ] ) decomposer = Decomposer( valid_utry, valid_target_gate_size, valid_model, valid_optimizer, valid_hierarchy_fn, valid_topology ) self.assertTrue( np.allclose( decomposer.utry, valid_utry ) ) self.assertTrue( decomposer.num_qubits == 3 ) self.assertTrue( decomposer.target_gate_size == 2 ) self.assertTrue( decomposer.hierarchy_fn(2112) == 2 ) model = decomposer.model.__name__ optimizer = decomposer.optimizer.__name__ self.assertTrue( model == valid_model ) self.assertTrue( optimizer == valid_optimizer ) for link in valid_topology.coupling_graph: self.assertTrue( link in decomposer.topology.coupling_graph ) self.assertTrue( len( decomposer.topology.coupling_graph ) == 2 )
def test_topology_constructor_cgraph ( self ): cgraph = [ (0, 1), (1, 2), (2, 3) ] t = Topology( 4, cgraph ) self.assertTrue( len( t.coupling_graph ) == 3 ) for link in cgraph: self.assertTrue( link in t.coupling_graph )
def test_topology_get_locations_invalid ( self ): cgraph = [ (0, 1), (1, 2), (2, 3) ] t = Topology( 4, cgraph ) self.assertRaises( ValueError, t.get_locations, 5 ) self.assertRaises( ValueError, t.get_locations, 0 ) self.assertRaises( ValueError, t.get_locations, -2 ) self.assertRaises( TypeError, t.get_locations, "a" )
def test_topology_constructor_alltoall ( self ): t = Topology( 2 ) self.assertTrue( len( t.coupling_graph ) == 1 ) self.assertTrue( (0, 1) in t.coupling_graph ) t = Topology( 3 ) self.assertTrue( len( t.coupling_graph ) == 3 ) self.assertTrue( (0, 1) in t.coupling_graph ) self.assertTrue( (0, 2) in t.coupling_graph ) self.assertTrue( (1, 2) in t.coupling_graph ) t = Topology( 4 ) self.assertTrue( len( t.coupling_graph ) == 6 ) self.assertTrue( (0, 1) in t.coupling_graph ) self.assertTrue( (0, 2) in t.coupling_graph ) self.assertTrue( (0, 3) in t.coupling_graph ) self.assertTrue( (1, 2) in t.coupling_graph ) self.assertTrue( (1, 3) in t.coupling_graph ) self.assertTrue( (2, 3) in t.coupling_graph )
def test_instantiater_instantiate_invalid ( self ): valid_tool = "QSearchTool" valid_topology = Topology( 3, None ) instantiater = Instantiater( valid_tool, valid_topology ) test_0 = 0 test_1 = "a" test_2 = ( 0, 1 ) test_3 = ( self.CNOT, 0 ) test_4 = ( self.CNOT, ( 0, 1 ) ) test_5 = [ ( self.CNOT, ( 0, 1 ) ) ] self.assertRaises( TypeError, instantiater.instantiate, test_0 ) self.assertRaises( TypeError, instantiater.instantiate, test_1 ) self.assertRaises( TypeError, instantiater.instantiate, test_2 ) self.assertRaises( TypeError, instantiater.instantiate, test_3 ) self.assertRaises( TypeError, instantiater.instantiate, test_4 ) self.assertRaises( TypeError, instantiater.instantiate, test_5 )
def test_instantiater_instantiate_valid ( self ): valid_tool = "QSearchTool" valid_topology = Topology( 3, None ) instantiater = Instantiater( valid_tool, valid_topology ) qasm_list = instantiater.instantiate( [ gate.Gate( self.CNOT, (0, 1) ) ] ) self.assertTrue( isinstance( qasm_list, list ) ) self.assertTrue( len( qasm_list ) == 1 ) self.assertTrue( isinstance( qasm_list[0], tuple ) ) self.assertTrue( len( qasm_list[0] ) == 2 ) self.assertTrue( isinstance( qasm_list[0][0], str ) ) self.assertTrue( "OPENQASM" in qasm_list[0][0] ) self.assertTrue( "cx" in qasm_list[0][0] ) self.assertTrue( "qreg q[2]" in qasm_list[0][0] ) self.assertTrue( qasm_list[0][1] == (0, 1) )
def __init__ ( self, utry, target_gate_size = 2, model = "PermModel", optimizer = "LBFGSOptimizer", hierarchy_fn = lambda x : x // 3 if x > 5 else 2, topology = None, intermediate_solution_callback = None, model_options = {} ): """ Initializes a decomposer. Args: utry (np.ndarray): A unitary matrix to decompose target_gate_size (int): After decomposition, this will be the largest size of any gate in the returned list. model (str): The circuit model to use during decomposition. optimizer (str): The optimizer to use during decomposition. hierarchy_fn (callable): This function determines the decomposition hierarchy. topoology (Topology): Determines the connection of qubits. If none, will be set to all-to-all. intermediate_solution_callback (None or callable): Callback function for intermediate solutions. If not None, then a function that takes in a list[Gates] and returns nothing. Raises: ValueError: If the target_gate_size is nonpositive or too large. RuntimeError: If the model or optimizer cannot be found. """ if not utils.is_unitary( utry, tol = 1e-14 ): logger.warning( "Unitary is not doubly-precise." ) logger.warning( "Proceeding with closest unitary to input." ) self.utry = utils.closest_unitary( utry ) else: self.utry = utry self.num_qubits = utils.get_num_qubits( utry ) if target_gate_size <= 0 or target_gate_size > self.num_qubits: raise ValueError( "Invalid target gate size." ) self.target_gate_size = target_gate_size if not callable( hierarchy_fn ): raise TypeError( "Invalid hierarchy function." ) if intermediate_solution_callback is not None: if not callable( intermediate_solution_callback ): raise TypeError( "Invalid intermediate solution callback." ) self.hierarchy_fn = hierarchy_fn self.intermediate_solution_callback = intermediate_solution_callback if topology is not None and not isinstance( topology, Topology ): raise TypeError( "Invalid topology." ) self.topology = topology or Topology( self.num_qubits ) if model not in plugins.get_models(): raise RuntimeError( f"Cannot find decomposition model: {model}" ) self.model = plugins.get_model( model ) if optimizer not in plugins.get_optimizers(): raise RuntimeError( f"Cannot find optimizer: {optimizer}" ) self.model_options = model_options self.optimizer = plugins.get_optimizer( optimizer ) logger.debug( "Created decomposer with %s and %s." % ( model, optimizer ) )
def test_topology_constructor_num_qubits ( self ): for n in [ 1, 2, 3, 4 ]: t = Topology( n ) self.assertTrue( t.num_qubits == n )
def test_instantiater_constructor_invalid(self): invalid_tool = "test_dummy_tool" valid_topology = Topology(3, None) self.assertRaises(RuntimeError, Instantiater, invalid_tool, valid_topology)
def test_instantiater_constructor_valid(self): valid_tool = "QSearchTool" valid_topology = Topology(3, None) instantiater = Instantiater(valid_tool, valid_topology) self.assertTrue(instantiater.tool.__class__.__name__ == valid_tool)
def synthesize ( utry, model = "PermModel", optimizer = "LBFGSOptimizer", tool = "QSearchTool", combiner = "NaiveCombiner", hierarchy_fn = lambda x : x // 3 if x > 5 else 2, coupling_graph = None, basis_gates = None, intermediate_solution_callback = None, model_options = {} ): """ Synthesize a unitary matrix and return qasm code using QFAST. Args: utry (np.ndarray): The unitary matrix to synthesize. model (str): The model to use during decomposition. optimizer (str): The optimizer to use during decomposition. tool (str): The native tool to use during instantiation. combiner (str): The combiner to use during recombination. hierarchy_fn (callable): This function determines the decomposition hierarchy. coupling_graph (None or list[tuple[int]]): Determines the connection of qubits. If none, will be set to all-to-all. basis_gates (None or list[str]): Determines the gate set for the final circuit. Only works with tools that implement this feature. intermediate_solution_callback (None or callable): Callback function for intermediate solutions. If not None, then a function that takes in a list[Gates] and returns nothing. model_options (Dict): kwargs for model Returns: (str): Qasm code implementing utry. Raises: TypeError: If the coupling_graph is invalid. RuntimeError: If the native tool cannot be found. """ if coupling_graph is not None: if not utils.is_valid_coupling_graph( coupling_graph ): raise TypeError( "The specified coupling graph is invalid." ) if combiner not in plugins.get_combiners(): raise RuntimeError( "Cannot find combiner." ) # Get target_gate_size for decomposition if tool not in plugins.get_native_tools(): raise RuntimeError( "Cannot find native tool." ) target_gate_size = plugins.get_native_tool( tool )().get_maximum_size() num_qubits = utils.get_num_qubits( utry ) topology = Topology( num_qubits, coupling_graph ) # Decompose the big input unitary into smaller unitary gates. decomposer = Decomposer( utry, target_gate_size = target_gate_size, model = model, optimizer = optimizer, topology = topology, hierarchy_fn = hierarchy_fn, intermediate_solution_callback = intermediate_solution_callback, model_options = model_options ) gate_list = decomposer.decompose() # Instantiate the small unitary gates into native code instantiater = Instantiater( tool, topology, basis_gates = basis_gates ) qasm_list = instantiater.instantiate( gate_list ) # Recombine all small circuits into one large output combiner = plugins.get_combiner( combiner )() qasm_out = combiner.combine( qasm_list ) return qasm_out