diff --git a/README.rst b/README.rst index 5703ea2bd..98b9cb943 100644 --- a/README.rst +++ b/README.rst @@ -205,6 +205,29 @@ Note that, by default, the ``qiskit.ibmq`` device uses the simulator backend dev.capabilities()['backend'] +When getting a ``qiskit.ibmq`` device a Qiskit provider is used to connect to the IBM Q systems. + +Custom providers can be passed as arguments when a ``qiskit.ibmq`` device is created: + +.. code-block:: python + + from qiskit import IBMQ + provider = IBMQ.enable_account('XYZ') + + import pennylane as qml + dev = qml.device('qiskit.ibmq', wires=2, backend='ibmq_qasm_simulator', provider=provider) + +If no provider is passed explicitly, then the official provider is used, with options of ``hub='ibm-q'``, ``group='open'`` and ``project='main'``. + +Custom provider options can be passed as keyword arguments when creating a device: + +.. code-block:: python + + import pennylane as qml + dev = qml.device('qiskit.ibmq', wires=2, backend='ibmq_qasm_simulator', ibmqx_token='XXX', hub='MYHUB', group='MYGROUP', project='MYPROJECT') + +More details on Qiskit providers can be found at the `IBMQ provider documentation `_. + .. gettingstarted-end-inclusion-marker-do-not-remove Please refer to the `plugin documentation `_ as diff --git a/pennylane_qiskit/ibmq.py b/pennylane_qiskit/ibmq.py index 54183c20b..f07edd943 100644 --- a/pennylane_qiskit/ibmq.py +++ b/pennylane_qiskit/ibmq.py @@ -33,7 +33,6 @@ ~~~~~~~~~~~~ """ import os -import warnings from qiskit import IBMQ from qiskit.providers.ibmq.exceptions import IBMQAccountError @@ -75,6 +74,11 @@ def __init__(self, wires, provider=None, backend="ibmq_qasm_simulator", shots=10 token = os.getenv("IBMQX_TOKEN") or kwargs.get("ibmqx_token", None) url = os.getenv("IBMQX_URL") or kwargs.get("ibmqx_url", None) + # Specify a single hub, group and project + hub = kwargs.get("hub", 'ibm-q') + group = kwargs.get("group", 'open') + project = kwargs.get("project", 'main') + if token is not None: # token was provided by the user, so attempt to enable an # IBM Q account manually @@ -101,6 +105,6 @@ def __init__(self, wires, provider=None, backend="ibmq_qasm_simulator", shots=10 # IBM Q account is now enabled # get a provider - p = provider or IBMQ.get_provider() + p = provider or IBMQ.get_provider(hub=hub, group=group, project=project) super().__init__(wires=wires, provider=p, backend=backend, shots=shots, **kwargs) diff --git a/tests/test_ibmq.py b/tests/test_ibmq.py index 4eb4c3ad1..b3b02ffad 100644 --- a/tests/test_ibmq.py +++ b/tests/test_ibmq.py @@ -8,6 +8,8 @@ from qiskit.providers.ibmq.exceptions import IBMQAccountError from pennylane_qiskit import IBMQDevice +from pennylane_qiskit import ibmq as ibmq +from pennylane_qiskit import qiskit_device as qiskit_device @pytest.fixture @@ -36,6 +38,74 @@ def test_account_already_loaded(token): dev = IBMQDevice(wires=1) assert dev.provider.credentials.is_ibmq() +class MockQiskitDeviceInit: + """A mocked version of the QiskitDevice __init__ method which + is called on by the IBMQDevice""" + + def mocked_init(self, wires, provider, backend, shots, **kwargs): + """Stores the provider which QiskitDevice.__init__ was + called with.""" + self.provider = provider + +def test_custom_provider(monkeypatch): + """Tests that a custom provider can be passed when creating an IBMQ + device.""" + mock_provider = "MockProvider" + mock_qiskit_device = MockQiskitDeviceInit() + + with monkeypatch.context() as m: + m.setattr(ibmq.QiskitDevice, "__init__",mock_qiskit_device.mocked_init) + m.setattr(ibmq.IBMQ, "enable_account", lambda *args, **kwargs: None) + + # Here mocking to a value such that it is not None + m.setattr(ibmq.IBMQ, "active_account", lambda *args, **kwargs: True) + dev = IBMQDevice(wires=2, backend='ibmq_qasm_simulator', provider=mock_provider) + + assert mock_qiskit_device.provider == mock_provider + +def mock_get_provider(*args, **kwargs): + """A mock function for the get_provider Qiskit function to record the + arguments which it was called with.""" + return (args, kwargs) + +def test_default_provider(monkeypatch): + """Tests that the default provider is used when no custom provider was + specified.""" + mock_qiskit_device = MockQiskitDeviceInit() + + with monkeypatch.context() as m: + m.setattr(ibmq.QiskitDevice, "__init__", mock_qiskit_device.mocked_init) + m.setattr(ibmq.IBMQ, "get_provider", mock_get_provider) + m.setattr(ibmq.IBMQ, "enable_account", lambda *args, **kwargs: None) + + # Here mocking to a value such that it is not None + m.setattr(ibmq.IBMQ, "active_account", lambda *args, **kwargs: True) + dev = IBMQDevice(wires=2, backend='ibmq_qasm_simulator') + + assert mock_qiskit_device.provider[0] == () + assert mock_qiskit_device.provider[1] == {'hub': 'ibm-q', 'group': 'open', 'project': 'main'} + +def test_custom_provider_hub_group_project(monkeypatch): + """Tests that the custom arguments passed during device instantiation are + used when calling get_provider.""" + mock_qiskit_device = MockQiskitDeviceInit() + + custom_hub = "SomeHub" + custom_group = "SomeGroup" + custom_project = "SomeProject" + + with monkeypatch.context() as m: + m.setattr(ibmq.QiskitDevice, "__init__", mock_qiskit_device.mocked_init) + m.setattr(ibmq.IBMQ, "get_provider", mock_get_provider) + m.setattr(ibmq.IBMQ, "enable_account", lambda *args, **kwargs: None) + + # Here mocking to a value such that it is not None + m.setattr(ibmq.IBMQ, "active_account", lambda *args, **kwargs: True) + dev = IBMQDevice(wires=2, backend='ibmq_qasm_simulator', hub=custom_hub, group=custom_group, project=custom_project) + + assert mock_qiskit_device.provider[0] == () + assert mock_qiskit_device.provider[1] == {'hub': custom_hub, 'group': custom_group, 'project': custom_project} + def test_load_from_disk(token): IBMQ.save_account(token)