sisyphus is a piece of software I wrote the context of my graduate automation project. My class was taking part in the IEEE ICRA 2012 Virtual Manufacturing Automation Competition and part of the competition, was to efficiently stack boxes of different sizes on a pallet.
As the knapsack problem is NP hard, sisyphus implements hybrid between heuristic and bruteforce approach.
The heuristic is, to group articles of same height into layers, and then stack those layers on top of each other.
Different techniques to form layers and different orderings of stacking them are then tried out in a bruteforce manner.
get the code
$ git clone email@example.com:josch/sisyphus.git $ cd sisyphus
compile evaluation library
$ wget http://sourceforge.net/projects/moast/files/Pallet%20Viewer/Version%203/palletandtruckviewer-3.0.tar.gz $ tar xf palletandtruckviewer-3.0.tar.gz $ cd palletandtruckviewer-3.0 $ patch -p0 < ../palletViewer-sharedlib.diff $ autoreconf -fi $ ./configure --libdir=`pwd`/.. $ make $ make install-strip $ cd ..
test evaluation library (optional)
$ python evaluate_multi.py examples/icra2012/packlist_R1.xml examples/icra2012/scorecog.xml $ python evaluate.py examples/icra2012/Challenge2.order.xml examples/icra2012/packlist_R2.xml examples/icra2012/scorecog.xml $ python evaluate.py examples/icra2012/Challenge2.order.xml examples/icra2012/packlist_R3.xml examples/icra2012/scorecogoverlap.xml
generate packlist files
$ ./run.sh examples/icra2011/palDay1R1Order.xml packlist.xml examples/icra2011/scoreAsPlannedConfig1.xml
bruteforce2.py takes an order.xml and tries different strategies to create many lists of layerlists. Each layerlist is built with a different selection of strategies. Available strategies are article rotation and pallet rotation which can both be either true or false. Per default, bruteforce2.py tries all combinations of article and pallet rotations, which means that for each new layer, there are four possibilities how articles can be arranged in it.
A layerlist is the result of one specific combinations of strategies. For example: first layer with article rotation and pallet rotation, second layer without article rotation but with pallet rotation, third layer with neither and so forth.
bruteforce2.py outputs those layerlists one per line in python pickle gzipped and base64 encoded format. Python pickle is used as a fast serialization format. The data is gzipped because it is given to bruteforce3.py as a commandline argument and those must not exceed an OS specific value. The data is base64 encoded to avoid zero bytes, newlines and other whitespace characters in them.
As a result, the output of bruteforce2.py can not only be saved in a file, but you can also used split(1) on it to distribute it onto many machines for evaluations. You can also use head(1), tail(1), sort(1) or uniq(1) on the output.
bruteforce3.py takes as arguments an order.xml, a packlist.xml output file, a scoring.xml file and a number of layerlists, encoded as described above. For each layerlist it will try every possible permutation of how to place them on top of each other and evaluate each of them by using palletViewer.
Multiple instances of bruteforce3.py can be run on the same machine. Before bruteforce3.py exits, it will put a file lock on score_max.lock and read the currently highest score from score_max. If the just calculated score is higher, it will write its own score into score_max and update the contents of packlist.xml as well.
Since score_max always contains the currently highest score, it can always easily be checked during a run, what the currently highest score achieved is.
Since packlist.xml always contains the best packlist found so far, bruteforce3.py can always be aborted in the middle of execution, should it take too long to evaluate while still maintaining the current best packlist. One does not need to wait until run.sh finished execution.
Since score_max contains the highest score, it should be removed before each new run. This is already done by run.sh.
rot_article - try different article rotations (default: True) rot_pallet - try different pallet rotations (default: True) rot_article_default - default article rotation (default: False) rot_pallet_default - default pallet rotation (default: False) iterations - maximum number of iterations (default: -1) randomize - try random strategies instead of sequential (default: False)
multi_pallet - respect pallet max height and spread over multiple pallets (default: False) permutations - whether to try all layer permutations or not permute at all (default: True) iterations - maximum number of iterations (default: -1) randomize - try random strategies instead of sequential (default: False)
For orders that are too big to enumerate all possible permutations of layer strategies and orderings, using randomize and iterations for bruteforce2.py and bruteforce3.py is recommended.
The according line in run.sh could be changed to:
iterations=100 randomize=1 python bruteforce2.py $1 | randomize=1 iterations=1000 xargs --max-procs=4 --max-args=1 python bruteforce3.py $1 $2 $3
add_approach_points.py takes a single or multi pallet packlist.xml and adds the three approach points to all the articles in it, relative to each articles position barcodes.py takes an order.xml and prints all barcodes in it densities.py takes an order.xml and prints the density of all articles descriptions.py takes an order.xml and prints a table with the description, ID, type, family, size and weight of every article evaluate_multi.py takes a multiple pallet packlist.xml and a scoring.xml and prints out the score that this packlist achieves evaluate.py takes an order.xml, a single pallet packlist.xml and a scoring.xml and prints out the score that this packlist achieves num_articles_order.py takes an order.xml and prints the number of articles within num_articles_packlist.py takes a single or multi pallet packlist.xml and prints the number of articles within pallets.py takes an order.xml and prints a table with the length, width, maximum load height and maximum load weight split_multi.py takes a multiple pallet packlist.xml and outputs pairs of single pallet packlist.xml and order.xml files by appending _$i.xml and _order_$i.xml to the original packlist.xml filename, respectively. The integer $i starts at 0 and will be incremented with each subsequent pallet.
evaluation of multi pallet packlists
palletViewer cannot parse multi pallet packlists, hence the packlists have to be split into several single pallet packlists. Each of those packlists is then given to palletViewer for evaluation. The individual results are averaged for a final score. To avoid each of the palletViewer instances complain about missing articles, an according order.xml file is created for each single pallet packlist.xml.
Multi pallet packlists can evaluated like this:
python evaluate_multi.py packlist.xml scoring.xml
Or manually like this:
python split_multi.py packlist.xml python evaluate.py packlist.xml_order_0.xml packlist_R1.xml_0.xml scoring.xml python evaluate.py packlist.xml_order_1.xml packlist_R1.xml_1.xml scoring.xml ...
there is a "memory leak" in bruteforce3.py. Every loop iteration where a new permutation is tried out will stay in memory. This is not needed and should not be the case. This is the reason why run.sh supplies bruteforce3.py with only one layerlist at a time because otherwise, memory consumption would grow too big.
ICRA 2012 VMAC
Sisyphus won the IEEE ICRA 2012 Virtual Manufacturing Automation Competition. Here is some media about our submission: