0
$\begingroup$

For a long-done project presentation, I implemented the Primal Effective Capacity (PECH) Heuristic to look for initial greedy solutions for the Multidimensional Knapsack (MDK) problem in Python. I looked into this specific heuristic because the class I was presenting to wanted a simple initial algorithm that was easy to understand, but also because my main PC only has 3GB of RAM and I needed in-place solving heuristic due to this limitation - loading large MDK problems into DoCplex always crashed my PC due to Mem Errors. Regardless, the definition of the MDK problem I was using is as follows:

$$\max z = \sum_{i\in N}c_ix_i$$

$$\text{Subject to: }\qquad\qquad\qquad\qquad\qquad\qquad$$ $$\sum_{i\in N}w_{ji}x_i\le b_j,\quad\forall j\in m$$ Where $N$ denotes the total number of items $x_i$, $m$ denotes the total number of bags $b_j$, $c_i$ denotes the cost of using item $x_i$, $𝑤_{ji}$ denotes the weight of using item $x_i$ in bag $b_j$. There are two formulations for $x_i$ that I am solving for each problem in which $x_i$ can either be binary or an integer.

The original steps of the PECH algorithm is as follows:

  • Select a $\alpha \in [0,1]$
  • Initialize all decision variables, $x_i=0$, for all $i$
  • Initialize all the Effective Capacity, $E$, to be the set of all indices of $x_i$
  • Then do the following while True:
    • Compute the effective capacity for all items $\bar y_i = \min_j \left(\lfloor \frac{\bar b_j}{w_{ji}}\rfloor : w_{ji} > 0 \right)\forall i\in E$

    • If $\bar y_i = 0\quad\forall i \in E$, then end the loop.

    • Compute the improvement for all items: $c_i\cdot \bar y_i,\quad\forall i\in E$, then select the best improvement $i^* = \arg\max_{i\in E}(c_i\cdot \bar y_i)$

    • Compute the improvement of the best element $i^*$: $y_{i^*}=\min(\bar u_{i^*}, \max(1, \lfloor \alpha\cdot\bar y_{i^*}\rfloor))$

    • Update the improving decision variable: $x_{i^*} = x_{i^*} + y_{i^*}$

    • Update the remaining capacities of each bag: $b_j = b_j - w_{ji^*}\cdot y_{i^*},\quad\forall j$

    • Update upper bounds: $u_{i^*} = u_{i^*}-y_{i^*}$

    • If $u_{i^*} = 0$ or $\alpha = 1$, then remove $i^*$ from $E$, and if $E$ is empty, then end the loop.

Report the final solution obtained.

Because I did not know at the time how to safely hot-remove indices $i^*$ from $E$ while not ruining the step involving $i^*$: $y_{i^*}=\min(\bar u_{i^*}, \max(1, \lfloor \alpha\cdot\bar y_{i^*}\rfloor))$, I changed the algorithm to become the following:

  • Select a $\alpha \in [0,1]$
  • Initialize all decision variables, $x_i=0$, for all $i$
  • Initialize all the Effective Capacity, $E$, to be the set of all indices of $x_i$
  • Initialize a integer counter $t$ with $t=0$
  • Initialize a largest number $M$.
  • Then do the following while True:
    • (all of these steps are the same)

    • If $u_{i^*} = 0$ or $\alpha = 1$, then set $c_{i^*} = -M$ and $t=t+1$, and if $t$ equals the amount of elements in $E$, then end the loop.

Report the final solution obtained.

I ended up figuring out later how to delete indexes from arrays, but one thing I noticed is that if I followed their original algorithm, I got a substantially much worse solution than the one I modified (both solutions are feasible). For example, for a binary 100 items and 5 bags knapsack problem, DoCplex reports an objective of $59822$, my modified PECH reports an objective of $58386$, and their original reports $37896$.

My question is this: Why did this simple change derive a better solution? Effectively, they both should be doing the same thing. I've been strongly thinking it was my code/implementation, but even when I had my professor and colleagues look at it and they said they didn't know and didn't see anything wrong or wierd (aside from the objective values).

$\endgroup$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Browse other questions tagged or ask your own question.