Skip to content

Commit

Permalink
Merge pull request #1347 from alejomonbar/Adding_unbalanced_penalization
Browse files Browse the repository at this point in the history
Adding the unbalanced penalization method
  • Loading branch information
arcondello committed Aug 17, 2023
2 parents 76ba6e7 + a2668cb commit 06f6ab6
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 36 deletions.
92 changes: 56 additions & 36 deletions dimod/binary/binary_quadratic_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
from typing import (Any, BinaryIO, ByteString, Callable, Dict,
Hashable, Iterable, Iterator,
Mapping, MutableMapping, Optional, Sequence,
Tuple, Union,
Tuple, Union, Literal
)

import numpy as np
Expand Down Expand Up @@ -702,14 +702,15 @@ def add_linear_equality_constraint(
self.offset += lagrange_multiplier * constant * constant

def add_linear_inequality_constraint(
self, terms: Iterable[Tuple[Variable, int]],
lagrange_multiplier: Bias,
label: str,
constant: int = 0,
lb: int = np.iinfo(np.int64).min,
ub: int = 0,
cross_zero: bool = False
) -> Iterable[Tuple[Variable, int]]:
self, terms: Iterable[Tuple[Variable, int]],
lagrange_multiplier: Bias,
label: str,
constant: int = 0,
lb: int = np.iinfo(np.int64).min,
ub: int = 0,
cross_zero: bool = False,
penalization_method: Literal["slack", "unbalanced"] = "slack",
) -> Iterable[Tuple[Variable, int]]:
"""Add a linear inequality constraint as a quadratic objective.
The linear inequality constraint is of the form:
Expand Down Expand Up @@ -739,6 +740,10 @@ def add_linear_inequality_constraint(
Upper bound for the constraint.
cross_zero:
When True, adds zero to the domain of constraint.
penalization_method:
Whether to use slack variables or the unbalanced penalization method [1].
("slack", "unbalanced")
[1] https://arxiv.org/abs/2211.13914
Returns:
slack_terms: Values of :math:`\sum_{i} b_{i} slack_{i}` as an
Expand Down Expand Up @@ -772,35 +777,50 @@ def add_linear_inequality_constraint(
raise ValueError(
f'The given constraint ({label}) is infeasible with any value'
' for state variables.')

slack_upper_bound = int(ub_c - lb_c)
if slack_upper_bound == 0:
self.add_linear_equality_constraint(terms, lagrange_multiplier, -ub_c)
if penalization_method == "slack":
slack_upper_bound = int(ub_c - lb_c)
if slack_upper_bound == 0:
self.add_linear_equality_constraint(terms, lagrange_multiplier, -ub_c)
return []
else:
slack_terms = []
zero_constraint = False
if cross_zero:
if lb_c > 0 or ub_c < 0:
if ub_c-slack_upper_bound > 0:
zero_constraint = True

num_slack = int(np.floor(np.log2(slack_upper_bound)))
slack_coefficients = [2 ** j for j in range(num_slack)]
if slack_upper_bound - 2 ** num_slack >= 0:
slack_coefficients.append(slack_upper_bound - 2 ** num_slack + 1)

for j, s in enumerate(slack_coefficients):
sv = self.add_variable(f'slack_{label}_{j}')
slack_terms.append((sv, s))

if zero_constraint:
sv = self.add_variable(f'slack_{label}_{num_slack + 1}')
slack_terms.append((sv, ub_c - slack_upper_bound))

self.add_linear_equality_constraint(terms + slack_terms,
lagrange_multiplier, -ub_c)
return slack_terms

elif penalization_method == "unbalanced":
if not isinstance(lagrange_multiplier, Iterable):
raise TypeError('A list with two lagrange_multiplier are needed'
' for the unbalanced penalization method.')

for v, bias in terms:
self.add_linear(v, lagrange_multiplier[0] * bias)
self.offset += -ub_c
self.add_linear_equality_constraint(terms, lagrange_multiplier[1], -ub_c)

return []
else:
slack_terms = []
zero_constraint = False
if cross_zero:
if lb_c > 0 or ub_c < 0:
if ub_c-slack_upper_bound > 0:
zero_constraint = True

num_slack = int(np.floor(np.log2(slack_upper_bound)))
slack_coefficients = [2 ** j for j in range(num_slack)]
if slack_upper_bound - 2 ** num_slack >= 0:
slack_coefficients.append(slack_upper_bound - 2 ** num_slack + 1)

for j, s in enumerate(slack_coefficients):
sv = self.add_variable(f'slack_{label}_{j}')
slack_terms.append((sv, s))

if zero_constraint:
sv = self.add_variable(f'slack_{label}_{num_slack + 1}')
slack_terms.append((sv, ub_c - slack_upper_bound))

self.add_linear_equality_constraint(terms + slack_terms,
lagrange_multiplier, -ub_c)
return slack_terms
raise ValueError(f"The method {penalization_method} is not a valid method."
' Choose between ["slack", "unbalanced"]')

def add_linear_from_array(self, linear: Sequence):
"""Add linear biases from an array-like to a binary quadratic model.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
features:
- Add ``penalization_method`` parameter to ``BinaryQuadraticModel.add_linear_inequality_constraint()``. It allows the use of unbalanced penalization https://arxiv.org/abs/2211.13914 instead of the slack variables method for the inequality constraints.

18 changes: 18 additions & 0 deletions tests/test_bqm.py
Original file line number Diff line number Diff line change
Expand Up @@ -3643,6 +3643,24 @@ def test_inequality_equality(self, name, BQM):
self.assertEqual(bqm_equal, bqm1)
self.assertEqual(bqm_equal, bqm2)

@parameterized.expand(BQMs.items())
def test_inequality_constraint_unbalanced(self, name, BQM):
bqm = BQM('BINARY')
num_variables = 3
x = {}
for i in range(num_variables):
x[i] = bqm.add_variable('x_{i}'.format(i=i))
terms = iter([(x[i], 2.0) for i in range(num_variables)])
unbalanced_terms = bqm.add_linear_inequality_constraint(
terms, lagrange_multiplier=[1.0, 1.0], label='inequality0', constant=0.0, ub=5,
penalization_method="unbalanced")
self.assertTrue(len(unbalanced_terms) == 0)
for i in x:
self.assertEqual(bqm.get_linear(x[i]), -14.0)
for j in x:
if j > i:
self.assertEqual(bqm.get_quadratic(x[i], x[j]), 8.0)

@parameterized.expand(BQMs.items())
def test_simple_constraint_iterator(self, name, BQM):
bqm = BQM('BINARY')
Expand Down

0 comments on commit 06f6ab6

Please sign in to comment.