Skip to content

Feature expansion and bug-fix #537

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

Merged
merged 43 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
7493ffa
WIP - added the option to switch sampler and relevant testing codes
Sep 18, 2024
b038055
removed the flux biases code
Sep 18, 2024
890c0cd
changed the initialization of mocking sampler from init to body
Sep 18, 2024
ec11dd1
simplified substitude_kwargs
Sep 19, 2024
cba4278
Fix num_qubits bug; raise mocked_parameters and dimod_sampler to clas…
jackraymond Sep 19, 2024
3ff3b2f
Add missing property. Make substitute_kwargs a class variable.
jackraymond Sep 19, 2024
be73d79
Minor corrections
jackraymond Sep 19, 2024
1a7d1a8
Revert change to support virtual graph composite
jackraymond Sep 19, 2024
4a22d49
num_qubits for pegasus fixed
jackraymond Sep 19, 2024
95907a8
moved class attributes to instance attributes
Sep 19, 2024
89ade5f
updated tests for mock dwave sampler
Sep 19, 2024
024ea98
Revert "updated tests for mock dwave sampler"
Sep 19, 2024
b773d54
created a local dictionary substitue_kwargs to ensure each instance o…
Sep 19, 2024
e0b2c7f
merge changes from bugfix
Sep 19, 2024
d7e6da0
updated gitignore
Sep 19, 2024
1fbf954
Update BQM definition to simplify variable weights
AndyZzzZzzZzz Sep 20, 2024
3dd1ce4
Replace single-read sample with num_reads=2 in MockDWaveSampler test
AndyZzzZzzZzz Sep 20, 2024
6e4e5cb
Update test to validate second sample state in MockDWaveSampler
AndyZzzZzzZzz Sep 20, 2024
466ef49
Remove redundant energy check in MockDWaveSampler test
AndyZzzZzzZzz Sep 20, 2024
7009297
Removed redundant comments
Sep 20, 2024
266297c
Polished formatting and removed changes in .gitignore
Sep 20, 2024
03fe17d
Renamed CustomSampler to ConstantSampler in test cases
Sep 20, 2024
32f5676
Updated documentation of MockDWaveSampler
Sep 20, 2024
2675f31
Bugfix: substitute sampler not working as expected
Sep 23, 2024
3edc2e1
Removed files
Sep 25, 2024
300fb99
Changed shortcircuit to None identity check
Sep 25, 2024
0dac9f3
Modified None identity check slightly
Sep 25, 2024
b0765b0
Add ss.info.update
Sep 25, 2024
b04b494
Added documentation for new parameters
Sep 25, 2024
ae705b2
Move comments to documentation
Sep 26, 2024
0389b60
Update exact_solver_cutoff along with its documentation
Sep 27, 2024
0bacfe1
Correct errors related to misnaming subtitute_* as mock_*
jackraymond Oct 7, 2024
74997f9
Delete duplicate file accidentally pushed
jackraymond Oct 7, 2024
6f3ecd9
Add note on return of ascent sampler
kevinchern Oct 7, 2024
5600592
Update dwave/system/testing.py
AndyZzzZzzZzz Oct 10, 2024
894fcd9
Update dwave/system/testing.py
AndyZzzZzzZzz Oct 10, 2024
c46359b
Update dwave/system/testing.py
AndyZzzZzzZzz Oct 10, 2024
de76c22
Fixed indentation
Oct 10, 2024
b40d778
Updated comment
Oct 10, 2024
2fce4d4
Added subtests in TestMockSampler
Oct 10, 2024
b3b728c
Modified comments
Oct 10, 2024
8eaf232
Minor adjustment for assert statement
Oct 10, 2024
a064358
Added test for kwargs overwrite in TestMockDWaveSampler
Oct 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,4 @@ ENV/
*.sublime-project
*.sublime-workspace

generated/
generated/
89 changes: 64 additions & 25 deletions dwave/system/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,15 @@
class MockDWaveSampler(dimod.Sampler, dimod.Structured):
"""Mock sampler modeled after DWaveSampler that can be used for tests.

Properties and topology parameters are populated qualitatively matching
Properties and topology parameters are populated to qualitatively match
online systems, and a placeholder sampler routine based on steepest descent
is instantiated.
is instantiated by default.

The `EXACT_SOLVER_CUTOFF_DEFAULT` defines the problem size threshold for using the exact solver.
For problems with fewer variables than this threshold, the exact ground state is computed
using a brute-force solver. This provides a reproducible solution for small problem sizes.

For larger problems, the `SteepestDescentSampler` is used as a placeholder solver.

Args:
nodelist (iterable of ints, optional):
Expand Down Expand Up @@ -73,6 +79,20 @@ class MockDWaveSampler(dimod.Sampler, dimod.Structured):
parameters. By default ``initial_state`` can also be mocked, if
dwave-greedy is installed. All other parameters are ignored and a
warning will be raised by default.

substitute_sampler (dimod.Sampler, optional, default=SteepestDescentSampler()):
The sampler to be used as a substitute when executing the mock sampler.
By default, `SteepestDescentSampler()` is employed, which performs a
deterministic steepest descent optimization on the BQM. Supported options are
any dimod-compatible sampler to customize the sampling behavior of
`MockDWaveSampler()`.

substitute_kwargs (dict, optional, default=[]):
A dictionary of keyword arguments to pass to the `substitute_sampler`'s
`sample` method. This allows users to configure the substitute sampler
with specific parameters like `num_reads`, `initial_state`, or other
sampler-specific options. If not provided, an empty dictionary is used
by default.

exact_solver_cutoff (int, optional, default=:attr:`EXACT_SOLVER_CUTOFF_DEFAULT`):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, it would make sense that if substitute_sampler is provided then this setting is ignored and the substitute sampler is applied for all samples. We can add this clarification (afer code modification).

For problems smaller or equal in size to ``exact_solver_cutoff``, the
Expand Down Expand Up @@ -108,23 +128,38 @@ class MockDWaveSampler(dimod.Sampler, dimod.Structured):
-1

"""
# Feature suggestion - add seed as an optional input, to allow reproducibility.

nodelist = None
edgelist = None
properties = None
parameters = None

# by default, use ExactSolver for problems up to size (inclusive):

EXACT_SOLVER_CUTOFF_DEFAULT = 16

def __init__(self,
nodelist=None, edgelist=None, properties=None,
broken_nodes=None, broken_edges=None,
topology_type=None, topology_shape=None,
parameter_warnings=True,
substitute_sampler=None,
substitute_kwargs=None,
exact_solver_cutoff=EXACT_SOLVER_CUTOFF_DEFAULT,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change this to None. See also comment on docstrings

if substitute_sampler is None:
          substitute_sampler = SteepestDescentSampler()
          if exact_solver_cutoff is None:
              exact_solver_cutoff = EXACT_SOLVER_CUTOFF_DEFAULT
  elif exact_solver_cutoff is None:
     exact_solver_cutoff = 0  # Always use substitute_sampler, never ExactSolver()

**config):

self.mocked_parameters={'answer_mode',
'max_answers',
'num_reads',
'label',
'initial_state',
}

if substitute_sampler is None:
substitute_sampler = SteepestDescentSampler()
self.substitute_sampler = substitute_sampler

if substitute_kwargs is None:
substitute_kwargs = {}
self.substitute_kwargs = substitute_kwargs

self.parameter_warnings = parameter_warnings
self.exact_solver_cutoff = exact_solver_cutoff

Expand Down Expand Up @@ -158,7 +193,6 @@ def __init__(self,
'chip_id': 'MockDWaveSampler',
'topology': {'type': topology_type, 'shape': topology_shape}
}

#Create graph object, introduce defects per input arguments
if nodelist is not None:
self.nodelist = nodelist.copy()
Expand All @@ -171,6 +205,11 @@ def __init__(self,
self.properties['topology']['shape'],
self.nodelist, self.edgelist)

if topology_type == 'pegasus':
m = self.properties['topology']['shape'][0]
num_qubits = m*(m-1)*24 # fabric_only=True technicality
else:
num_qubits = len(solver_graph)
if broken_nodes is None and broken_edges is None:
self.nodelist = sorted(solver_graph.nodes)
self.edgelist = sorted(tuple(sorted(edge))
Expand All @@ -189,7 +228,7 @@ def __init__(self,
and (v, u) not in broken_edges)
#Finalize yield-dependent properties:
self.properties.update({
'num_qubits': len(solver_graph),
'num_qubits': num_qubits,
'qubits': self.nodelist.copy(),
'couplers': self.edgelist.copy(),
'anneal_offset_ranges': [[-0.5, 0.5] if i in self.nodelist
Expand Down Expand Up @@ -295,6 +334,7 @@ def __init__(self,
'tags': [],
'category': 'qpu',
'quota_conversion_rate': 1,
'fast_anneal_time_range': [0.005, 83000.0],
})

if properties is not None:
Expand All @@ -313,15 +353,9 @@ def from_qpu_sampler(cls, sampler):
def sample(self, bqm, **kwargs):

# Check kwargs compatibility with parameters and substitute sampler:
mocked_parameters={'answer_mode',
'max_answers',
'num_reads',
'label',
'initial_state',
}
for kw in kwargs:
if kw in self.parameters:
if self.parameter_warnings and kw not in mocked_parameters:
if self.parameter_warnings and kw not in self.mocked_parameters:
warnings.warn(f'{kw!r} parameter is valid for DWaveSampler(), '
'but not mocked in MockDWaveSampler().')
else:
Expand All @@ -344,19 +378,21 @@ def sample(self, bqm, **kwargs):
label = kwargs.get('label')
if label is not None:
info.update(problem_label=label)

#Special handling of flux_biases, for compatibility with virtual graphs


# Special handling of flux_biases, for compatibility with virtual graphs
flux_biases = kwargs.get('flux_biases')
if flux_biases is not None:
self.flux_biases_flag = True

substitute_kwargs = {'num_reads' : kwargs.get('num_reads')}
if substitute_kwargs['num_reads'] is None:
substitute_kwargs['num_reads'] = 1
# Create a local dictionary combining self.substitute_kwargs and relevant kwargs
substitute_kwargs = self.substitute_kwargs.copy()

# Handle 'num_reads', defaulting to 1 if not provided
num_reads = kwargs.get('num_reads', substitute_kwargs.get('num_reads', 1))
substitute_kwargs['num_reads'] = num_reads

initial_state = kwargs.get('initial_state')
if initial_state is not None:
if 'initial_state' in kwargs:
initial_state = kwargs['initial_state']
# Initial state format is a list of (qubit,values)
# value=3 denotes an unused variable (should be absent
# from bqm).
Expand All @@ -366,9 +402,12 @@ def sample(self, bqm, **kwargs):
if pair[1]!=3],dtype=float),
[pair[0] for pair in initial_state if pair[1]!=3])

ss = SteepestDescentSampler().sample(bqm, **substitute_kwargs)
ss.info.update(info)
sampler_kwargs = kwargs.copy()
sampler_kwargs.update(substitute_kwargs)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, update order should be reversed.

Base kwargs are the default values for the substitute sampler, and .sample()-local kwargs are more specific, hence they should overwrite the base kwargs.


ss = self.substitute_sampler.sample(bqm, **sampler_kwargs)
ss.info.update(info)

# determine ground state exactly for small problems
if 0 < len(bqm) <= self.exact_solver_cutoff and len(ss) >= 1:
ground = dimod.ExactSolver().sample(bqm).truncate(1)
Expand Down
Empty file added tests/-v
Empty file.
79 changes: 79 additions & 0 deletions tests/test_mock_sampler.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,86 @@ def test_yield_arguments(self):
broken_edges=delete_edges)
self.assertTrue(len(sampler.nodelist)==4)
self.assertTrue(len(sampler.edgelist)==1)

def test_custom_mock_sampler(self):
"""Test that MockDWaveSampler uses the provided custom mocking_sampler."""

# Define a constant sampler that always returns the same sample
class ConstantSampler(dimod.Sampler):
properties = {}
parameters = {'num_reads': []}

def sample(self, bqm, **kwargs):
num_reads = kwargs.get('num_reads', 1)
sample = {v: 1 for v in bqm.variables}
samples = [sample] * num_reads
energies = [bqm.energy(sample) for sample in samples]
return dimod.SampleSet.from_samples(samples, vartype=bqm.vartype, energy=energies)

constant_sampler = ConstantSampler()

# Create a simple BQM
bqm = dimod.BQM({'a': 1, 'b': 1}, {}, 0.0, vartype="SPIN")

# Instantiate MockDWaveSampler with nodelist and edgelist including 'a' and 'b'
sampler = MockDWaveSampler(
substitute_sampler=constant_sampler,
nodelist=['a', 'b'],
edgelist=[('a', 'b')]
)

# Sample using the MockDWaveSampler with the custom sampler
ss = sampler.sample(bqm, num_reads=2, answer_mode='raw') # First sample is overwritten by ExactSampler

# Reconstruct the second sample as a dictionary
second_sample_array = ss.record.sample[1]
variables = ss.variables
second_sample = dict(zip(variables, second_sample_array))

# Expected sample from the custom sampler
expected_sample = {v: 1 for v in bqm.variables}

# Check that the sample returned is as expected from the custom sampler
self.assertEqual(second_sample, expected_sample, 'Second sample was not the expected excited state')

def test_mocking_sampler_params(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will review after the comment on whether sample should consume kwargs for the substitute sampler has been addressed

"""Test that mocking_sampler_params are correctly passed to the mocking_sampler."""

# Define a constant sampler that checks for a custom parameter
class ConstantSampler(dimod.Sampler):
properties = {}
parameters = {'custom_param': []}

def sample(self, bqm, **kwargs):
custom_param = kwargs.get('custom_param')
# Raise exception if parameters passed incorrectly
if custom_param != 'test_value':
raise ValueError("custom_param not passed correctly")
# Return a default sample
sample = {v: -1 for v in bqm.variables}
return dimod.SampleSet.from_samples_bqm(sample, bqm)

constant_sampler = ConstantSampler()

# Create a simple BQM
bqm = dimod.BQM({'a': 1, 'b': 1}, {('a', 'b'): 1}, 0.0, vartype="SPIN")

# Instantiate MockDWaveSampler with nodelist and edgelist including 'a' and 'b'
sampler = MockDWaveSampler(
substitute_sampler=constant_sampler,
substitute_kwargs={'custom_param': 'test_value'},
nodelist=['a', 'b'],
edgelist=[('a', 'b')]
)

# Sample using the MockDWaveSampler
ss = sampler.sample(bqm)

# Check that the sample returned is as expected from the custom sampler
expected_sample = {'a': -1, 'b': -1}
self.assertEqual(ss.first.sample, expected_sample)
self.assertEqual(ss.first.energy, bqm.energy(expected_sample))

class TestMockLeapHybridDQMSampler(unittest.TestCase):
def test_sampler(self):
sampler = MockLeapHybridDQMSampler()
Expand Down
2 changes: 1 addition & 1 deletion tests/test_virtual_graph_composite.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def test_smoke(self):

# depending on how recenlty flux bias data was gathered, this may be true
child_sampler.flux_biases_flag = False

child_sampler.mocked_parameters.add('flux_biases') # Don't raise warning
if sampler.flux_biases:
sampler.sample_ising({'a': -1}, {})
self.assertTrue(child_sampler.flux_biases_flag) # true when some have been provided to sample_ising
Expand Down