Skip to content

Commit

Permalink
Add quadratic multi knapsack generator
Browse files Browse the repository at this point in the history
  • Loading branch information
k8culver committed Dec 18, 2024
1 parent 7de900c commit 24429b3
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 3 deletions.
74 changes: 71 additions & 3 deletions dimod/generators/multi_knapsack.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from dimod.constrained import ConstrainedQuadraticModel
from dimod.typing import ArrayLike

__all__ = ['multi_knapsack', 'random_multi_knapsack']
__all__ = ['multi_knapsack', 'quadratic_multi_knapsack', 'random_multi_knapsack']


def multi_knapsack(values: ArrayLike,
Expand All @@ -42,7 +42,7 @@ def multi_knapsack(values: ArrayLike,
A constrained quadratic model encoding the multiple-knapsack problem.
Variables are labelled as ``x_{i}_{j}``, where ``x_{i}_{j} == 1`` means
that item ``i`` is placed in bin ``j``.
that item ``i`` is placed in knapsack ``j``.
"""
values = np.asarray(values)
Expand Down Expand Up @@ -76,6 +76,74 @@ def multi_knapsack(values: ArrayLike,
return model


def quadratic_multi_knapsack(values: ArrayLike,
weights: ArrayLike,
profits: ArrayLike,
capacities: ArrayLike,
) -> ConstrainedQuadraticModel:
"""Generate a constrained quadratic model encoding a quadratic multiple
knapsack problem.
The quadratic multiple knapsack problem seeks to fit the most value into each
knapsack of weight less than or equal to each knapsack's capacity and maximize
profits associated with adding any two items to the same knapsack.
Args:
values: A list of each item's value.
weights: A list of each item's associated weight.
profits: A matrix where entry (i, j) is the value of adding items i and j together.
capacities: A list of the maximum weights each knapsack can hold.
Returns:
A constrained quadratic model encoding the quadratic multiple knapsack problem.
Variables are labelled as ``x_{i}_{j}``, where ``x_{i}_{j} == 1`` means
that item ``i`` is placed in knapsack ``j``.
"""
values = np.asarray(values)
weights = np.asarray(weights)
profits = np.asarray(profits)

if values.shape != weights.shape:
raise ValueError("`values` and `weights` must have the same shape")

if not np.array_equal(profits, profits.T):
raise ValueError("`profits` must be symmetric")

if values.shape[0] != profits.shape[0]:
raise ValueError("`profits` must have an entry for each pair of items")

model = ConstrainedQuadraticModel()
obj = BinaryQuadraticModel(vartype='BINARY')
x = {(i, j): obj.add_variable(f'x_{i}_{j}') for i in range(values.shape[0]) for j in range(len(capacities))}

for i, value in enumerate(values):
for j in range(len(capacities)):
obj.set_linear(x[(i, j)], -value)

for i, profit in np.ndenumerate(profits):
if i[0] < i[1]:
for j in range(len(capacities)):
obj.set_quadratic(x[i[0], j], x[i[1], j], -profit)

model.set_objective(obj)

# Each item at most goes to one bin.
for i in range(values.shape[0]):
model.add_constraint(
[(x[(i, j)], 1) for j in range(len(capacities))] + [(-1,)],
sense="<=", label='item_placing_{}'.format(i))

# Build knapsack capacity constraints
for j, capacity in enumerate(capacities):
model.add_constraint(
[(x[(i, j)], weight) for i, weight in enumerate(weights)] + [(-capacity,)],
sense="<=", label='capacity_bin_{}'.format(j))

return model


def random_multi_knapsack(num_items: int,
num_bins: int,
seed: int = 32,
Expand All @@ -101,7 +169,7 @@ def random_multi_knapsack(num_items: int,
A constrained quadratic model encoding the multiple-knapsack problem.
Variables are labelled as ``x_{i}_{j}``, where ``x_{i}_{j} == 1`` means
that item ``i`` is placed in bin ``j``.
that item ``i`` is placed in knapsack ``j``.
"""

Expand Down
1 change: 1 addition & 0 deletions docs/reference/generators.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ Optimization
maximum_weight_independent_set
mimo
multi_knapsack
multi_quadratic_knapsack
quadratic_assignment
quadratic_knapsack
random_bin_packing
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
features:
- |
Add ``quadratic_multi_knapsack`` generator function to ``dimod.generators``.
19 changes: 19 additions & 0 deletions tests/test_generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,25 @@ def test_model(self):
self.assertEqual(len(cqm.variables), num_items*num_bins)
self.assertEqual(len(cqm.constraints), num_bins+num_items)

def test_quadratic_exceptions(self):
with self.assertRaises(ValueError):
dimod.generators.quadratic_multi_knapsack([1], [], [], [])

with self.assertRaises(ValueError):
dimod.generators.quadratic_multi_knapsack([1], [1], [[1, 1]], [])

with self.assertRaises(ValueError):
dimod.generators.quadratic_multi_knapsack([1], [1], [[1, 1], [1, 1]], [])

def test_quadratic_model(self):
values = [1, 2, 3]
weights = [3, 2, 1]
profits = [[1, 2, 3], [2, 2, 2], [3, 2, 1]]
capacity = [1, 2]
cqm = dimod.generators.quadratic_multi_knapsack(values, weights, profits, capacity)
self.assertEqual(len(cqm.variables), len(values)*len(capacity))
self.assertEqual(len(cqm.constraints), len(weights)+len(capacity))


class TestGates(unittest.TestCase):
def test_gates_no_aux(self):
Expand Down

0 comments on commit 24429b3

Please sign in to comment.