diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 54e759d6396b..138fa106f9af 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -64,7 +64,10 @@ Changed - `IBMQ.save_account()` now takes an `overwrite` option to replace an existing account on disk. Default is False (#1295). - Backend and Provider methods defined in the specification use model objects - rather than dicts, along with validation against schemas (#1249, #1277). + rather than dicts, along with validation against schemas (#1249, #1277). The + updated methods include: + - ``backend.status()``(#1301). + - ``backend.configuration()`` (and ``__init__``) (#1323). - ``backend.provider()`` is now a method instead of a property (#1312). - Remove local backend (Aer) fallback (#1303) diff --git a/qiskit/backends/aer/aerprovider.py b/qiskit/backends/aer/aerprovider.py index 1056c12f7581..3000c222e7d6 100644 --- a/qiskit/backends/aer/aerprovider.py +++ b/qiskit/backends/aer/aerprovider.py @@ -115,7 +115,7 @@ def _verify_aer_backends(self): for backend_cls in AER_STANDARD_BACKENDS: try: backend_instance = self._get_backend_instance(backend_cls) - backend_name = backend_instance.configuration()['name'] + backend_name = backend_instance.name() ret[backend_name] = backend_instance except QISKitError as err: # Ignore backends that could not be initialized. @@ -132,8 +132,7 @@ def _get_backend_instance(self, backend_cls): Returns: BaseBackend: a backend instance. Raises: - QISKitError: if the backend could not be instantiated or does not - provide a valid configuration containing a name. + QISKitError: if the backend could not be instantiated. """ # Verify that the backend can be instantiated. try: @@ -142,12 +141,6 @@ def _get_backend_instance(self, backend_cls): raise QISKitError('Backend %s could not be instantiated: %s' % (backend_cls, err)) - # Verify that the instance has a minimal valid configuration. - try: - _ = backend_instance.configuration()['name'] - except (LookupError, TypeError): - raise QISKitError('Backend %s has an invalid configuration') - return backend_instance def __str__(self): diff --git a/qiskit/backends/aer/qasm_simulator.py b/qiskit/backends/aer/qasm_simulator.py index 85cbf4df6c10..f291e2cdd5d0 100644 --- a/qiskit/backends/aer/qasm_simulator.py +++ b/qiskit/backends/aer/qasm_simulator.py @@ -20,6 +20,7 @@ import numpy as np +from qiskit.backends.models import BackendConfiguration from qiskit.result._utils import copy_qasm_from_qobj_into_result, result_from_old_style_dict from qiskit.backends import BaseBackend from qiskit.backends.aer.aerjob import AerJob @@ -46,34 +47,40 @@ class QasmSimulator(BaseBackend): """C++ quantum circuit simulator with realistic noise""" DEFAULT_CONFIGURATION = { - 'name': 'qasm_simulator', + 'backend_name': 'qasm_simulator', + 'backend_version': '1.0.0', + 'n_qubits': -1, 'url': 'https://github.com/QISKit/qiskit-terra/src/qasm-simulator-cpp', 'simulator': True, 'local': True, - 'description': 'A C++ realistic noise simulator for qobj files', - 'coupling_map': 'all-to-all', - "basis_gates": 'u0,u1,u2,u3,cx,cz,id,x,y,z,h,s,sdg,t,tdg,rzz,' + - 'snapshot,wait,noise,save,load' + 'conditional': True, + 'open_pulse': False, + 'description': 'A C++ realistic noise simulator for qasm experiments', + 'basis_gates': ['u0', 'u1', 'u2', 'u3', 'cx', 'cz', 'id', 'x', 'y', 'z', + 'h', 's', 'sdg', 't', 'tdg', 'rzz', 'snapshot', 'wait', + 'noise', 'save', 'load'], + 'gates': [{'name': 'TODO', 'parameters': [], 'qasm_def': 'TODO'}] } def __init__(self, configuration=None, provider=None): - super().__init__(configuration=configuration or self.DEFAULT_CONFIGURATION.copy(), + super().__init__(configuration=(configuration or + BackendConfiguration.from_dict(self.DEFAULT_CONFIGURATION)), provider=provider) # Try to use the default executable if not specified. - if self._configuration.get('exe'): - paths = [self._configuration.get('exe')] + if 'exe' in self._configuration: + paths = [self._configuration.exe] else: paths = DEFAULT_SIMULATOR_PATHS # Ensure that the executable is available. try: - self._configuration['exe'] = next( + self._configuration.exe = next( path for path in paths if (os.path.exists(path) and os.path.getsize(path) > 100)) except StopIteration: raise FileNotFoundError('Simulator executable not found (using %s)' % - self._configuration.get('exe', 'default locations')) + getattr(self._configuration, 'exe', 'default locations')) def run(self, qobj): """Run a qobj on the backend.""" @@ -84,7 +91,7 @@ def run(self, qobj): def _run_job(self, job_id, qobj): self._validate(qobj) - result = run(qobj, self._configuration['exe']) + result = run(qobj, self._configuration.exe) result['job_id'] = job_id copy_qasm_from_qobj_into_result(qobj, result) @@ -104,33 +111,39 @@ class CliffordSimulator(BaseBackend): """"C++ Clifford circuit simulator with realistic noise.""" DEFAULT_CONFIGURATION = { - 'name': 'clifford_simulator', - 'url': 'https://github.com/QISKit/qiskit-terra/src/qasm-simulator', + 'backend_name': 'clifford_simulator', + 'backend_version': '1.0.0', + 'n_qubits': -1, + 'url': 'https://github.com/QISKit/qiskit-terra/src/qasm-simulator-cpp', 'simulator': True, 'local': True, + 'conditional': True, + 'open_pulse': False, 'description': 'A C++ Clifford simulator with approximate noise', - 'coupling_map': 'all-to-all', - 'basis_gates': 'cx,id,x,y,z,h,s,sdg,snapshot,wait,noise,save,load' + 'basis_gates': ['cx', 'id', 'x', 'y', 'z', 'h', 's', 'sdg', 'snapshot', + 'wait', 'noise', 'save', 'load'], + 'gates': [{'name': 'TODO', 'parameters': [], 'qasm_def': 'TODO'}] } def __init__(self, configuration=None, provider=None): - super().__init__(configuration=configuration or self.DEFAULT_CONFIGURATION.copy(), + super().__init__(configuration=(configuration or + BackendConfiguration.from_dict(self.DEFAULT_CONFIGURATION)), provider=provider) # Try to use the default executable if not specified. - if self._configuration.get('exe'): - paths = [self._configuration.get('exe')] + if 'exe' in self._configuration: + paths = [self._configuration.exe] else: paths = DEFAULT_SIMULATOR_PATHS # Ensure that the executable is available. try: - self._configuration['exe'] = next( + self._configuration.exe = next( path for path in paths if (os.path.exists(path) and os.path.getsize(path) > 100)) except StopIteration: raise FileNotFoundError('Simulator executable not found (using %s)' % - self._configuration.get('exe', 'default locations')) + getattr(self._configuration, 'exe', 'default locations')) def run(self, qobj): """Run a Qobj on the backend. @@ -159,7 +172,7 @@ def _run_job(self, job_id, qobj): qobj_dict['config'] = {'simulator': 'clifford'} qobj = Qobj.from_dict(qobj_dict) - result = run(qobj, self._configuration['exe']) + result = run(qobj, self._configuration.exe) result['job_id'] = job_id return result_from_old_style_dict( diff --git a/qiskit/backends/aer/qasm_simulator_py.py b/qiskit/backends/aer/qasm_simulator_py.py index 37e52673141b..19ff6c9aff1f 100644 --- a/qiskit/backends/aer/qasm_simulator_py.py +++ b/qiskit/backends/aer/qasm_simulator_py.py @@ -78,6 +78,7 @@ import numpy as np +from qiskit.backends.models import BackendConfiguration from qiskit.result._utils import copy_qasm_from_qobj_into_result, result_from_old_style_dict from qiskit.backends import BaseBackend from qiskit.backends.aer.aerjob import AerJob @@ -90,17 +91,22 @@ class QasmSimulatorPy(BaseBackend): """Python implementation of a qasm simulator.""" DEFAULT_CONFIGURATION = { - 'name': 'qasm_simulator_py', + 'backend_name': 'qasm_simulator_py', + 'backend_version': '2.0.0', + 'n_qubits': -1, 'url': 'https://github.com/QISKit/qiskit-terra', 'simulator': True, 'local': True, - 'description': 'A python simulator for qasm files', - 'coupling_map': 'all-to-all', - 'basis_gates': 'u1,u2,u3,cx,id,snapshot' + 'conditional': True, + 'open_pulse': False, + 'description': 'A python simulator for qasm experiments', + 'basis_gates': ['u1', 'u2', 'u3', 'cx', 'id', 'snapshot'], + 'gates': [{'name': 'TODO', 'parameters': [], 'qasm_def': 'TODO'}] } def __init__(self, configuration=None, provider=None): - super().__init__(configuration=configuration or self.DEFAULT_CONFIGURATION.copy(), + super().__init__(configuration=(configuration or + BackendConfiguration.from_dict(self.DEFAULT_CONFIGURATION)), provider=provider) self._local_random = random.Random() @@ -243,7 +249,7 @@ def _run_job(self, job_id, qobj): for circuit in qobj.experiments: result_list.append(self.run_circuit(circuit)) end = time.time() - result = {'backend': self._configuration['name'], + result = {'backend': self.name(), 'id': qobj.qobj_id, 'job_id': job_id, 'result': result_list, @@ -342,7 +348,7 @@ def run_circuit(self, circuit): params = operation.params self._add_qasm_snapshot(params[0]) else: - backend = self._configuration['name'] + backend = self.name() err_msg = '{0} encountered unrecognized operation "{1}"' raise SimulatorError(err_msg.format(backend, operation.name)) diff --git a/qiskit/backends/aer/statevector_simulator.py b/qiskit/backends/aer/statevector_simulator.py index c9c781fc04c0..6f3572acff0c 100644 --- a/qiskit/backends/aer/statevector_simulator.py +++ b/qiskit/backends/aer/statevector_simulator.py @@ -14,6 +14,7 @@ import logging import uuid +from qiskit.backends.models import BackendConfiguration from qiskit.qobj import QobjInstruction from .qasm_simulator import QasmSimulator from ._simulatorerror import SimulatorError @@ -26,17 +27,24 @@ class StatevectorSimulator(QasmSimulator): """C++ statevector simulator""" DEFAULT_CONFIGURATION = { - 'name': 'statevector_simulator', + 'backend_name': 'statevector_simulator', + 'backend_version': '1.0.0', + 'n_qubits': -1, 'url': 'https://github.com/QISKit/qiskit-terra/src/qasm-simulator-cpp', 'simulator': True, 'local': True, - 'description': 'A C++ statevector simulator for qobj files', - 'coupling_map': 'all-to-all', - 'basis_gates': 'u1,u2,u3,cx,cz,id,x,y,z,h,s,sdg,t,tdg,rzz,load,save,snapshot' + 'conditional': False, + 'open_pulse': False, + 'description': 'A single-shot C++ statevector simulator for the |0> state evolution', + 'basis_gates': ['u1', 'u2', 'u3', 'cx', 'cz', 'id', 'x', 'y', 'z', 'h', + 's', 'sdg', 't', 'tdg', 'rzz', 'load', 'save', + 'snapshot'], + 'gates': [{'name': 'TODO', 'parameters': [], 'qasm_def': 'TODO'}] } def __init__(self, configuration=None, provider=None): - super().__init__(configuration=configuration or self.DEFAULT_CONFIGURATION.copy(), + super().__init__(configuration=(configuration or + BackendConfiguration.from_dict(self.DEFAULT_CONFIGURATION)), provider=provider) def run(self, qobj): diff --git a/qiskit/backends/aer/statevector_simulator_py.py b/qiskit/backends/aer/statevector_simulator_py.py index e04396c6d230..435dbb891aef 100644 --- a/qiskit/backends/aer/statevector_simulator_py.py +++ b/qiskit/backends/aer/statevector_simulator_py.py @@ -23,6 +23,7 @@ from qiskit.backends.aer.aerjob import AerJob from qiskit.backends.aer._simulatorerror import SimulatorError +from qiskit.backends.models import BackendConfiguration from qiskit.qobj import QobjInstruction from .qasm_simulator_py import QasmSimulatorPy @@ -33,17 +34,22 @@ class StatevectorSimulatorPy(QasmSimulatorPy): """Python statevector simulator.""" DEFAULT_CONFIGURATION = { - 'name': 'statevector_simulator_py', + 'backend_name': 'statevector_simulator_py', + 'backend_version': '1.0.0', + 'n_qubits': -1, 'url': 'https://github.com/QISKit/qiskit-terra', 'simulator': True, 'local': True, + 'conditional': False, + 'open_pulse': False, 'description': 'A Python statevector simulator for qobj files', - 'coupling_map': 'all-to-all', - 'basis_gates': 'u1,u2,u3,cx,id,snapshot' + 'basis_gates': ['u1', 'u2', 'u3', 'cx', 'id', 'snapshot'], + 'gates': [{'name': 'TODO', 'parameters': [], 'qasm_def': 'TODO'}] } def __init__(self, configuration=None, provider=None): - super().__init__(configuration=configuration or self.DEFAULT_CONFIGURATION.copy(), + super().__init__(configuration=(configuration or + BackendConfiguration.from_dict(self.DEFAULT_CONFIGURATION)), provider=provider) def run(self, qobj): diff --git a/qiskit/backends/aer/unitary_simulator_py.py b/qiskit/backends/aer/unitary_simulator_py.py index 2e0390959d3d..079d59213565 100644 --- a/qiskit/backends/aer/unitary_simulator_py.py +++ b/qiskit/backends/aer/unitary_simulator_py.py @@ -86,6 +86,7 @@ import numpy as np +from qiskit.backends.models import BackendConfiguration from qiskit.result._utils import copy_qasm_from_qobj_into_result, result_from_old_style_dict from qiskit.backends import BaseBackend from qiskit.backends.aer.aerjob import AerJob @@ -103,17 +104,22 @@ class UnitarySimulatorPy(BaseBackend): """Python implementation of a unitary simulator.""" DEFAULT_CONFIGURATION = { - 'name': 'unitary_simulator_py', + 'backend_name': 'unitary_simulator_py', + 'backend_version': '1.0.0', + 'n_qubits': -1, 'url': 'https://github.com/QISKit/qiskit-terra', 'simulator': True, 'local': True, - 'description': 'A python simulator for unitary matrix', - 'coupling_map': 'all-to-all', - 'basis_gates': 'u1,u2,u3,cx,id' + 'conditional': False, + 'open_pulse': False, + 'description': 'A python simulator for unitary matrix corresponding to a circuit', + 'basis_gates': ['u1', 'u2', 'u3', 'cx', 'id'], + 'gates': [{'name': 'TODO', 'parameters': [], 'qasm_def': 'TODO'}] } def __init__(self, configuration=None, provider=None): - super().__init__(configuration=configuration or self.DEFAULT_CONFIGURATION.copy(), + super().__init__(configuration=(configuration or + BackendConfiguration.from_dict(self.DEFAULT_CONFIGURATION)), provider=provider) # Define attributes inside __init__. @@ -189,7 +195,7 @@ def _run_job(self, job_id, qobj): for circuit in qobj.experiments: result_list.append(self.run_circuit(circuit)) end = time.time() - result = {'backend': self._configuration['name'], + result = {'backend': self.name(), 'id': qobj.qobj_id, 'job_id': job_id, 'result': result_list, diff --git a/qiskit/backends/basebackend.py b/qiskit/backends/basebackend.py index 5fb4982923a4..e61ca5f27301 100644 --- a/qiskit/backends/basebackend.py +++ b/qiskit/backends/basebackend.py @@ -28,15 +28,13 @@ def __init__(self, configuration, provider=None): not available. Args: - configuration (dict): configuration dictionary + configuration (BackendConfiguration): backend configuration provider (BaseProvider): provider responsible for this backend Raises: FileNotFoundError if backend executable is not available. QISKitError: if there is no name in the configuration """ - if 'name' not in configuration: - raise qiskit.QISKitError('backend does not have a name.') self._configuration = configuration self._provider = provider @@ -46,7 +44,11 @@ def run(self, qobj): pass def configuration(self): - """Return backend configuration""" + """Return the backend configuration. + + Returns: + BackendConfiguration: the configuration fot the backend. + """ return self._configuration def properties(self): @@ -57,7 +59,7 @@ def provider(self): """Return the backend Provider. Returns: - BaseProvider: the Provider responsible for this backend. + BaseProvider: the Provider responsible for the backend. """ return self._provider @@ -74,8 +76,12 @@ def status(self): status_msg='') def name(self): - """Return backend name""" - return self._configuration['name'] + """Return backend name. + + Returns: + str: the name of the backend. + """ + return self._configuration.backend_name def __str__(self): return self.name() diff --git a/qiskit/backends/ibmq/api/ibmqconnector.py b/qiskit/backends/ibmq/api/ibmqconnector.py index 231e4e8f8bda..edc209e807cc 100644 --- a/qiskit/backends/ibmq/api/ibmqconnector.py +++ b/qiskit/backends/ibmq/api/ibmqconnector.py @@ -15,6 +15,8 @@ import requests from requests_ntlm import HttpNtlmAuth +from qiskit._util import _camel_case_to_snake_case + logger = logging.getLogger(__name__) CLIENT_APPLICATION = 'qiskit-api-py' @@ -508,7 +510,7 @@ def _check_backend(self, backend, endpoint): # Check for new-style backends backends = self.available_backends() for backend_ in backends: - if backend_['name'] == original_backend: + if backend_['backend_name'] == original_backend: return original_backend # backend unrecognized return None @@ -802,15 +804,48 @@ def available_backends(self, hub=None, group=None, project=None, self.req.credential.set_user_id(user_id) if not self.check_credentials(): raise CredentialsError('credentials invalid') - else: - url = get_backend_url(self.config, hub, group, project) + url = get_backend_url(self.config, hub, group, project) + + response = self.req.get(url) + if (response is not None) and (isinstance(response, dict)): + return [] + + # Pre-process configurations in order to make them schema-conformant. + # TODO: this should be removed once devices return the proper format. + ret = [] + for original_config in response: + if original_config.get('status', 'off') != 'on': + continue + + config = {} + + # Convert camelCase to snake_case. + for key in original_config.keys(): + new_key = _camel_case_to_snake_case(key) + if new_key not in ['id', 'serial_number', 'topology_id', + 'status']: + config[new_key] = original_config[key] + + # Empty and non-schema conformat versions. + if not re.match(r'[0-9]+.[0-9]+.[0-9]+', config.get('version', '')): + config['version'] = '0.0.0' + # Coupling map for simulators. + if config.get('coupling_map', None) == 'all-to-all': + config.pop('coupling_map') + # Other fields. + config['basis_gates'] = config['basis_gates'].split(',') + config['local'] = config.get('local', False) + config['open_pulse'] = config.get('open_pulse', False) + config['conditional'] = config.get('conditional', True) + config['backend_name'] = config.pop('name') + config['backend_version'] = config.pop('version') + config['gates'] = [{'name': 'TODO', 'parameters': [], 'qasm_def': 'TODO'}] + + # Append to returned list. + ret.append(config) - ret = self.req.get(url) - if (ret is not None) and (isinstance(ret, dict)): - return [] - return [backend for backend in ret - if backend.get('status') == 'on'] + return ret def api_version(self): """ diff --git a/qiskit/backends/ibmq/ibmqbackend.py b/qiskit/backends/ibmq/ibmqbackend.py index 2b251c6ceeae..9af530b38ca0 100644 --- a/qiskit/backends/ibmq/ibmqbackend.py +++ b/qiskit/backends/ibmq/ibmqbackend.py @@ -32,24 +32,15 @@ def __init__(self, configuration, provider, credentials, api): """Initialize remote backend for IBM Quantum Experience. Args: - configuration (dict): configuration of backend. + configuration (BackendConfiguration): configuration of backend. provider (IBMQProvider): provider. credentials (Credentials): credentials. api (IBMQConnector): api for communicating with the Quantum Experience. """ super().__init__(provider=provider, configuration=configuration) - self._api = api - if self._configuration: - configuration_edit = {} - for key, vals in self._configuration.items(): - new_key = _camel_case_to_snake_case(key) - configuration_edit[new_key] = vals - self._configuration = configuration_edit - # FIXME: This is a hack to make sure that the - # local : False is added to the online device - self._configuration['local'] = False + self._api = api self._credentials = credentials self.hub = credentials.hub self.group = credentials.group @@ -65,7 +56,8 @@ def run(self, qobj): IBMQJob: an instance derived from BaseJob """ job_class = _job_class_from_backend_support(self) - job = job_class(self, None, self._api, not self.configuration()['simulator'], qobj=qobj) + job = job_class(self, None, self._api, + not self.configuration().simulator, qobj=qobj) job.submit() return job @@ -146,7 +138,7 @@ def jobs(self, limit=50, skip=0, status=None, db_filter=None): Raises: IBMQBackendValueError: status keyword value unrecognized """ - backend_name = self.configuration()['name'] + backend_name = self.name() api_filter = {'backend.name': backend_name} if status: if isinstance(status, str): @@ -175,7 +167,7 @@ def jobs(self, limit=50, skip=0, status=None, db_filter=None): job_list = [] for job_info in job_info_list: job_class = _job_class_from_job_response(job_info) - is_device = not bool(self._configuration.get('simulator')) + is_device = not bool(self.configuration().simulator) job = job_class(self, job_info.get('id'), self._api, is_device, creation_date=job_info.get('creationDate'), api_status=job_info.get('status')) @@ -203,7 +195,7 @@ def retrieve_job(self, job_id): raise IBMQBackendError('Failed to get job "{}":{}' .format(job_id, str(ex))) job_class = _job_class_from_job_response(job_info) - is_device = not bool(self._configuration.get('simulator')) + is_device = not bool(self.configuration().simulator) job = job_class(self, job_info.get('id'), self._api, is_device, creation_date=job_info.get('creationDate'), api_status=job_info.get('status')) @@ -234,5 +226,5 @@ def _job_class_from_job_response(job_response): def _job_class_from_backend_support(backend): - support_qobj = backend.configuration().get('allow_q_object') + support_qobj = getattr(backend.configuration(), 'allow_q_object', False) return IBMQJob if support_qobj else IBMQJobPreQobj diff --git a/qiskit/backends/ibmq/ibmqsingleprovider.py b/qiskit/backends/ibmq/ibmqsingleprovider.py index 462c6ec7640b..c0bf60cd8a53 100644 --- a/qiskit/backends/ibmq/ibmqsingleprovider.py +++ b/qiskit/backends/ibmq/ibmqsingleprovider.py @@ -9,9 +9,8 @@ from collections import OrderedDict - -from qiskit._util import _camel_case_to_snake_case from qiskit.backends import BaseProvider +from qiskit.backends.models import BackendConfiguration from qiskit.backends.providerutils import filter_backends from .api import IBMQConnector @@ -45,7 +44,7 @@ def backends(self, name=None, filters=None, **kwargs): backends = self._backends.values() if name: - kwargs['name'] = name + kwargs['backend_name'] = name return filter_backends(backends, filters=filters, **kwargs) @@ -78,28 +77,6 @@ def _authenticate(cls, credentials): raise ConnectionError("Couldn't connect to IBMQ server: {0}" .format(ex)) from root_exception - @classmethod - def _parse_backend_configuration(cls, config): - """Parse a backend configuration returned by IBMQ. - - Args: - config (dict): raw configuration as returned by IBMQ. - - Returns: - dict: parsed configuration. - """ - edited_config = { - 'local': False - } - - for key in config.keys(): - new_key = _camel_case_to_snake_case(key) - if new_key not in ['id', 'serial_number', 'topology_id', - 'status']: - edited_config[new_key] = config[key] - - return edited_config - def _discover_remote_backends(self): """Return the remote backends available. @@ -110,11 +87,11 @@ def _discover_remote_backends(self): ret = OrderedDict() configs_list = self._api.available_backends() for raw_config in configs_list: - config = self._parse_backend_configuration(raw_config) - ret[config['name']] = IBMQBackend(configuration=config, - provider=self._ibm_provider, - credentials=self.credentials, - api=self._api) + config = BackendConfiguration.from_dict(raw_config) + ret[config.backend_name] = IBMQBackend(configuration=config, + provider=self._ibm_provider, + credentials=self.credentials, + api=self._api) return ret diff --git a/qiskit/backends/models/backendconfiguration.py b/qiskit/backends/models/backendconfiguration.py index 7233d21cd843..66d82299462f 100644 --- a/qiskit/backends/models/backendconfiguration.py +++ b/qiskit/backends/models/backendconfiguration.py @@ -44,7 +44,8 @@ class BackendConfigurationSchema(BaseSchema): validate=Or([Equal(-1), Range(min=1)])) basis_gates = List(String(), required=True, validate=Length(min=1)) - gates = Nested(GateConfigSchema, required=True, many=True) + gates = Nested(GateConfigSchema, required=True, many=True, + validate=Length(min=1)) local = Boolean(required=True) simulator = Boolean(required=True) conditional = Boolean(required=True) diff --git a/qiskit/backends/providerutils.py b/qiskit/backends/providerutils.py index ba337e450912..629061eff28b 100644 --- a/qiskit/backends/providerutils.py +++ b/qiskit/backends/providerutils.py @@ -29,9 +29,9 @@ def filter_backends(backends, filters=None, **kwargs): list[BaseBackend]: a list of backend instances matching the conditions. """ - def _match_all(dict_, criteria): - """Return True if all items in criteria matches items in dict_.""" - return all(dict_.get(key_) == value_ for + def _match_all(obj, criteria): + """Return True if all items in criteria matches items in obj.""" + return all(getattr(obj, key_, None) == value_ for key_, value_ in criteria.items()) # Inspect the backends to decide which filters belong to @@ -54,7 +54,7 @@ def _match_all(dict_, criteria): # each backend). if status_filters: backends = [b for b in backends if - _match_all(b.status().to_dict(), status_filters)] + _match_all(b.status(), status_filters)] # 3. Apply acceptor filter. backends = list(filter(filters, backends)) diff --git a/qiskit/transpiler/_transpiler.py b/qiskit/transpiler/_transpiler.py index de063ee3573f..1e96ad42a507 100644 --- a/qiskit/transpiler/_transpiler.py +++ b/qiskit/transpiler/_transpiler.py @@ -55,13 +55,13 @@ def transpile(circuits, backend, basis_gates=None, coupling_map=None, initial_la # 1. do all circuits have same coupling map? # 2. do all circuit have the same basis set? # 3. do they all have same registers etc? - backend_conf = backend.configuration() # Check for valid parameters for the experiments. if hpc is not None and \ not all(key in hpc for key in ('multi_shot_optimization', 'omp_num_threads')): raise TranspilerError('Unknown HPC parameter format!') - basis_gates = basis_gates or backend_conf['basis_gates'] - coupling_map = coupling_map or backend_conf['coupling_map'] + basis_gates = basis_gates or ','.join(backend.configuration().basis_gates) + coupling_map = coupling_map or getattr(backend.configuration(), + 'coupling_map', None) # step 1: Making the list of dag circuits dags = _circuits_2_dags(circuits) @@ -77,7 +77,7 @@ def transpile(circuits, backend, basis_gates=None, coupling_map=None, initial_la # TODO: move this inside mapper pass. initial_layouts = [] for dag in dags: - if (initial_layout is None and not backend.configuration()['simulator'] + if (initial_layout is None and not backend.configuration().simulator and not _matches_coupling_map(dag, coupling_map)): _initial_layout = _pick_best_layout(dag, backend) initial_layouts.append(_initial_layout) @@ -313,11 +313,11 @@ def _best_subset(backend, n_qubits): elif n_qubits <= 0: raise QISKitError('Number of qubits <= 0.') - device_qubits = backend.configuration()['n_qubits'] + device_qubits = backend.configuration().n_qubits if n_qubits > device_qubits: raise QISKitError('Number of qubits greater than device.') - cmap = np.asarray(backend.configuration()['coupling_map']) + cmap = np.asarray(getattr(backend.configuration(), 'coupling_map', None)) data = np.ones_like(cmap[:, 0]) sp_cmap = sp.coo_matrix((data, (cmap[:, 0], cmap[:, 1])), shape=(device_qubits, device_qubits)).tocsr() diff --git a/qiskit/validation/base.py b/qiskit/validation/base.py index 72c21b25feb2..0c1fcb9b6e4f 100644 --- a/qiskit/validation/base.py +++ b/qiskit/validation/base.py @@ -323,6 +323,14 @@ def __reduce__(self): """ return _base_model_from_kwargs, (self.__class__, self.__dict__) + def __contains__(self, item): + """Custom implementation of membership test. + + Implement the ``__contains__`` method for catering to the common case + of finding out if a model contains a certain key (``key in model``). + """ + return item in self.__dict__ + class ObjSchema(BaseSchema): """Generic object schema.""" diff --git a/test/performance/vqe.py b/test/performance/vqe.py index 4eaceb3560b3..f145912ac554 100644 --- a/test/performance/vqe.py +++ b/test/performance/vqe.py @@ -74,7 +74,8 @@ def vqe(molecule='H2', depth=6, max_trials=200, shots=1): if 'statevector' not in device: H = group_paulis(pauli_list) - entangler_map = get_backend(device).configuration()['coupling_map'] + entangler_map = getattr(get_backend(device).configuration(), + 'coupling_map', 'all-to-all') if entangler_map == 'all-to-all': entangler_map = {i: [j for j in range(n_qubits) if j != i] for i in range(n_qubits)} diff --git a/test/python/aer/test_simulator_interfaces.py b/test/python/aer/test_simulator_interfaces.py index 111122cc452a..783fdb31887c 100644 --- a/test/python/aer/test_simulator_interfaces.py +++ b/test/python/aer/test_simulator_interfaces.py @@ -109,7 +109,7 @@ def test_qasm_reset_measure(self): result_py = execute(circuit, sim_py, shots=shots, seed=1).result() counts_cpp = result_cpp.get_counts() counts_py = result_py.get_counts() - self.assertDictAlmostEqual(counts_cpp, counts_py, shots * 0.042) + self.assertDictAlmostEqual(counts_cpp, counts_py, shots * 0.06) if __name__ == '__main__': diff --git a/test/python/aer/test_statevector_simulator.py b/test/python/aer/test_statevector_simulator.py index 9888712e2942..1f8f6aa064dd 100644 --- a/test/python/aer/test_statevector_simulator.py +++ b/test/python/aer/test_statevector_simulator.py @@ -8,7 +8,7 @@ # pylint: disable=missing-docstring,broad-except import unittest -from qiskit import execute, load_qasm_file +from qiskit import execute, QuantumCircuit from qiskit import Aer from ..common import QiskitTestCase, requires_cpp_simulator @@ -19,7 +19,7 @@ class StatevectorSimulatorTest(QiskitTestCase): def setUp(self): self.qasm_filename = self._get_resource_path('qasm/simple.qasm') - self.q_circuit = load_qasm_file(self.qasm_filename, name='example') + self.q_circuit = QuantumCircuit.from_qasm_file(self.qasm_filename) def test_statevector_simulator(self): """Test final state vector for single circuit run.""" diff --git a/test/python/common.py b/test/python/common.py index 8c2370145cd6..064551df092c 100644 --- a/test/python/common.py +++ b/test/python/common.py @@ -24,6 +24,10 @@ from ._test_options import get_test_options +# Allows shorter stack trace for .assertDictAlmostEqual +__unittest = True # pylint: disable=invalid-name + + class Path(Enum): """Helper with paths commonly used during the tests.""" # Main SDK path: qiskit/ diff --git a/test/python/ibmq/test_ibmq_qobj.py b/test/python/ibmq/test_ibmq_qobj.py index 196f23dcf33f..4e3867edec5a 100644 --- a/test/python/ibmq/test_ibmq_qobj.py +++ b/test/python/ibmq/test_ibmq_qobj.py @@ -55,7 +55,8 @@ def test_operational(self): def test_allow_qobj(self): """Test if backend support Qobj. """ - self.assertTrue(self._remote_backend.configuration()['allow_q_object']) + self.assertTrue(getattr(self._remote_backend.configuration(), + 'allow_q_object', False)) @slow_test @requires_qe_access diff --git a/test/python/test_backends.py b/test/python/test_backends.py index 258ed0f52f6f..164165c0cdd5 100644 --- a/test/python/test_backends.py +++ b/test/python/test_backends.py @@ -102,15 +102,15 @@ def test_aer_backend_configuration(self): If all correct should pass the validation. """ + schema_path = self._get_resource_path( + 'backend_configuration_schema.json', path=Path.SCHEMAS) + with open(schema_path, 'r') as schema_file: + schema = json.load(schema_file) + aer_backends = Aer.backends() for backend in aer_backends: configuration = backend.configuration() - schema_path = self._get_resource_path( - 'deprecated/backends/backend_configuration_schema_old_py.json', - path=Path.SCHEMAS) - with open(schema_path, 'r') as schema_file: - schema = json.load(schema_file) - jsonschema.validate(configuration, schema) + jsonschema.validate(configuration.to_dict(), schema) @requires_qe_access def test_remote_backend_configuration(self, qe_token, qe_url): @@ -118,15 +118,16 @@ def test_remote_backend_configuration(self, qe_token, qe_url): If all correct should pass the validation. """ + schema_path = self._get_resource_path( + 'backend_configuration_schema.json', path=Path.SCHEMAS) + with open(schema_path, 'r') as schema_file: + schema = json.load(schema_file) + IBMQ.enable_account(qe_token, qe_url) - remotes = IBMQ.backends(simulator=False) + remotes = IBMQ.backends() for backend in remotes: configuration = backend.configuration() - schema_path = self._get_resource_path( - 'deprecated/backends/backend_configuration_schema_old_py.json', path=Path.SCHEMAS) - with open(schema_path, 'r') as schema_file: - schema = json.load(schema_file) - jsonschema.validate(configuration, schema) + jsonschema.validate(configuration.to_dict(), schema) def test_aer_backend_properties(self): """Test backend properties. @@ -153,7 +154,7 @@ def test_remote_backend_properties(self, qe_token, qe_url): properties = backend.properties() # FIXME test against schema and decide what properties # is for a simulator - if backend.configuration()['simulator']: + if backend.configuration().simulator: self.assertEqual(len(properties), 0) else: self.assertTrue(all(key in properties for key in ( diff --git a/test/python/test_compiler.py b/test/python/test_compiler.py index eafa7ad27c52..7fb9509c3a4c 100644 --- a/test/python/test_compiler.py +++ b/test/python/test_compiler.py @@ -17,6 +17,8 @@ from qiskit import transpiler from qiskit import compile from qiskit import Result +from qiskit.backends.models import BackendConfiguration +from qiskit.backends.models.backendconfiguration import GateConfig from qiskit.dagcircuit import DAGCircuit from qiskit import execute from qiskit._qiskiterror import QISKitError @@ -38,11 +40,18 @@ def configuration(self): [6, 7], [6, 11], [7, 10], [8, 7], [9, 8], [9, 10], [11, 10], [12, 5], [12, 11], [12, 13], [13, 4], [13, 14], [15, 0], [15, 2], [15, 14]] - return { - 'name': 'fake', 'basis_gates': 'u1,u2,u3,cx,id', - 'simulator': False, 'n_qubits': 16, - 'coupling_map': qx5_cmap - } + return BackendConfiguration( + backend_name='fake', + backend_version='0.0.0', + n_qubits=16, + basis_gates=['u1', 'u2', 'u3', 'cx', 'id'], + simulator=False, + local=True, + conditional=False, + open_pulse=False, + gates=[GateConfig(name='TODO', parameters=[], qasm_def='TODO')], + coupling_map=qx5_cmap, + ) class TestCompiler(QiskitTestCase): @@ -390,7 +399,7 @@ def test_mapping_already_satisfied(self): compiled_ops = qobj.experiments[0].instructions for operation in compiled_ops: if operation.name == 'cx': - self.assertIn(operation.qubits, backend.configuration()['coupling_map']) + self.assertIn(operation.qubits, backend.configuration().coupling_map) def test_compile_circuits_diff_registers(self): """Compile list of circuits with different qreg names. diff --git a/test/python/test_filter_backends.py b/test/python/test_filter_backends.py index 76c78e29f1d1..5a3b39631d77 100644 --- a/test/python/test_filter_backends.py +++ b/test/python/test_filter_backends.py @@ -39,8 +39,8 @@ def test_filter_config_callable(self, qe_token, qe_url): """Test filtering by lambda function on configuration properties""" IBMQ.enable_account(qe_token, qe_url) filtered_backends = IBMQ.backends( - filters=lambda x: (not x.configuration()['simulator'] - and x.configuration()['n_qubits'] > 5)) + filters=lambda x: (not x.configuration().simulator + and x.configuration().n_qubits > 5)) self.assertTrue(filtered_backends) @requires_qe_access diff --git a/test/python/test_mapper.py b/test/python/test_mapper.py index deaabc56e47a..a1ae0265fdb6 100644 --- a/test/python/test_mapper.py +++ b/test/python/test_mapper.py @@ -12,8 +12,10 @@ import unittest import qiskit.wrapper -from qiskit import compile -from qiskit import load_qasm_string, mapper, qasm, unroll, Aer +from qiskit import compile, QuantumCircuit +from qiskit import mapper, qasm, unroll, Aer +from qiskit.backends.models import BackendConfiguration +from qiskit.backends.models.backendconfiguration import GateConfig from qiskit.qobj import Qobj from qiskit.transpiler._transpiler import transpile_dag from qiskit.dagcircuit._dagcircuit import DAGCircuit @@ -32,11 +34,19 @@ def name(self): def configuration(self): qx4_cmap = [[1, 0], [2, 0], [2, 1], [3, 2], [3, 4], [4, 2]] - return { - 'name': 'fake_qx4', 'basis_gates': 'u1,u2,u3,cx,id', - 'simulator': False, 'n_qubits': 5, - 'coupling_map': qx4_cmap - } + + return BackendConfiguration( + backend_name='fake_qx4', + backend_version='0.0.0', + n_qubits=5, + basis_gates=['u1', 'u2', 'u3', 'cx', 'id'], + simulator=False, + local=True, + conditional=False, + open_pulse=False, + gates=[GateConfig(name='TODO', parameters=[], qasm_def='TODO')], + coupling_map=qx4_cmap, + ) class FakeQX5BackEnd(object): @@ -48,11 +58,19 @@ def configuration(self): [6, 7], [6, 11], [7, 10], [8, 7], [9, 8], [9, 10], [11, 10], [12, 5], [12, 11], [12, 13], [13, 4], [13, 14], [15, 0], [15, 2], [15, 14]] - return { - 'name': 'fake_qx5', 'basis_gates': 'u1,u2,u3,cx,id', - 'simulator': False, 'n_qubits': 16, - 'coupling_map': qx5_cmap - } + + return BackendConfiguration( + backend_name='fake_qx5', + backend_version='0.0.0', + n_qubits=16, + basis_gates=['u1', 'u2', 'u3', 'cx', 'id'], + simulator=False, + local=True, + conditional=False, + open_pulse=False, + gates=[GateConfig(name='TODO', parameters=[], qasm_def='TODO')], + coupling_map=qx5_cmap, + ) class MapperTest(QiskitTestCase): @@ -67,7 +85,8 @@ def test_mapper_overoptimization(self): The mapper should not change the semantics of the input. An overoptimization introduced the issue #81: https://github.com/QISKit/qiskit-terra/issues/81 """ - circ = qiskit.load_qasm_file(self._get_resource_path('qasm/overoptimization.qasm')) + circ = QuantumCircuit.from_qasm_file( + self._get_resource_path('qasm/overoptimization.qasm')) coupling_map = [[0, 2], [1, 2], [2, 3]] result1 = qiskit.execute(circ, backend=Aer.get_backend("qasm_simulator_py"), coupling_map=coupling_map, seed=self.seed) @@ -85,7 +104,8 @@ def test_math_domain_error(self): avoided. See: https://github.com/QISKit/qiskit-terra/issues/111 """ - circ = qiskit.load_qasm_file(self._get_resource_path('qasm/math_domain_error.qasm')) + circ = QuantumCircuit.from_qasm_file( + self._get_resource_path('qasm/math_domain_error.qasm')) coupling_map = [[0, 2], [1, 2], [2, 3]] shots = 2000 qobj = qiskit.execute(circ, backend=Aer.get_backend("qasm_simulator_py"), @@ -122,7 +142,8 @@ def test_optimize_1q_gates_issue159(self): def test_random_parameter_circuit(self): """Run a circuit with randomly generated parameters.""" - circ = qiskit.load_qasm_file(self._get_resource_path('qasm/random_n5_d5.qasm')) + circ = QuantumCircuit.from_qasm_file( + self._get_resource_path('qasm/random_n5_d5.qasm')) coupling_map = [[0, 1], [1, 2], [2, 3], [3, 4]] shots = 1024 qobj = qiskit.execute(circ, backend=Aer.get_backend("qasm_simulator_py"), @@ -251,10 +272,10 @@ def test_yzy_zyz_cases(self): See: https://github.com/QISKit/qiskit-terra/issues/607 """ backend = FakeQX4BackEnd() - circ1 = load_qasm_string(YZY_ZYZ_1) + circ1 = QuantumCircuit.from_qasm_str(YZY_ZYZ_1) qobj1 = compile(circ1, backend) self.assertIsInstance(qobj1, Qobj) - circ2 = load_qasm_string(YZY_ZYZ_2) + circ2 = QuantumCircuit.from_qasm_str(YZY_ZYZ_2) qobj2 = compile(circ2, backend) self.assertIsInstance(qobj2, Qobj) @@ -262,9 +283,10 @@ def test_move_measurements(self): """Measurements applied AFTER swap mapping. """ backend = FakeQX5BackEnd() - cmap = backend.configuration()['coupling_map'] - circ = qiskit.load_qasm_file(self._get_resource_path('qasm/move_measurements.qasm'), - name='move') + cmap = backend.configuration().coupling_map + circ = QuantumCircuit.from_qasm_file( + self._get_resource_path('qasm/move_measurements.qasm')) + dag_circuit = DAGCircuit.fromQuantumCircuit(circ) lay = {('qa', 0): ('q', 0), ('qa', 1): ('q', 1), ('qb', 0): ('q', 15), ('qb', 1): ('q', 2), ('qb', 2): ('q', 14), ('qN', 0): ('q', 3),