From acc5e685cc36d10df20d0921d613d35e9925480a Mon Sep 17 00:00:00 2001 From: pfarre Date: Thu, 15 Aug 2024 13:40:45 -0700 Subject: [PATCH 1/9] add LinearAncillaComposite --- dwave/system/composites/__init__.py | 1 + dwave/system/composites/linear_ancilla.py | 213 ++++++++++++++++++ ...ar-ancilla-composite-3281ed6733b0f0c7.yaml | 4 + tests/test_linear_ancilla_composite.py | 122 ++++++++++ 4 files changed, 340 insertions(+) create mode 100644 dwave/system/composites/linear_ancilla.py create mode 100644 releasenotes/notes/add-linear-ancilla-composite-3281ed6733b0f0c7.yaml create mode 100644 tests/test_linear_ancilla_composite.py diff --git a/dwave/system/composites/__init__.py b/dwave/system/composites/__init__.py index b4e19023..7ef72fb2 100644 --- a/dwave/system/composites/__init__.py +++ b/dwave/system/composites/__init__.py @@ -17,3 +17,4 @@ from dwave.system.composites.tiling import * from dwave.system.composites.virtual_graph import * from dwave.system.composites.reversecomposite import * +from dwave.system.composites.linear_ancilla import * diff --git a/dwave/system/composites/linear_ancilla.py b/dwave/system/composites/linear_ancilla.py new file mode 100644 index 00000000..426a8719 --- /dev/null +++ b/dwave/system/composites/linear_ancilla.py @@ -0,0 +1,213 @@ +# coding: utf-8 +# Copyright 2018 D-Wave Systems Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Embedding composite to implement linear fields as polarized ancilla qubits. +""" + +import numbers + +from typing import Sequence, Mapping, Any + +from collections import defaultdict + +import numpy as np + +from dimod.decorators import nonblocking_sample_method +import dimod + + +__all__ = ["LinearAncillaComposite"] + + +class LinearAncillaComposite(dimod.ComposedSampler, dimod.Structured): + """Implements linear fields as polarized ancilla qubits. + + Linear field `h_i` of qubit `i` is implemented through a coupling `J_{ij}` between + the qubit and a neighbouring qubit `j` that is fully polarized with a large flux bias. + + Args: + child_sampler (:class:`dimod.Sampler`): + A dimod sampler, such as a :obj:`DWaveSampler`, that has flux bias controls. + + """ + + def __init__( + self, + child_sampler: dimod.Sampler, + ): + self.children = [child_sampler] + self.parameters = child_sampler.parameters.copy() + self.properties = dict(child_properties=child_sampler.properties.copy()) + self.nodelist = child_sampler.nodelist + self.edgelist = child_sampler.edgelist + + def nodelist(self): + pass # overwritten by init + + def edgelist(self): + pass # overwritten by init + + children = None # overwritten by init + """list [child_sampler]: List containing the structured sampler.""" + + parameters = None # overwritten by init + """dict[str, list]: Parameters in the form of a dict. + + For an instantiated composed sampler, keys are the keyword parameters + accepted by the child sampler and parameters added by the composite. + """ + + properties = None # overwritten by init + """dict: Properties in the form of a dict. + + Contains the properties of the child sampler. + """ + + @nonblocking_sample_method + def sample( + self, + bqm: dimod.BinaryQuadraticModel, + *, + h_tolerance: numbers.Number = 0, + default_flux_bias_range: Sequence[float] = [-0.005, 0.005], + **parameters, + ): + """Sample from the provided binary quadratic model. + + + Args: + bqm (:obj:`~dimod.BinaryQuadraticModel`): + Binary quadratic model to be sampled from. + + h_tolerance (:class:`numbers.Number`): + Magnitude of the linear bias can be left on the qubit. Assumed to be positive. Defaults to zero. + + default_flux_bias_range (:class:`typing.Sequence`): + Flux bias range safely accepted by the QPU, the larger the better. + + **parameters: + Parameters for the sampling method, specified by the child + sampler. + + Returns: + :obj:`~dimod.SampleSet` + + """ + if h_tolerance < 0: + raise ValueError("h_tolerance needs to be positive or zero") + + child = self.child + qpu_properties = innermost_child_properties(child) + g_target = child.to_networkx_graph() + g_source = dimod.to_networkx_graph(bqm) + j_range = qpu_properties["extended_j_range"] + flux_bias_range = qpu_properties.get("flux_bias_range", default_flux_bias_range) + + # Positive couplings tend to have smaller control error, + # we default to them if they have the same magnitude than negative couplings + # https://docs.dwavesys.com/docs/latest/c_qpu_ice.html#overview-of-ice + largest_j = j_range[1] if abs(j_range[1]) >= abs(j_range[0]) else j_range[0] + largest_j_sign = np.sign(largest_j) + + # To implement the bias sign through flux bias sign, + # we pick a range (magnitude) that we can sign-flip + fb_magnitude = min(abs(b) for b in flux_bias_range) + flux_biases = [0] * qpu_properties["num_qubits"] + + _bqm = bqm.copy() + used_ancillas = defaultdict(list) + for variable, bias in bqm.iter_linear(): + if abs(bias) <= h_tolerance: + continue + if abs(bias) - h_tolerance > abs(largest_j): + return NotImplementedError( + "linear biases larger than the strongest coupling are not supported yet" + ) # TODO: implement larger biases through multiple ancillas + + available_ancillas = set(g_target.adj[variable]) - set(g_source.nodes()) + if not len(available_ancillas): + raise ValueError(f"variable {variable} has no ancillas available") + unused_ancillas = available_ancillas - set(used_ancillas) + if len(unused_ancillas): + ancilla = unused_ancillas.pop() + # bias sign is handled by the flux bias + flux_biases[ancilla] = np.sign(bias) * largest_j_sign * fb_magnitude + _bqm.add_interaction( + variable, ancilla, (abs(bias) - h_tolerance) * largest_j_sign + ) + else: + if qpu_properties["j_range"][0] <= bias <= qpu_properties["j_range"][1]: + # If j can be sign-flipped, select the least used ancilla + ancilla = sorted( + list(available_ancillas), key=lambda x: len(used_ancillas[x]) + )[0] + _bqm.add_interaction( + variable, + ancilla, + (bias - h_tolerance * np.sign(bias)) + * np.sign([flux_biases[ancilla]]), + ) + else: + # Ancilla sharing is limited to flux biases with a sign + signed_ancillas = [ + ancilla + for ancilla in available_ancillas + if largest_j_sign + == np.sign(flux_biases[ancilla]) * np.sign(bias) + ] + if not len(signed_ancillas): + return ValueError( + f"variable {variable} has no ancillas available" + ) + else: + ancilla = sorted( + list(signed_ancillas), key=lambda x: len(used_ancillas[x]) + )[0] + _bqm.add_interaction( + variable, + ancilla, + largest_j_sign * (abs(bias) - h_tolerance), + ) + + used_ancillas[ancilla].append(variable) + _bqm.set_linear(variable, h_tolerance * np.sign(bias)) + + sampleset = self.child.sample(_bqm, flux_biases=flux_biases, **parameters) + yield + yield dimod.SampleSet.from_samples_bqm( + [ + {k: v for k, v in sample.items() if k not in used_ancillas} + for sample in sampleset.samples() + ], + bqm=bqm, + info=sampleset.info.update(used_ancillas), + ) + + +def innermost_child_properties(sampler: dimod.Sampler) -> Mapping[str, Any]: + """Returns the properties of the inner-most child sampler in a composite. + + Args: + sampler: A dimod sampler + + Returns: + properties (dict): The properties of the inner-most sampler + + """ + + try: + return innermost_child_properties(sampler.child) + except AttributeError: + return sampler.properties diff --git a/releasenotes/notes/add-linear-ancilla-composite-3281ed6733b0f0c7.yaml b/releasenotes/notes/add-linear-ancilla-composite-3281ed6733b0f0c7.yaml new file mode 100644 index 00000000..569089e7 --- /dev/null +++ b/releasenotes/notes/add-linear-ancilla-composite-3281ed6733b0f0c7.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Add `LinearAncillaComposite` for implementing linear biases through ancilla qubits diff --git a/tests/test_linear_ancilla_composite.py b/tests/test_linear_ancilla_composite.py new file mode 100644 index 00000000..77fdce30 --- /dev/null +++ b/tests/test_linear_ancilla_composite.py @@ -0,0 +1,122 @@ +# Copyright 2018 D-Wave Systems Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest +import collections + +import numpy as np + +import networkx as nx + +import dimod +from dimod import TrackingComposite, StructureComposite + +from dwave.system.testing import MockDWaveSampler +from dwave.system import LinearAncillaComposite + + +class TestLinearAncillaComposite(unittest.TestCase): + def setUp(self): + self.qpu = MockDWaveSampler(properties=dict(extended_j_range=[-2, 1])) + self.tracked_qpu = TrackingComposite(self.qpu) + + self.sampler = LinearAncillaComposite( + StructureComposite( + self.tracked_qpu, + nodelist=self.qpu.nodelist, + edgelist=self.qpu.edgelist, + ) + ) + + self.submask = nx.subgraph( + self.sampler.to_networkx_graph(), + list(self.sampler.nodelist)[::2], + ) + + # this problem should run + self.linear_problem = dimod.BinaryQuadraticModel.from_ising( + {i: (-1) ** i for i in self.submask.nodes()}, + {}, + ) + + # this problem shouldn't run + self.linear_problem_full_graph = dimod.BinaryQuadraticModel.from_ising( + {i: (-1) ** i for i in self.qpu.nodelist}, + {}, + ) + + def test_only_quadratic(self): + """if no linear biases, the bqm remains intact""" + + bqm = dimod.generators.ran_r(1, self.submask, seed=1) + self.sampler.sample(bqm) + self.assertEqual(bqm, self.tracked_qpu.input["bqm"]) + + def test_h_tolerance_too_large(self): + """if h tolerance is larger than the linear biases, + the bqm remains intact + """ + + self.sampler.sample(self.linear_problem, h_tolerance=1.01) + self.assertEqual(self.linear_problem, self.tracked_qpu.input["bqm"]) + + def test_intermediate_h_tolerance(self): + """check the desired h-tolerance is left in the qubit bias""" + + h_tolerance = 0.5 + self.sampler.sample(self.linear_problem, h_tolerance=h_tolerance) + for variable, bias in self.tracked_qpu.input["bqm"].linear.items(): + if variable in self.linear_problem.variables: # skip the ancillas + self.assertEqual( + bias, + np.sign(self.linear_problem.get_linear(variable)) * h_tolerance, + ) + + def test_no_ancillas_available(self): + """send a problem that uses all the qubits, not leaving any ancillas available""" + + with self.assertRaises(ValueError): + ss = self.sampler.sample(self.linear_problem_full_graph) + + def test_ancillas_present(self): + """check the solver used ancillas""" + + self.sampler.sample(self.linear_problem) + self.assertGreater( + len(self.tracked_qpu.input["bqm"].variables), + len(self.linear_problem.variables), + ) + + def test_ancilla_cleanup(self): + """check the problem returned has no additional variables""" + + sampleset = self.sampler.sample(self.linear_problem) + self.assertEqual( + len(self.linear_problem.variables), + len(sampleset.variables), + ) + + def test_flux_biases_present(self): + """check flux biases are applied to non-data qubits""" + + self.sampler.sample(self.linear_problem) + flux_biases = np.array(self.tracked_qpu.input["flux_biases"]) + + # flux biases are used + self.assertGreater(sum(flux_biases != 0), 0) + + # the qubits with flux biases are not data qubits + for qubit, flux_bias in enumerate(flux_biases): + if flux_bias != 0: + self.assertNotIn(qubit, self.linear_problem.variables) From b7d11f53b8bbaf4037661e98b9e7f65faa9a3f24 Mon Sep 17 00:00:00 2001 From: pfarre Date: Fri, 16 Aug 2024 16:06:32 -0700 Subject: [PATCH 2/9] formatting --- dwave/system/composites/linear_ancilla.py | 8 +++----- .../add-linear-ancilla-composite-3281ed6733b0f0c7.yaml | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/dwave/system/composites/linear_ancilla.py b/dwave/system/composites/linear_ancilla.py index 426a8719..551ec284 100644 --- a/dwave/system/composites/linear_ancilla.py +++ b/dwave/system/composites/linear_ancilla.py @@ -13,15 +13,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Embedding composite to implement linear fields as polarized ancilla qubits. +"""Composite to implement linear coefficients with ancilla qubits biased with flux-bias offsets. """ +from collections import defaultdict import numbers - from typing import Sequence, Mapping, Any -from collections import defaultdict - import numpy as np from dimod.decorators import nonblocking_sample_method @@ -32,7 +30,7 @@ class LinearAncillaComposite(dimod.ComposedSampler, dimod.Structured): - """Implements linear fields as polarized ancilla qubits. + """Implements linear fields as ancilla qubits polarized with strong flux biases. Linear field `h_i` of qubit `i` is implemented through a coupling `J_{ij}` between the qubit and a neighbouring qubit `j` that is fully polarized with a large flux bias. diff --git a/releasenotes/notes/add-linear-ancilla-composite-3281ed6733b0f0c7.yaml b/releasenotes/notes/add-linear-ancilla-composite-3281ed6733b0f0c7.yaml index 569089e7..ab7ecf1f 100644 --- a/releasenotes/notes/add-linear-ancilla-composite-3281ed6733b0f0c7.yaml +++ b/releasenotes/notes/add-linear-ancilla-composite-3281ed6733b0f0c7.yaml @@ -1,4 +1,4 @@ --- features: - | - Add `LinearAncillaComposite` for implementing linear biases through ancilla qubits + Add `LinearAncillaComposite` for implementing linear coefficients through ancilla qubits polarized with strong flux biases From 983e5ced47dc7c35c7b844d577c30adf95e69570 Mon Sep 17 00:00:00 2001 From: pfarre Date: Mon, 19 Aug 2024 16:43:03 -0700 Subject: [PATCH 3/9] add note abou auto_scale support --- dwave/system/composites/linear_ancilla.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dwave/system/composites/linear_ancilla.py b/dwave/system/composites/linear_ancilla.py index 551ec284..608400fe 100644 --- a/dwave/system/composites/linear_ancilla.py +++ b/dwave/system/composites/linear_ancilla.py @@ -84,6 +84,9 @@ def sample( ): """Sample from the provided binary quadratic model. + .. note:: + This composite does not suport the auto_scale parameter. BQM Scaling + can be done with :class:`dimod.ScaleComposite`. Args: bqm (:obj:`~dimod.BinaryQuadraticModel`): From d8416ff59d1c1d22b3d1f88ad061ede737c64d75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Farr=C3=A9?= Date: Wed, 9 Oct 2024 21:06:24 -0700 Subject: [PATCH 4/9] Add LinearAncillaComposite to docs --- docs/reference/composites.rst | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/docs/reference/composites.rst b/docs/reference/composites.rst index a14d8378..39593d92 100644 --- a/docs/reference/composites.rst +++ b/docs/reference/composites.rst @@ -326,3 +326,36 @@ Methods ReverseAdvanceComposite.sample ReverseAdvanceComposite.sample_ising ReverseAdvanceComposite.sample_qubo + + +Linear Bias +=========== + +Composites for implementing linear biases in D-Wave systems through auxiliary methods. + + +LinearAncillaComposite +----------------------- + +.. autoclass:: LinearAncillaComposite + +Properties +~~~~~~~~~~ + +.. autosummary:: + :toctree: generated/ + + LinearAncillaComposite.child + LinearAncillaComposite.children + LinearAncillaComposite.parameters + LinearAncillaComposite.properties + +Methods +~~~~~~~ + +.. autosummary:: + :toctree: generated/ + + LinearAncillaComposite.sample + LinearAncillaComposite.sample_ising + LinearAncillaComposite.sample_qubo From 74f4e3159bc6b7f2c05fc645325d1a0b746f27f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Farr=C3=A9?= Date: Thu, 10 Oct 2024 16:53:09 -0700 Subject: [PATCH 5/9] minor formatting --- dwave/system/composites/__init__.py | 2 +- dwave/system/composites/linear_ancilla.py | 32 +++++++++++------------ tests/test_linear_ancilla_composite.py | 13 ++++----- 3 files changed, 22 insertions(+), 25 deletions(-) diff --git a/dwave/system/composites/__init__.py b/dwave/system/composites/__init__.py index 7ef72fb2..954c5e6c 100644 --- a/dwave/system/composites/__init__.py +++ b/dwave/system/composites/__init__.py @@ -14,7 +14,7 @@ from dwave.system.composites.cutoffcomposite import * from dwave.system.composites.embedding import * +from dwave.system.composites.linear_ancilla import * from dwave.system.composites.tiling import * from dwave.system.composites.virtual_graph import * from dwave.system.composites.reversecomposite import * -from dwave.system.composites.linear_ancilla import * diff --git a/dwave/system/composites/linear_ancilla.py b/dwave/system/composites/linear_ancilla.py index 608400fe..43e4237a 100644 --- a/dwave/system/composites/linear_ancilla.py +++ b/dwave/system/composites/linear_ancilla.py @@ -1,4 +1,3 @@ -# coding: utf-8 # Copyright 2018 D-Wave Systems Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,21 +15,22 @@ """Composite to implement linear coefficients with ancilla qubits biased with flux-bias offsets. """ -from collections import defaultdict import numbers + +from collections import defaultdict from typing import Sequence, Mapping, Any +import dimod import numpy as np from dimod.decorators import nonblocking_sample_method -import dimod __all__ = ["LinearAncillaComposite"] class LinearAncillaComposite(dimod.ComposedSampler, dimod.Structured): - """Implements linear fields as ancilla qubits polarized with strong flux biases. + """Implements linear biases as ancilla qubits polarized with strong flux biases. Linear field `h_i` of qubit `i` is implemented through a coupling `J_{ij}` between the qubit and a neighbouring qubit `j` that is fully polarized with a large flux bias. @@ -110,9 +110,9 @@ def sample( raise ValueError("h_tolerance needs to be positive or zero") child = self.child - qpu_properties = innermost_child_properties(child) - g_target = child.to_networkx_graph() - g_source = dimod.to_networkx_graph(bqm) + qpu_properties = _innermost_child_properties(child) + target_graph = child.to_networkx_graph() + source_graph = dimod.to_networkx_graph(bqm) j_range = qpu_properties["extended_j_range"] flux_bias_range = qpu_properties.get("flux_bias_range", default_flux_bias_range) @@ -133,14 +133,14 @@ def sample( if abs(bias) <= h_tolerance: continue if abs(bias) - h_tolerance > abs(largest_j): - return NotImplementedError( - "linear biases larger than the strongest coupling are not supported yet" + return ValueError( + "linear biases larger than the strongest coupling are not supported" ) # TODO: implement larger biases through multiple ancillas - available_ancillas = set(g_target.adj[variable]) - set(g_source.nodes()) + available_ancillas = set(target_graph.adj[variable]) - source_graph.nodes() if not len(available_ancillas): raise ValueError(f"variable {variable} has no ancillas available") - unused_ancillas = available_ancillas - set(used_ancillas) + unused_ancillas = available_ancillas - used_ancillas.keys() if len(unused_ancillas): ancilla = unused_ancillas.pop() # bias sign is handled by the flux bias @@ -150,7 +150,7 @@ def sample( ) else: if qpu_properties["j_range"][0] <= bias <= qpu_properties["j_range"][1]: - # If j can be sign-flipped, select the least used ancilla + # If j can be sign-flipped, select the least used ancilla regardless of the flux bias sign ancilla = sorted( list(available_ancillas), key=lambda x: len(used_ancillas[x]) )[0] @@ -161,12 +161,12 @@ def sample( * np.sign([flux_biases[ancilla]]), ) else: - # Ancilla sharing is limited to flux biases with a sign + # Ancilla sharing is limited to flux biases with appropiate sign signed_ancillas = [ ancilla for ancilla in available_ancillas if largest_j_sign - == np.sign(flux_biases[ancilla]) * np.sign(bias) + == np.sign(flux_biases[ancilla] * bias) ] if not len(signed_ancillas): return ValueError( @@ -197,7 +197,7 @@ def sample( ) -def innermost_child_properties(sampler: dimod.Sampler) -> Mapping[str, Any]: +def _innermost_child_properties(sampler: dimod.Sampler) -> Mapping[str, Any]: """Returns the properties of the inner-most child sampler in a composite. Args: @@ -209,6 +209,6 @@ def innermost_child_properties(sampler: dimod.Sampler) -> Mapping[str, Any]: """ try: - return innermost_child_properties(sampler.child) + return _innermost_child_properties(sampler.child) except AttributeError: return sampler.properties diff --git a/tests/test_linear_ancilla_composite.py b/tests/test_linear_ancilla_composite.py index 77fdce30..57aa559a 100644 --- a/tests/test_linear_ancilla_composite.py +++ b/tests/test_linear_ancilla_composite.py @@ -12,15 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest import collections - -import numpy as np - -import networkx as nx +import unittest import dimod -from dimod import TrackingComposite, StructureComposite +import networkx as nx +import numpy as np from dwave.system.testing import MockDWaveSampler from dwave.system import LinearAncillaComposite @@ -29,10 +26,10 @@ class TestLinearAncillaComposite(unittest.TestCase): def setUp(self): self.qpu = MockDWaveSampler(properties=dict(extended_j_range=[-2, 1])) - self.tracked_qpu = TrackingComposite(self.qpu) + self.tracked_qpu = dimod.TrackingComposite(self.qpu) self.sampler = LinearAncillaComposite( - StructureComposite( + dimod.StructureComposite( self.tracked_qpu, nodelist=self.qpu.nodelist, edgelist=self.qpu.edgelist, From 63e40268534849c73005df831abe65c26431118f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Farr=C3=A9?= Date: Tue, 15 Oct 2024 18:33:18 -0700 Subject: [PATCH 6/9] change description from generalized to specific --- docs/reference/composites.rst | 67 ++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/docs/reference/composites.rst b/docs/reference/composites.rst index 39593d92..ae3d1614 100644 --- a/docs/reference/composites.rst +++ b/docs/reference/composites.rst @@ -268,6 +268,41 @@ Methods VirtualGraphComposite.sample_ising VirtualGraphComposite.sample_qubo + + +Linear Bias +=========== + +Composite for using auxiliary qubits to bias problem qubits. + + +LinearAncillaComposite +----------------------- + +.. autoclass:: LinearAncillaComposite + +Properties +~~~~~~~~~~ + +.. autosummary:: + :toctree: generated/ + + LinearAncillaComposite.child + LinearAncillaComposite.children + LinearAncillaComposite.parameters + LinearAncillaComposite.properties + +Methods +~~~~~~~ + +.. autosummary:: + :toctree: generated/ + + LinearAncillaComposite.sample + LinearAncillaComposite.sample_ising + LinearAncillaComposite.sample_qubo + + Reverse Anneal ============== @@ -327,35 +362,3 @@ Methods ReverseAdvanceComposite.sample_ising ReverseAdvanceComposite.sample_qubo - -Linear Bias -=========== - -Composites for implementing linear biases in D-Wave systems through auxiliary methods. - - -LinearAncillaComposite ------------------------ - -.. autoclass:: LinearAncillaComposite - -Properties -~~~~~~~~~~ - -.. autosummary:: - :toctree: generated/ - - LinearAncillaComposite.child - LinearAncillaComposite.children - LinearAncillaComposite.parameters - LinearAncillaComposite.properties - -Methods -~~~~~~~ - -.. autosummary:: - :toctree: generated/ - - LinearAncillaComposite.sample - LinearAncillaComposite.sample_ising - LinearAncillaComposite.sample_qubo From 5867eba34295121afae62ca0be777e9bc45e885d Mon Sep 17 00:00:00 2001 From: pfarre Date: Thu, 21 Nov 2024 18:38:46 -0800 Subject: [PATCH 7/9] docstring formatting, variable naming --- dwave/system/composites/__init__.py | 2 +- dwave/system/composites/linear_ancilla.py | 29 +++++++++++++---------- tests/test_linear_ancilla_composite.py | 2 +- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/dwave/system/composites/__init__.py b/dwave/system/composites/__init__.py index 954c5e6c..6a847af5 100644 --- a/dwave/system/composites/__init__.py +++ b/dwave/system/composites/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2018 D-Wave Systems Inc. +# Copyright 2024 D-Wave Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/dwave/system/composites/linear_ancilla.py b/dwave/system/composites/linear_ancilla.py index 43e4237a..60f014cb 100644 --- a/dwave/system/composites/linear_ancilla.py +++ b/dwave/system/composites/linear_ancilla.py @@ -1,4 +1,4 @@ -# Copyright 2018 D-Wave Systems Inc. +# Copyright 2024 D-Wave Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -32,13 +32,14 @@ class LinearAncillaComposite(dimod.ComposedSampler, dimod.Structured): """Implements linear biases as ancilla qubits polarized with strong flux biases. - Linear field `h_i` of qubit `i` is implemented through a coupling `J_{ij}` between - the qubit and a neighbouring qubit `j` that is fully polarized with a large flux bias. + Linear bias :math:`h_i` of qubit :math:`i` is implemented through a coupling + :math:`J_{ij}` between the qubit and a neighboring qubit :math:`j` that has a + large flux-bias offset. Args: child_sampler (:class:`dimod.Sampler`): - A dimod sampler, such as a :obj:`DWaveSampler`, that has flux bias controls. - + A dimod sampler, such as a :class:`~dwave.system.samplers.DWaveSampler()`, + that has flux bias controls. """ def __init__( @@ -79,31 +80,34 @@ def sample( bqm: dimod.BinaryQuadraticModel, *, h_tolerance: numbers.Number = 0, - default_flux_bias_range: Sequence[float] = [-0.005, 0.005], + default_flux_bias_range: Sequence[float] = (-0.005, 0.005), **parameters, ): """Sample from the provided binary quadratic model. .. note:: This composite does not suport the auto_scale parameter. BQM Scaling - can be done with :class:`dimod.ScaleComposite`. + can be done with :class:`~dimod.ScaleComposite`. Args: bqm (:obj:`~dimod.BinaryQuadraticModel`): Binary quadratic model to be sampled from. h_tolerance (:class:`numbers.Number`): - Magnitude of the linear bias can be left on the qubit. Assumed to be positive. Defaults to zero. + Magnitude of the linear bias to be set directly on problem qubits; above this the bias + is emulated by the flux-bias offset to an ancilla qubit. Assumed to be positive. + Defaults to zero. default_flux_bias_range (:class:`typing.Sequence`): - Flux bias range safely accepted by the QPU, the larger the better. + Flux-bias range, as a two-tuple, supported by the QPU. The values must be large enough to + ensure qubits remain polarized throughout the annealing process. **parameters: Parameters for the sampling method, specified by the child sampler. Returns: - :obj:`~dimod.SampleSet` + :class:`~dimod.SampleSet`. """ if h_tolerance < 0: @@ -113,13 +117,14 @@ def sample( qpu_properties = _innermost_child_properties(child) target_graph = child.to_networkx_graph() source_graph = dimod.to_networkx_graph(bqm) - j_range = qpu_properties["extended_j_range"] + extended_j_range = qpu_properties["extended_j_range"] + # flux_bias_range is not supported at the moment flux_bias_range = qpu_properties.get("flux_bias_range", default_flux_bias_range) # Positive couplings tend to have smaller control error, # we default to them if they have the same magnitude than negative couplings # https://docs.dwavesys.com/docs/latest/c_qpu_ice.html#overview-of-ice - largest_j = j_range[1] if abs(j_range[1]) >= abs(j_range[0]) else j_range[0] + largest_j = extended_j_range[1] if abs(extended_j_range[1]) >= abs(extended_j_range[0]) else extended_j_range[0] largest_j_sign = np.sign(largest_j) # To implement the bias sign through flux bias sign, diff --git a/tests/test_linear_ancilla_composite.py b/tests/test_linear_ancilla_composite.py index 57aa559a..91816ba1 100644 --- a/tests/test_linear_ancilla_composite.py +++ b/tests/test_linear_ancilla_composite.py @@ -1,4 +1,4 @@ -# Copyright 2018 D-Wave Systems Inc. +# Copyright 2024 D-Wave Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. From 6751401d50f382af59aed89141176d69e1199b53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Farr=C3=A9?= Date: Tue, 26 Nov 2024 18:41:39 -0800 Subject: [PATCH 8/9] add example to composite docstring; formatting --- dwave/system/composites/linear_ancilla.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/dwave/system/composites/linear_ancilla.py b/dwave/system/composites/linear_ancilla.py index 60f014cb..5cddeebb 100644 --- a/dwave/system/composites/linear_ancilla.py +++ b/dwave/system/composites/linear_ancilla.py @@ -40,6 +40,19 @@ class LinearAncillaComposite(dimod.ComposedSampler, dimod.Structured): child_sampler (:class:`dimod.Sampler`): A dimod sampler, such as a :class:`~dwave.system.samplers.DWaveSampler()`, that has flux bias controls. + + Examples: + This example submits a two-qubit problem consisting of linear biases with oposed sign + and anti-ferromagnetic coupling. A D-Wave system solves it in the fast anneal + protocol through the use of ancilla qubits. + + >>> from dwave.system import DWaveSampler, EmbeddingComposite, LinearAncillaComposite + ... + >>> sampler = EmbeddingComposite(LinearAncillaComposite(DWaveSampler())) + >>> sampleset = sampler.sample_ising({0:1, 1:-1}, {(0, 1): 1}, fast_anneal=True) + >>> sampleset.first.energy + -3 + """ def __init__( @@ -80,17 +93,17 @@ def sample( bqm: dimod.BinaryQuadraticModel, *, h_tolerance: numbers.Number = 0, - default_flux_bias_range: Sequence[float] = (-0.005, 0.005), + default_flux_bias_range: tuple[float, float] = (-0.005, 0.005), **parameters, ): """Sample from the provided binary quadratic model. .. note:: - This composite does not suport the auto_scale parameter. BQM Scaling - can be done with :class:`~dimod.ScaleComposite`. + This composite does not support the :ref:`param_autoscale` parameter; use the + :class:`~dwave.preprocessing.composites.ScaleComposite` for scaling. Args: - bqm (:obj:`~dimod.BinaryQuadraticModel`): + bqm (:class:`~dimod.binary.BinaryQuadraticModel`): Binary quadratic model to be sampled from. h_tolerance (:class:`numbers.Number`): @@ -124,7 +137,7 @@ def sample( # Positive couplings tend to have smaller control error, # we default to them if they have the same magnitude than negative couplings # https://docs.dwavesys.com/docs/latest/c_qpu_ice.html#overview-of-ice - largest_j = extended_j_range[1] if abs(extended_j_range[1]) >= abs(extended_j_range[0]) else extended_j_range[0] + largest_j = max(extended_j_range[::-1], key=abs) largest_j_sign = np.sign(largest_j) # To implement the bias sign through flux bias sign, From 080bfa2fa816b1556e75c6d4c51694efc1ee5b0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Farr=C3=A9?= Date: Wed, 27 Nov 2024 10:54:55 -0800 Subject: [PATCH 9/9] skip docs test; minor formatting --- dwave/system/composites/linear_ancilla.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dwave/system/composites/linear_ancilla.py b/dwave/system/composites/linear_ancilla.py index 5cddeebb..2f00342a 100644 --- a/dwave/system/composites/linear_ancilla.py +++ b/dwave/system/composites/linear_ancilla.py @@ -42,15 +42,15 @@ class LinearAncillaComposite(dimod.ComposedSampler, dimod.Structured): that has flux bias controls. Examples: - This example submits a two-qubit problem consisting of linear biases with oposed sign - and anti-ferromagnetic coupling. A D-Wave system solves it in the fast anneal - protocol through the use of ancilla qubits. + This example submits a two-qubit problem consisting of linear biases with opposed signs + and anti-ferromagnetic coupling. A D-Wave quantum computer solves it with the fast-anneal + protocol using ancilla qubits to represent the linear biases. >>> from dwave.system import DWaveSampler, EmbeddingComposite, LinearAncillaComposite ... >>> sampler = EmbeddingComposite(LinearAncillaComposite(DWaveSampler())) - >>> sampleset = sampler.sample_ising({0:1, 1:-1}, {(0, 1): 1}, fast_anneal=True) - >>> sampleset.first.energy + >>> sampleset = sampler.sample_ising({0:1, 1:-1}, {(0, 1): 1}, fast_anneal=True) # doctest: +SKIP + >>> sampleset.first.energy # doctest: +SKIP -3 """ @@ -111,7 +111,7 @@ def sample( is emulated by the flux-bias offset to an ancilla qubit. Assumed to be positive. Defaults to zero. - default_flux_bias_range (:class:`typing.Sequence`): + default_flux_bias_range (:class:`tuple`): Flux-bias range, as a two-tuple, supported by the QPU. The values must be large enough to ensure qubits remain polarized throughout the annealing process.