def test_api_device_status(self): ''' Check the status of a real chip ''' api = IBMQuantumExperience(API_TOKEN) is_available = api.device_status() self.assertIsNotNone(is_available)
class QuantumProgram(object): """ Quantum Program Class Class internal properties """ __online_devices = [ "real", "ibmqx2", "ibmqx3", "simulator", "ibmqx_qasm_simulator" ] __local_devices = ["local_unitary_simulator", "local_qasm_simulator"] __quantum_program = {} __api = {} __api_config = {} """ Elements that are not python identifiers or string constants are denoted by "--description (type)--". For example, a circuit's name is denoted by "--circuit name (string)--" and might have the value "teleport". __quantum_program = { "circuits": { --circuit name (string)--: { "circuit": --circuit object (TBD)--, "execution": { #### FILLED IN AFTER RUN -- JAY WANTS THIS MOVED DOWN ONE LAYER #### --device name (string)--: { "coupling_map": --adjacency list (dict)--, "basis_gates": --comma separated gate names (string)--, "compiled_circuit": --compiled quantum circuit (currently QASM text)--, "shots": --shots (int)--, "max_credits": --credits (int)--, "result": { "data": { #### DATA CAN BE A DIFFERENT DICTIONARY FOR EACH BACKEND #### "counts": {’00000’: XXXX, ’00001’: XXXXX}, "time" : xx.xxxxxxxx }, "date" : "2017−05−09Txx:xx:xx.xxxZ", "status": --status (string)-- } }, } } } __to_execute = { --device name (string)--: [ { "name": --circuit name (string)--, "coupling_map": --adjacency list (dict)--, "basis_gates": --comma separated gate names (string)--, "compiled_circuit": --compiled quantum circuit (currently QASM text)--, "shots": --shots (int)--, "max_credits": --credits (int)-- "seed": --initial seed for the simulator (int) -- }, ... ] } """ # -- FUTURE IMPROVEMENTS -- # TODO: for status results choose ALL_CAPS, or This but be consistent # TODO: coupling_map, basis_gates will move to compiled_circuit object # TODO: compiled_circuit is currently QASM text. In the future we will # make a method in the QuantumCircuit object that makes an object # to be passed to the runner and this will live in compiled_circuit. def __init__(self, specs=None, name=""): self.__quantum_program = {"circuits": {}} self.__quantum_registers = {} self.__classical_registers = {} self.__init_circuit = None self.__last_device_backend = "" self.__to_execute = {} self.mapper = mapper if specs: self.__init_specs(specs) # API functions def get_api_config(self): """Return the program specs""" return self.__api.req.credential.config def _setup_api(self, token, url): try: self.__api = IBMQuantumExperience(token, {"url": url}) return True except BaseException: print('---- Error: Exception connect to servers ----') return False def set_api(self, token=None, url=None): """Set the API conf""" if not token: token = self.__api_config["token"] else: self.__api_config["token"] = token if not url: url = self.__api_config["url"] else: self.__api_config["url"] = {"url": url} api = self._setup_api(token, url) return api def set_api_token(self, token): """ Set the API Token """ self.set_api(token=token) def set_api_url(self, url): """ Set the API url """ self.set_api(url=url) def get_device_status(self, device): """Return the online device status via QX API call device is the name of the real chip """ if device in self.__online_devices: return self.__api.device_status(device) else: return {"status": "Error", "result": "This device doesn't exist"} def get_device_calibration(self, device): """Return the online device calibrations via QX API call device is the name of the real chip """ if device in self.__online_devices: return self.__api.device_calibration(device) else: return {"status": "Error", "result": "This device doesn't exist"} # Building parts of the program def create_quantum_registers(self, name, size): """Create a new set of Quantum Registers""" self.__quantum_registers[name] = QuantumRegister(name, size) print(">> quantum_registers created:", name, size) return self.__quantum_registers[name] def create_quantum_registers_group(self, registers_array): """Create a new set of Quantum Registers based on a array of that""" new_registers = [] for register in registers_array: register = self.create_quantum_registers(register["name"], register["size"]) new_registers.append(register) return new_registers def create_classical_registers(self, name, size): """Create a new set of Classical Registers""" self.__classical_registers[name] = ClassicalRegister(name, size) print(">> classical_registers created:", name, size) return self.__classical_registers[name] def create_classical_registers_group(self, registers_array): """Create a new set of Classical Registers based on a array of that""" new_registers = [] for register in registers_array: new_registers.append( self.create_classical_registers(register["name"], register["size"])) return new_registers def create_circuit(self, name, qregisters=None, cregisters=None, circuit_object=None): """Create a new Quantum Circuit into the Quantum Program name is a string, the name of the circuit qregisters is an Array of Quantum Registers, can be String, by name or the object reference cregisters is an Array of Classical Registers, can be String, by name or the object reference """ if not qregisters: qregisters = [] if not cregisters: cregisters = [] if not circuit_object: circuit_object = QuantumCircuit() self.__quantum_program['circuits'][name] = { "name": name, "circuit": circuit_object } for register in qregisters: if isinstance(register, str): self.__quantum_program['circuits'][name]['circuit'].add( self.__quantum_registers[register]) else: self.__quantum_program['circuits'][name]['circuit'].add( register) for register in cregisters: if isinstance(register, str): self.__quantum_program['circuits'][name]['circuit'].add( self.__classical_registers[register]) else: self.__quantum_program['circuits'][name]['circuit'].add( register) return self.__quantum_program['circuits'][name]['circuit'] def get_quantum_registers(self, name): """Return a Quantum Register by name""" return self.__quantum_registers[name] def get_classical_registers(self, name): """Return a Classical Register by name""" return self.__classical_registers[name] def get_circuit(self, name): """Return a Circuit Object by name""" return self.__quantum_program['circuits'][name]['circuit'] def get_quantum_elements(self, specs=None): """Return the basic elements, Circuit, Quantum Registers, Classical Registers""" return self.__init_circuit, \ self.__quantum_registers[list(self.__quantum_registers)[0]], \ self.__classical_registers[list(self.__classical_registers)[0]] def load_qasm(self, name="", qasm_file=None, basis_gates=None): """ Load qasm file qasm_file qasm file name """ if not qasm_file: print('"Not filename provided') return {"status": "Error", "result": "Not filename provided"} if not basis_gates: basis_gates = "u1,u2,u3,cx,id" # QE target basis if name == "": name = qasm_file circuit_object = qasm.Qasm(filename=qasm_file).parse() # Node (AST) # TODO: add method to convert to QuantumCircuit object from Node self.__quantum_program['circuits'][name] = {"circuit": circuit_object} return {"status": "COMPLETED", "result": 'all done'} def __init_specs(self, specs): """Populate the Quantum Program Object with initial Specs""" quantumr = [] classicalr = [] if "api" in specs: if specs["api"]["token"]: self.__api_config["token"] = specs["api"]["token"] if specs["api"]["url"]: self.__api_config["url"] = specs["api"]["url"] if "circuits" in specs: for circuit in specs["circuits"]: quantumr = self.create_quantum_registers_group( circuit["quantum_registers"]) classicalr = self.create_classical_registers_group( circuit["classical_registers"]) self.__init_circuit = self.create_circuit( name=circuit["name"], qregisters=quantumr, cregisters=classicalr) else: if "quantum_registers" in specs: print(">> quantum_registers created") quantumr = specs["quantum_registers"] self.create_quantum_registers(quantumr["name"], quantumr["size"]) if "classical_registers" in specs: print(">> quantum_registers created") classicalr = specs["classical_registers"] self.create_classical_registers(classicalr["name"], classicalr["size"]) if quantumr and classicalr: self.create_circuit(name=specs["name"], qregisters=quantumr["name"], cregisters=classicalr["name"]) def add_circuit(self, name, circuit_object): """Add a new circuit based on an Object representation. name is the name or index of one circuit.""" self.__quantum_program['circuits'][name] = { "name": name, "circuit": circuit_object } return circuit_object def get_qasm_image(self, circuit): """Get image circuit representation from API.""" pass def get_qasm(self, name): """get the circut by name. name of the circuit""" if name in self.__quantum_program['circuits']: return self.__quantum_program['circuits'][name]['circuit'].qasm() else: return {"status": "Error", "result": 'Circuit not found'} def get_qasms(self, list_circuit_name): """get the circut by name. name of the circuit""" qasm_source = [] for name in list_circuit_name: qasm_source.append(self.get_qasm(name)) return qasm_source # Compiling methods def unroller_code(self, circuit, basis_gates=None): """ Unroll the code circuit is circuits to unroll basis_gates are the base gates, which by default are: u1,u2,u3,cx,id """ if not basis_gates: basis_gates = "u1,u2,u3,cx,id" # QE target basis unrolled_circuit = unroll.Unroller( qasm.Qasm(data=circuit.qasm()).parse(), unroll.CircuitBackend(basis_gates.split(","))) unrolled_circuit.execute() circuit_unrolled = unrolled_circuit.backend.circuit # circuit DAG qasm_source = circuit_unrolled.qasm(qeflag=True) return qasm_source, circuit_unrolled def compile(self, name_of_circuits, device="local_qasm_simulator", shots=1024, max_credits=3, basis_gates=None, coupling_map=None, seed=None): """Compile the name_of_circuits by names. name_of_circuits is a list of circuit names to compile. device is the target device name. basis_gates are the base gates by default are: u1,u2,u3,cx,id coupling_map is the adjacency list for coupling graph This method adds elements of the following form to the self.__to_execute list corresponding to the device: --device name (string)--: [ { "name": --circuit name (string)--, "coupling_map": --adjacency list (dict)--, "basis_gates": --comma separated gate names (string)--, "compiled_circuit": --compiled quantum circuit (currently QASM text)--, "shots": --shots (int)--, "max_credits": --credits (int)-- "seed": --initial seed for the simulator (int) -- }, ... ] } """ if name_of_circuits == []: return {"status": "Error", "result": 'No circuits'} for name in name_of_circuits: if name not in self.__quantum_program["circuits"]: return { "status": "Error", "result": "%s not in QuantumProgram" % name } # TODO: The circuit object has to have .qasm() method (be careful) qasm_compiled, dag_unrolled = self.unroller_code( self.__quantum_program['circuits'][name]['circuit'], basis_gates) if coupling_map: print("pre-mapping properties: %s" % dag_unrolled.property_summary()) # Insert swap gates coupling = self.mapper.Coupling(coupling_map) dag_unrolled, final_layout = self.mapper.swap_mapper( dag_unrolled, coupling) print("layout: %s" % final_layout) # Expand swaps qasm_compiled, dag_unrolled = self.unroller_code(dag_unrolled) # Change cx directions dag_unrolled = mapper.direction_mapper(dag_unrolled, coupling) # Simplify cx gates mapper.cx_cancellation(dag_unrolled) # Simplify single qubit gates dag_unrolled = mapper.optimize_1q_gates(dag_unrolled) qasm_compiled = dag_unrolled.qasm(qeflag=True) print("post-mapping properties: %s" % dag_unrolled.property_summary()) # TODO: add timestamp, compilation if device not in self.__to_execute: self.__to_execute[device] = [] job = {} job["name"] = name job["coupling_map"] = coupling_map job["basis_gates"] = basis_gates job["shots"] = shots job["max_credits"] = max_credits # TODO: This will become a new compiled circuit object in the # future. See future improvements at the top of this # file. job["compiled_circuit"] = qasm_compiled job["seed"] = random.random() if seed is not None: job["seed"] = seed self.__to_execute[device].append(job) return {"status": "COMPLETED", "result": 'all done'} def get_compiled_qasm(self, name, device=None): """Get the compiled qasm for the named circuit and device. If device is None, it defaults to the last device. """ if not device: device = self.__last_device_backend try: return self.__quantum_program["circuits"][name]["execution"][ device]["compiled_circuit"] except KeyError: return "No compiled qasm for this circuit" def print_execution_list(self, verbose=False): """Print the compiled circuits that are ready to run. verbose controls how much is returned. """ for device, jobs in self.__to_execute.items(): print("%s:" % device) for job in jobs: print(" %s:" % job["name"]) print(" shots = %d" % job["shots"]) print(" max_credits = %d" % job["max_credits"]) print(" seed (simulator only) = %d" % job["seed"]) if verbose: print(" compiled_circuit =") print("// *******************************************") print(job["compiled_circuit"], end="") print("// *******************************************") #runners def run(self, wait=5, timeout=60): """Run a program (a pre-compiled quantum program). All input for run comes from self.__to_execute wait time is how long to check if the job is completed timeout is time until the execution stopa """ for backend in self.__to_execute: self.__last_device_backend = backend if backend in self.__online_devices: last_shots = -1 last_max_credits = -1 jobs = [] for job in self.__to_execute[backend]: jobs.append({'qasm': job["compiled_circuit"]}) shots = job["shots"] max_credits = job["max_credits"] if last_shots == -1: last_shots = shots else: if last_shots != shots: # Clear the list of compiled programs to execute self.__to_execute = {} return { "status": "Error", "result": 'Online devices only support job batches with equal numbers of shots' } if last_max_credits == -1: last_max_credits = max_credits else: if last_max_credits != max_credits: # Clear the list of compiled programs to execute self.__to_execute = {} return { "status": "Error", "result": 'Online devices only support job batches with equal max credits' } print("running on backend: %s" % (backend)) output = self.__api.run_job(jobs, backend, last_shots, last_max_credits) if 'error' in output: # Clear the list of compiled programs to execute self.__to_execute = {} return {"status": "Error", "result": output['error']} job_result = self.wait_for_job(output['id'], wait=wait, timeout=timeout) if job_result['status'] == 'Error': # Clear the list of compiled programs to execute self.__to_execute = {} return job_result else: jobs = [] for job in self.__to_execute[backend]: jobs.append({ "compiled_circuit": job["compiled_circuit"], "shots": job["shots"], "seed": job["seed"] }) print("running on backend: %s" % (backend)) if backend == "local_qasm_simulator": job_result = self.run_local_qasm_simulator(jobs) elif backend == "local_unitary_simulator": job_result = self.run_local_unitary_simulator(jobs) else: # Clear the list of compiled programs to execute self.__to_execute = {} return { "status": "Error", "result": 'Not a local simulator' } assert len(self.__to_execute[backend]) == len( job_result["qasms"] ), "Internal error in QuantumProgram.run(), job_result" # Fill data into self.__quantum_program for this backend index = 0 for job in self.__to_execute[backend]: name = job["name"] if name not in self.__quantum_program["circuits"]: # Clear the list of compiled programs to execute self.__to_execute = {} return { "status": "Error", "result": "Internal error, circuit not found" } if not "execution" in self.__quantum_program["circuits"][name]: self.__quantum_program["circuits"][name]["execution"] = {} # We override the results if backend not in self.__quantum_program["circuits"][name][ "execution"]: self.__quantum_program["circuits"][name]["execution"][ backend] = {} # TODO: return date, executionId, ... for field in [ "coupling_map", "basis_gates", "compiled_circuit", "shots", "max_credits", "seed" ]: self.__quantum_program["circuits"][name]["execution"][ backend][field] = job[field] self.__quantum_program["circuits"][name]["execution"][backend][ "result"] = job_result["qasms"][index]["result"] self.__quantum_program["circuits"][name]["execution"][backend][ "status"] = job_result["qasms"][index]["status"] index += 1 # Clear the list of compiled programs to execute self.__to_execute = {} return {"status": "COMPLETED", "result": 'all done'} def wait_for_job(self, jobid, wait=5, timeout=60): """Wait until all status results are 'COMPLETED'. jobids is a list of id strings. api is an IBMQuantumExperience object. wait is the time to wait between requests, in seconds timeout is how long we wait before failing, in seconds Returns an list of results that correspond to the jobids. """ timer = 0 timeout_over = False job = self.__api.get_job(jobid) while job['status'] == 'RUNNING': if timer == timeout: return {"status": "Error", "result": "Time Out"} time.sleep(wait) timer += wait print("status = %s (%d seconds)" % (job['status'], timer)) job = self.__api.get_job(jobid) if job['status'] == 'ERROR_CREATING_JOB' or job[ 'status'] == 'ERROR_RUNNING_JOB': return {"status": "Error", "result": job['status']} # Get the results return job def run_local_qasm_simulator(self, jobs): """run_local_qasm_simulator, run a program (precompile of quantum circuits). jobs is list of dicts {"compiled_circuit": simulator input data, "shots": integer num shots} returns job_results = { "qasms": [ { "result": DATA, "status": DATA, }, ... ] } """ job_results = {"qasms": []} for job in jobs: one_result = {'result': None, 'status': "Error"} qasm_circuit = simulators.QasmSimulator(job["compiled_circuit"], job["shots"], job["seed"]).run() one_result["result"] = {} one_result["result"]["data"] = qasm_circuit["data"] one_result["status"] = qasm_circuit["status"] job_results['qasms'].append(one_result) return job_results def run_local_unitary_simulator(self, jobs): """run_local_unitary_simulator, run a program (precompile of quantum circuits). jobs is list of dicts {"compiled_circuit": simulator input data} returns job_results = { "qasms": [ { "result": DATA, "status": DATA, }, ... ] } """ job_results = {"qasms": []} for job in jobs: one_result = {'result': None, 'status': "Error"} unitary_circuit = simulators.UnitarySimulator( job["compiled_circuit"]).run() one_result["result"] = {} one_result["result"]["data"] = unitary_circuit["data"] one_result["status"] = unitary_circuit["status"] job_results['qasms'].append(one_result) return job_results def execute(self, name_of_circuits, device="local_qasm_simulator", shots=1024, max_credits=3, wait=5, timeout=60, basis_gates=None, coupling_map=None, seed=None): """Execute, compile, and run a program (array of quantum circuits). program is a list of quantum_circuits api is the api for the device device is a string for real or simulator shots is the number of shots max_credits is the maximum credits for the experiments basis_gates are the base gates, which by default are: u1,u2,u3,cx,id """ self.compile(name_of_circuits, device, shots, max_credits, basis_gates, coupling_map, seed) output = self.run(wait, timeout) return output # method to process the data def get_result(self, name, device=None): """get the get_result from one circut and backend name of the circuit device that is use to compile, run, or execute """ if not device: device = self.__last_device_backend if name in self.__quantum_program["circuits"]: return self.__quantum_program["circuits"][name]['execution'][ device]['result'] else: return {"status": "Error", "result": 'Circuit not found'} def get_data(self, name, device=None): """Get the dict of labels and counts from the output of get_job. results are the list of results name is the name or index of one circuit.""" if not device: device = self.__last_device_backend return self.__quantum_program["circuits"][name]['execution'][device][ 'result']['data'] def get_counts(self, name, device=None): """Get the dict of labels and counts from the output of get_job. name is the name or index of one circuit.""" if not device: device = self.__last_device_backend try: return self.__quantum_program["circuits"][name]['execution'][ device]['result']['data']['counts'] except KeyError: return {"status": "Error", "result": 'Error in circuit name'} def plotter(self, name, device=None, method="histogram", number_to_keep=None): """Plot the results method: histogram/qsphere circuit: Print one circuit """ data = self.get_counts(name, device) if method == "histogram": basicplotter.plot_histogram(data, number_to_keep) else: pass # TODO: add basicplotter.plot_qsphere(data) for unitary simulator def average_data(self, name, observable): """Compute the mean value of an diagonal observable. Takes in an observable in dictionary format and then calculates the sum_i value(i) P(i) where value(i) is the value of the observable for state i. returns a double """ counts = self.get_counts(name) temp = 0 tot = sum(counts.values()) for key in counts: if key in observable: temp += counts[key] * observable[key] / tot return temp