working first implementation

This commit is contained in:
josch 2012-05-02 11:12:10 +02:00
parent 14ffc19da8
commit 41e5ec2706
5 changed files with 717 additions and 0 deletions

318
arrange_spread2.py Normal file
View file

@ -0,0 +1,318 @@
import svg
import random
# rounds to the next biggest even number
def roundeven(num):
return (num+1)/2*2
def arrange_in_layer(abin, pwidth, pheight, rot_article=False):
# articles are longer than wider
# default rotation: width: x-direction
# height: y-direction
layer = list()
rest = list()
root = {'x': 0, 'y': 0,
'width': pwidth, 'height': pheight,
'article': None,
'down': None,
'right': None}
# traverse the tree until a node is found that is big enough for article
# with size width x height and return this node or None if not found
def find_node(root, width, height):
if root is None:
return None
elif root['article']:
return (find_node(root['right'], width, height)
or find_node(root['down'], width, height))
elif width <= root['width'] and height <= root['height']:
return root
else:
return None
# after finding a node where an article fits, put article into the node and
# create childnodes
def split_node(node, width, height, article):
node['article'] = article
node['article']['PlacePosition']['X'] = node['x']+width/2
node['article']['PlacePosition']['Y'] = node['y']+height/2
if node['width'] > 0 and node['height']-height > 0:
node['down'] = {'x': node['x'], 'y': node['y']+height,
'width': node['width'],
'height': node['height']-height,
'article': None,
'down': None,
'right': None}
else:
node['down'] = None
if node['width']-width > 0 and height > 0:
node['right'] = {'x': node['x']+width, 'y': node['y'],
'width': node['width']-width,
'height': height,
'article': None,
'down': None,
'right': None}
else:
node['right'] = None
return node
# for each article in abin, check and place article at a node. If it doesnt
# fit, try to rotate. If it still doesnt fit, append to rest
for article in abin:
# output format only accepts integer positions, round article sizes up
# to even numbers
owidth, oheight = article['Article']['Length'], article['Article']['Width']
if rot_article:
article['Orientation'] = 2
width, height = roundeven(oheight), roundeven(owidth)
else:
article['Orientation'] = 1
width, height = roundeven(owidth), roundeven(oheight)
node = find_node(root, width, height)
if (node):
node = split_node(node, width, height, article)
else:
# rotate article
# output format only accepts integer positions, round article sizes up
# to even numbers
if rot_article:
article['Orientation'] = 1
width, height = roundeven(owidth), roundeven(oheight)
else:
article['Orientation'] = 2
width, height = roundeven(oheight), roundeven(owidth)
node = find_node(root, width, height)
if (node):
node = split_node(node, width, height, article)
else:
# rotate back
if rot_article:
article['Orientation'] = 2
else:
article['Orientation'] = 1
rest.append(article)
# gather all articles that were
def find_articles(node):
if not node['article']:
return
layer.append(node['article'])
if node['right']:
find_articles(node['right'])
if node['down']:
find_articles(node['down'])
find_articles(root)
return root, layer, rest
# generate a list of random articles of three different type
# each type is of the same size and color
# numbers of articles of each type linearly depend on their area
# articles are generated with more width that height
def generate_bin():
abin = []
for i in 1,2,3:
w, h = random.randint(20,150), random.randint(20,150)
if h > w:
w, h = h, w
color = random.randint(0,255), random.randint(0,255), random.randint(0,255)
for j in range(200000/(w*h)):
abin.append({'x':0, 'y':0, 'width':w, 'height':h, 'color':color})
return abin
# given a node tree with articles inside, spread them out over the full
# available area evenly
def spread_articles(root):
def get_width(article):
if article['Orientation'] == 1:
return article['Article']['Length']
else:
return article['Article']['Width']
def get_height(article):
if article['Orientation'] == 1:
return article['Article']['Width']
else:
return article['Article']['Length']
# get only nodes on the left branch of the tree. This is all nodes below
def get_down_nodes(node):
if node is None or not node['article']:
return []
else:
return [node] + get_down_nodes(node['down'])
# move this node and its whole subtree down
def move_tree_down(node, y):
if not node['article']:
return
node['article']['PlacePosition']['Y'] += y
if node['right']:
move_tree_down(node['right'], y)
if node['down']:
move_tree_down(node['down'], y)
# for each child on the very left, spread vertically and adjust subtree y
# position accordingly
def spread_vertically(node):
downnodes = get_down_nodes(node)
# process innermost nodes before outer nodes
for n in downnodes:
if n['right']:
spread_vertically(n['right'])
if len(downnodes) == 0:
return
elif len(downnodes) == 1:
# arrange them in the center of parent
# treat the article height as even and round the gap size to the
# next smallest even number
gap = (node['height']-roundeven(get_height(downnodes[0]['article'])))/4*2
move_tree_down(node, gap)
else:
# get the sum of all heights of the leftmodes articles as if they
# had even heights
sumdownnodes = sum([roundeven(get_height(n['article'])) for n in downnodes])
# do some fancy math to figure out even gap sizes between
# the nodes
d, m = divmod((node['height']-sumdownnodes)/2, len(downnodes)-1)
gaps = (m)*[(d+1)*2]+((len(downnodes)-1)-m)*[d*2]
# iteratively move trees down by vgap except for first row
for node, gap in zip(downnodes[1:], gaps):
move_tree_down(node, gap)
# for a given node, return a tuple consisting of a list of nodes that
# make out the longest row in horizontal direction and a list of nodes that
# start a shorter end
def get_max_horiz_nodes(node):
if node is None or not node['article']:
return [], []
elif node['down'] and node['down']['article']:
# if the node has an article below, check out the rightbranch
rightbranch, sr = get_max_horiz_nodes(node['right'])
rightbranch = [node] + rightbranch
# as well as the down branch
downbranch, sd = get_max_horiz_nodes(node['down'])
# get information about the last article in each branch
ar = rightbranch[len(rightbranch)-1]['article']
ad = downbranch[len(downbranch)-1]['article']
# and return as the first tuple entry the branch that stretches the
# longest while having as the second tuple entry the nodes that
# were dismissed as starting shorter branches
if ar['PlacePosition']['X']+get_width(ar)/2 > ad['PlacePosition']['X']+get_width(ad)/2:
return rightbranch, sr+[downbranch[0]]
else:
return downbranch, sd+[rightbranch[0]]
else:
# if there is no article below, just recursively call itself on the
# next node to the right
rightbranch, short = get_max_horiz_nodes(node['right'])
return [node] + rightbranch, short
# move a node and the article inside to the right and reduce node width
# recursively call for children
def move_tree_right(node, x):
if not node['article']:
return
node['article']['PlacePosition']['X'] += x
node['x'] += x
node['width'] -= x
if node['right']:
move_tree_right(node['right'], x)
if node['down']:
move_tree_right(node['down'], x)
# for each child on the very right, spread horizontally and adjust subtree
# x position accordingly
def spread_horizontally(node):
maxhoriznodes, short = get_max_horiz_nodes(node['right'])
maxhoriznodes = [node] + maxhoriznodes
if len(maxhoriznodes) == 0:
return
elif len(maxhoriznodes) == 1:
# arrange them in the center of parent
# treat article width as even and round the gap size to the next
# smallest even number
gap = (node['width']-roundeven(get_width(maxhoriznodes[0]['article'])))/4*2
maxhoriznodes[0]['article']['PlacePosition']['X'] += gap
else:
# get the sum of all widths of the articles that make the longest
# row of articles as if they had even widths
summaxhoriznodes= sum([roundeven(get_width(n['article'])) for n in maxhoriznodes])
# do some fancy math to figure out even gap sizes between the nodes
d, m = divmod((node['width']-summaxhoriznodes)/2, len(maxhoriznodes)-1)
gaps = (m)*[(d+1)*2]+((len(maxhoriznodes)-1)-m)*[d*2]
# iteratively move trees right by hgap except for first node
for node, gap in zip(maxhoriznodes[1:], gaps):
move_tree_right(node, gap)
# recursively call for all nodes starting a shorter subtree
for node in short:
spread_horizontally(node)
# spread nodes vertically
spread_vertically(root)
# and horizontally
for node in get_down_nodes(root):
spread_horizontally(node)
# sanity checks
def sanity_check(layer):
def intersects(a1, a2):
return (a1['x'] < a2['x']+a2['width']
and a1['x']+a1['width'] > a2['x']
and a1['y'] < a2['y']+a2['height']
and a1['y']+a1['height'] > a2['y'])
odds = list()
overhangs = list()
inters = list()
for article1 in layer:
if (article1['x']%2 != 0
or article1['y']%2 != 0):
odds.append(article1)
if (article1['x'] < 0
or article1['y'] < 0
or article1['x']+article1['width'] > pwidth
or article1['y']+article1['height'] > pheight):
overhangs.append(article1)
for article2 in layer:
if article1 == article2:
continue
if intersects(article1, article2):
inters.append((article1, article2))
for odd in odds:
print "odd:", odd
for overhang in overhangs:
print "overhang:", overhang
for inter in inters:
print "intersect:", inter
if len(odds) or len(overhangs) or len(inters):
print layer
exit(1)
# draw layer of articles
def draw_layer(filename, layer):
scene = svg.Scene(filename, (pwidth, pheight))
for a in layer:
scene.add(svg.Rectangle((a['x'], a['y']),(a['width'], a['height']),a['color']))
scene.write()
# return all articles in a node tree
def find_articles(node):
if node is None or not node['article']:
return []
else:
return [node['article']] + find_articles(node['right']) + find_articles(node['down'])
if __name__ == "__main__":
abin = generate_bin()
pwidth, pheight = 800, 600
abin = sorted(abin, key=lambda article: article['width']*article['height'], reverse=True)
root, layer, rest = arrange_in_layer(abin, pwidth, pheight)
draw_layer('test1', layer)
spread_articles(root)
layer = find_articles(root)
draw_layer('test2', layer)
sanity_check(layer)

79
bruteforce2.py Normal file
View file

@ -0,0 +1,79 @@
import sys
import subprocess
import itertools
import shutil
from util import xmlfiletodict, dicttoxmlfile, get_pallet, get_articles, get_packlist_dict
from arrange_spread2 import arrange_in_layer, spread_articles, find_articles
import cPickle
from binascii import b2a_base64
def get_layers(bins, pallet, rot_article=False, rot_pallet=False):
for abin in bins:
bins[abin] = sorted(bins[abin], key=lambda article: article['Article']['Length']*article['Article']['Width'], reverse=True)
plength, pwidth = (pallet['Dimensions']['Length'], pallet['Dimensions']['Width'])
root, layer, rest = arrange_in_layer(bins[abin], plength, pwidth, rot_article=rot_article)
while layer:
spread_articles(root)
occupied_area = 0
for article in layer:
length, width = article['Article']['Length'], article['Article']['Width']
occupied_area += length*width
# print "layer occupation:", occupied_area/float(plength*pwidth)
if occupied_area/float(plength*pwidth) <= 0.7:
rot_article, rot_pallet = (yield None, layer)
else:
rot_article, rot_pallet = (yield layer, None)
root, layer, rest = arrange_in_layer(rest, plength, pwidth, rot_article=rot_article)
def main():
if len(sys.argv) != 3:
print "usage:", sys.argv[0], "order.xml packlist.xml"
exit(1)
orderline = xmlfiletodict(sys.argv[1])
pallet = get_pallet(orderline)
articles = get_articles(orderline)
bins = dict()
for article in articles:
abin = bins.get(article['Article']['Height'])
if abin:
abin.append(article)
else:
bins[article['Article']['Height']] = [article]
scores = list()
stuff1 = list()
#for order in itertools.product([True, False], repeat=12):
for order in [[True]*12,]:
rests = list()
layers = list()
it = get_layers(bins, pallet, order[0], False)
layer, rest = it.next()
if layer:
layers.append(layer)
if rest:
rests.append(rest)
fail = True
for rot_article in order[1:]:
try:
layer, rest = it.send((rot_article, False))
if layer:
layers.append(layer)
if rest:
rests.append(rest)
except StopIteration:
fail = False
break
if fail:
raise Exception("finished early")
print b2a_base64(cPickle.dumps((layers, rests, pallet))),
if __name__ == "__main__":
main()

81
bruteforce3.py Normal file
View file

@ -0,0 +1,81 @@
import sys
import subprocess
import itertools
import shutil
from util import xmlfiletodict, dicttoxmlfile, get_pallet, get_articles, get_packlist_dict
from arrange_spread2 import arrange_in_layer, spread_articles, find_articles
import cPickle
import marshal
from binascii import a2b_base64
import tempfile
import os
def evaluate_layers_rests(layers, rests, scores, pallet):
rest_layers = list()
# sort rests by space they cover and move them to the center of the pile
# append them to the layer list
for rest in sorted(rests, key=lambda rest: sum([article['Article']['Length']*article['Article']['Width'] for article in rest]), reverse=True):
plength, pwidth = (pallet['Dimensions']['Length'], pallet['Dimensions']['Width'])
root, layer, rest = arrange_in_layer(rest, plength, pwidth)
com_x = 0
com_y = 0
for article in layer:
com_x += article['PlacePosition']['X']
com_y += article['PlacePosition']['Y']
com_x, com_y = com_x/len(layer), com_y/len(layer)
diff_x, diff_y = plength*0.5-com_x, pwidth*0.5-com_y
for article in layer:
article['PlacePosition']['X'] += diff_x
article['PlacePosition']['Y'] += diff_y
rest_layers.append(layer)
for permut_layers in itertools.permutations(layers):
pack_sequence = 1
pack_height = 0
articles_to_pack = list()
for layer in list(permut_layers)+rest_layers:
pack_height += layer[0]['Article']['Height']
#if pack_height > pallet['Dimensions']['MaxLoadHeight']:
# break
for article in layer:
article['PackSequence'] = pack_sequence
article['PlacePosition']['Z'] = pack_height
articles_to_pack.append(article)
pack_sequence += 1
packlist = get_packlist_dict(pallet, articles_to_pack)
_, tmp = tempfile.mkstemp()
dicttoxmlfile(packlist, tmp)
# ugly, ugly, ugly, ugly hack - dont copy this...
score = float(subprocess.check_output("../palletandtruckviewer-3.0/palletViewer -o "
+sys.argv[1]+" -p "+tmp
+" -s ../icra2011TestFiles/scoreAsPlannedConfig1.xml --headless | grep Score", shell=True).split(' ')[1].strip())
scores.append(score)
if score > max(scores+[0]):
shutil.move(tmp, sys.argv[2])
else:
os.remove(tmp)
def main():
scores = list()
for arg in sys.argv[3:]:
layers, rests, pallet = cPickle.loads(a2b_base64(arg))
evaluate_layers_rests(layers, rests, scores, pallet)
print max(scores)
#print "max:", max(scores)
#print "min:", min(scores)
#mean = sum(scores)/len(scores)
#print "mean:", mean
#from math import sqrt
#print "stddev:", sqrt(sum([(x-mean)**2 for x in scores])/len(scores))
if __name__ == "__main__":
main()

42
svg.py Normal file
View file

@ -0,0 +1,42 @@
import os
class Scene:
def __init__(self, name, size):
self.name = name
self.items = []
self.size = size
def add(self, item):
self.items.append(item)
def svgstr(self):
svgstr = "<?xml version=\"1.0\"?>\n"
svgstr += "<svg width=\"%d\" height=\"%d\">\n"%self.size
svgstr += " <g style=\"fill-opacity:1.0; stroke:black; stroke-width:1;\">\n"
for item in self.items:
svgstr += item.svgstr()
svgstr += " </g>\n</svg>\n"
return svgstr
def write(self, filename=None):
if not filename:
filename = self.name + ".svg"
with open(filename, "w") as f:
f.write(self.svgstr())
class Rectangle:
def __init__(self, pos, size, color):
self.pos = pos
self.size = size
self.color = color
def svgstr(self):
svgstr = " <rect x=\"%d\" y=\"%d\" "%self.pos
svgstr += "width=\"%d\" height=\"%d\" "%self.size
svgstr += "style=\"fill:#%02x%02x%02x;\"/>\n"%self.color
return svgstr
if __name__ == "__main__":
scene = Scene('test', (400, 400))
scene.add(Rectangle((0,0),(100,100),(255,0,0)))
scene.write()

197
util.py Normal file
View file

@ -0,0 +1,197 @@
from xml.etree import ElementTree
def xmltodict(element):
if not isinstance(element, ElementTree.Element):
raise ValueError("must pass xml.etree.ElementTree.Element object")
def xmltodict_handler(parent_element):
result = dict()
for element in parent_element:
if len(element):
obj = xmltodict_handler(element)
else:
obj = element.text
if result.get(element.tag):
if hasattr(result[element.tag], "append"):
result[element.tag].append(obj)
else:
result[element.tag] = [result[element.tag], obj]
else:
result[element.tag] = obj
return result
return {element.tag: xmltodict_handler(element)}
def dicttoxml(element):
if not isinstance(element, dict):
raise ValueError("must pass dict type")
if len(element) != 1:
raise ValueError("dict must have exactly one root key")
def dicttoxml_handler(result, key, value):
if isinstance(value, list):
for e in value:
dicttoxml_handler(result, key, e)
elif isinstance(value, basestring):
elem = ElementTree.Element(key)
elem.text = value
result.append(elem)
elif isinstance(value, int) or isinstance(value, float):
elem = ElementTree.Element(key)
elem.text = str(value)
result.append(elem)
elif value is None:
result.append(ElementTree.Element(key))
else:
res = ElementTree.Element(key)
for k, v in value.items():
dicttoxml_handler(res, k, v)
result.append(res)
result = ElementTree.Element(element.keys()[0])
for key, value in element[element.keys()[0]].items():
dicttoxml_handler(result, key, value)
return result
def xmlfiletodict(filename):
return xmltodict(ElementTree.parse(filename).getroot())
def dicttoxmlfile(element, filename):
ElementTree.ElementTree(dicttoxml(element)).write(filename)
def xmlstringtodict(xmlstring):
return xmltodict(ElementTree.fromstring(xmlstring).getroot())
def dicttoxmlstring(element):
return ElementTree.tostring(dicttoxml(element))
def get_pallet(orderline):
p = orderline['Message']['PalletInit']['Pallets']['Pallet']
return {
'PalletNumber': int(p['PalletNumber']),
'Description': p['Description'],
'Dimensions': {
'MaxLoadHeight': int(p['Dimensions']['MaxLoadHeight']),
'MaxLoadWeight': int(p['Dimensions']['MaxLoadWeight']),
'Length': int(p['Dimensions']['Length']),
'Width': int(p['Dimensions']['Width']),
}
}
def get_articles(orderline):
articles = list()
for o in orderline['Message']['Order']['OrderLines']['OrderLine']:
for barcode in o['Barcodes']['Barcode']:
articles.append(
{'ApproachPoint1': {'X': 0, 'Y': 0, 'Z': 0},
'ApproachPoint2': {'X': 0, 'Y': 0, 'Z': 0},
'ApproachPoint3': {'X': 0, 'Y': 0, 'Z': 0},
'Article': { 'Description': o['Article']['Description'],
'ID': int(o['Article']['ID']),
'Type': int(o['Article']['Type']), # currently only Type=1 is allowed
'Family': int(o['Article']['Family']),
'Length': int(o['Article']['Length']), # should be larger than width
'Width': int(o['Article']['Width']),
'Height': int(o['Article']['Height']),
'Weight': int(o['Article']['Weight']) # in grams
},
'Barcode': barcode,
'Orientation': 1, # 1: 0 deg the long side parallel to X direction
# 2: 90 deg the long side parallel to Y direction
'PackSequence': 0,
'PlacePosition': {'X': 0, 'Y': 0, 'Z': 0}
}
)
return articles
def get_packlist_dict(pallet, articles):
pallet['Packages'] = {'Package': articles}
return {'Response':
{'PackList':
{'OrderID': '1',
'PackPallets':
{'PackPallet': pallet }
}
}
}
if __name__ == "__main__":
tree = ElementTree.parse('../icra2011TestFiles/GT/gt_d1r1.wpacklist.xml')
#tree = ElementTree.parse('../icra2011TestFiles/palDay1R1Order.xml')
root = tree.getroot()
xmldict = xmltodict(root)
from pprint import pprint
#for package in xmldict['PackList']['PackPallets']['PackPallet']['Packages']['Package']:
# pprint(package)
root = dicttoxml(xmldict)
xmldict = xmltodict(root)
pprint(xmldict)
packages = [{'ApproachPoint1': {'X': '0',
'Y': '0',
'Z': '0'},
'ApproachPoint2': {'X': '0',
'Y': '0',
'Z': '0'},
'ApproachPoint3': {'X': '0',
'Y': '0',
'Z': '0'},
'Article': {'Description': '3',
'Family': '0',
'Height': '41',
'ID': '3',
'Length': '44',
'Type': '0',
'Weight': '500',
'Width': '132'},
'Barcode': None,
'Orientation': '1',
'PackSequence': '1',
'PlacePosition': {'X': '286',
'Y': '330',
'Z': '41'},
'StackHeightBefore': '0'},
{'ApproachPoint1': {'X': '0',
'Y': '0',
'Z': '0'},
'ApproachPoint2': {'X': '0',
'Y': '0',
'Z': '0'},
'ApproachPoint3': {'X': '0',
'Y': '0',
'Z': '0'},
'Article': {'Description': '4',
'Family': '0',
'Height': '41',
'ID': '4',
'Length': '66',
'Type': '0',
'Weight': '550',
'Width': '132'},
'Barcode': None,
'Orientation': '1',
'PackSequence': '54',
'PlacePosition': {'X': '33',
'Y': '66',
'Z': '164'},
'StackHeightBefore': '0'}]
packlist = {'Response': {'PackList': {'OrderID': '1',
'PackPallets': {'PackPallet': {'BruttoWeight': '63000',
'Description': None,
'Dimensions': {'Length': '308',
'MaxLoadHeight': '406',
'MaxLoadWeight': '99999',
'Width': '396'},
'NumberofPackages': '54',
'Overhang': {'Length': '0',
'Width': '0'},
'Packages': {'Package': packages},
'PalletNumber': '1'}}}}}