Skip to content

Commit

Permalink
Add MTHG for Generalized Assignment Problem (#29)
Browse files Browse the repository at this point in the history
* Add MTHG for Generalized Assignment Problem

* Correct README example order

* Fix README typo
  • Loading branch information
jmyrberg authored Aug 6, 2022
1 parent 2da591e commit 5ed14c9
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 59 deletions.
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Solving knapsack problems with Python using algorithms by [Martello and Toth](ht
* Multiple 0-1 knapsack problem: MTM, MTHM
* Change-making problem: MTC2
* Bounded change-making problem: MTCB
* Generalized assignment problem: MTG
* Generalized assignment problem: MTG, MTHG

Documentation is available [here](https://mknapsack.readthedocs.io).

Expand Down Expand Up @@ -141,19 +141,19 @@ res = solve_bounded_change_making(weights, n_items, capacity)
```python
from mknapsack import solve_generalized_assignment

# Given seven item types with the following knapsack dependent weights:
weights = [[4, 1, 2, 1, 4, 3, 8],
[9, 9, 8, 1, 3, 8, 7]]

# ...and the following knapsack dependent weights:
# Given seven item types with the following knapsack dependent profits:
profits = [[6, 9, 4, 2, 10, 3, 6],
[4, 8, 9, 1, 7, 5, 4]]

# ...and two knapsack with the following capacities:
# ...and the following knapsack dependent weights:
weights = [[4, 1, 2, 1, 4, 3, 8],
[9, 9, 8, 1, 3, 8, 7]]

# ...and two knapsacks with the following capacities:
capacities = [11, 22]

# Assign items into the knapsacks while maximizing profits
res = solve_generalized_assignment(weights, profits, capacities)
res = solve_generalized_assignment(profits, weights, capacities)
```


Expand Down
2 changes: 2 additions & 0 deletions mknapsack/_algos.f
Original file line number Diff line number Diff line change
Expand Up @@ -7854,6 +7854,8 @@ subroutine mthg ( n, m, p, w, c, minmax, z, xstar, jck )
c parameters are unchanged, but p(i,j) is set to 0 for all pairs
c (i,j) such that w(i,j) .gt. c(i) .
c
cf2py intent(in) n, m, p, w, c, minmax, jck
cf2py intent(out) z, xstar
integer p(50,500),w(50,500),c(50),xstar(500),z
integer zm
integer best(500)
Expand Down
7 changes: 7 additions & 0 deletions mknapsack/_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@ class FortranInputCheckError(Exception):
-5: 'One or more of weights is greater than knapsack capacity',
-6: 'One or more knapsacks cannot fit any items',
-7: 'Number of branching trees is too small for the problem size'
},
'mthg': {
-1: 'Number of knapsacks is less than 2',
-2: 'Number of items is less than 2',
-3: 'Profit, weight, or capacity is <= 0',
-4: 'One or more of weights is greater than knapsack capacity',
-5: 'One or more knapsacks cannot fit any items'
}
}

Expand Down
80 changes: 63 additions & 17 deletions mknapsack/_generalized_assignment.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import numpy as np

from mknapsack._algos import mtg
from mknapsack._algos import mtg, mthg
from mknapsack._exceptions import FortranInputCheckError, NoSolutionError, \
ProblemSizeError
from mknapsack._utils import preprocess_array, pad_array
Expand All @@ -21,7 +21,7 @@ def solve_generalized_assignment(
weights: List[List[int]],
capacities: List[int],
maximize: bool = True,
method: str = 'mtg',
method: str = 'mthg',
method_kwargs: Optional[dict] = None,
verbose: bool = False
) -> np.ndarray:
Expand All @@ -41,15 +41,22 @@ def solve_generalized_assignment(
method:
Algorithm to use for solving, should be one of
- 'mtg' - provides a fast heuristical solution that might not
be the global optimum, but is suitable for larger problems,
or an exact solution if required
- 'mtg' - provides a fast heuristical solution or an exact
solution if required, but is not the most suitable for larger
problems
The problem size is limited as follows:
* Number of knapsacks <= 10
* Number of items <= 100
Defaults to 'mtg'.
- 'mthg' - provides a fast heuristical solution that might not
be the global optimum, but is suitable for larger problems
The problem size is limited as follows:
* Number of knapsacks <= 50
* Number of items <= 500
Defaults to 'mthg'.
method_kwargs:
Keyword arguments to pass to a given `method`.
Expand All @@ -62,6 +69,10 @@ def solve_generalized_assignment(
* **check_inputs** (int, optional) - Whether to check
inputs or not (0=no, 1=yes). Defaults to 1.
- 'mthg'
* **check_inputs** (int, optional) - Whether to check
inputs or not (0=no, 1=yes). Defaults to 1.
Defaults to None.
Returns:
Expand All @@ -81,10 +92,10 @@ def solve_generalized_assignment(
from mknapsack import solve_generalized_assignment
res = solve_generalized_assignment(
weights=[[4, 1, 2, 1, 4, 3, 8],
[9, 9, 8, 1, 3, 8, 7]],
profits=[[6, 9, 4, 2, 10, 3, 6],
[4, 8, 9, 1, 7, 5, 4]],
weights=[[4, 1, 2, 1, 4, 3, 8],
[9, 9, 8, 1, 3, 8, 7]],
capacities=[11, 22],
maximize=True
)
Expand Down Expand Up @@ -117,17 +128,17 @@ def solve_generalized_assignment(
raise ValueError('Profits length must be equal to weights '
f'({n_profits != n_weights}')

# The problem size is limited on Fortran side
error_msg = (
'The number of {0} {1} exceeds {2}, and there is no guarantee '
'that the algorithm will work as intended or provides a solution '
'at all')

method = method.lower()
method_kwargs = method_kwargs or {}
if method == 'mtg':
maxm = 10
maxn = 100

# The problem size is limited on Fortran side
error_msg = (
'The number of {0} {1} exceeds {2}, and there is no guarantee '
'that the algorithm will work as intended or provides a solution '
'at all')
if m > maxm:
raise ProblemSizeError(error_msg.format('knapsacks', m, maxm))
if n > maxn:
Expand All @@ -152,9 +163,7 @@ def solve_generalized_assignment(
back=back,
jck=method_kwargs.get('check_inputs', 1)
)
print(z)
print(x)
print(jb)

if z == 0:
raise NoSolutionError('No feasible solution found')
elif z < 0:
Expand All @@ -166,6 +175,43 @@ def solve_generalized_assignment(
logger.info(f'Solution vector: {x}')
bound_name = 'Upper' if maximize else 'Lower'
logger.info(f'{bound_name} bound: {jb}')
elif method == 'mthg':
maxm = 50
maxn = 500

# The problem size is limited on Fortran side
error_msg = (
'The number of {0} {1} exceeds {2}, and there is no guarantee '
'that the algorithm will work as intended or provides a solution '
'at all')
if m > maxm:
raise ProblemSizeError(error_msg.format('knapsacks', m, maxm))
if n > maxn:
raise ProblemSizeError(error_msg.format('items', n, maxn))

p = pad_array(profits, (maxm, maxn))
w = pad_array(weights, (maxm, maxn))
c = pad_array(capacities, maxm)

z, x = mthg(
n=n,
m=m,
p=p,
w=w,
c=c,
minmax=2 if maximize else 1,
jck=method_kwargs.get('check_inputs', 1)
)

if z == 0:
raise NoSolutionError('No feasible solution found')
elif z < 0:
raise FortranInputCheckError(method=method, z=z)

if verbose:
logger.info(f'Method: "{method}"')
logger.info(f'Total profit: {z}')
logger.info(f'Solution vector: {x}')
else:
raise ValueError(f'Given method "{method}" not known')

Expand Down
Loading

0 comments on commit 5ed14c9

Please sign in to comment.