def test_bad_url(self): """Connect with a bad URL.""" with requests_mock.Mocker() as m: setup_server(m) with self.assertRaises(SAPIError) as err: with Client(bad_url, token) as client: client.get_solvers() # TODO: fix when exceptions/sapi call generalized self.assertEqual(err.exception.error_code, 404)
def test_result_structure(self): with Client(**config) as client: solver = client.get_solver() computation = solver.sample_ising({}, {}) result = computation.result() self.assertIn('solutions', result) self.assertIn('energies', result) self.assertIn('num_occurrences', result) self.assertIn('timing', result)
def setUp(self): # mock client self.client = Client(endpoint='e', token='t', solver=dict(name__contains='test')) self.client._fetch_solvers = lambda **kw: self.solvers self.client._submit = lambda *pa, **kw: None self.client.upload_problem_encoded = lambda *pa, **kw: Present( result=mock_problem_id) # mock solvers self.structured_solver = StructuredSolver( 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": "test-qpu-solver", "description": "A test solver 1", "status": "online" }) self.unstructured_solver = UnstructuredSolver( client=self.client, data={ "properties": { "supported_problem_types": ["bqm"], "parameters": { "num_reads": "Number of samples to return." }, "category": "hybrid", }, "id": "test-unstructured-solver", "description": "A test unstructured solver" }) self.solvers = [self.structured_solver] # we can't use unstructured solvers without dimod installed, # so don't even try testing it if dimod: self.solvers.append(self.unstructured_solver) # reset all event handlers from dwave.cloud.events import _client_event_hooks_registry as reg reg.update({k: [] for k in reg})
def test_polling_recovery_after_5xx(self): "Polling shouldn't be aborted on 5xx responses." # we need a "global session", because mocked responses are stateful def global_mock_session(): session = mock.Mock() # on submit, return status pending session.post = lambda path, _: choose_reply( path, {'problems/': [self.sapi.continue_reply(id='123')]}) # on first and second status poll, fail with 503 and 504 # on third status poll, return completed statuses = iter([503, 504]) def continue_then_complete(path, state={'count': 0}): state['count'] += 1 if state['count'] < 3: return choose_reply( path, replies={ 'problems/?id=123': [self.sapi.continue_reply(id='123')], 'problems/123/': self.sapi.continue_reply(id='123') }, statuses={ 'problems/?id=123': statuses, 'problems/123/': statuses }) else: return choose_reply( path, { 'problems/?id=123': [self.sapi.complete_no_answer_reply(id='123')], 'problems/123/': self.sapi.complete_reply(id='123') }) session.get = continue_then_complete return session session = global_mock_session() with mock.patch.object(Client, 'create_session', lambda self: session): with Client(endpoint='endpoint', token='token') as client: solver = Solver(client, self.sapi.solver.data) future = solver.sample_qubo({}) future.result() # after third poll, back-off interval should be 4 x initial back-off self.assertAlmostEqual( future._poll_backoff, client.poll_backoff_min * client.poll_backoff_base**2)
def test_future_structure(self): with Client(**config) as client: solver = client.get_solver() computation = solver.sample_ising({}, {}) _ = computation.result() self.assertIsInstance(computation.id, str) self.assertEqual(computation.remote_status, Client.STATUS_COMPLETE) self.assertEqual(computation.solver, solver) self.assertIsInstance(computation.time_received, datetime) self.assertIsInstance(computation.time_solved, datetime)
def test_submit_list_problem(self): """Submit a problem using a list for the linear terms.""" with Client(**config) as client: solver = client.get_solver() linear = [1 if qubit in solver.nodes else 0 for qubit in range(0, max(solver.nodes)+1)] quad = {key: random.choice([-1, 1]) for key in solver.undirected_edges} self._submit_and_check(solver, linear, quad)
def test_submit_immediate_reply(self): """Construction of and sampling from an unstructured solver works.""" # build a test problem bqm = dimod.BQM.from_ising({}, {'ab': 1}) # use a global mocked session, so we can modify it on-fly session = mock.Mock() # construct a functional solver by mocking client and api response data with mock.patch.object(Client, 'create_session', lambda self: session): with Client('endpoint', 'token') as client: solver = UnstructuredSolver(client, unstructured_solver_data()) # direct bqm sampling ss = dimod.ExactSolver().sample(bqm) session.post = lambda path, _: choose_reply( path, {'problems/': complete_reply(ss)}) fut = solver.sample_bqm(bqm) numpy.testing.assert_array_equal(fut.sampleset, ss) numpy.testing.assert_array_equal(fut.samples, ss.record.sample) numpy.testing.assert_array_equal(fut.energies, ss.record.energy) numpy.testing.assert_array_equal(fut.occurrences, ss.record.num_occurrences) # ising sampling lin, quad, _ = bqm.to_ising() ss = dimod.ExactSolver().sample_ising(lin, quad) session.post = lambda path, _: choose_reply( path, {'problems/': complete_reply(ss)}) fut = solver.sample_ising(lin, quad) numpy.testing.assert_array_equal(fut.sampleset, ss) numpy.testing.assert_array_equal(fut.samples, ss.record.sample) numpy.testing.assert_array_equal(fut.energies, ss.record.energy) numpy.testing.assert_array_equal(fut.occurrences, ss.record.num_occurrences) # qubo sampling qubo, _ = bqm.to_qubo() ss = dimod.ExactSolver().sample_qubo(qubo) session.post = lambda path, _: choose_reply( path, {'problems/': complete_reply(ss)}) fut = solver.sample_qubo(qubo) numpy.testing.assert_array_equal(fut.sampleset, ss) numpy.testing.assert_array_equal(fut.samples, ss.record.sample) numpy.testing.assert_array_equal(fut.energies, ss.record.energy) numpy.testing.assert_array_equal(fut.occurrences, ss.record.num_occurrences)
def test_retrieve_answer(self): """Answer retrieved based on problem_id in a new client.""" with Client(**config) as client: solver = client.get_solver() h = {v: -1 for v in solver.nodes} f = solver.sample_ising(h, {}) # the id is not set right away while f.id is None: time.sleep(.01) id_ = f.id with Client(**config) as client: # get a "new" client f2 = client.retrieve_answer(id_) self.assertIn('solutions', f2.result())
def test_sample_cqm_smoke_test(self): """Construction of and sampling from an unstructured CQM solver works.""" # construct a small 3-variable CQM of mixed vartypes try: import dimod mixed = dimod.QM() mixed.add_variable('BINARY', 'a') mixed.add_variable('SPIN', 'b') mixed.add_variable('INTEGER', 'c') cqm = dimod.CQM() cqm.set_objective(mixed) cqm.add_constraint(mixed, rhs=1, sense='==') except: # dimod or dimod with CQM support not available, so just use a mock cqm = mock.Mock() cqm.to_file.return_value = io.BytesIO(b'123') problem_type = 'cqm' # use a global mocked session, so we can modify it on-fly session = mock.Mock() # upload is now part of submit, so we need to mock it mock_problem_id = 'mock-problem-id' def mock_upload(self, bqm): return Present(result=mock_problem_id) # construct a functional solver by mocking client and api response data with mock.patch.multiple(Client, create_session=lambda self: session, upload_problem_encoded=mock_upload): with Client('endpoint', 'token') as client: solver = CQMSolver( client, unstructured_solver_data(problem_type=problem_type)) # use bqm for mock response (for now) ss = dimod.ExactSolver().sample(dimod.BQM.empty('SPIN')) ss.info.update(problem_id=mock_problem_id) session.post = lambda path, _: choose_reply( path, { 'problems/': complete_reply( ss, id_=mock_problem_id, type_=problem_type) }) # verify decoding works fut = solver.sample_cqm(cqm) numpy.testing.assert_array_equal(fut.sampleset, ss) numpy.testing.assert_array_equal(fut.problem_type, problem_type)
def setUp(self): # mock solvers self.solver1 = Solver(client=None, 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'" } }, "id": "solver1", "description": "A test solver 1", "status": "online" }) self.solver2 = Solver(client=None, data={ "properties": { "supported_problem_types": ["qubo", "ising"], "qubits": [0, 1, 2, 3, 4], "couplers": [[0, 1], [0, 2], [1, 2], [2, 3], [3, 4]], "num_qubits": 5, "num_reads_range": [0, 200], "parameters": { "num_reads": "Number of samples to return.", "flux_biases": "Supported ...", "anneal_schedule": "Supported ..." }, "vfyc": True }, "id": "solver2", "description": "A test solver 2" }) self.solver3 = Solver(client=None, data={ "properties": { "supported_problem_types": ["qubo", "ising"], "qubits": [0, 1], "couplers": [[0, 1]], "num_qubits": 7, "num_reads_range": [0, 1000], "parameters": {"num_reads": "Number of samples to return."}, "vfyc": False }, "id": "c4-sw_solver3", "description": "A test of software solver" }) self.solvers = [self.solver1, self.solver2, self.solver3] # mock client self.client = Client('endpoint', 'token') self.client._fetch_solvers = lambda **kw: self.solvers
def test_max_num_reads(self): with Client(**config) as client: solver = client.get_solver() if solver.qpu: dnr = solver.max_num_reads() # double the anneal_time anneal_time = 2 * solver.properties['default_annealing_time'] self.assertEqual( dnr // 2, self.max_num_reads(annealing_time=anneal_time)) else: self.assertEqual(solver.max_num_reads(), solver.properties['num_reads_range'][1])
def test_submit_extra_qubit(self): """Submit a defective problem with an unsupported variable.""" with Client(**config) as client: solver = client.get_solver() # Build a linear problem and add a variable that shouldn't exist linear, quad = generate_random_ising_problem(solver) linear[max(solver.nodes) + 1] = 1 with self.assertRaises(InvalidProblemError): results = solver.sample_ising(linear, quad) results.samples
def test_max_num_reads(self): with Client(**config) as client: solver = client.get_solver() if solver.qpu: # for lower anneal time num_reads is bounded by num_reads_range anneal_time = 10 * solver.properties['default_annealing_time'] num_reads = solver.max_num_reads(annealing_time=anneal_time) # doubling the anneal_time, num_reads halves self.assertEqual(num_reads // 2, solver.max_num_reads(annealing_time=2*anneal_time)) else: self.assertEqual(solver.max_num_reads(), solver.properties['num_reads_range'][1])
def test_sample_dqm_smoke_test(self): """Construction of and sampling from an unstructured DQM solver works.""" try: import dimod dqm = dimod.DQM() dqm.add_variable(5) dqm.add_variable(7) dqm.set_linear_case(0, 3, 1.5) dqm.set_quadratic(0, 1, {(0, 1): 1.5, (3, 4): 1}) except: # dimod or dimod with DQM support not available, so just use a mock dqm = mock.Mock() dqm.to_file.return_value = io.BytesIO(b'123') problem_type = 'dqm' # use a global mocked session, so we can modify it on-fly session = mock.Mock() # upload is now part of submit, so we need to mock it mock_problem_id = 'mock-problem-id' def mock_upload(self, bqm): return Present(result=mock_problem_id) # construct a functional solver by mocking client and api response data with mock.patch.multiple(Client, create_session=lambda self: session, upload_problem_encoded=mock_upload): with Client('endpoint', 'token') as client: solver = DQMSolver( client, unstructured_solver_data(problem_type=problem_type)) # use bqm for mock response (for now) ss = dimod.ExactSolver().sample(dimod.BQM.empty('SPIN')) ss.info.update(problem_id=mock_problem_id) session.post = lambda path, _: choose_reply( path, { 'problems/': complete_reply( ss, id_=mock_problem_id, type_=problem_type) }) # verify decoding works fut = solver.sample_dqm(dqm) numpy.testing.assert_array_equal(fut.sampleset, ss) numpy.testing.assert_array_equal(fut.problem_type, problem_type)
def test_request_raw_list_with_numpy(self): """Submit a problem using a dict for the linear terms.""" # Connect with Client(**config) as client: assert dwave.cloud.computation._numpy solver = client.get_solver() solver.return_matrix = False # Build a problem linear = {index: random.choice([-1, 1]) for index in solver.nodes} quad = {key: random.choice([-1, 1]) for key in solver.undirected_edges} # Solve the problem self._submit_and_check(solver, linear, quad)
def test_submit_partial_problem(self): """Submit a problem with only some of the terms set.""" with Client(**config) as client: solver = client.get_solver() # Build a linear problem, then remove half the qubits linear, quad = generate_random_ising_problem(solver) nodes = list(solver.nodes) for index in nodes[0:len(nodes)//2]: del linear[index] quad = {key: value for key, value in quad.items() if index not in key} self._submit_and_check(solver, linear, quad)
def setUp(self): # mock solvers self.solver1 = Solver(client=None, data={ "properties": { "supported_problem_types": ["qubo", "ising"], "qubits": [0, 1, 2], "couplers": [[0, 1], [0, 2], [1, 2]], "num_qubits": 3, "parameters": { "num_reads": "Number of samples to return." } }, "id": "solver1", "description": "A test solver 1", "status": "online" }) self.solver2 = Solver(client=None, data={ "properties": { "supported_problem_types": ["qubo", "ising"], "qubits": [0, 1, 2, 3, 4], "couplers": [[0, 1], [0, 2], [1, 2], [2, 3], [3, 4]], "num_qubits": 5, "parameters": { "num_reads": "Number of samples to return.", "flux_biases": "Supported ..." }, "vfyc": True }, "id": "solver2", "description": "A test solver 2" }) self.solvers = [self.solver1, self.solver2] # mock client self.client = Client('endpoint', 'token') self.client._solvers = { self.solver1.id: self.solver1, self.solver2.id: self.solver2 } self.client._all_solvers_ready = True
def test_polling_recovery_after_5xx(self): "Polling shouldn't be aborted on 5xx responses." with Client('endpoint', 'token') as client: client.session = mock.Mock() # on submit, return status pending client.session.post = lambda path, _: choose_reply( path, { 'endpoint/problems/': '[%s]' % continue_reply( '123', 'abc123') }) # on first and second status poll, fail with 503 and 504 # on third status poll, return completed statuses = iter([503, 504]) def continue_then_complete(path, state={'count': 0}): state['count'] += 1 if state['count'] < 3: return choose_reply( path, replies={ 'endpoint/problems/?id=123': '[%s]' % continue_reply('123', 'abc123'), 'endpoint/problems/123/': continue_reply('123', 'abc123') }, statuses={ 'endpoint/problems/?id=123': statuses, 'endpoint/problems/123/': statuses }) else: return choose_reply( path, { 'endpoint/problems/?id=123': '[%s]' % complete_no_answer_reply('123', 'abc123'), 'endpoint/problems/123/': complete_reply('123', 'abc123') }) client.session.get = continue_then_complete solver = Solver(client, solver_data('abc123')) future = solver.sample_qubo({}) future.result() # after third poll, back-off interval should be 4 x initial back-off self.assertEqual(future._poll_backoff, Client._POLL_BACKOFF_MIN * 2**2)
def test_order_by_respects_default_solver(self): """order_by used in isolation should not affect default_solver filters (issue #401)""" with Client('endpoint', 'token', solver=dict(name='qpu2')) as client: # mock the network call to fetch all solvers client._fetch_solvers = lambda **kw: self.solvers # the default solver was set on client init self.assertEqual(client.get_solver(), self.qpu2) # the default solver should not change when we add order_by self.assertEqual(client.get_solver(order_by='id'), self.qpu2) with Client('endpoint', 'token', solver=dict(category='qpu')) as client: # mock the network call to fetch all solvers client._fetch_solvers = lambda **kw: self.solvers # test default order_by is avg_load self.assertEqual(client.get_solver(), self.qpu1) # but we can change it, without affecting solver filters self.assertEqual(client.get_solver(order_by='-avg_load'), self.qpu2)
def test_request_raw_matrix_with_no_numpy(self): """Submit a problem using a dict for the linear terms.""" # Connect with Client(**config) as client: dwave.cloud.computation._numpy = False solver = client.get_solver() solver.return_matrix = True # Build a problem linear = {index: random.choice([-1, 1]) for index in solver.nodes} quad = {key: random.choice([-1, 1]) for key in solver.undirected_edges} # Solve the problem with self.assertRaises(ValueError): self._submit_and_check(solver, linear, quad, answer_mode='raw')
def test_submit_null_reply(self): """Get an error when the server's response is incomplete.""" with Client('endpoint', 'token') as client: client.session = mock.Mock() client.session.post = lambda a, _: choose_reply( a, {'endpoint/problems/': ''}) solver = Solver(client, solver_data('abc123')) # Build a problem linear = {index: 1 for index in solver.nodes} quad = {key: -1 for key in solver.undirected_edges} results = solver.sample_ising(linear, quad, num_reads=100) with self.assertRaises(ValueError): results.samples
def test_get_solver_reproducible(self): """get_solver should return same solver (assuming cache hasn't changed)""" with requests_mock.mock() as m: setup_server(m) # prefer solvers with longer name: that's our second solver defaults = dict(solver=dict(order_by=lambda s: -len(s.id))) with Client(url, token, defaults=defaults) as client: solver = client.get_solver() self.assertEqual(solver.id, solver2_name) solver = client.get_solver() self.assertEqual(solver.id, solver2_name)
def test_exponential_backoff_polling(self): "After each poll, back-off should double" # we need a "global session", because mocked responses are stateful def global_mock_session(): session = mock.Mock() # on submit, return status pending session.post = lambda path, _: choose_reply( path, {'problems/': [self.sapi.continue_reply(id='123')]}) # on first and second status poll, return pending # on third status poll, return completed def continue_then_complete(path, state={'count': 0}): state['count'] += 1 if state['count'] < 3: return choose_reply( path, { 'problems/?id=123': [self.sapi.continue_reply(id='123')], 'problems/123/': self.sapi.continue_reply(id='123') }) else: return choose_reply( path, { 'problems/?id=123': [self.sapi.complete_no_answer_reply(id='123')], 'problems/123/': self.sapi.complete_reply(id='123') }) session.get = continue_then_complete 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) future = solver.sample_qubo({}) future.result() # after third poll, back-off interval should be 4 x initial back-off self.assertEqual(future._poll_backoff, client.poll_backoff_min * 2**2)
def test_submit_cancel_reply(self): """Handle a response for a canceled job.""" with Client('endpoint', 'token') as client: client.session = mock.Mock() client.session.post = lambda a, _: choose_reply( a, {'endpoint/problems/': '[%s]' % cancel_reply('123', 'abc123')}) solver = Solver(client, solver_data('abc123')) # Build a problem linear = {index: 1 for index in solver.nodes} quad = {key: -1 for key in solver.undirected_edges} results = solver.sample_ising(linear, quad, num_reads=100) with self.assertRaises(CanceledFutureError): results.samples
def test_submit_immediate_error_reply(self): """Handle an (obvious) error on problem submission.""" with Client('endpoint', 'token') as client: client.session = mock.Mock() client.session.post = lambda a, _: choose_reply( a, { 'endpoint/problems/': '[%s]' % immediate_error_reply( 400, "Missing parameter 'num_reads' in problem JSON") }) solver = Solver(client, solver_data('abc123')) linear, quad = generate_random_ising_problem(solver) results = solver.sample_ising(linear, quad) with self.assertRaises(SolverFailureError): results.samples
def test_request_raw_matrix_with_numpy(self): """Submit a problem using a dict for the linear terms.""" # Connect with Client(**config) as client: assert dwave.cloud.computation._numpy solver = client.get_solver() solver.return_matrix = True # Build a problem linear = {index: random.choice([-1, 1]) for index in solver.nodes} quad = {key: random.choice([-1, 1]) for key in solver.undirected_edges} # Solve the problem result = self._submit_and_check(solver, linear, quad, answer_mode='raw') self.assertIsInstance(result.samples, numpy.ndarray) self.assertIsInstance(result.energies, numpy.ndarray) self.assertIsInstance(result.occurrences, numpy.ndarray)
def test_submit_bqm_problem(self): """Submit a problem with all supported coefficients set.""" with Client(**config) as client: solver = client.get_solver() linear, quad = generate_random_ising_problem(solver) bqm = dimod.BinaryQuadraticModel.from_ising(linear, quad) results = solver.sample_bqm(bqm, num_reads=100) # Did we get the right number of samples? self.assertEqual(100, sum(results.occurrences)) # Make sure the number of occurrences and energies are all correct for energy, state in zip(results.energies, results.samples): self.assertAlmostEqual(energy, evaluate_ising(linear, quad, state))
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_label_is_sent(self, name, label): """Problem label is set on problem submit.""" with Client('endpoint', 'token') as client: solver = Solver(client, self.sapi.solver.data) problems = self.generate_sample_problems(solver) for method_name, problem_args in problems: with self.subTest(method_name=method_name): sample = getattr(solver, method_name) with mock.patch.object( Client, '_submit', self.on_submit_label_verifier(label)): with self.assertRaises(self.PrimaryAssertionSatisfied): sample(*problem_args, label=label).result()
def test_as_completed(self): """Submit a batch of problems then use `as_completed` to iterate over all of them.""" with Client(**config) as client: solver = client.get_solver() linear, quad = generate_random_ising_problem(solver) # Sample the solution 100x40 times computations = [solver.sample_ising(linear, quad, num_reads=40) for _ in range(100)] # Go over computations, one by one, as they're done and check they're OK for computation in dwave.cloud.computation.Future.as_completed(computations): self.assertTrue(computation.done()) self.assertEqual(40, sum(computation.occurrences)) for energy, state in zip(computation.energies, computation.samples): self.assertAlmostEqual(energy, evaluate_ising(linear, quad, state))