open-ship 0.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +2 -0
- data/Manifest +16 -5
- data/lib/open-ship/gga4r/array_helper.rb +30 -0
- data/lib/open-ship/gga4r/gga4r_main.rb +180 -0
- data/lib/open-ship/gga4r/version.rb +9 -0
- data/lib/open-ship/gga4r.rb +1 -0
- data/lib/open-ship/label/carton_label.rb +61 -0
- data/lib/open-ship/label/text_label.rb +51 -0
- data/lib/{openship → open-ship}/label.rb +1 -1
- data/lib/open-ship/sortr.rb +279 -0
- data/lib/{openship → open-ship}/sscc.rb +0 -0
- data/lib/open-ship.rb +13 -0
- data/open-ship.gemspec +4 -4
- data/spec/open-ship/carton_spec.rb +142 -0
- data/spec/open-ship/label/carton_label_spec.rb +18 -0
- data/spec/open-ship/label/text_label_spec.rb +21 -0
- data/spec/{openship → open-ship}/sscc_spec.rb +0 -0
- data/spec/spec_helper.rb +6 -1
- data/tmp/test_carton_label.pdf +398 -0
- data/tmp/test_text_label.pdf +139 -0
- data.tar.gz.sig +4 -1
- metadata +29 -11
- metadata.gz.sig +0 -0
- data/lib/openship/openship.rb +0 -3
- data/lib/openship.rb +0 -9
data/CHANGELOG
CHANGED
data/Manifest
CHANGED
@@ -3,9 +3,20 @@ LICENSE
|
|
3
3
|
Manifest
|
4
4
|
README
|
5
5
|
Rakefile
|
6
|
-
lib/
|
7
|
-
lib/
|
8
|
-
lib/
|
9
|
-
lib/
|
10
|
-
|
6
|
+
lib/open-ship.rb
|
7
|
+
lib/open-ship/gga4r.rb
|
8
|
+
lib/open-ship/gga4r/array_helper.rb
|
9
|
+
lib/open-ship/gga4r/gga4r_main.rb
|
10
|
+
lib/open-ship/gga4r/version.rb
|
11
|
+
lib/open-ship/label.rb
|
12
|
+
lib/open-ship/label/carton_label.rb
|
13
|
+
lib/open-ship/label/text_label.rb
|
14
|
+
lib/open-ship/sortr.rb
|
15
|
+
lib/open-ship/sscc.rb
|
16
|
+
spec/open-ship/carton_spec.rb
|
17
|
+
spec/open-ship/label/carton_label_spec.rb
|
18
|
+
spec/open-ship/label/text_label_spec.rb
|
19
|
+
spec/open-ship/sscc_spec.rb
|
11
20
|
spec/spec_helper.rb
|
21
|
+
tmp/test_carton_label.pdf
|
22
|
+
tmp/test_text_label.pdf
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class Array
|
2
|
+
|
3
|
+
|
4
|
+
# Yields given bloc using arrays items pair by pair.
|
5
|
+
# e.g.
|
6
|
+
# <code>
|
7
|
+
# ["a","b","c","d"].each_pair do |first, second|
|
8
|
+
# puts second + " - " + second
|
9
|
+
# end
|
10
|
+
# </code>
|
11
|
+
# will print:
|
12
|
+
# b - a
|
13
|
+
# c - d
|
14
|
+
#
|
15
|
+
def each_pair
|
16
|
+
num = self.size/2
|
17
|
+
(0..num-1).collect do |index|
|
18
|
+
yield self[index*2], self[(index*2)+1]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Splits the array into two parts first from position
|
23
|
+
# 0 to "position" and second from position "position+1" to
|
24
|
+
# last position.
|
25
|
+
# Returns two new arrays.
|
26
|
+
def separate(position)
|
27
|
+
return self[0..position], self[position+1..-1]
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
require "yaml"
|
2
|
+
require "logger"
|
3
|
+
require "rubygems"
|
4
|
+
require "active_support"
|
5
|
+
|
6
|
+
class GeneticAlgorithm
|
7
|
+
attr_reader :generations, :p_combination, :p_mutation
|
8
|
+
|
9
|
+
# Must be initialized with a Array of chromosomes
|
10
|
+
# To be a chomosome the object must implement the next methods:
|
11
|
+
# - fitness
|
12
|
+
# - recombine
|
13
|
+
# - mutate
|
14
|
+
# Accepts the next properties:
|
15
|
+
# - extra_generations: adds given array of generations to the GeneticAlgorithm's own array of generations.
|
16
|
+
# - p_combination: probability of combiantion ( by default 0.2 )
|
17
|
+
# - p_mutation: probability of mutation ( by default 0.01 )
|
18
|
+
# - max_population: maximum number of individuals that are allowed to form a generation.
|
19
|
+
# - logger: logger to write messages if given.
|
20
|
+
def initialize(in_pop, prop = {})
|
21
|
+
@generations = [in_pop]
|
22
|
+
@generations += prop[:extra_generations] if prop[:extra_generations]
|
23
|
+
@p_combination = prop[:p_combination] || 0.2
|
24
|
+
@p_mutation = prop[:p_mutation] || 0.01
|
25
|
+
@max_population = prop[:max_population]
|
26
|
+
@logger = prop[:logger] if prop[:logger]
|
27
|
+
@use_threads = prop[:use_threads] if prop[:use_threads]
|
28
|
+
# mean_fitness
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
# HELPER METHODS
|
33
|
+
|
34
|
+
# Returns the number of generations that are in the GeneticAlgorithm object.
|
35
|
+
def num_generations
|
36
|
+
@generations.size - 1
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns an array with the best fitted individuals for given
|
40
|
+
# generation number ( by default from last generation ).
|
41
|
+
def best_fit(num_generation = -1)
|
42
|
+
raise "Generation not generated still num generations = #{num_generations}" if num_generation > num_generations
|
43
|
+
generation = @generations[num_generation]
|
44
|
+
max_fitness = generation.collect { |chromosome| chromosome.fitness }.max
|
45
|
+
generation.select { |chromosome| chromosome.fitness == max_fitness }
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns the mean of the fitness for given
|
49
|
+
# generation number ( by default from last generation ).
|
50
|
+
def mean_fitness(num = -1)
|
51
|
+
raise "Generation not generated still num generations = #{num_generations}" if num > self.num_generations
|
52
|
+
num = self.num_generations if num == -1
|
53
|
+
sum_fitness = 0
|
54
|
+
@generations[num].each { |chromosome| sum_fitness += chromosome.fitness }
|
55
|
+
sum_fitness.to_f / @generations[num].size.to_f
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns a GeneticAlgorithm object with the generations
|
59
|
+
# loaded from given files and with properties prop.
|
60
|
+
# Files must contain the chromosomes in YAML format.
|
61
|
+
def self.populate_from_files(a_filenames, prop = {})
|
62
|
+
a_filenames = [a_filenames] if a_filenames.class == String
|
63
|
+
|
64
|
+
loaded_generations = a_filenames.collect { |filename| YAML.load(File.open(filename, "r")) }
|
65
|
+
prop[:extra_generations] = loaded_generations[1..-1] if loaded_generations.size > 1
|
66
|
+
return GeneticAlgorithm.new(loaded_generations[0], prop)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Saves into filename and in yaml format the generation that matchs with given
|
70
|
+
# generation number ( by default from last generation ).
|
71
|
+
def save_generation(filename, num_generation = -1)
|
72
|
+
f = File.new(filename, "w")
|
73
|
+
f.write(self.generations[num_generation].to_yaml)
|
74
|
+
f.close
|
75
|
+
end
|
76
|
+
|
77
|
+
# EVOLUTION METHODS
|
78
|
+
|
79
|
+
# Evolves the actual generation num_steps steps (1 by default).
|
80
|
+
def evolve(num_steps = 1)
|
81
|
+
num_steps.times do
|
82
|
+
@generations << evaluation_with_threads(@generations[-1])
|
83
|
+
selection!
|
84
|
+
recombination!
|
85
|
+
mutation!
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Prepares given generation for evaluation ( evaluates its fitness ).
|
90
|
+
def evaluation(g)
|
91
|
+
@logger.debug "Evaluation " + g.size.to_s + " chromosomes." if @logger
|
92
|
+
i = 0
|
93
|
+
g.collect do |chromosome|
|
94
|
+
i += 1
|
95
|
+
@logger.debug "Evaluating chromosome #{i}:" if @logger
|
96
|
+
@logger.debug "#{chromosome.stats.join("\n")}" if @logger
|
97
|
+
chromosome.fitness
|
98
|
+
chromosome
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Prepares given generation for evaluation ( evaluates its fitness ),
|
103
|
+
# using Threads
|
104
|
+
def evaluation_with_threads(g)
|
105
|
+
@logger.debug "Evaluation " + g.size.to_s + " chromosomes." if @logger
|
106
|
+
threads = []
|
107
|
+
i = 0
|
108
|
+
g.each do |chromosome|
|
109
|
+
i += 1
|
110
|
+
@logger.debug "Evaluating chromosome #{i}:" if @logger
|
111
|
+
@logger.debug "#{chromosome.stats.join("\n")}" if @logger
|
112
|
+
threads << Thread.new(chromosome) do |t_chromosome|
|
113
|
+
t_chromosome.fitness
|
114
|
+
@logger.debug "Thread finished #{Thread.current.object_id} - #{Thread.current.status}" if @logger
|
115
|
+
end
|
116
|
+
end
|
117
|
+
# Wait for threads for finish
|
118
|
+
threads.each do |thread|
|
119
|
+
@logger.debug "#{thread.status}" if @logger
|
120
|
+
thread.join
|
121
|
+
@logger.debug "#{thread.status}" if @logger
|
122
|
+
end
|
123
|
+
return g
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
# Selects population to survive and recombine
|
128
|
+
def selection(g)
|
129
|
+
g_tmp = remainder_stochastic_sampling(g)
|
130
|
+
g_tmp = g_tmp.sort_by {|i| -i.fitness }[0..(@max_population-1)] if @max_population && (g_tmp.size > @max_population)
|
131
|
+
g_tmp
|
132
|
+
end
|
133
|
+
def selection!; @generations[-1] = selection(@generations[-1]); end
|
134
|
+
|
135
|
+
# Recombines population
|
136
|
+
def recombination(g)
|
137
|
+
@logger.debug "Recombination " + g.size.to_s + " chromosomes." if @logger
|
138
|
+
new_generation = g.dup.shuffle!
|
139
|
+
@logger.debug "Shuffled!" if @logger
|
140
|
+
new_children = []
|
141
|
+
new_generation.in_groups_of(2) do |chromosome1, chromosome2|
|
142
|
+
next if chromosome2.nil?
|
143
|
+
if rand > (1 - @p_combination)
|
144
|
+
@logger.debug "Recombining" if @logger
|
145
|
+
new_children << chromosome1.recombine(chromosome2)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
new_generation + new_children
|
149
|
+
end
|
150
|
+
|
151
|
+
def recombination!; @generations[-1] = recombination(@generations[-1]); end
|
152
|
+
|
153
|
+
# Mutates population
|
154
|
+
def mutation(g)
|
155
|
+
@logger.debug "Mutation " + g.size.to_s + " chromosomes." if @logger
|
156
|
+
new_generation = g.dup
|
157
|
+
new_generation.each do |chromosome|
|
158
|
+
if rand > (1 - @p_mutation)
|
159
|
+
@logger.debug "Mutate" if @logger
|
160
|
+
chromosome.mutate
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
def mutation!; @generations[-1] = mutation(@generations[-1]); end
|
165
|
+
|
166
|
+
# Remainder Stochastic Sampling algorithm for selection.
|
167
|
+
def remainder_stochastic_sampling(g)
|
168
|
+
new_generation = []
|
169
|
+
g.each do |chromosome|
|
170
|
+
num_rep = 0
|
171
|
+
if chromosome.fitness > 0
|
172
|
+
num_rep += (chromosome.fitness.to_f/mean_fitness).to_i
|
173
|
+
num_rep += 1 if rand > (1 - (chromosome.fitness/mean_fitness)%1)
|
174
|
+
end
|
175
|
+
new_generation = new_generation + ([chromosome] * num_rep)
|
176
|
+
end
|
177
|
+
new_generation
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Dir[File.join(File.dirname(__FILE__), 'gga4r/**/*.rb')].sort.each { |lib| require lib }
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'prawn'
|
2
|
+
require 'prawn/measurement_extensions'
|
3
|
+
require 'barby'
|
4
|
+
require 'barby/outputter/prawn_outputter'
|
5
|
+
|
6
|
+
module OpenShip
|
7
|
+
module Label
|
8
|
+
|
9
|
+
class CartonLabel
|
10
|
+
|
11
|
+
attr_accessor :product, :style, :sku, :upc, :quantity, :origin
|
12
|
+
|
13
|
+
def gtin
|
14
|
+
self.upc.to_s.rjust(14, "0")
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_pdf
|
18
|
+
document = Prawn::Document.new(:page_size => [11.cm, 6.7.cm],
|
19
|
+
:right_margin => 0.0.cm,
|
20
|
+
:left_margin => 0.0.cm,
|
21
|
+
:top_margin => 0.0.cm,
|
22
|
+
:bottom_margin => 0.0.cm,
|
23
|
+
:page_layout => :portrait)
|
24
|
+
|
25
|
+
barcode = Barby::Code25Interleaved.new(self.gtin)
|
26
|
+
|
27
|
+
barcode.annotate_pdf(document, {:x => 0.5.cm, :y => 1.7.cm, :xdim => 0.073.cm, :height => 2.5.cm})
|
28
|
+
|
29
|
+
document.stroke do
|
30
|
+
document.rectangle [0.5.cm,6.4.cm], 1.5.cm, 1.5.cm
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
document.text_box("QTY", :size => 16, :align => :center, :at => [0.5.cm, 6.3.cm], :width => 1.5.cm, :height => 1.cm)
|
35
|
+
document.text_box(self.quantity.to_s, :size => 20, :align => :center, :at => [0.5.cm, 5.7.cm], :width => 1.5.cm, :height => 1.5.cm)
|
36
|
+
|
37
|
+
|
38
|
+
document.text_box(self.gtin[0..7], :size => 16, :align => :left, :at => [2.2.cm, 6.3.cm], :width => 2.cm, :height => 1.cm)
|
39
|
+
document.text_box(self.gtin[8..12], :size => 20, :align => :left, :at => [2.2.cm, 5.7.cm], :width => 2.5.cm, :height => 1.5.cm)
|
40
|
+
|
41
|
+
document.text_box(self.product, :size => 12, :align => :right, :at => [5.5.cm, 6.4.cm], :width => 5.cm, :height => 0.5.cm)
|
42
|
+
document.text_box(self.style, :size => 12, :align => :right, :at => [5.5.cm, 5.9.cm], :width => 5.cm, :height => 0.5.cm)
|
43
|
+
document.text_box(self.sku, :size => 12, :align => :right, :at => [5.5.cm, 5.4.cm], :width => 5.cm, :height => 0.5.cm)
|
44
|
+
|
45
|
+
document.text_box(self.origin, :size => 10, :align => :center, :at => [0.cm, 0.75.cm], :width => 11.cm, :height => 0.5.cm)
|
46
|
+
|
47
|
+
document.font("Courier")
|
48
|
+
|
49
|
+
document.text_box(self.gtin[0..0], :size => 16, :align => :left, :at => [1.25.cm, 1.5.cm], :width => 0.8.cm, :height => 1.cm)
|
50
|
+
document.text_box(self.gtin[1..2], :size => 16, :align => :left, :at => [2.3.cm, 1.5.cm], :width => 1.cm, :height => 1.cm)
|
51
|
+
document.text_box(self.gtin[3..7], :size => 16, :align => :left, :at => [3.7.cm, 1.5.cm], :width => 2.cm, :height => 1.cm)
|
52
|
+
document.text_box(self.gtin[8..12], :size => 16, :align => :left, :at => [6.45.cm, 1.5.cm], :width => 2.cm, :height => 1.cm)
|
53
|
+
document.text_box(self.gtin[13..13], :size => 16, :align => :left, :at => [9.15.cm, 1.5.cm], :width => 1.cm, :height => 1.cm)
|
54
|
+
|
55
|
+
document
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'prawn'
|
2
|
+
require 'prawn/measurement_extensions'
|
3
|
+
require 'barby'
|
4
|
+
require 'barby/outputter/prawn_outputter'
|
5
|
+
|
6
|
+
module OpenShip
|
7
|
+
module Label
|
8
|
+
|
9
|
+
class TextLabel
|
10
|
+
|
11
|
+
attr_accessor :address1, :address2, :address3, :address4,
|
12
|
+
:sku, :po, :line, :quantity, :total_cartons
|
13
|
+
|
14
|
+
def to_pdf
|
15
|
+
document = Prawn::Document.new(:page_size => [20.cm, 15.cm],
|
16
|
+
:page_layout => :portrait)
|
17
|
+
|
18
|
+
|
19
|
+
document.text_box(self.address1, :size => 16, :align => :left, :at => [0.2.cm, 11.cm], :width => 12.cm, :height => 0.75.cm)
|
20
|
+
document.text_box(self.address2, :size => 16, :align => :left, :at => [0.2.cm, 10.25.cm], :width => 12.cm, :height => 0.75.cm)
|
21
|
+
document.text_box(self.address3, :size => 16, :align => :left, :at => [0.2.cm, 9.5.cm], :width => 12.cm, :height => 0.75.cm)
|
22
|
+
document.text_box(self.address4, :size => 16, :align => :left, :at => [0.2.cm, 8.75.cm], :width => 12.cm, :height => 0.75.cm)
|
23
|
+
|
24
|
+
|
25
|
+
po_line = "PO#: #{self.po}"
|
26
|
+
document.text_box(po_line, :size => 16, :align => :left, :at => [0.2.cm, 7.25.cm], :width => 12.cm, :height => 0.75.cm)
|
27
|
+
|
28
|
+
line_no = "Line #: #{self.line}"
|
29
|
+
document.text_box(line_no, :size => 16, :align => :left, :at => [0.2.cm, 6.5.cm], :width => 12.cm, :height => 0.75.cm)
|
30
|
+
|
31
|
+
style_line = "Mfg Style #: #{self.sku}"
|
32
|
+
document.text_box(style_line, :size => 16, :align => :left, :at => [0.2.cm, 5.75.cm], :width => 12.cm, :height => 0.75.cm)
|
33
|
+
|
34
|
+
quantity_line = "Inner Pk Qty: #{self.quantity}"
|
35
|
+
document.text_box(quantity_line, :size => 16, :align => :left, :at => [0.2.cm, 5.cm], :width => 12.cm, :height => 0.75.cm)
|
36
|
+
|
37
|
+
total_cartons_line = "Total Master Pk. Cartons per Line Item: #{self.total_cartons}"
|
38
|
+
document.text_box(total_cartons_line, :size => 16, :align => :left, :at => [0.2.cm, 4.25.cm], :width => 15.cm, :height => 0.75.cm)
|
39
|
+
|
40
|
+
|
41
|
+
document.text_box("Pre-Priced: NO", :size => 20, :align => :left, :at => [0.2.cm, 3.cm], :width => 12.cm, :height => 1.cm)
|
42
|
+
document.text_box("FRAGILE: NO", :size => 20, :align => :left, :at => [0.2.cm, 2.cm], :width => 12.cm, :height => 1.cm)
|
43
|
+
|
44
|
+
|
45
|
+
document
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,279 @@
|
|
1
|
+
require "activesupport"
|
2
|
+
|
3
|
+
module OpenShip
|
4
|
+
|
5
|
+
|
6
|
+
|
7
|
+
class BoxPosition
|
8
|
+
attr_accessor :position, :box
|
9
|
+
|
10
|
+
def get_relative_space
|
11
|
+
self.box.get_space(:relative_position => self.position)
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
class Position
|
17
|
+
|
18
|
+
attr_accessor :x, :y, :z
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
class Box
|
23
|
+
attr_accessor :length, :width, :height, :label, :weight, :product_quantity
|
24
|
+
|
25
|
+
def volume
|
26
|
+
(length * width * height)
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
class Carton < Box
|
34
|
+
attr_accessor :margin, :box_positions, :width_margin, :length_margin
|
35
|
+
|
36
|
+
def initialize()
|
37
|
+
self.box_positions = []
|
38
|
+
end
|
39
|
+
|
40
|
+
@last_box = nil
|
41
|
+
|
42
|
+
def space_packed(pos)
|
43
|
+
packed = false
|
44
|
+
self.box_positions.each { |bp|
|
45
|
+
unless packed
|
46
|
+
if ((pos.x >= bp.position.x) && (pos.x < (bp.box.width + bp.position.x)))
|
47
|
+
if ((pos.y >= bp.position.y) && (pos.y < (bp.box.length + bp.position.y)))
|
48
|
+
if ((pos.z >= bp.position.z) && (pos.z < (bp.box.height + bp.position.z)))
|
49
|
+
packed = true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
}
|
55
|
+
packed
|
56
|
+
end
|
57
|
+
|
58
|
+
def get_space(opts={})
|
59
|
+
space = []
|
60
|
+
z_pos = 0
|
61
|
+
while (z_pos < self.height)
|
62
|
+
y_pos = 0
|
63
|
+
while(y_pos < self.length)
|
64
|
+
x_pos = 0
|
65
|
+
while(x_pos < self.width)
|
66
|
+
pos = Position.new
|
67
|
+
pos.x = x_pos
|
68
|
+
pos.y = y_pos
|
69
|
+
pos.z = z_pos
|
70
|
+
unless self.space_packed(pos)
|
71
|
+
space << pos
|
72
|
+
end
|
73
|
+
x_pos += 1
|
74
|
+
end
|
75
|
+
y_pos += 1
|
76
|
+
end
|
77
|
+
z_pos += 1
|
78
|
+
end
|
79
|
+
space
|
80
|
+
end
|
81
|
+
|
82
|
+
def free_space
|
83
|
+
self.volume - (self.box_positions.collect { |bp| bp.box }.sum { |bx| bx.volume })
|
84
|
+
end
|
85
|
+
|
86
|
+
def position_for_box(box)
|
87
|
+
spot = nil
|
88
|
+
free_space = self.get_space
|
89
|
+
free_space.each { |sp|
|
90
|
+
if ((self.width - sp.x) >= box.width)
|
91
|
+
if ((self.length - sp.y) >= box.length)
|
92
|
+
if ((self.height - sp.z) >= box.height)
|
93
|
+
# Test for potential overlaps
|
94
|
+
overlap = false
|
95
|
+
self.box_positions.each { |bp|
|
96
|
+
|
97
|
+
if ( ((bp.position.x + bp.box.width) > sp.x) && ((sp.x + box.width) > bp.position.x) )
|
98
|
+
if ( ((bp.position.z + bp.box.height) > sp.z) && ((sp.z + box.height) > bp.position.z) )
|
99
|
+
if ( ((bp.position.y + bp.box.length) > sp.y) && ((sp.y + box.length) > bp.position.y) )
|
100
|
+
overlap = true;
|
101
|
+
break
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
}
|
106
|
+
unless overlap
|
107
|
+
spot = sp
|
108
|
+
break
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
}
|
114
|
+
spot
|
115
|
+
end
|
116
|
+
|
117
|
+
def add_box(box)
|
118
|
+
if box.volume > self.free_space
|
119
|
+
return nil
|
120
|
+
end
|
121
|
+
pos = self.position_for_box(box)
|
122
|
+
if pos
|
123
|
+
bp = OpenShip::BoxPosition.new
|
124
|
+
bp.box = box
|
125
|
+
bp.position = pos
|
126
|
+
self.box_positions << bp
|
127
|
+
end
|
128
|
+
pos
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
class OpenShip::Shipment
|
136
|
+
|
137
|
+
@@fitness_values = []
|
138
|
+
|
139
|
+
|
140
|
+
attr_accessor :boxes_to_stores, :cartons_to_stores, :logger, :carton_dispenser
|
141
|
+
|
142
|
+
class CartonDispenser
|
143
|
+
|
144
|
+
def get_carton
|
145
|
+
cart = OpenShip::Carton.new
|
146
|
+
cart.width = 20
|
147
|
+
cart.height = 20
|
148
|
+
cart.length = 10
|
149
|
+
cart
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
def initialize(opts = {})
|
155
|
+
if opts[:logger]
|
156
|
+
@logger = opts[:logger]
|
157
|
+
else
|
158
|
+
@logger = Logger.new(STDOUT)
|
159
|
+
@logger.level = Logger::WARN
|
160
|
+
end
|
161
|
+
if opts[:carton_dispenser]
|
162
|
+
@carton_dispenser = opts[:carton_dispenser]
|
163
|
+
else
|
164
|
+
@carton_dispenser = CartonDispenser.new
|
165
|
+
end
|
166
|
+
@cartons_to_stores = nil
|
167
|
+
@boxes_to_stores = {}
|
168
|
+
end
|
169
|
+
|
170
|
+
def calculate_cartons
|
171
|
+
@cartons_to_stores = {}
|
172
|
+
self.boxes_to_stores.each { |k, v|
|
173
|
+
@cartons_to_stores[k] ||= []
|
174
|
+
cart = @carton_dispenser.get_carton
|
175
|
+
@cartons_to_stores[k] << cart
|
176
|
+
v.each { |box|
|
177
|
+
pos = cart.add_box(box)
|
178
|
+
if pos.nil?
|
179
|
+
cart = @carton_dispenser.get_carton
|
180
|
+
@cartons_to_stores[k] << cart
|
181
|
+
pos = cart.add_box(box)
|
182
|
+
if pos.nil?
|
183
|
+
raise "Box is too big for carton."
|
184
|
+
end
|
185
|
+
end
|
186
|
+
}
|
187
|
+
}
|
188
|
+
@cartons_to_stores
|
189
|
+
end
|
190
|
+
|
191
|
+
def fitness
|
192
|
+
|
193
|
+
if @cartons_to_stores.nil?
|
194
|
+
self.calculate_cartons
|
195
|
+
end
|
196
|
+
|
197
|
+
free_space = self.cartons_to_stores.sum { |k, v| v.sum { |c| c.free_space } }
|
198
|
+
box_count = self.boxes_to_stores.sum { |k, v| v.count }
|
199
|
+
carton_count = self.cartons_to_stores.sum { |k, v| v.count }
|
200
|
+
@logger.debug "Box Count: " + box_count.to_s
|
201
|
+
@logger.debug "Carton Free Space: " + free_space.to_s
|
202
|
+
@logger.debug "Carton Count: " + carton_count.to_s
|
203
|
+
fitness = (box_count.to_f / carton_count.to_f)
|
204
|
+
@@fitness_values << fitness
|
205
|
+
@logger.debug "Fitness Mean: " + fitness_mean.to_s
|
206
|
+
@logger.debug "Fitness: " + fitness.to_s
|
207
|
+
fitness
|
208
|
+
end
|
209
|
+
|
210
|
+
def stats
|
211
|
+
["Total Volume :" + self.boxes_to_stores.sum { |k, v| v.sum { |b| b.volume } }.to_s]
|
212
|
+
end
|
213
|
+
|
214
|
+
|
215
|
+
def recombine(c2)
|
216
|
+
return1 = self.clone
|
217
|
+
return2 = c2.clone
|
218
|
+
self.boxes_to_stores.each { |k, v|
|
219
|
+
bxs = c2.boxes_to_stores[k]
|
220
|
+
|
221
|
+
cross_point = (rand * bxs.size).to_i
|
222
|
+
c1_a, c1_b = v.separate(cross_point)
|
223
|
+
c2_a, c2_b = bxs.separate(cross_point)
|
224
|
+
return1.boxes_to_stores[k] = c1_a + c2_b
|
225
|
+
return2.boxes_to_stores[k] = c2_a + c1_b
|
226
|
+
}
|
227
|
+
return1.cartons_to_stores = nil
|
228
|
+
return2.cartons_to_stores = nil
|
229
|
+
return1
|
230
|
+
end
|
231
|
+
|
232
|
+
def fitness_mean
|
233
|
+
(@@fitness_values.sum { |f| f } / @@fitness_values.length)
|
234
|
+
end
|
235
|
+
|
236
|
+
def mutate
|
237
|
+
#if (self.fitness < self.fitness_mean)
|
238
|
+
@logger.debug "################ Making a mutation!"
|
239
|
+
self.boxes_to_stores.each { |k, v|
|
240
|
+
self.boxes_to_stores[k] = v.shuffle
|
241
|
+
}
|
242
|
+
#end
|
243
|
+
self
|
244
|
+
end
|
245
|
+
|
246
|
+
def run_ga(generations = 10, population_size = 10)
|
247
|
+
@@fitness_values = []
|
248
|
+
|
249
|
+
population = []
|
250
|
+
population_size.times {
|
251
|
+
#ship = OpenShip::Shipment.new(:logger => @logger, :carton_dispenser => @carton_dispenser)
|
252
|
+
ship = self.clone
|
253
|
+
ship.boxes_to_stores = self.boxes_to_stores.clone
|
254
|
+
|
255
|
+
ship.boxes_to_stores.each { |k, v|
|
256
|
+
ship.boxes_to_stores[k] = v.shuffle
|
257
|
+
}
|
258
|
+
population << ship
|
259
|
+
}
|
260
|
+
ga = GeneticAlgorithm.new(population, {:logger => @logger})
|
261
|
+
generations.times { ga.evolve }
|
262
|
+
best_fit = ga.best_fit[0]
|
263
|
+
best_fit.cartons_to_stores.each { |k, v|
|
264
|
+
puts k
|
265
|
+
v.each { |cart|
|
266
|
+
@logger.debug "Carton"
|
267
|
+
cart.box_positions.each { |bp|
|
268
|
+
@logger.debug "length: " + bp.box.length.to_s + " width: " + bp.box.width.to_s + " height: " + bp.box.height.to_s
|
269
|
+
@logger.debug "x: " + bp.position.x.to_s + " y: " + bp.position.y.to_s + " z: " + bp.position.z.to_s
|
270
|
+
}
|
271
|
+
}
|
272
|
+
}
|
273
|
+
best_fit.fitness
|
274
|
+
best_fit
|
275
|
+
end
|
276
|
+
|
277
|
+
end
|
278
|
+
|
279
|
+
end
|
File without changes
|
data/lib/open-ship.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
$LOAD_PATH << File.dirname(__FILE__) unless $LOAD_PATH.include?(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
module OpenShip
|
4
|
+
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'open-ship/sscc'
|
8
|
+
require 'open-ship/sortr'
|
9
|
+
require 'open-ship/label'
|
10
|
+
require 'open-ship/label/carton_label'
|
11
|
+
require 'open-ship/label/text_label'
|
12
|
+
require 'open-ship/gga4r'
|
13
|
+
|