def test_submit_continue_then_ok_reply(self):
        """Handle polling for a complete problem."""

        # each thread can have its instance of a session because
        # the mocked responses are stateless
        def create_mock_session(client):
            session = mock.Mock()
            session.post = lambda a, _: choose_reply(
                a, {'problems/': [self.sapi.continue_reply(id='123')]})
            session.get = lambda a: choose_reply(
                a, {
                    'problems/?id=123':
                    [self.sapi.complete_no_answer_reply(id='123')],
                    'problems/123/':
                    self.sapi.complete_reply(id='123')
                })
            return session

        with mock.patch.object(Client, 'create_session', create_mock_session):
            with Client('endpoint', 'token') as client:
                solver = Solver(client, self.sapi.solver.data)

                linear, quadratic = self.sapi.problem
                params = dict(num_reads=100)
                results = solver.sample_ising(linear, quadratic, **params)

                self._check(results, linear, quadratic, **params)
    def test_deprecations(self):
        """Proper deprecation warnings are raised."""
        def create_mock_session(client):
            session = mock.Mock()
            session.post = lambda a, _: choose_reply(a, {
                'problems/': [self.sapi.complete_no_answer_reply(id='123')]
            })
            session.get = lambda a: choose_reply(
                a, {'problems/123/': self.sapi.complete_reply(id='123')})
            return session

        with mock.patch.object(Client, 'create_session', create_mock_session):
            with Client('endpoint', 'token') as client:
                solver = Solver(client, self.sapi.solver.data)

                linear, quadratic = self.sapi.problem
                params = dict(num_reads=100)
                results = solver.sample_ising(linear, quadratic, **params)

                # aliased keys are deprecated in 0.8.0
                with self.assertWarns(DeprecationWarning):
                    results['samples']
                with self.assertWarns(DeprecationWarning):
                    results['occurrences']

                # .error is deprecated in 0.7.x, scheduled for removal in 0.9.0
                with self.assertWarns(DeprecationWarning):
                    results.error

                # .occurrences is deprecated in 0.8.0, scheduled for removal in 0.10.0+
                with self.assertWarns(DeprecationWarning):
                    results.occurrences
    def test_submit_offset_answer_does_not_include_it(self):
        """Handle a normal query with offset and response that doesn't include it."""

        # ising problem energy offset
        offset = 3

        # each thread can have its instance of a session because
        # the mocked responses are stateless
        def create_mock_session(client):
            session = mock.Mock()
            session.post = lambda a, _: choose_reply(a, {
                'problems/': [self.sapi.complete_no_answer_reply(id='123')]
            })
            session.get = lambda a: choose_reply(
                a, {'problems/123/': self.sapi.complete_reply(id='123')})
            return session

        with mock.patch.object(Client, 'create_session', create_mock_session):
            with Client('endpoint', 'token') as client:
                solver = Solver(client, self.sapi.solver.data)

                linear, quadratic = self.sapi.problem
                params = dict(num_reads=100)
                results = solver.sample_ising(linear, quadratic, offset,
                                              **params)

                # although SAPI response doesn't include offset, Future should patch it on-the-fly
                self._check(results,
                            linear,
                            quadratic,
                            offset=offset,
                            **params)
    def test_answer_load_error(self):
        """Answer load error is propagated as exception."""

        error_code = 404
        error_message = 'Problem not found'

        # each thread can have its instance of a session because
        # the mocked responses are stateless
        def create_mock_session(client):
            session = mock.Mock()
            session.post = lambda path, _: choose_reply(
                path,
                {'problems/': [self.sapi.complete_no_answer_reply(id='123')]})
            session.get = lambda path: choose_reply(
                path,
                replies={'problems/123/': error_message},
                statuses={'problems/123/': iter([error_code])})
            return session

        with mock.patch.object(Client, 'create_session', create_mock_session):
            with Client('endpoint', 'token') as client:
                solver = Solver(client, self.sapi.solver.data)

                linear, quadratic = self.sapi.problem
                future = solver.sample_ising(linear, quadratic)

                with self.assertRaises(SolverError) as exc:
                    future.result()

                self.assertEqual(str(exc.exception), error_message)
    def test_submit_immediate_error_reply(self):
        """Handle an (obvious) error on problem submission."""

        # each thread can have its instance of a session because
        # the mocked responses are stateless
        def create_mock_session(client):
            session = mock.Mock()
            session.post = lambda a, _: choose_reply(
                a, {
                    'problems/': [
                        self.sapi.immediate_error_reply(
                            code=400,
                            msg="Missing parameter 'num_reads' in problem JSON"
                        )
                    ]
                })
            return session

        with mock.patch.object(Client, 'create_session', create_mock_session):
            with Client('endpoint', 'token') as client:
                solver = Solver(client, self.sapi.solver.data)

                linear, quadratic = self.sapi.problem
                results = solver.sample_ising(linear, quadratic)

                with self.assertRaises(SolverFailureError):
                    results.samples
    def test_cancel_without_id(self):
        """Make sure the cancel method submits to the right endpoint.

        When cancel is called before the submission has returned the problem id.
        """
        submission_id = 'test-id'
        release_reply = threading.Event()

        # each thread can have its instance of a session because
        # we use a global lock (event) in the mocked responses
        def create_mock_session(client):
            reply_body = [self.sapi.continue_reply(id=submission_id)]

            session = mock.Mock()
            session.get = lambda a: choose_reply(
                a, {'problems/?id={}'.format(submission_id): reply_body})

            def post(a, _):
                release_reply.wait()
                return choose_reply(a, {'problems/': reply_body})

            session.post = post
            session.delete = DeleteEvent.handle

            return session

        with mock.patch.object(Client, 'create_session', create_mock_session):
            with Client('endpoint', 'token') as client:
                solver = Solver(client, self.sapi.solver.data)

                linear, quadratic = self.sapi.problem

                future = solver.sample_ising(linear, quadratic)
                future.cancel()

                try:
                    release_reply.set()
                    future.samples
                    self.fail()
                except DeleteEvent as event:
                    if event.url == 'problems/':
                        self.assertEqual(event.body,
                                         '["{}"]'.format(submission_id))
                    else:
                        self.assertEqual(event.url,
                                         'problems/{}/'.format(submission_id))
    def test_submit_null_reply(self):
        """Get an error when the server's response is incomplete."""

        # each thread can have its instance of a session because
        # the mocked responses are stateless
        def create_mock_session(client):
            session = mock.Mock()
            session.post = lambda a, _: choose_reply(a, {'problems/': ''})
            return session

        with mock.patch.object(Client, 'create_session', create_mock_session):
            with Client('endpoint', 'token') as client:
                solver = Solver(client, self.sapi.solver.data)

                linear, quadratic = self.sapi.problem
                results = solver.sample_ising(linear, quadratic)

                with self.assertRaises(InvalidAPIResponseError):
                    results.samples
    def test_submit_cancel_reply(self):
        """Handle a response for a canceled job."""

        # each thread can have its instance of a session because
        # the mocked responses are stateless
        def create_mock_session(client):
            session = mock.Mock()
            session.post = lambda a, _: choose_reply(
                a, {'problems/': [self.sapi.cancel_reply()]})
            return session

        with mock.patch.object(Client, 'create_session', create_mock_session):
            with Client('endpoint', 'token') as client:
                solver = Solver(client, self.sapi.solver.data)

                linear, quadratic = self.sapi.problem
                results = solver.sample_ising(linear, quadratic)

                with self.assertRaises(CanceledFutureError):
                    results.samples
    def test_id_integration(self):
        """Problem ID getter blocks correctly when ID set by the client."""

        submission_id = 'test-id'
        solver_name = 'solver-id'
        release_reply = threading.Event()

        # each thread can have its instance of a session because
        # we use a global lock (event) in the mocked responses
        def create_mock_session(client):
            session = mock.Mock()

            # delayed submit; emulates waiting in queue
            def post(path, _):
                release_reply.wait()
                reply_body = self.sapi.complete_reply(id=submission_id,
                                                      solver=solver_name)
                return choose_reply(path, {'problems/': [reply_body]})

            session.post = post

            return session

        with mock.patch.object(Client, 'create_session', create_mock_session):
            with Client('endpoint', 'token') as client:
                solver = Solver(client, self.sapi.solver.data)

                linear, quadratic = self.sapi.problem

                future = solver.sample_ising(linear, quadratic)

                # initially, the id is not available
                with self.assertRaises(TimeoutError):
                    future.wait_id(timeout=1)

                # release the mocked sapi reply with the id
                release_reply.set()

                # verify the id is now available
                self.assertEqual(future.wait_id(), submission_id)
    def test_submit_offset_wrong_offset_in_answer(self):
        """Energy levels don't match because offset in answer is respected, even if wrong"""

        # ising problem energy offset
        offset = 3
        answer_offset = 2 * offset  # make it wrong

        # each thread can have its instance of a session because
        # the mocked responses are stateless
        def create_mock_session(client):
            session = mock.Mock()
            session.post = lambda a, _: choose_reply(a, {
                'problems/': [self.sapi.complete_no_answer_reply(id='123')]
            })
            session.get = lambda a: choose_reply(
                a, {
                    'problems/123/':
                    self.sapi.complete_reply(
                        id='123', answer_patch=dict(offset=answer_offset))
                })
            return session

        with mock.patch.object(Client, 'create_session', create_mock_session):
            with Client('endpoint', 'token') as client:
                solver = Solver(client, self.sapi.solver.data)

                linear, quadratic = self.sapi.problem
                params = dict(num_reads=100)
                results = solver.sample_ising(linear, quadratic, offset,
                                              **params)

                # since SAPI response includes offset, Future shouldn't patch it;
                # but because the offset in answer is wrong, energies are off
                with self.assertRaises(AssertionError):
                    self._check(results,
                                linear,
                                quadratic,
                                offset=offset,
                                **params)
Example #11
0
class TestEventDispatch(unittest.TestCase):
    def setUp(self):
        # mock client
        self.client = Client(token='token', solver={'qpu': True})
        self.client._fetch_solvers = lambda **kw: self.solvers
        self.client._submit = lambda *pa, **kw: None

        # mock solvers
        self.solver = Solver(client=self.client,
                             data={
                                 "properties": {
                                     "supported_problem_types":
                                     ["qubo", "ising"],
                                     "qubits": [0, 1, 2],
                                     "couplers": [[0, 1], [0, 2], [1, 2]],
                                     "num_qubits": 3,
                                     "num_reads_range": [0, 100],
                                     "parameters": {
                                         "num_reads":
                                         "Number of samples to return.",
                                         "postprocess":
                                         "either 'sampling' or 'optimization'"
                                     },
                                     "topology": {
                                         "type": "chimera",
                                         "shape": [16, 16, 4]
                                     },
                                     "category": "qpu",
                                     "tags": ["lower_noise"]
                                 },
                                 "id": "solver1",
                                 "description": "A test solver 1",
                                 "status": "online"
                             })
        self.solvers = [self.solver]

    def test_validation(self):
        """Event name and handler are validated."""

        with self.assertRaises(ValueError):
            add_handler('invalid_event_name', lambda: None)
        with self.assertRaises(TypeError):
            add_handler('before_client_init', None)

    def test_client_init(self):
        """Before/After client init events are dispatched with correct signatures."""

        # setup event handlers
        memo = {}

        def handler(event, **data):
            memo[event] = data

        add_handler('before_client_init', handler)
        add_handler('after_client_init', handler)

        # client init
        client = Client(token='token', unknown='unknown')

        # test entry values
        before = memo['before_client_init']
        self.assertEqual(before['obj'], client)
        self.assertEqual(before['args']['endpoint'], None)
        self.assertEqual(before['args']['token'], 'token')
        self.assertEqual(before['args']['kwargs']['unknown'], 'unknown')

        # test exit values
        after = memo['after_client_init']
        self.assertEqual(after['obj'], client)
        self.assertEqual(after['args']['token'], 'token')
        self.assertEqual(after['args']['kwargs']['unknown'], 'unknown')
        self.assertEqual(after['return_value'], None)

    def test_get_solvers(self):
        """Before/After get_solvers events are dispatched with correct signatures."""

        # setup event handlers
        memo = {}

        def handler(event, **data):
            memo[event] = data

        add_handler('before_get_solvers', handler)
        add_handler('after_get_solvers', handler)

        # get solver(s)
        self.client.get_solver()

        # test entry values
        before = memo['before_get_solvers']
        self.assertEqual(before['obj'], self.client)
        self.assertIn('refresh', before['args'])
        self.assertIn('filters', before['args'])
        self.assertIn('qpu', before['args']['filters'])

        # test exit values
        after = memo['after_get_solvers']
        self.assertEqual(after['obj'], self.client)
        self.assertIn('qpu', after['args']['filters'])
        self.assertEqual(after['return_value'], self.solvers)

    def test_sample(self):
        """Before/After solver sample events are dispatched with correct signatures."""

        # setup event handlers
        memo = {}

        def handler(event, **data):
            memo[event] = data

        add_handler('before_sample', handler)
        add_handler('after_sample', handler)

        # sample
        lin = {0: 1}
        quad = {(0, 1): 1}
        offset = 2
        params = dict(num_reads=100)
        future = self.solver.sample_ising(lin, quad, offset, **params)

        # test entry values
        before = memo['before_sample']
        args = dict(type_='ising',
                    linear=lin,
                    quadratic=quad,
                    offset=offset,
                    params=params,
                    undirected_biases=False)
        self.assertEqual(before['obj'], self.solver)
        self.assertDictEqual(before['args'], args)

        # test exit values
        after = memo['after_sample']
        self.assertEqual(after['obj'], self.solver)
        self.assertDictEqual(after['args'], args)
        self.assertEqual(after['return_value'], future)
    def test_submit_continue_then_ok_and_error_reply(self):
        """Handle polling for the status of multiple problems."""

        # we need a "global session", because mocked responses are stateful
        def global_mock_session():
            session = mock.Mock()

            # on first status poll, return pending for both problems
            # on second status poll, return error for first problem and complete for second
            def continue_then_complete(path, state={'count': 0}):
                state['count'] += 1
                if state['count'] < 2:
                    return choose_reply(
                        path, {
                            'problems/?id=1':
                            [self.sapi.continue_reply(id='1')],
                            'problems/?id=2':
                            [self.sapi.continue_reply(id='2')],
                            'problems/1/':
                            self.sapi.continue_reply(id='1'),
                            'problems/2/':
                            self.sapi.continue_reply(id='2'),
                            'problems/?id=1,2': [
                                self.sapi.continue_reply(id='1'),
                                self.sapi.continue_reply(id='2')
                            ],
                            'problems/?id=2,1': [
                                self.sapi.continue_reply(id='2'),
                                self.sapi.continue_reply(id='1')
                            ]
                        })
                else:
                    return choose_reply(
                        path, {
                            'problems/?id=1': [self.sapi.error_reply(id='1')],
                            'problems/?id=2':
                            [self.sapi.complete_no_answer_reply(id='2')],
                            'problems/1/':
                            self.sapi.error_reply(id='1'),
                            'problems/2/':
                            self.sapi.complete_reply(id='2'),
                            'problems/?id=1,2': [
                                self.sapi.error_reply(id='1'),
                                self.sapi.complete_no_answer_reply(id='2')
                            ],
                            'problems/?id=2,1': [
                                self.sapi.complete_no_answer_reply(id='2'),
                                self.sapi.error_reply(id='1')
                            ]
                        })

            def accept_problems_with_continue_reply(path, body,
                                                    ids=iter('12')):
                problems = json.loads(body)
                return choose_reply(
                    path, {
                        'problems/': [
                            self.sapi.continue_reply(id=next(ids))
                            for _ in problems
                        ]
                    })

            session.get = continue_then_complete
            session.post = accept_problems_with_continue_reply

            return session

        session = global_mock_session()

        with mock.patch.object(Client, 'create_session', lambda self: session):
            with Client('endpoint', 'token') as client:
                solver = Solver(client, self.sapi.solver.data)

                linear, quadratic = self.sapi.problem
                params = dict(num_reads=100)

                results1 = solver.sample_ising(linear, quadratic, **params)
                results2 = solver.sample_ising(linear, quadratic, **params)

                with self.assertRaises(SolverFailureError):
                    self._check(results1, linear, quadratic, **params)
                self._check(results2, linear, quadratic, **params)