Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
54 changes: 35 additions & 19 deletions dimod/binary/binary_quadratic_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -1797,15 +1797,21 @@ def min_and_max(iterable):
and (b, a) not in
ignored_interactions)])

inv_scalar = max(lin_min / lin_range[0], lin_max / lin_range[1],
quad_min / quad_range[0], quad_max / quad_range[1])

if inv_scalar != 0:
self.scale(1 / inv_scalar, ignored_variables=ignored_variables,
# Figure out what multiplier/divisor will get us into range
divisor, multiplier = max(
[(lin_min, lin_range[0]),
(lin_max, lin_range[1]),
(quad_min, quad_range[0]),
(quad_max, quad_range[1])],
key=lambda tpl: tpl[0] / tpl[1],
)

if divisor != 0:
self.scale(multiplier=multiplier, divisor=divisor, ignored_variables=ignored_variables,
ignored_interactions=ignored_interactions,
ignore_offset=ignore_offset)

return 1.0 / inv_scalar
return multiplier / divisor
else:
return 1.0

Expand Down Expand Up @@ -1992,30 +1998,39 @@ def resize(self, n: int):
"""
return self.data.resize

def scale(self, scalar, ignored_variables=None, ignored_interactions=None,
ignore_offset=False):
"""Multiply all biases by the specified scalar.
def scale(
self,
multiplier: float,
divisor: float = 1,
*,
ignored_variables: Optional[abc.Iterable[Variable]] = None,
ignored_interactions: Optional[abc.Iterable[tuple[Variable, Variable]]] = None,
ignore_offset: bool = False,
):
"""Multiply all biases by the specified values.

Args:
scalar (number):
Value by which to scale the energy range of the binary
quadratic model.
multiplier:
Value by which to multiply all biases in the model.

divisor:
Value by which to divide all biases in the model.

ignored_variables (iterable, optional):
ignored_variables:
Biases associated with these variables are not scaled.

ignored_interactions (iterable[tuple], optional):
ignored_interactions:
Biases associated with these interactions, formatted as an
iterable of 2-tuples, are not scaled.

ignore_offset (bool, default=False):
ignore_offset:
If True, the offset is not scaled.

"""
if ignored_variables is None and ignored_interactions is None \
and ignore_offset is False:
try:
self.data.scale(scalar)
self.data.scale(multiplier, divisor)
return
except AttributeError:
pass
Expand All @@ -2033,15 +2048,16 @@ def scale(self, scalar, ignored_variables=None, ignored_interactions=None,
for v in self.variables:
if v in ignored_variables:
continue
self.set_linear(v, scalar*self.get_linear(v))
self.set_linear(v, multiplier * self.get_linear(v) / divisor)

for u, v, bias in self.iter_quadratic():
if (u, v) in ignored_interactions or (v, u) in ignored_interactions:
continue
self.set_quadratic(u, v, scalar*self.get_quadratic(u, v))
self.set_quadratic(u, v, multiplier * self.get_quadratic(u, v) / divisor)

if not ignore_offset:
self.offset *= scalar
self.offset *= multiplier
self.offset /= divisor

@forwarding_method
def set_linear(self, v: Variable, bias: Bias):
Expand Down
10 changes: 7 additions & 3 deletions dimod/cyqmbase/cyqmbase_template.pyx.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ from dimod.libcpp.vartypes cimport Vartype as cppVartype

from dimod.cyutilities cimport as_numpy_float
from dimod.sampleset import as_samples
from dimod.typing cimport Numeric, float64_t, int8_t
from dimod.typing cimport Numeric, float64_t, int8_t, int64_t
from dimod.variables import Variables
from dimod.vartypes import Vartype

Expand Down Expand Up @@ -405,8 +405,12 @@ cdef class cyQMBase_template:

return v

def scale(self, bias_type scalar):
self.base.scale(scalar)
def scale(self, multiplier, divisor=1):
dtype = np.result_type(multiplier, divisor)
if isinstance(dtype, np.integer):
self.base.scale[int64_t](<int64_t?>multiplier, <int64_t?>divisor)
else:
self.base.scale[double](<double?>multiplier, <double?>divisor)

def upper_bound(self, v):
cdef Py_ssize_t vi = self.variables.index(v)
Expand Down
15 changes: 10 additions & 5 deletions dimod/include/dimod/abc.h
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,8 @@ class QuadraticModelBase {
virtual void remove_variables(const std::vector<index_type>& variables);

/// Multiply all biases by the value of `scalar`.
void scale(bias_type scalar);
template <typename T>
void scale(T multiplier, T divisor = 1);

/// Set the linear bias of variable `v`.
void set_linear(index_type v, bias_type bias);
Expand Down Expand Up @@ -1034,19 +1035,23 @@ void QuadraticModelBase<bias_type, index_type>::resize(index_type n) {
}

template <class bias_type, class index_type>
void QuadraticModelBase<bias_type, index_type>::scale(bias_type scalar) {
offset_ *= scalar;
template <typename T>
void QuadraticModelBase<bias_type, index_type>::scale(T multiplier, T divisor) {
offset_ *= multiplier;
offset_ /= divisor;

// linear biases
for (bias_type& bias : linear_biases_) {
bias *= scalar;
bias *= multiplier;
bias /= divisor;
}

if (has_adj()) {
// quadratic biases
for (auto& n : (*adj_ptr_)) {
for (auto& term : n) {
term.bias *= scalar;
term.bias *= multiplier;
term.bias /= divisor;
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion dimod/libcpp/abc.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ cdef extern from "dimod/abc.h" namespace "dimod::abc" nogil:
bias_type quadratic_at(index_type, index_type) except+
bint remove_interaction(index_type, index_type)
void remove_variable(index_type)
void scale(bias_type)
void scale[T](T)
void scale[T](T, T)
void set_linear(index_type, bias_type)
void set_offset(bias_type)
void set_quadratic(index_type, index_type, bias_type) except+
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
features:
- Add ``divisor`` argument to ``BinaryQuadraticModel.scale()``.
upgrade:
- Rename ``scalar`` argument of ``BinaryQuadraticModel.scale()`` to ``multiplier``.
16 changes: 15 additions & 1 deletion tests/test_bqm.py
Original file line number Diff line number Diff line change
Expand Up @@ -1875,6 +1875,13 @@ def test_return_value(self, name, BQM):

self.assertEqual(bqm.normalize([-1, 1]), .5)

@parameterized.expand(BQMs.items())
def test_numeric(self, name, BQM):
# chosen because 187. * (6. / 187.) == 6.000000000000001
bqm = dimod.BQM({0: 187}, {}, 0, "SPIN")
bqm.normalize(bias_range=[-6, 6])
self.assertEqual(bqm.linear[0], 6)


class TestObjectDtype(unittest.TestCase):
def test_dtypes_array_like_ints(self):
Expand Down Expand Up @@ -2325,7 +2332,14 @@ def test_typical(self, name, BQM):
assert_consistent_bqm(bqm)

with self.assertRaises(TypeError):
bqm.scale('a')
bqm.scale('hithere')

@parameterized.expand(BQMs.items())
def test_numeric(self, name, BQM):
# chosen because 187. * (6. / 187.) == 6.000000000000001
bqm = dimod.BQM({0: 187}, {}, 0, "SPIN")
bqm.scale(multiplier=6, divisor=187)
self.assertEqual(bqm.linear[0], 6)


class TestSetLinear(unittest.TestCase):
Expand Down