Skip to content

Commit

Permalink
Fix an issue with Failure simulations handling (Qiskit#167)
Browse files Browse the repository at this point in the history
* Fix an issue with Failure simulations handling

* Add tests for simple Success and Failure simulation events by mocking simulators
  • Loading branch information
atilag authored and chriseclectic committed Apr 24, 2019
1 parent f42eeea commit 044d6c5
Show file tree
Hide file tree
Showing 4 changed files with 296 additions and 2 deletions.
2 changes: 1 addition & 1 deletion qiskit/providers/aer/backends/aerbackend.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ def _validate_controller_output(self, output):
if not output.get("success", False):
logger.error("%s: simulation failed", self.name())
# Check for error message in the failed circuit
for res in output.get('results'):
for res in output.get('results', []):
if not res.get('success', False):
raise AerError(res.get("status", None))
# If no error was found check for error message at qobj level
Expand Down
41 changes: 41 additions & 0 deletions test/terra/backends/qasm_simulator/qasm_basics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-

# Copyright 2018, IBM.
#
# This source code is licensed under the Apache License, Version 2.0 found in
# the LICENSE.txt file in the root directory of this source tree.
"""
QasmSimulator Integration Tests
"""
from test.terra.utils.mock import FakeFailureQasmSimulator, FakeSuccessQasmSimulator
from qiskit.transpiler import transpile
from qiskit.compiler import assemble_circuits
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
from qiskit.providers.aer import AerError


class QasmBasicsTests:
"""QasmSimulator basic tests."""

def test_simulation_succeed(self):
"""Test the we properly manage simulation failures."""
mocked_backend = FakeSuccessQasmSimulator(time_alive=0)
qr = QuantumRegister(1)
cr = ClassicalRegister(1)
succeed_circuit = QuantumCircuit(qr, cr)
quantum_circuit = transpile(succeed_circuit, mocked_backend)
qobj = assemble_circuits(quantum_circuit)
result = mocked_backend.run(qobj).result()
self.is_completed(result)


def test_simulation_failed(self):
"""Test the we properly manage simulation failures."""
mocked_backend = FakeFailureQasmSimulator(time_alive=0)
qr = QuantumRegister(1)
cr = ClassicalRegister(1)
failed_circuit = QuantumCircuit(qr, cr)
quantum_circuit = transpile(failed_circuit, mocked_backend)
qobj = assemble_circuits(quantum_circuit)
job = mocked_backend.run(qobj)
self.assertRaises(AerError, job.result)
4 changes: 3 additions & 1 deletion test/terra/backends/test_qasm_simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from test.terra.backends.qasm_simulator.qasm_extra import QasmExtraTests
from test.terra.backends.qasm_simulator.qasm_thread_management import QasmThreadManagementTests
from test.terra.backends.qasm_simulator.qasm_fusion import QasmFusionTests
from test.terra.backends.qasm_simulator.qasm_basics import QasmBasicsTests


class TestQasmSimulator(common.QiskitAerTestCase,
Expand All @@ -47,7 +48,8 @@ class TestQasmSimulator(common.QiskitAerTestCase,
QasmAlgorithmTestsMinimalBasis,
QasmExtraTests,
QasmThreadManagementTests,
QasmFusionTests):
QasmFusionTests,
QasmBasicsTests):
"""QasmSimulator automatic method tests."""


Expand Down
251 changes: 251 additions & 0 deletions test/terra/utils/mock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
# -*- coding: utf-8 -*-

# Copyright 2018, IBM.
#
# This source code is licensed under the Apache License, Version 2.0 found in
# the LICENSE.txt file in the root directory of this source tree.

# pylint: disable=missing-docstring

"""
Utilities for mocking the IBMQ provider, including job responses and backends.
The module includes dummy provider, backends, and jobs. The purpose of
these classes is to trick backends for testing purposes:
testing local timeouts, arbitrary responses or behavior, etc.
The mock devices are mainly for testing the compiler.
"""

import uuid
import logging
from concurrent import futures
import time

from qiskit.result import Result
from qiskit.providers import BaseBackend, BaseJob
from qiskit.providers.models import BackendProperties, BackendConfiguration
from qiskit.providers.models.backendconfiguration import GateConfig
from qiskit.qobj import (QasmQobj, QobjExperimentHeader, QobjHeader,
QasmQobjInstruction, QasmQobjExperimentConfig,
QasmQobjExperiment, QasmQobjConfig)
from qiskit.providers.jobstatus import JobStatus
from qiskit.providers.baseprovider import BaseProvider
from qiskit.providers.exceptions import QiskitBackendNotFoundError
from qiskit.providers.aer import AerError


logger = logging.getLogger(__name__)


class FakeProvider(BaseProvider):
"""Dummy provider just for testing purposes.
Only filtering backends by name is implemented.
"""

def get_backend(self, name=None, **kwargs):
backend = self._backends[0]
if name:
filtered_backends = [backend for backend in self._backends
if backend.name() == name]
if not filtered_backends:
raise QiskitBackendNotFoundError()
else:
backend = filtered_backends[0]
return backend

def backends(self, name=None, **kwargs):
return self._backends

def __init__(self):
# TODO Add the rest of simulators that we want to mock
self._backends = [FakeSuccessQasmSimulator(),
FakeFailureQasmSimulator()]
super().__init__()


class FakeBackend(BaseBackend):
"""This is a dummy backend just for testing purposes."""

def __init__(self, configuration, time_alive=10):
"""
Args:
configuration (BackendConfiguration): backend configuration
time_alive (int): time to wait before returning result
"""
super().__init__(configuration)
self.time_alive = time_alive

def properties(self):
"""Return backend properties"""
properties = {
'backend_name': self.name(),
'backend_version': self.configuration().backend_version,
'last_update_date': '2000-01-01 00:00:00Z',
'qubits': [[{'name': 'TODO', 'date': '2000-01-01 00:00:00Z',
'unit': 'TODO', 'value': 0}]],
'gates': [{'qubits': [0], 'gate': 'TODO',
'parameters':
[{'name': 'TODO', 'date': '2000-01-01 00:00:00Z',
'unit': 'TODO', 'value': 0}]}],
'general': []
}

return BackendProperties.from_dict(properties)

def run(self, qobj):
job_id = str(uuid.uuid4())
job = FakeJob(self, self.run_job, job_id, qobj)
job.submit()
return job

# pylint: disable=unused-argument
def run_job(self, job_id, qobj):
"""Main dummy run loop"""
time.sleep(self.time_alive)

return Result.from_dict({
'job_id': job_id,
'backend_name': self.name(),
'backend_version': self.configuration().backend_version,
'qobj_id': qobj.qobj_id,
'results': [],
'status': 'COMPLETED',
'success': True
})


class FakeSuccessQasmSimulator(FakeBackend):
"""A fake QASM simulator backend that always returns SUCCESS"""

def __init__(self, time_alive=10):
configuration = BackendConfiguration(
backend_name='fake_success_qasm_simulator',
backend_version='0.0.0',
n_qubits=5,
basis_gates=['u1', 'u2', 'u3', 'cx', 'cz', 'id', 'x', 'y', 'z',
'h', 's', 'sdg', 't', 'tdg', 'ccx', 'swap',
'snapshot', 'unitary'],
simulator=True,
local=True,
conditional=True,
open_pulse=False,
memory=True,
max_shots=65536,
gates=[GateConfig(name='TODO', parameters=[], qasm_def='TODO')]
)

super().__init__(configuration, time_alive=time_alive)


class FakeFailureQasmSimulator(FakeBackend):
"""A fake simulator backend."""

def __init__(self, time_alive=10):
configuration = BackendConfiguration(
backend_name='fake_failure_qasm_simulator',
backend_version='0.0.0',
n_qubits=5,
basis_gates=['u1', 'u2', 'u3', 'cx', 'cz', 'id', 'x', 'y', 'z',
'h', 's', 'sdg', 't', 'tdg', 'ccx', 'swap',
'snapshot', 'unitary'],
simulator=True,
local=True,
conditional=True,
open_pulse=False,
memory=True,
max_shots=65536,
gates=[GateConfig(name='TODO', parameters=[], qasm_def='TODO')]
)

super().__init__(configuration, time_alive=time_alive)


# pylint: disable=unused-argument
def run_job(self, job_id, qobj):
"""Main dummy run loop"""
time.sleep(self.time_alive)

raise AerError("Mocking a failure in the QASM Simulator")

class FakeJob(BaseJob):
"""Fake simulator job"""
_executor = futures.ThreadPoolExecutor()

def __init__(self, backend, fn, job_id, qobj):
super().__init__(backend, job_id)
self._backend = backend
self._job_id = job_id
self._qobj = qobj
self._future = None
self._future_callback = fn

def submit(self):
self._future = self._executor.submit(
self._future_callback, self._job_id, self._qobj
)

def result(self, timeout=None):
# pylint: disable=arguments-differ
return self._future.result(timeout=timeout)

def cancel(self):
return self._future.cancel()

def status(self):
if self._running:
_status = JobStatus.RUNNING
elif not self._done:
_status = JobStatus.QUEUED
elif self._cancelled:
_status = JobStatus.CANCELLED
elif self._done:
_status = JobStatus.DONE
elif self._error:
_status = JobStatus.ERROR
else:
raise Exception('Unexpected state of {0}'.format(
self.__class__.__name__))
_status_msg = None
return {'status': _status,
'status_msg': _status_msg}

def job_id(self):
return self._job_id

def backend(self):
return self._backend

@property
def _cancelled(self):
return self._future.cancelled()

@property
def _done(self):
return self._future.done()

@property
def _running(self):
return self._future.running()

@property
def _error(self):
return self._future.exception(timeout=0)


def new_fake_qobj():
"""Create fake `Qobj` and backend instances."""
backend = FakeQasmSimulator()
return QasmQobj(
qobj_id='test-id',
config=QasmQobjConfig(shots=1024, memory_slots=1, max_credits=100),
header=QobjHeader(backend_name=backend.name()),
experiments=[QasmQobjExperiment(
instructions=[
QasmQobjInstruction(name='barrier', qubits=[1])
],
header=QobjExperimentHeader(),
config=QasmQobjExperimentConfig(seed=123456)
)]
)

0 comments on commit 044d6c5

Please sign in to comment.