open-ship 0.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|