Skip to content

Commit

Permalink
Merge pull request #444 from randomir/feature/problem-label
Browse files Browse the repository at this point in the history
Add problem labelling support
  • Loading branch information
randomir committed Jan 13, 2021
2 parents febc4fe + a483f03 commit 9f707b1
Show file tree
Hide file tree
Showing 7 changed files with 292 additions and 51 deletions.
1 change: 1 addition & 0 deletions dwave/cloud/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1286,6 +1286,7 @@ def _handle_problem_status(self, message, future):
raise InvalidAPIResponseError("'id' missing in problem description response")

future.id = message['id']
future.label = message.get('label')
future.remote_status = status = message['status']

# The future may not have the ID set yet
Expand Down
7 changes: 6 additions & 1 deletion dwave/cloud/computation.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ def __init__(self, solver, id_, return_matrix=False):
#: The id the server will use to identify this problem, None until the id is actually known
self.id = id_

#: Problem label, as (optionally) set on submission. None until parsed from a response.
self.label = None

#: `datetime` the Future was created (immediately before enqueued in Client's submit queue)
self.time_created = utcnow()

Expand Down Expand Up @@ -789,8 +792,10 @@ def wait_sampleset(self):
vartype_from_problem_type = {'ising': 'SPIN', 'qubo': 'BINARY'}
vartype = vartype_from_problem_type[self.problem_type]

# include timing and id in info
# include timing and id/label in info
info = dict(timing=self.timing, problem_id=self.id)
if self.label is not None:
info.update(problem_label=self.label)

sampleset = dimod.SampleSet.from_samples(
(samples, variables), vartype=vartype,
Expand Down
123 changes: 85 additions & 38 deletions dwave/cloud/solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def _retrieve_problem(self, id_):
id_: Identification of the query.
Returns:
:obj: `Future`
:class:`~dwave.cloud.computation.Future`
"""
future = Future(self, id_, self.return_matrix)
self.client._poll(future)
Expand Down Expand Up @@ -257,7 +257,7 @@ class BaseUnstructuredSolver(BaseSolver):
Events are not yet dispatched from unstructured solvers.
"""

def sample_ising(self, linear, quadratic, offset=0, **params):
def sample_ising(self, linear, quadratic, offset=0, label=None, **params):
"""Sample from the specified :term:`Ising` model.
Args:
Expand All @@ -275,11 +275,15 @@ def sample_ising(self, linear, quadratic, offset=0, **params):
offset (optional, default=0):
Constant offset applied to the model.
label (str, optional):
Problem label you can optionally tag submissions with for ease
of identification.
**params:
Parameters for the sampling method, solver-specific.
Returns:
:class:`Future`
:class:`~dwave.cloud.computation.Future`
Note:
To use this method, dimod package has to be installed.
Expand All @@ -291,9 +295,9 @@ def sample_ising(self, linear, quadratic, offset=0, **params):
"Re-install the library with 'bqm'/'dqm' support.")

bqm = dimod.BinaryQuadraticModel.from_ising(linear, quadratic, offset)
return self.sample_bqm(bqm, **params)
return self.sample_bqm(bqm, label=label, **params)

def sample_qubo(self, qubo, offset=0, **params):
def sample_qubo(self, qubo, offset=0, label=None, **params):
"""Sample from the specified :term:`QUBO`.
Args:
Expand All @@ -306,11 +310,15 @@ def sample_qubo(self, qubo, offset=0, **params):
offset (optional, default=0):
Constant offset applied to the model.
label (str, optional):
Problem label you can optionally tag submissions with for ease
of identification.
**params:
Parameters for the sampling method, solver-specific.
Returns:
:class:`Future`
:class:`~dwave.cloud.computation.Future`
Note:
To use this method, dimod package has to be installed.
Expand All @@ -322,7 +330,7 @@ def sample_qubo(self, qubo, offset=0, **params):
"Re-install the library with 'bqm'/'dqm' support.")

bqm = dimod.BinaryQuadraticModel.from_qubo(qubo, offset)
return self.sample_bqm(bqm, **params)
return self.sample_bqm(bqm, label=label, **params)

def _encode_problem_for_upload(self, problem):
"""Encode problem for upload to solver.
Expand Down Expand Up @@ -353,7 +361,8 @@ def upload_problem(self, problem):
data = self._encode_problem_for_upload(problem)
return self.client.upload_problem_encoded(data)

def _encode_problem_for_submission(self, problem, problem_type, params):
def _encode_problem_for_submission(self, problem, problem_type, params,
label=None):
"""Encode `problem` for submitting in `ref` format. Upload the
problem if it's not already uploaded.
Expand All @@ -367,6 +376,9 @@ def _encode_problem_for_submission(self, problem, problem_type, params):
params (dict):
Parameters for the sampling method, solver-specific.
label (str, optional):
Problem label.
Returns:
str:
JSON-encoded problem submit body
Expand All @@ -379,17 +391,20 @@ def _encode_problem_for_submission(self, problem, problem_type, params):
"we need to upload it first.")
problem_id = self.upload_problem(problem).result()

body = json.dumps({
body = {
'solver': self.id,
'data': encode_problem_as_ref(problem_id),
'type': problem_type,
'params': params
})
logger.trace("Sampling request encoded as: %s", body)
}
if label is not None:
body['label'] = label
body_data = json.dumps(body)
logger.trace("Sampling request encoded as: %s", body_data)

return body
return body_data

def sample_problem(self, problem, problem_type=None, **params):
def sample_problem(self, problem, problem_type=None, label=None, **params):
"""Sample from the specified problem.
Args:
Expand All @@ -402,6 +417,10 @@ def sample_problem(self, problem, problem_type=None, **params):
Problem type, one of the handled problem types by the solver.
If not specified, the first handled problem type is used.
label (str, optional):
Problem label you can optionally tag submissions with for ease
of identification.
**params:
Parameters for the sampling method, solver-specific.
Expand All @@ -416,7 +435,8 @@ def sample_problem(self, problem, problem_type=None, **params):
# encode the request (body as future)
body = self.client._encode_problem_executor.submit(
self._encode_problem_for_submission,
problem=problem, problem_type=problem_type, params=params)
problem=problem, problem_type=problem_type, params=params,
label=label)

# computation future holds a reference to the remote job
computation = Future(
Expand Down Expand Up @@ -460,14 +480,18 @@ def _encode_problem_for_upload(self, bqm):

return data

def sample_bqm(self, bqm, **params):
def sample_bqm(self, bqm, label=None, **params):
"""Sample from the specified :term:`BQM`.
Args:
bqm (:class:`~dimod.BinaryQuadraticModel`/str):
A binary quadratic model, or a reference to one
(Problem ID returned by `.upload_bqm` method).
label (str, optional):
Problem label you can optionally tag submissions with for ease
of identification.
**params:
Parameters for the sampling method, solver-specific.
Expand All @@ -477,7 +501,7 @@ def sample_bqm(self, bqm, **params):
Note:
To use this method, dimod package has to be installed.
"""
return self.sample_problem(bqm, **params)
return self.sample_problem(bqm, label=label, **params)

def upload_bqm(self, bqm):
"""Upload the specified :term:`BQM` to SAPI, returning a Problem ID
Expand Down Expand Up @@ -535,23 +559,27 @@ def _encode_problem_for_upload(self, dqm):
logger.debug("Problem encoded for upload: %r", data)
return data

def sample_bqm(self, bqm, **params):
def sample_bqm(self, bqm, label=None, **params):
from dwave.cloud.utils import bqm_to_dqm

# to sample BQM problems, we need to convert them to DQM
dqm = bqm_to_dqm(bqm)

# TODO: convert sampleset back
return self.sample_dqm(dqm, **params)
return self.sample_dqm(dqm, label=label, **params)

def sample_dqm(self, dqm, **params):
def sample_dqm(self, dqm, label=None, **params):
"""Sample from the specified :term:`DQM`.
Args:
dqm (:class:`~dimod.DiscreteQuadraticModel`/str):
A discrete quadratic model, or a reference to one
(Problem ID returned by `.upload_dqm` method).
label (str, optional):
Problem label you can optionally tag submissions with for ease
of identification.
**params:
Parameters for the sampling method, solver-specific.
Expand All @@ -561,7 +589,7 @@ def sample_dqm(self, dqm, **params):
Note:
To use this method, dimod package has to be installed.
"""
return self.sample_problem(dqm, **params)
return self.sample_problem(dqm, label=label, **params)

def upload_dqm(self, dqm):
"""Upload the specified :term:`DQM` to SAPI, returning a Problem ID
Expand Down Expand Up @@ -710,7 +738,7 @@ def max_num_reads(self, **params):

# Sampling methods

def sample_ising(self, linear, quadratic, offset=0, **params):
def sample_ising(self, linear, quadratic, offset=0, label=None, **params):
"""Sample from the specified :term:`Ising` model.
Args:
Expand All @@ -725,14 +753,18 @@ def sample_ising(self, linear, quadratic, offset=0, **params):
that are 2-tuples of variables and values are quadratic biases
associated with the pair of variables (the interaction).
offset (optional, default=0):
offset (float, optional, default=0):
Constant offset applied to the model.
label (str, optional):
Problem label you can optionally tag submissions with for ease
of identification.
**params:
Parameters for the sampling method, solver-specific.
Returns:
:class:`Future`
:class:`~dwave.cloud.computation.Future`
Examples:
This example creates a client using the local system's default D-Wave Cloud Client
Expand All @@ -758,9 +790,9 @@ def sample_ising(self, linear, quadratic, offset=0, **params):
"""
# Our linear and quadratic objective terms are already separated in an
# ising model so we can just directly call `_sample`.
return self._sample('ising', linear, quadratic, offset, params)
return self._sample('ising', linear, quadratic, offset, params, label=label)

def sample_qubo(self, qubo, offset=0, **params):
def sample_qubo(self, qubo, offset=0, label=None, **params):
"""Sample from the specified :term:`QUBO`.
Args:
Expand All @@ -773,11 +805,15 @@ def sample_qubo(self, qubo, offset=0, **params):
offset (optional, default=0):
Constant offset applied to the model.
label (str, optional):
Problem label you can optionally tag submissions with for ease
of identification.
**params:
Parameters for the sampling method, solver-specific.
Returns:
:class:`Future`
:class:`~dwave.cloud.computation.Future`
Examples:
This example creates a client using the local system's default D-Wave Cloud Client
Expand All @@ -803,20 +839,24 @@ def sample_qubo(self, qubo, offset=0, **params):
"""
linear, quadratic = reformat_qubo_as_ising(qubo)
return self._sample('qubo', linear, quadratic, offset, params)
return self._sample('qubo', linear, quadratic, offset, params, label=label)

def sample_bqm(self, bqm, **params):
def sample_bqm(self, bqm, label=None, **params):
"""Sample from the specified :term:`BQM`.
Args:
bqm (:class:`~dimod.BinaryQuadraticModel`):
A binary quadratic model.
label (str, optional):
Problem label you can optionally tag submissions with for ease
of identification.
**params:
Parameters for the sampling method, solver-specific.
Returns:
:class:`Future`
:class:`~dwave.cloud.computation.Future`
Note:
To use this method, dimod package has to be installed.
Expand All @@ -828,16 +868,17 @@ def sample_bqm(self, bqm, **params):
"Re-install the library with 'bqm' support.")

if bqm.vartype is dimod.SPIN:
return self._sample('ising', bqm.linear, bqm.quadratic, bqm.offset,
params, undirected_biases=True)
problem_type = 'ising'
elif bqm.vartype is dimod.BINARY:
return self._sample('qubo', bqm.linear, bqm.quadratic, bqm.offset,
params, undirected_biases=True)
problem_type = 'qubo'
else:
raise TypeError("unknown/unsupported vartype")

return self._sample(problem_type, bqm.linear, bqm.quadratic, bqm.offset,
params, label=label, undirected_biases=True)

def _sample(self, type_, linear, quadratic, offset, params,
undirected_biases=False):
label=None, undirected_biases=False):
"""Internal method for `sample_ising`, `sample_qubo` and `sample_bqm`.
Args:
Expand All @@ -853,17 +894,20 @@ def _sample(self, type_, linear, quadratic, offset, params,
params (dict):
Parameters for the sampling method, solver-specific.
label (str, optional):
Problem label.
undirected_biases (boolean, default=False):
Are (quadratic) biases specified on undirected edges? For
triangular or symmetric matrix of quadratic biases set it to
``True``.
Returns:
:class:`Future`
:class:`~dwave.cloud.computation.Future`
"""

args = dict(type_=type_, linear=linear, quadratic=quadratic,
offset=offset, params=params,
offset=offset, params=params, label=label,
undirected_biases=undirected_biases)
dispatch_event('before_sample', obj=self, args=args)

Expand All @@ -883,13 +927,16 @@ def _sample(self, type_, linear, quadratic, offset, params,
# transform some of the parameters in-place
self._format_params(type_, combined_params)

body_data = json.dumps({
body_dict = {
'solver': self.id,
'data': encode_problem_as_qp(self, linear, quadratic, offset,
undirected_biases=undirected_biases),
'type': type_,
'params': combined_params
})
}
if label is not None:
body_dict['label'] = label
body_data = json.dumps(body_dict)
logger.trace("Encoded sample request: %s", body_data)

body = Present(result=body_data)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ def handler(event, **data):
before = memo['before_sample']
args = dict(type_='ising', linear=lin, quadratic=quad,
offset=offset, params=params,
undirected_biases=False)
undirected_biases=False, label=None)
self.assertEqual(before['obj'], self.solver)
self.assertDictEqual(before['args'], args)

Expand Down

0 comments on commit 9f707b1

Please sign in to comment.