def synthesize(self, utry, **kwargs): """ Synthesis function with this tool. Args: utry (np.ndarray): The unitary to synthesize. Returns qasm (str): The synthesized QASM output. Raises: TypeError: If utry is not a valid unitary. ValueError: If the utry has invalid dimensions. """ if not utils.is_unitary(utry, tol=1e-14): raise TypeError("utry must be a valid unitary.") if utry.shape[0] > 2**self.get_maximum_size(): raise ValueError("utry has incorrect dimensions.") num_qubits = utils.get_num_qubits(utry) basis_gates = ['u1', 'u2', 'u3', 'cx', 'id'] circ = qiskit.QuantumCircuit(num_qubits) circ.iso(utry, list(reversed(range(num_qubits))), []) circ = qiskit.transpile(circ, optimization_level=3, basis_gates=basis_gates) return circ.qasm()
def __init__(self, utry, location): """ Gate Class Constructor Args: utry (np.ndarray): The gate's unitary operation. location (tuple[int]): The set of qubits the gate acts on. Raises: TypeError: If unitary or location are invalid. """ if not utils.is_unitary(utry, tol=1e-14): raise TypeError("Invalid unitary.") self.utry = utry self.num_qubits = utils.get_num_qubits(self.utry) if not utils.is_valid_location(location): raise TypeError("Invalid location.") if len(location) != self.num_qubits: raise ValueError("Invalid size of location.") self.location = location
def __init__(self, utry, gate_size, locations, optimizer, success_threshold=1e-3, partial_solution_callback=None): """ Default constructor for CircuitModels. Args: utry (np.ndarray): The unitary to model. gate_size (int): The size of the model's gates. locations (List[Tuple[int]]): The valid locations for gates. optimizer (Optimizer): The optimizer available for use. success_threshold (float): The distance criteria for success. partial_solution_callback (None or callable): callback for partial solutions. If not None, then callable that takes a list[gate.Gate] and returns nothing. Raises: ValueError: If the gate_size or locations are invalid. """ if partial_solution_callback is not None: if not callable(partial_solution_callback): raise TypeError("Invalid partial_solution_callback.") self.num_qubits = utils.get_num_qubits(utry) if gate_size >= self.num_qubits: raise ValueError("Invalid gate_size") self.gate_size = gate_size if not utils.is_valid_locations(locations, self.num_qubits, self.gate_size): raise TypeError("Invalid locations") self.partial_solution_callback = partial_solution_callback self.locations = locations self.optimizer = optimizer self.utry = utry self.utry_dag = utry.conj().T self.success_threshold = success_threshold self.gates = [] self.param_ranges = [0] self.x = self.get_initial_input()
def run_tests(): # Register Signal Handlers signal.signal(signal.SIGALRM, term_trial) signal.signal(signal.SIGINT, term_trial) # Initialize Data Folders if not os.path.isdir(".checkpoints"): os.makedirs(".checkpoints") start_date = str(date.today()) exp_folder = ".checkpoints/" + start_date + "/" if not os.path.isdir(exp_folder): os.makedirs(exp_folder) data = {} hierarchy_fn = lambda x: 3 if x > 6 else 2 for file in os.listdir(): if os.path.isfile(file) and file[-8:] == ".unitary": name = file[:-8] utry = np.loadtxt(file, dtype=np.complex128) num_qubits = utils.get_num_qubits(utry) # Record QFAST's logger stream = StringIO() handler = logging.StreamHandler(stream) handler.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG) logger.addHandler(handler) logger.info("-" * 40) logger.info(get_exp_header()) logger.info(start_date) logger.info(name) logger.info("-" * 40) soltree = SolutionTree(utry) timeout = False timeouts = { 3: 10 * 60, 4: 20 * 60, 5: 45 * 60, 6: 90 * 60, 7: 360 * 60 } signal.alarm(timeouts[num_qubits]) # Set Random Seed np.random.seed(21211411) # Run Benchmark start = timer() try: qasm = synthesize( utry, model="PermModel", hierarchy_fn=hierarchy_fn, intermediate_solution_callback=soltree.add_intermediate, model_options={ "partial_solution_callback": soltree.add_partial, "success_threshold": 1e-3 }) except TrialTerminatedException: timeout = True except: timeout = True logger.error( "Benchmark %s encountered error during execution." % name) logger.error(traceback.format_exc()) traceback.print_exc() end = timer() handler.flush() logger.removeHandler(handler) handler.close() log_out = stream.getvalue() if not timeout: data[name] = (num_qubits, qasm.count("cx"), end - start, get_error(utry, qasm), qasm, log_out, soltree) else: data[name] = (num_qubits, log_out, soltree) # Save data filenum = 0 filename = exp_folder + name + str(filenum) + ".dat" while os.path.isfile(filename): filenum += 1 filename = exp_folder + name + str(filenum) + ".dat" file = open(filename, 'wb') pickle.dump(data[name], file) # Print Summary for test in data: if len(data[test]) < 6: continue print("-" * 40) print(test) print("CX count:", data[test][1]) print("Time (s):", data[test][2]) print("Error:", data[test][3]) print("-" * 40)
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_num_qubits(self): for i in range(4): M = np.identity(2**i) self.assertTrue(get_num_qubits(M) == i)
def synthesize(self, utry, **kwargs): """ Synthesis function with this tool. Args: utry (np.ndarray): The unitary to synthesize. Returns qasm (str): The synthesized QASM output. Raises: TypeError: If utry is not a valid unitary. ValueError: If the utry has invalid dimensions. """ if not utils.is_unitary(utry, tol=1e-14): raise TypeError("utry must be a valid unitary.") if utry.shape[0] > 2**self.get_maximum_size(): raise ValueError("utry has incorrect dimensions.") # Parse kwargs basis_gates = ["cx"] coupling_graph = [(0, 1), (1, 2)] if "basis_gates" in kwargs: basis_gates = kwargs["basis_gates"] or basis_gates if "coupling_graph" in kwargs: coupling_graph = kwargs["coupling_graph"] or coupling_graph # Prepermute unitary to line up coupling_graph # This is done because qsearch handles pure linear topologies best if utils.get_num_qubits(utry) == 3: a = (0, 1) in coupling_graph b = (1, 2) in coupling_graph c = (0, 2) in coupling_graph if not (a and b): if (a and c): # Permute 0 and 1 P = perm.calc_permutation_matrix(3, (1, 0, 2)) utry = P @ utry @ P.T elif (b and c): # Permute 1 and 2 P = perm.calc_permutation_matrix(3, (0, 2, 1)) utry = P @ utry @ P.T else: raise ValueError("Invalid coupling graph.") # Pass options into qsearch, being maximally quiet, # and set the target to utry opts = options.Options() opts.target = utry opts.gateset = self.map_basis_str_to_gateset(basis_gates) opts.verbosity = 0 opts.write_to_stdout = False opts.reoptimize_size = 7 # use the LEAP compiler, which scales better than normal qsearch compiler = leap_compiler.LeapCompiler() output = compiler.compile(opts) # LEAP requires some post-processing pp = post_processing.LEAPReoptimizing_PostProcessor() output = pp.post_process_circuit(output, opts) output = assemblers.ASSEMBLER_IBMOPENQASM.assemble(output) # Renumber qubits in circuit if we flipped the unitary if utils.get_num_qubits(utry) == 3: a = (0, 1) in coupling_graph b = (1, 2) in coupling_graph c = (0, 2) in coupling_graph if not (a and b): if (a and c): # Permute 0 and 1 str0 = "[0]" str1 = "[1]" elif (b and c): # Permute 1 and 2 str0 = "[1]" str1 = "[2]" output = output.replace(str0, "[tmp]") output = output.replace(str1, str0) output = output.replace("[tmp]", str1) return output
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