Skip to content

Commit 2e187a4

Browse files
authored
Merge pull request #1399 from randomir/feature/closeable-sampler-composite
Add scoped resource support
2 parents a5a5aea + 9cbce7d commit 2e187a4

16 files changed

Lines changed: 309 additions & 25 deletions

File tree

dimod/core/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@
1616
from dimod.core.initialized import Initialized
1717
from dimod.core.polysampler import PolySampler, ComposedPolySampler
1818
from dimod.core.sampler import Sampler
19+
from dimod.core.scoped import Scoped
1920
from dimod.core.structured import Structured

dimod/core/composite.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,26 @@
4848
import abc
4949

5050
from dimod.core.sampler import Sampler
51+
from dimod.core.scoped import Scoped
5152

5253
__all__ = ['Composite', 'ComposedSampler']
5354

5455

55-
class Composite(abc.ABC):
56+
class Composite(Scoped):
5657
"""Abstract base class for dimod composites.
5758
5859
Provides the :attr:`Composite.child` mixin property and defines the :attr:`Composite.children`
5960
abstract property to be implemented. These define the supported samplers for the composed sampler.
6061
62+
:class:`.Composite` implements the :class:`~dimod.core.scoped.Scoped` interface, and
63+
provides :meth:`.close` method that closes all child samplers or composites.
64+
65+
.. versionchanged:: 0.12.19
66+
Implemented the :class:`~dimod.core.scoped.Scoped` interface. Now all
67+
composites support context manager protocol and release scope-based
68+
resources of sub-samplers/composites by default.
6169
"""
70+
6271
@abc.abstractproperty
6372
def children(self):
6473
"""list[ :obj:`.Sampler`]: List of child samplers that that are used by
@@ -74,6 +83,29 @@ def child(self):
7483
except IndexError:
7584
raise RuntimeError("A Composite must have at least one child Sampler")
7685

86+
def close(self):
87+
"""Release any scope-bound resources of child samplers or composites.
88+
89+
.. note::
90+
If a :class:`.Composite` subclass doesn't allocate resources that have
91+
to be explicitly released, there's no need to override the default
92+
:meth:`~.Composite.close` implementation.
93+
94+
However, if you do implement :meth:`~.Composite.close` on a subclass,
95+
make sure to either call ``super().close()``, or to explicitly close
96+
all child samplers/composites.
97+
98+
.. versionadded:: 0.12.19
99+
:class:`.Composite` now implements the :class:`~dimod.core.scoped.Scoped`
100+
interface. The default :meth:`~.Composite.close` method recursively closes
101+
composite's children.
102+
"""
103+
104+
for child in self.children:
105+
if hasattr(child, 'close') and callable(child.close):
106+
child.close()
107+
super().close()
108+
77109

78110
class ComposedSampler(Sampler, Composite):
79111
"""Abstract base class for dimod composed samplers.

dimod/core/initialized.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
"""
2222
import abc
2323

24-
from collections import namedtuple
2524
from numbers import Integral
2625

2726
import typing

dimod/core/polysampler.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@
2020

2121
import abc
2222
import collections.abc
23-
import warnings
2423

2524
from dimod.core.composite import Composite
25+
from dimod.core.scoped import Scoped
2626
from dimod.higherorder.polynomial import BinaryPolynomial
2727
from dimod.sampleset import SampleSet
2828

@@ -31,12 +31,16 @@
3131
__all__ = 'PolySampler', 'ComposedPolySampler'
3232

3333

34-
class PolySampler(abc.ABC):
34+
class PolySampler(Scoped):
3535
"""Sampler that supports binary polynomials.
3636
3737
Binary polynomials are an extension of binary quadratic models that allow
3838
higher-order interactions.
3939
40+
.. versionchanged:: 0.12.19
41+
:class:`.PolySampler` now implements the :class:`~dimod.core.scoped.Scoped`
42+
interface, so it supports context manager protocol by default.
43+
4044
"""
4145
@abc.abstractproperty # for python2 compatibility
4246
def parameters(self):
@@ -105,6 +109,13 @@ def sample_hubo(self, H: collections.abc.Mapping[tuple[Variable, Variable], Bias
105109
"""
106110
return self.sample_poly(BinaryPolynomial.from_hubo(H), **kwargs)
107111

112+
def close(self):
113+
"""Release allocated resources.
114+
115+
Override to release sampler-allocated resources.
116+
"""
117+
pass
118+
108119

109120
class ComposedPolySampler(PolySampler, Composite):
110121
"""Abstract base class for dimod composed polynomial samplers.

dimod/core/sampler.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ def parameters(self):
112112
import warnings
113113

114114
from dimod.binary_quadratic_model import BinaryQuadraticModel
115+
from dimod.core.scoped import Scoped
115116
from dimod.exceptions import InvalidSampler, SamplerUnknownArgWarning
116117
from dimod.sampleset import SampleSet
117118
from dimod.typing import Bias, Variable
@@ -164,14 +165,23 @@ def samplemixinmethod(method):
164165
return method
165166

166167

167-
class Sampler(metaclass=SamplerABCMeta):
168+
class Sampler(Scoped, metaclass=SamplerABCMeta):
168169
"""Abstract base class for dimod samplers.
169170
170171
Provides all methods :meth:`~.Sampler.sample`, :meth:`~.Sampler.sample_ising`,
171172
:meth:`~.Sampler.sample_qubo` assuming at least one is implemented.
172173
173174
Also includes utility method :meth:`~.Sampler.remove_unknown_kwargs`,
174175
which may be used in sample methods to handle unknown kwargs.
176+
177+
:class:`.Sampler` implements the :class:`~dimod.core.scoped.Scoped` interface,
178+
but the default implementation of :meth:`~.Sampler.close` does nothing. It
179+
should be overridden to release any scope-based sampler resources.
180+
181+
.. versionchanged:: 0.12.19
182+
:class:`.Sampler` now implements the :class:`~dimod.core.scoped.Scoped`
183+
interface, meaning all samplers support context manager protocol by
184+
default.
175185
"""
176186

177187
@abc.abstractproperty # for python2 compatibility
@@ -317,3 +327,10 @@ def remove_unknown_kwargs(self, **kwargs) -> dict[str, typing.Any]:
317327
kwargs.pop(kw)
318328

319329
return kwargs
330+
331+
def close(self):
332+
"""Release allocated resources.
333+
334+
Override to release sampler-allocated resources.
335+
"""
336+
pass

dimod/core/scoped.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Copyright 2025 D-Wave
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import abc
16+
17+
__all__ = ['Scoped']
18+
19+
20+
class Scoped(abc.ABC):
21+
"""Abstract base class for components that allocate scope-bound resources.
22+
23+
:class:`.Scoped` requires the concrete implementation to provide resources
24+
clean-up via :meth:`~.Scoped.close` method. With that, context manager
25+
protocol is automatically supported (resources are released on context exit).
26+
27+
The scoped resource should either be used from a `runtime context`_, e.g.:
28+
29+
>>> with SomeScopedSampler() as sampler: # doctest: +SKIP
30+
... sampler.sample(...)
31+
32+
or explicitly disposed with a call to the :meth:`.close` method.
33+
34+
.. _runtime context: https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers
35+
36+
.. versionadded:: 0.12.19
37+
:class:`.Scoped` abstract base class.
38+
39+
Both :class:`~dimod.code.sampler.Sampler` and :class:`~dimod.core.composite.Composite`
40+
implement :class:`.Scoped` interface.
41+
"""
42+
43+
@abc.abstractmethod
44+
def close(self):
45+
"""Release allocated system resources that are hard or impossible to
46+
garbage-collect.
47+
"""
48+
pass
49+
50+
def __enter__(self):
51+
"""Return `self` upon entering the runtime context."""
52+
return self
53+
54+
def __exit__(self, exc_type, exc_value, traceback):
55+
"""Release system resources allocated and raise any exception triggered
56+
within the runtime context.
57+
"""
58+
self.close()
59+
# raise exceptions from the runtime context as they're not handled here
60+
return None

dimod/core/structured.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,6 @@ def sample(self, bqm):
7272
"""
7373
from __future__ import annotations
7474

75-
from collections import namedtuple
76-
7775
import abc
7876
import typing
7977

dimod/discrete/discrete_quadratic_model.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,12 @@
1414

1515
import collections.abc as abc
1616
import io
17-
import json
1817
import warnings
1918

2019
from collections import defaultdict, namedtuple
2120
from typing import List, Tuple, Union, Generator, Iterator
2221

2322
import numpy as np
24-
from numpy.core.shape_base import stack
2523

2624
from dimod.discrete.cydiscrete_quadratic_model import cyDiscreteQuadraticModel
2725
from dimod.sampleset import as_samples

dimod/quadratic/quadratic_model.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
from __future__ import annotations
3131

3232
import collections.abc
33-
import struct
3433
import tempfile
3534
import typing
3635

dimod/reference/composites/higherordercomposites.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,14 @@
2424
* :meth:`.PolySampler.sample_hubo`
2525
2626
"""
27-
import warnings
2827

2928
from collections import defaultdict
3029

3130
import numpy as np
3231

3332
from dimod.core.polysampler import ComposedPolySampler, PolySampler
3433
from dimod.higherorder.polynomial import BinaryPolynomial
35-
from dimod.higherorder.utils import make_quadratic, poly_energies
34+
from dimod.higherorder.utils import make_quadratic
3635
from dimod.sampleset import SampleSet, append_variables
3736

3837

0 commit comments

Comments
 (0)