Skip to content

Add experimental support for != constraints #1284

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

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
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
4 changes: 3 additions & 1 deletion dimod/binary/binary_quadratic_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
from dimod.quadratic.quadratic_model import _VariableArray
from dimod.serialization.fileview import SpooledTemporaryFile, _BytesIO, VariablesSection
from dimod.serialization.fileview import load, read_header, write_header
from dimod.sym import Eq, Ge, Le
from dimod.sym import Eq, Ge, Le, Ne
from dimod.typing import (Bias, BQMVectors, LabelledBQMVectors, QuadraticVectors,
Variable, VartypeLike)
from dimod.variables import Variables, iter_deserialize_variables
Expand Down Expand Up @@ -482,6 +482,8 @@ def __le__(self, other: Bias):
return NotImplemented

def __ne__(self, other):
if isinstance(other, Number):
return Ne(self, other)
return not self.is_equal(other)

@property
Expand Down
6 changes: 5 additions & 1 deletion dimod/constrained/cyconstrained.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ from dimod.libcpp.abc cimport QuadraticModelBase as cppQuadraticModelBase
from dimod.libcpp.constrained_quadratic_model cimport Sense as cppSense, Penalty as cppPenalty, Constraint as cppConstraint
from dimod.libcpp.vartypes cimport Vartype as cppVartype, vartype_info as cppvartype_info

from dimod.sym import Sense, Eq, Ge, Le
from dimod.sym import Sense, Eq, Ge, Le, Ne
from dimod.variables import Variables
from dimod.vartypes import as_vartype, Vartype
from dimod.views.quadratic import QuadraticViewsMixin
Expand All @@ -55,6 +55,8 @@ cdef cppSense cppsense(object sense) except? cppSense.GE:
return cppSense.LE
elif sense is Sense.Ge:
return cppSense.GE
elif sense is Sense.Ne:
return cppSense.NE
else:
raise RuntimeError(f"unexpected sense: {sense!r}")

Expand Down Expand Up @@ -85,6 +87,8 @@ cdef class cyConstraintsView:
return Le(lhs, rhs)
elif self.parent.cppcqm.constraint_ref(vi).sense() == cppSense.GE:
return Ge(lhs, rhs)
elif self.parent.cppcqm.constraint_ref(vi).sense() == cppSense.NE:
return Ne(lhs, rhs)
else:
raise RuntimeError("unexpected Sense")

Expand Down
16 changes: 11 additions & 5 deletions dimod/include/dimod/constraint.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

namespace dimod {

enum Sense { LE, GE, EQ };
enum Sense { LE, GE, EQ, NE };

enum Penalty { LINEAR, QUADRATIC, CONSTANT };

Expand Down Expand Up @@ -144,10 +144,16 @@ void Constraint<bias_type, index_type>::scale(bias_type scalar) {
base_type::scale(scalar);
rhs_ *= scalar;
if (scalar < 0) {
if (sense_ == Sense::LE) {
sense_ = Sense::GE;
} else if (sense_ == Sense::GE) {
sense_ = Sense::LE;
switch (sense_) {
case Sense::LE :
sense_ = Sense::GE;
break;
case Sense::GE :
sense_ = Sense::LE;
break;
case Sense::EQ:
case Sense::NE:
break;
}
}
}
Expand Down
1 change: 1 addition & 0 deletions dimod/libcpp/constrained_quadratic_model.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ cdef extern from "dimod/constrained_quadratic_model.h" namespace "dimod" nogil:
EQ
LE
GE
NE

enum Penalty:
LINEAR
Expand Down
7 changes: 6 additions & 1 deletion dimod/quadratic/quadratic_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
from dimod.serialization.fileview import SpooledTemporaryFile, _BytesIO
from dimod.serialization.fileview import VariablesSection, Section
from dimod.serialization.fileview import load, read_header, write_header
from dimod.sym import Eq, Ge, Le, Comparison
from dimod.sym import Eq, Ge, Le, Ne, Comparison
from dimod.typing import Variable, Bias, VartypeLike
from dimod.variables import Variables
from dimod.vartypes import Vartype, as_vartype
Expand Down Expand Up @@ -347,6 +347,11 @@ def __le__(self, other: Bias) -> Comparison:
return Le(self, other)
return NotImplemented

def __ne__(self, other: Number) -> Comparison:
if isinstance(other, Number):
return Ne(self, other)
return NotImplemented

@property
def dtype(self) -> np.dtype:
"""Data-type of the model's biases."""
Expand Down
10 changes: 10 additions & 0 deletions dimod/sym.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class Sense(enum.Enum):
* ``Le``: less or equal (:math:`\le`)
* ``Ge``: greater or equal (:math:`\ge`)
* ``Eq``: equal (:math:`=`)
* ``Ne``: not equal( :math:`\ne`)

Example:

Expand All @@ -43,6 +44,7 @@ class Sense(enum.Enum):
Le = '<='
Ge = '>='
Eq = '=='
Ne = '!='


class Comparison(abc.ABC):
Expand Down Expand Up @@ -96,3 +98,11 @@ class Ge(Comparison, sense='>='):

class Le(Comparison, sense='<='):
pass


class Ne(Comparison, sense='!='):
def __bool__(self):
try:
return not self.lhs.is_equal(self.rhs)
except AttributeError:
return False
11 changes: 11 additions & 0 deletions tests/test_constrained.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,17 @@ def test_duplicate(self):
with self.assertRaises(ValueError):
cqm.add_constraint(bqm <= 5, label='hello')

def test_ne(self):
cqm = dimod.CQM()

x = dimod.Binary('x')
i = dimod.Integer('i')

c0 = cqm.add_constraint(x != 1)
cqm.add_constraint(i != 1)

self.assertIs(cqm.constraints[c0].sense, dimod.sym.Sense.Ne)

def test_symbolic(self):
cqm = CQM()

Expand Down
50 changes: 50 additions & 0 deletions testscpp/tests/test_constrained_quadratic_model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,56 @@ TEST_CASE("Bug 0") {
}
}

TEST_CASE("Test Constraint::scale()") {
GIVEN("A CQM with several constraints") {
auto cqm = dimod::ConstrainedQuadraticModel<double>();
cqm.add_variables(Vartype::BINARY, 1);
auto c0 = cqm.add_linear_constraint({0}, {4}, Sense::EQ, 2);
auto c1 = cqm.add_linear_constraint({0}, {4}, Sense::LE, 2);
auto c2 = cqm.add_linear_constraint({0}, {4}, Sense::GE, 2);

cqm.constraint_ref(c0).set_offset(8);
cqm.constraint_ref(c1).set_offset(8);
cqm.constraint_ref(c2).set_offset(8);

WHEN("we scale the constraints by a positive number") {
cqm.constraint_ref(c0).scale(.5);
cqm.constraint_ref(c1).scale(.5);
cqm.constraint_ref(c2).scale(.5);

THEN("the biases are scaled and everything else stays the same") {
for (auto c = c0; c <= c2; ++c) {
CHECK(cqm.constraint_ref(c).offset() == 4);
CHECK(cqm.constraint_ref(c).linear(0) == 2);
CHECK(cqm.constraint_ref(c).rhs() == 1);
}

CHECK(cqm.constraint_ref(c0).sense() == Sense::EQ);
CHECK(cqm.constraint_ref(c1).sense() == Sense::LE);
CHECK(cqm.constraint_ref(c2).sense() == Sense::GE);
}
}

WHEN("we scale the constraints by a negative number") {
cqm.constraint_ref(c0).scale(-.5);
cqm.constraint_ref(c1).scale(-.5);
cqm.constraint_ref(c2).scale(-.5);

THEN("the biases are scaled and some of the signs flip") {
for (auto c = c0; c <= c2; ++c) {
CHECK(cqm.constraint_ref(c).offset() == -4);
CHECK(cqm.constraint_ref(c).linear(0) == -2);
CHECK(cqm.constraint_ref(c).rhs() == -1);
}

CHECK(cqm.constraint_ref(c0).sense() == Sense::EQ);
CHECK(cqm.constraint_ref(c1).sense() == Sense::GE); // flipped
CHECK(cqm.constraint_ref(c2).sense() == Sense::LE); // flipped
}
}
}
}

TEST_CASE("Test CQM.add_constraint()") {
GIVEN("A CQM and a BQM") {
auto cqm = dimod::ConstrainedQuadraticModel<double>();
Expand Down