Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add problem labelling support #444

Merged
merged 6 commits into from
Jan 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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