Skip to content

Commit 77392d7

Browse files
authored
Merge pull request #556 from randomir/feature/add-ctx-mgr-support-to-dwavesamplers/issue-91
Add support for context manager protocol to all samplers
2 parents 46efbe5 + 3b86960 commit 77392d7

12 files changed

+233
-142
lines changed

dwave/system/samplers/clique.py

+29-5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
from contextlib import AbstractContextManager
1516
from numbers import Number
1617
from typing import Tuple
1718

@@ -115,7 +116,7 @@ def sample(self, bqm, **parameters):
115116

116117
yield sampleset
117118

118-
class DWaveCliqueSampler(dimod.Sampler):
119+
class DWaveCliqueSampler(dimod.Sampler, AbstractContextManager):
119120
r"""A sampler for solving clique binary quadratic models on the D-Wave system.
120121
121122
This sampler wraps
@@ -159,6 +160,23 @@ class DWaveCliqueSampler(dimod.Sampler):
159160
**config:
160161
Keyword arguments, as accepted by :class:`.DWaveSampler`
161162
163+
.. versionadded:: 1.29.0
164+
Support for context manager protocol.
165+
166+
Note:
167+
The recommended way to use :class:`DWaveCliqueSampler` is from a
168+
`runtime context <https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers>`_:
169+
170+
>>> with DWaveCliqueSampler() as sampler: # doctest: +SKIP
171+
... sampler.sample(...)
172+
173+
Alternatively, call the :meth:`~DWaveCliqueSampler.close` method to
174+
terminate the sampler resources:
175+
176+
>>> sampler = DWaveCliqueSampler() # doctest: +SKIP
177+
...
178+
>>> sampler.close() # doctest: +SKIP
179+
162180
Examples:
163181
This example creates a BQM based on a 6-node clique (complete graph),
164182
with random :math:`\pm 1` values assigned to nodes, and submits it to
@@ -172,11 +190,10 @@ class DWaveCliqueSampler(dimod.Sampler):
172190
...
173191
>>> bqm = dimod.generators.ran_r(1, 6)
174192
...
175-
>>> sampler = DWaveCliqueSampler() # doctest: +SKIP
176-
>>> sampler.largest_clique_size > 5 # doctest: +SKIP
193+
>>> with DWaveCliqueSampler() as sampler: # doctest: +SKIP
194+
... print(sampler.largest_clique_size > 5)
195+
... sampleset = sampler.sample(bqm, num_reads=100)
177196
True
178-
>>> sampleset = sampler.sample(bqm, num_reads=100) # doctest: +SKIP
179-
>>> sampler.close() # doctest: +SKIP
180197
181198
"""
182199
def __init__(self, *,
@@ -197,6 +214,13 @@ def close(self):
197214
"""
198215
self.child.close()
199216

217+
def __exit__(self, exc_type, exc_value, traceback):
218+
"""Release system resources allocated and raise any exception triggered
219+
within the runtime context.
220+
"""
221+
self.close()
222+
return None
223+
200224
@property
201225
def parameters(self) -> dict:
202226
try:

dwave/system/samplers/dwave_sampler.py

+54-33
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import copy
2424
import collections.abc as abc
2525
from collections import defaultdict
26+
from contextlib import AbstractContextManager
2627
from typing import Optional, Dict
2728

2829
import dimod
@@ -86,7 +87,7 @@ def qpu_graph(topology_type, topology_shape, nodelist, edgelist):
8687
return G
8788

8889

89-
class DWaveSampler(dimod.Sampler, dimod.Structured):
90+
class DWaveSampler(dimod.Sampler, dimod.Structured, AbstractContextManager):
9091
"""A class for using D-Wave quantum computers as samplers for binary quadratic models.
9192
9293
You can configure your :term:`solver` selection and usage by setting parameters,
@@ -127,12 +128,29 @@ class DWaveSampler(dimod.Sampler, dimod.Structured):
127128
**config:
128129
Keyword arguments passed to :meth:`~dwave.cloud.client.Client.from_config`.
129130
131+
.. versionadded:: 1.29.0
132+
Support for context manager protocol.
133+
130134
Note:
131135
Prior to version 1.0.0, :class:`.DWaveSampler` used the ``base`` client,
132136
allowing non-QPU solvers to be selected.
133137
To reproduce the old behavior, instantiate :class:`.DWaveSampler` with
134138
``client='base'``.
135139
140+
Note:
141+
The recommended way to use :class:`DWaveSampler` is from a
142+
`runtime context <https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers>`_:
143+
144+
>>> with DWaveSampler() as sampler:
145+
... sampler.sample_ising(...) # doctest: +SKIP
146+
147+
Alternatively, call the :meth:`~DWaveSampler.close` method to
148+
terminate the sampler resources:
149+
150+
>>> sampler = DWaveSampler()
151+
...
152+
>>> sampler.close()
153+
136154
Examples:
137155
This example submits a two-variable Ising problem mapped directly to two
138156
adjacent qubits on a D-Wave system. ``qubit_a`` is the first qubit in
@@ -148,16 +166,14 @@ class DWaveSampler(dimod.Sampler, dimod.Structured):
148166
149167
>>> from dwave.system import DWaveSampler
150168
...
151-
>>> sampler = DWaveSampler()
152-
...
153-
>>> qubit_a = sampler.nodelist[0]
154-
>>> qubit_b = next(iter(sampler.adjacency[qubit_a]))
155-
>>> sampleset = sampler.sample_ising({qubit_a: -1, qubit_b: 1},
156-
... {},
157-
... num_reads=100)
158-
>>> print(sampleset.first.sample[qubit_a] == 1 and sampleset.first.sample[qubit_b] == -1)
169+
>>> with DWaveSampler() as sampler:
170+
... qubit_a = sampler.nodelist[0]
171+
... qubit_b = next(iter(sampler.adjacency[qubit_a]))
172+
... sampleset = sampler.sample_ising({qubit_a: -1, qubit_b: 1},
173+
... {},
174+
... num_reads=100)
175+
... print(sampleset.first.sample[qubit_a] == 1 and sampleset.first.sample[qubit_b] == -1)
159176
True
160-
>>> sampler.close()
161177
162178
See `Ocean Glossary <https://docs.ocean.dwavesys.com/en/stable/concepts/index.html>`_
163179
for explanations of technical terms in descriptions of Ocean tools.
@@ -194,6 +210,13 @@ def close(self):
194210
"""
195211
self.client.close()
196212

213+
def __exit__(self, exc_type, exc_value, traceback):
214+
"""Release system resources allocated and raise any exception triggered
215+
within the runtime context.
216+
"""
217+
self.close()
218+
return None
219+
197220
def _get_solver(self, *, refresh: bool = False, penalty: Optional[Dict[str, int]] = None):
198221
"""Get the least penalized solver from the list of solvers filtered and
199222
ordered according to user config.
@@ -234,8 +257,8 @@ def properties(self):
234257
Examples:
235258
236259
>>> from dwave.system import DWaveSampler
237-
>>> sampler = DWaveSampler()
238-
>>> sampler.properties # doctest: +SKIP
260+
>>> with DWaveSampler() as sampler: # doctest: +SKIP
261+
... sampler.properties
239262
{'anneal_offset_ranges': [[-0.2197463755538704, 0.03821687759418928],
240263
[-0.2242514597680286, 0.01718456460967399],
241264
[-0.20860153999435985, 0.05511969218508182],
@@ -265,8 +288,8 @@ def parameters(self):
265288
Examples:
266289
267290
>>> from dwave.system import DWaveSampler
268-
>>> sampler = DWaveSampler()
269-
>>> sampler.parameters # doctest: +SKIP
291+
>>> with DWaveSampler() as sampler: # doctest: +SKIP
292+
... sampler.parameters
270293
{'anneal_offsets': ['parameters'],
271294
'anneal_schedule': ['parameters'],
272295
'annealing_time': ['parameters'],
@@ -296,8 +319,8 @@ def edgelist(self):
296319
First 5 entries of the coupler list for one Advantage system.
297320
298321
>>> from dwave.system import DWaveSampler
299-
>>> sampler = DWaveSampler()
300-
>>> sampler.edgelist[:5] # doctest: +SKIP
322+
>>> with DWaveSampler() as sampler: # doctest: +SKIP
323+
... sampler.edgelist[:5]
301324
[(30, 31), (30, 45), (30, 2940), (30, 2955), (30, 2970)]
302325
303326
See `Ocean Glossary <https://docs.ocean.dwavesys.com/en/stable/concepts/index.html>`_
@@ -320,8 +343,8 @@ def nodelist(self):
320343
First 5 entries of the node list for one Advantage system.
321344
322345
>>> from dwave.system import DWaveSampler
323-
>>> sampler = DWaveSampler()
324-
>>> sampler.nodelist[:5] # doctest: +SKIP
346+
>>> with DWaveSampler() as sampler: # doctest: +SKIP
347+
... sampler.nodelist[:5]
325348
[30, 31, 32, 33, 34]
326349
327350
See `Ocean Glossary <https://docs.ocean.dwavesys.com/en/stable/concepts/index.html>`_
@@ -403,14 +426,13 @@ def sample(self, bqm, warnings=None, **kwargs):
403426
404427
>>> from dwave.system import DWaveSampler
405428
...
406-
>>> sampler = DWaveSampler()
407-
...
408-
>>> qubit_a = sampler.nodelist[0]
409-
>>> qubit_b = next(iter(sampler.adjacency[qubit_a]))
410-
>>> sampleset = sampler.sample_ising({qubit_a: -1, qubit_b: 1},
411-
... {},
412-
... num_reads=100)
413-
>>> print(sampleset.first.sample[qubit_a] == 1 and sampleset.first.sample[qubit_b] == -1)
429+
>>> with DWaveSampler() as sampler:
430+
... qubit_a = sampler.nodelist[0]
431+
... qubit_b = next(iter(sampler.adjacency[qubit_a]))
432+
... sampleset = sampler.sample_ising({qubit_a: -1, qubit_b: 1},
433+
... {},
434+
... num_reads=100)
435+
... print(sampleset.first.sample[qubit_a] == 1 and sampleset.first.sample[qubit_b] == -1)
414436
True
415437
416438
See `Ocean Glossary <https://docs.ocean.dwavesys.com/en/stable/concepts/index.html>`_
@@ -523,10 +545,9 @@ def validate_anneal_schedule(self, anneal_schedule):
523545
This example sets a quench schedule on a D-Wave system.
524546
525547
>>> from dwave.system import DWaveSampler
526-
>>> sampler = DWaveSampler()
527-
>>> quench_schedule=[[0.0, 0.0], [12.0, 0.6], [12.8, 1.0]]
528-
>>> DWaveSampler().validate_anneal_schedule(quench_schedule) # doctest: +SKIP
529-
>>>
548+
>>> with DWaveSampler() as sampler: # doctest: +SKIP
549+
... quench_schedule=[[0.0, 0.0], [12.0, 0.6], [12.8, 1.0]]
550+
... DWaveSampler().validate_anneal_schedule(quench_schedule)
530551
531552
"""
532553
if 'anneal_schedule' not in self.parameters:
@@ -597,9 +618,9 @@ def to_networkx_graph(self):
597618
598619
>>> from dwave.system import DWaveSampler
599620
...
600-
>>> sampler = DWaveSampler()
601-
>>> g = sampler.to_networkx_graph() # doctest: +SKIP
602-
>>> len(g.nodes) > 5000 # doctest: +SKIP
621+
>>> with DWaveSampler() as sampler: # doctest: +SKIP
622+
... g = sampler.to_networkx_graph()
623+
... len(g.nodes) > 5000
603624
True
604625
"""
605626
return qpu_graph(self.properties['topology']['type'],

0 commit comments

Comments
 (0)