opt_alg_framework 1.0.3
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.
- checksums.yaml +15 -0
- data/.gitignore +62 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +86 -0
- data/Rakefile +1 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lib/opt_alg_framework/algorithm/local_search/hill_climbing.rb +34 -0
- data/lib/opt_alg_framework/algorithm/local_search/simulated_annealing.rb +71 -0
- data/lib/opt_alg_framework/algorithm/local_search/tabu_search.rb +47 -0
- data/lib/opt_alg_framework/operator/crossover/permutation/two_point_crossover.rb +33 -0
- data/lib/opt_alg_framework/operator/selector/tournament_selection.rb +22 -0
- data/lib/opt_alg_framework/operator/tweak/random_swap.rb +18 -0
- data/lib/opt_alg_framework/problem/README.md +20 -0
- data/lib/opt_alg_framework/problem/fsp.rb +88 -0
- data/lib/opt_alg_framework/version.rb +3 -0
- data/lib/opt_alg_framework.rb +6 -0
- data/opt_alg_framework.gemspec +27 -0
- metadata +94 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NTBlMTIwMTFlNmJhY2Q4YmEzYTdiNDdmYjA4NTgxOGM4NzlhNTI3NA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZmUwM2I2ZjM1YjBhNzY0YjBlOTNmYWVhM2JlY2Q5YTkyOTcwMGY1NA==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NWUwZjdjYjFkZDgwYTI4ZDM5NTUxNWVkZjZkNzRmMjE2OTc2ZTZhMjg3ZjY5
|
10
|
+
Y2NlNmRiYzdlNWIxYTc1MDBiMWEzODNmNzljYmU2Nzc1NDAwNTkwNWE4YTk1
|
11
|
+
MTE3ODY4YmQwYWJiM2NjMzc0OGJmYThmZTUyMDdmMzNlNmQ0NmI=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MDcwNmEzZDQ2NDFlN2NlMGFkMTAxYTllMDdlYjYzOWRiODU5MjY5MGEyM2Jh
|
14
|
+
NDAzZWZjYTQxZThhN2Y3YWIyNmQwMmNmOThmZWU5ODUzNDEzYWE0ODRmNTM1
|
15
|
+
NDQ0MzhlNGJkMDc4OTE1OTAyZjBlNWRmM2IwYTI1MmY0NjI1NmM=
|
data/.gitignore
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
<<<<<<< HEAD
|
2
|
+
/.bundle/
|
3
|
+
/.yardoc
|
4
|
+
/Gemfile.lock
|
5
|
+
/_yardoc/
|
6
|
+
/coverage/
|
7
|
+
/doc/
|
8
|
+
/pkg/
|
9
|
+
/spec/reports/
|
10
|
+
/tmp/
|
11
|
+
=======
|
12
|
+
*.gem
|
13
|
+
*.rbc
|
14
|
+
/.config
|
15
|
+
/coverage/
|
16
|
+
/InstalledFiles
|
17
|
+
/pkg/
|
18
|
+
/spec/reports/
|
19
|
+
/spec/examples.txt
|
20
|
+
/test/tmp/
|
21
|
+
/test/version_tmp/
|
22
|
+
/tmp/
|
23
|
+
|
24
|
+
# Used by dotenv library to load environment variables.
|
25
|
+
# .env
|
26
|
+
|
27
|
+
## Specific to RubyMotion:
|
28
|
+
.dat*
|
29
|
+
.repl_history
|
30
|
+
build/
|
31
|
+
*.bridgesupport
|
32
|
+
build-iPhoneOS/
|
33
|
+
build-iPhoneSimulator/
|
34
|
+
|
35
|
+
## Specific to RubyMotion (use of CocoaPods):
|
36
|
+
#
|
37
|
+
# We recommend against adding the Pods directory to your .gitignore. However
|
38
|
+
# you should judge for yourself, the pros and cons are mentioned at:
|
39
|
+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
40
|
+
#
|
41
|
+
# vendor/Pods/
|
42
|
+
|
43
|
+
## Documentation cache and generated files:
|
44
|
+
/.yardoc/
|
45
|
+
/_yardoc/
|
46
|
+
/doc/
|
47
|
+
/rdoc/
|
48
|
+
|
49
|
+
## Environment normalization:
|
50
|
+
/.bundle/
|
51
|
+
/vendor/bundle
|
52
|
+
/lib/bundler/man/
|
53
|
+
|
54
|
+
# for a library or gem, you might want to ignore these files since the code is
|
55
|
+
# intended to run in multiple environments; otherwise, check them in:
|
56
|
+
# Gemfile.lock
|
57
|
+
# .ruby-version
|
58
|
+
# .ruby-gemset
|
59
|
+
|
60
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
61
|
+
.rvmrc
|
62
|
+
>>>>>>> a7821ff49db723ae966f50a7686a6e443d19b148
|
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Rodrigo W. Ehresmann
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
# OptAlgFramework
|
2
|
+
|
3
|
+
Opt-Alg-Framework is a framework to work and build optimization algorithm. The basic idea is: you will have a codificated problem with a method to calculate its fitnes, an algorithm to improve the this fitness and operators used with the algorithm to build new solutions.
|
4
|
+
|
5
|
+
What is already implemented:
|
6
|
+
|
7
|
+
* Problems:
|
8
|
+
* Flow Shop Permutation
|
9
|
+
|
10
|
+
* Algorithms:
|
11
|
+
* Local Searches:
|
12
|
+
* Hill-Climbing
|
13
|
+
* Simulated Annealing
|
14
|
+
* Tabu Search
|
15
|
+
|
16
|
+
* Operators
|
17
|
+
* Crossover:
|
18
|
+
* Two Point Crossover (permutational)
|
19
|
+
* Selector:
|
20
|
+
* Tournament Selection
|
21
|
+
* Tweak:
|
22
|
+
* Random Swap
|
23
|
+
|
24
|
+
## Installation
|
25
|
+
|
26
|
+
Add this line to your application's Gemfile:
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
gem 'opt_alg_framework'
|
30
|
+
```
|
31
|
+
|
32
|
+
And then execute:
|
33
|
+
|
34
|
+
$ bundle
|
35
|
+
|
36
|
+
Or install it yourself as:
|
37
|
+
|
38
|
+
$ gem install opt_alg_framework
|
39
|
+
|
40
|
+
## Usage
|
41
|
+
|
42
|
+
Example: read two instances of FSP problem (about the instances format, read README in *problem* directory!) and get its best results with Simulated Annealing algorithm, using RandomSwap operator.
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
operator = Operator::Tweak::RandomSwap.new
|
46
|
+
problem = Problem::FSP.new
|
47
|
+
problem.load_schedule(path)
|
48
|
+
algorithm = Algorithm::LocalSearch::SimulatedAnnealing.new max_iterations: 10,
|
49
|
+
cooling_rate: 0.009,
|
50
|
+
problem: problem,
|
51
|
+
tweak_operator: operator
|
52
|
+
|
53
|
+
puts algorithm.start[:fitness]
|
54
|
+
```
|
55
|
+
|
56
|
+
## Development
|
57
|
+
|
58
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` or, alternatively, `bundle console` for an interactive prompt that will allow you to experiment.
|
59
|
+
|
60
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
61
|
+
|
62
|
+
####Some conventions must be followed:
|
63
|
+
|
64
|
+
* All problem classes need to have its fitness method acessible with the name *fitness*;
|
65
|
+
* All swap operator classes need to have its swap method acessible with the name *tweak*;
|
66
|
+
* All crossover operator classes need to have its crossover method named *cross*;
|
67
|
+
* All selector operator classes need to have its selection method names *select*;
|
68
|
+
* All algorithm classes need to have its main method names *start*;
|
69
|
+
* In the algorithms, a solution is represent with a hash structure, and it's mandatory have at least the pair key-value *:solution* with the representation of the solution and *:fitness* with the fitness of the solution.
|
70
|
+
|
71
|
+
####TODO:
|
72
|
+
|
73
|
+
* Treatments to verify if the conventions are being followed;
|
74
|
+
* General implementations, like:
|
75
|
+
* Local search algorithms;
|
76
|
+
* Population based algorithms;
|
77
|
+
* Operators (crossover, swap, selector) to any type of problem (permutational, binary, etc);
|
78
|
+
* Different type of problems.
|
79
|
+
|
80
|
+
## Contributing
|
81
|
+
|
82
|
+
1. Fork it ( https://github.com/[my-github-username]/opt_alg_framework/fork )
|
83
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
84
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
85
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
86
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "opt_alg_framework"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
module Algorithm
|
2
|
+
module LocalSearch
|
3
|
+
class HillClimbing
|
4
|
+
# Initialize passing a instantiated class of a problem and tweak operator
|
5
|
+
def initialize(params)
|
6
|
+
@tweak_operator = params[:tweak_operator]
|
7
|
+
@problem = params[:problem]
|
8
|
+
end
|
9
|
+
|
10
|
+
# Main method
|
11
|
+
def start
|
12
|
+
best = encapsulate_solution(@problem.default_solution)
|
13
|
+
while true do
|
14
|
+
r = encapsulate_solution(@tweak_operator.tweak(best[:solution]))
|
15
|
+
if r[:fitness] < best[:fitness]
|
16
|
+
best = r.dup
|
17
|
+
else
|
18
|
+
break
|
19
|
+
end
|
20
|
+
end
|
21
|
+
best
|
22
|
+
end
|
23
|
+
|
24
|
+
# Solution is a hash, with the keys :solution and :fitness
|
25
|
+
def encapsulate_solution(solution)
|
26
|
+
hash = Hash.new
|
27
|
+
hash[:solution] = solution
|
28
|
+
hash[:fitness] = @problem.fitness(tasks_sequence: solution)
|
29
|
+
hash
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Algorithm
|
2
|
+
module LocalSearch
|
3
|
+
class SimulatedAnnealing
|
4
|
+
include Math
|
5
|
+
attr_reader :temperature
|
6
|
+
|
7
|
+
# Initialize specifying the cooling rate percentage (@cooling_rate), number of iterations in each
|
8
|
+
# temperature (@max_iterations) and passing an instantiated problem and tweak operator class.
|
9
|
+
def initialize(params)
|
10
|
+
@cooling_rate = params[:cooling_rate]
|
11
|
+
@problem = params[:problem]
|
12
|
+
@tweak_operator = params[:tweak_operator]
|
13
|
+
@max_iterations = params[:max_iterations]
|
14
|
+
@temperature = initial_temperature
|
15
|
+
end
|
16
|
+
|
17
|
+
# Main method.
|
18
|
+
def start
|
19
|
+
best = encapsulate_solution(@problem.default_solution.shuffle)
|
20
|
+
current = best.dup
|
21
|
+
temperature = @temperature
|
22
|
+
while temperature > 1 do
|
23
|
+
iteration = 1
|
24
|
+
while iteration < @max_iterations do
|
25
|
+
neighbor = encapsulate_solution(@tweak_operator.tweak(current[:solution]))
|
26
|
+
current = neighbor.dup if accept?(current[:fitness], neighbor[:fitness], temperature)
|
27
|
+
best = current.dup if current[:fitness] < best[:fitness]
|
28
|
+
iteration += 1
|
29
|
+
end
|
30
|
+
temperature *= 1 - @cooling_rate
|
31
|
+
end
|
32
|
+
best
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# Calculate the initial temperature, generating N neighbors of an initial solution and
|
38
|
+
# setting as initial temperature the neighbor with the worst fitness.
|
39
|
+
def initial_temperature
|
40
|
+
solution = encapsulate_solution(@problem.default_solution.shuffle)
|
41
|
+
max = solution
|
42
|
+
@max_iterations.times do
|
43
|
+
neighbor = encapsulate_solution(@tweak_operator.tweak(solution[:solution]))
|
44
|
+
max = neighbor if neighbor[:fitness] > max[:fitness]
|
45
|
+
end
|
46
|
+
max[:fitness]
|
47
|
+
end
|
48
|
+
|
49
|
+
# A solution is a hash, with the keys :solution and :fitness.
|
50
|
+
def encapsulate_solution(solution)
|
51
|
+
hash = Hash.new
|
52
|
+
hash[:solution] = solution
|
53
|
+
hash[:fitness] = @problem.fitness(tasks_sequence: solution)
|
54
|
+
hash
|
55
|
+
end
|
56
|
+
|
57
|
+
# Calculate the acceptance probability, if and only if the neighbor fitness is worst than
|
58
|
+
# the current fitness.
|
59
|
+
def accept?(fitness, neighbor_fitness, temperature)
|
60
|
+
return true if neighbor_fitness < fitness
|
61
|
+
probability = E ** - ((fitness - neighbor_fitness).abs.fdiv(temperature)) # E is the Euller number
|
62
|
+
r = Random.rand(0.0..1)
|
63
|
+
if r < probability
|
64
|
+
return true
|
65
|
+
else
|
66
|
+
false
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Algorithm
|
2
|
+
module LocalSearch
|
3
|
+
class TabuSearch
|
4
|
+
|
5
|
+
# Initialize specifying as parameters the tabu list size (@list_max_size), number of tweaks in each
|
6
|
+
# current solution (@tweak_number), number of iteratiions to perform in the algorithm (@iterations)
|
7
|
+
# and an instantiated problem and tweak operator class.
|
8
|
+
def initialize(params)
|
9
|
+
@tabu_list = []
|
10
|
+
@list_max_size = params[:list_max_size]
|
11
|
+
@tweak_number = params[:tweak_number]
|
12
|
+
@iterations = params[:iterations]
|
13
|
+
@tweak_operator = params[:tweak_operator]
|
14
|
+
@problem = params[:problem]
|
15
|
+
end
|
16
|
+
|
17
|
+
# Main method.
|
18
|
+
def start
|
19
|
+
s = encapsulate_solution(@problem.default_solution.dup)
|
20
|
+
best = s.dup
|
21
|
+
@tabu_list << s[:solution]
|
22
|
+
@iterations.times do
|
23
|
+
@tabu_list.shift if @tabu_list.size == @list_max_size
|
24
|
+
r = encapsulate_solution(@tweak_operator.tweak(s[:solution]))
|
25
|
+
(@tweak_number - 1).times do
|
26
|
+
w = encapsulate_solution(@tweak_operator.tweak(s[:solution]))
|
27
|
+
r = w.dup if !@tabu_list.include?(w) && w[:fitness] < r[:fitness] || @tabu_list.include?(r[:solution])
|
28
|
+
end
|
29
|
+
if !@tabu_list.include?(r[:solution])
|
30
|
+
s = r.dup
|
31
|
+
@tabu_list << r
|
32
|
+
end
|
33
|
+
best = s.dup if s[:fitness] < best[:fitness]
|
34
|
+
end
|
35
|
+
best
|
36
|
+
end
|
37
|
+
|
38
|
+
# A solution is a hash, with the keys :solution and :fitness.
|
39
|
+
def encapsulate_solution(solution)
|
40
|
+
hash = Hash.new
|
41
|
+
hash[:solution] = solution
|
42
|
+
hash[:fitness] = @problem.fitness(tasks_sequence: solution)
|
43
|
+
hash
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Operator
|
2
|
+
module Crossover
|
3
|
+
module Permutation
|
4
|
+
|
5
|
+
class TwoPointCrossover
|
6
|
+
# Main method.
|
7
|
+
def cross(chromossome1, chromossome2)
|
8
|
+
point1 = Random.rand(0..chromossome1.size - 1)
|
9
|
+
point2 = Random.rand(point1..chromossome1.size - 1)
|
10
|
+
start_piece = Array.new
|
11
|
+
middle_piece = Array.new
|
12
|
+
end_piece = Array.new
|
13
|
+
|
14
|
+
point1.upto(point2) do |i| # Get the middle of the chromossome1, between the two points
|
15
|
+
middle_piece << chromossome1[i]
|
16
|
+
end
|
17
|
+
# After the tasks of the middle are choosed, there is no need of them in the chromossomes anymore
|
18
|
+
chromossome1 -= middle_piece
|
19
|
+
chromossome2 -= middle_piece
|
20
|
+
chromossome2.size.times do |i| # Loop to check the order of the remaining genes in the chromossome2
|
21
|
+
if start_piece.size <= point1 # Check if the chromossomes will be placed before or after the "middle" point
|
22
|
+
start_piece << chromossome1[i]
|
23
|
+
else
|
24
|
+
end_piece << chromossome1[i]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
start_piece + middle_piece + end_piece
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Operator
|
2
|
+
module Selector
|
3
|
+
|
4
|
+
class TournamentSelection
|
5
|
+
# Initialize informing the tournament size.
|
6
|
+
def initialize(size)
|
7
|
+
@size = size
|
8
|
+
end
|
9
|
+
|
10
|
+
# Main method.
|
11
|
+
def select(population)
|
12
|
+
best_individual = population[Random.rand(0...population.size)]
|
13
|
+
0.upto(@size) do
|
14
|
+
individual = population[Random.rand(0...population.size)]
|
15
|
+
best_individual = individual if individual[:fitness] < best_individual[:fitness]
|
16
|
+
end
|
17
|
+
best_individual
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Operator
|
2
|
+
module Tweak
|
3
|
+
|
4
|
+
class RandomSwap
|
5
|
+
# Main method.
|
6
|
+
def tweak(solution)
|
7
|
+
copy = solution.dup
|
8
|
+
piece1 = Random.rand(0...copy.size)
|
9
|
+
piece2 = Random.rand(0...copy.size)
|
10
|
+
x = copy[piece1]
|
11
|
+
copy[piece1] = copy[piece2]
|
12
|
+
copy[piece2] = x
|
13
|
+
copy
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# About the instaces of the problems
|
2
|
+
|
3
|
+
## FSP (Flow Shop Permutation)
|
4
|
+
The instance of this kind of problem is a matrix, specifically a matrix where the rows are tasks and columns are machines. The fitness method need that to work properly.
|
5
|
+
|
6
|
+
A valid instance example is shown bellow, with 5 tasks (lines) and 3 machines (columns):
|
7
|
+
|
8
|
+
> 12 65 23 <br>
|
9
|
+
> 45 25 12 <br>
|
10
|
+
> 17 32 56 <br>
|
11
|
+
> 33 21 22 <br>
|
12
|
+
> 10 9 11 <br>
|
13
|
+
|
14
|
+
However, if your instance file is an inverted matrix (rows are machines and columns are tasks), just set the *transpose* parameter of *load_schedule* method to *true*:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
fsp.load_schedule(path_to_instance, true)
|
18
|
+
```
|
19
|
+
|
20
|
+
That will invert the matrix to the right format.
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Problem
|
2
|
+
|
3
|
+
# FSP class have a inner class Schedule
|
4
|
+
class FSP
|
5
|
+
# Inner class who represents the production schedule, that is, a matrix were the rows are the tasks
|
6
|
+
# and the columns the machines.
|
7
|
+
class Schedule
|
8
|
+
require 'matrix'
|
9
|
+
|
10
|
+
attr_reader :schedule
|
11
|
+
|
12
|
+
# Fill the schedule reading the an instance from a file
|
13
|
+
def build_from_file(path, transpose)
|
14
|
+
rows = Array.new
|
15
|
+
File.foreach(path).each do |line|
|
16
|
+
rows << line.split(" ").collect{ |e| e.to_i }
|
17
|
+
end
|
18
|
+
@schedule = transpose ? Matrix.rows(rows).transpose : Matrix.rows(rows)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Given a sequence of tasks, reorder the schedule in this sequence
|
22
|
+
def reorder_schedule(tasks_sequence)
|
23
|
+
rows = Array.new
|
24
|
+
tasks_sequence.each do |task|
|
25
|
+
rows << @schedule.row(task)
|
26
|
+
end
|
27
|
+
Matrix.rows(rows)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_reader :default_solution # Is the sequence of tasks ordered from 0...N
|
32
|
+
|
33
|
+
# Used in makespan function to compare two execution times (also if one of them or the two are blank)
|
34
|
+
@@bigger = Proc.new do |a, b|
|
35
|
+
a ||= b ||= 0
|
36
|
+
if a > b ||= 0 then a else b end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Initialize the FSP problem with a empty schedule
|
40
|
+
def initialize
|
41
|
+
@schedule = Schedule.new()
|
42
|
+
end
|
43
|
+
|
44
|
+
# Load the production schedule from a file
|
45
|
+
def load_schedule(path, transpose = false)
|
46
|
+
@schedule.build_from_file(path, transpose)
|
47
|
+
@default_solution = (0...@schedule.schedule.row_size).to_a
|
48
|
+
end
|
49
|
+
|
50
|
+
# Fitness function. The hash options are:
|
51
|
+
# - schedule: matrix of the production schedule;
|
52
|
+
# - task: task index;
|
53
|
+
# - machine: machine index;
|
54
|
+
# - memory: store the total time spent at the point where the task index X is processed at the machine index Y
|
55
|
+
# (that avoid desnecessary recursive calls);
|
56
|
+
# - tasks_sequence: sequence of tasks used to reorganize the schedule after calculate its makespan.
|
57
|
+
# The default use of the method is: inform the tasks sequence as parameter and the method do all the work,
|
58
|
+
# returning the makespan as result.
|
59
|
+
def makespan(options = {}, block = @@bigger)
|
60
|
+
if options[:tasks_sequence]
|
61
|
+
schedule = @schedule.reorder_schedule(options[:tasks_sequence])
|
62
|
+
makespan({schedule: schedule, task: schedule.row_size - 1, machine: schedule.column_size - 1, memory: {}}, block)
|
63
|
+
else
|
64
|
+
schedule = options[:schedule]
|
65
|
+
task = options[:task]
|
66
|
+
machine = options[:machine]
|
67
|
+
memory = options[:memory]
|
68
|
+
key = "#{task},#{machine}"
|
69
|
+
time = schedule[task, machine]
|
70
|
+
if task == 0 && machine == 0
|
71
|
+
return time
|
72
|
+
end
|
73
|
+
if task > 0 # Before everithing, calculate the time spent in the tasks from N to 0
|
74
|
+
time_task_before = memory["#{task - 1},#{machine}"]
|
75
|
+
time_task_before = makespan({schedule: schedule, task: task - 1, machine: machine, memory: memory}, block) if memory["#{task - 1},#{machine}"].nil?
|
76
|
+
end
|
77
|
+
if machine > 0 # Calculate the time spent at the machines from N to 0
|
78
|
+
time_machine_before = memory["#{task},#{machine - 1}"]
|
79
|
+
time_machine_before = makespan({schedule: schedule, task: task, machine: machine - 1, memory: memory}, block) if memory["#{task},#{machine - 1}"].nil?
|
80
|
+
end
|
81
|
+
total_time = block.call(time_task_before, time_machine_before) + time # Calculate the total time
|
82
|
+
memory[key] = total_time # Store the total time
|
83
|
+
total_time
|
84
|
+
end
|
85
|
+
end
|
86
|
+
alias :fitness :makespan # Needed because, in the algorithm classes, to be generic we call the 'fitness' function (all problem classes need to have a 'fitness' function)
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'opt_alg_framework/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "opt_alg_framework"
|
8
|
+
spec.version = OptAlgFramework::VERSION
|
9
|
+
spec.authors = ["rodrigo-ehresmann"]
|
10
|
+
spec.email = ["igoehresmann@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Gem with a optimization algorithm framework that implements problems, algorithms and operator classes.}
|
13
|
+
spec.homepage = "https://github.com/rodrigo-ehresmann/opt_alg_framework"
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = "exe"
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
if spec.respond_to?(:metadata)
|
22
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
23
|
+
end
|
24
|
+
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.9"
|
26
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: opt_alg_framework
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- rodrigo-ehresmann
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-06-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.9'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.9'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
description:
|
42
|
+
email:
|
43
|
+
- igoehresmann@gmail.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- .gitignore
|
49
|
+
- .rspec
|
50
|
+
- .travis.yml
|
51
|
+
- Gemfile
|
52
|
+
- LICENSE.txt
|
53
|
+
- README.md
|
54
|
+
- Rakefile
|
55
|
+
- bin/console
|
56
|
+
- bin/setup
|
57
|
+
- lib/opt_alg_framework.rb
|
58
|
+
- lib/opt_alg_framework/algorithm/local_search/hill_climbing.rb
|
59
|
+
- lib/opt_alg_framework/algorithm/local_search/simulated_annealing.rb
|
60
|
+
- lib/opt_alg_framework/algorithm/local_search/tabu_search.rb
|
61
|
+
- lib/opt_alg_framework/operator/crossover/permutation/two_point_crossover.rb
|
62
|
+
- lib/opt_alg_framework/operator/selector/tournament_selection.rb
|
63
|
+
- lib/opt_alg_framework/operator/tweak/random_swap.rb
|
64
|
+
- lib/opt_alg_framework/problem/README.md
|
65
|
+
- lib/opt_alg_framework/problem/fsp.rb
|
66
|
+
- lib/opt_alg_framework/version.rb
|
67
|
+
- opt_alg_framework.gemspec
|
68
|
+
homepage: https://github.com/rodrigo-ehresmann/opt_alg_framework
|
69
|
+
licenses:
|
70
|
+
- MIT
|
71
|
+
metadata:
|
72
|
+
allowed_push_host: https://rubygems.org
|
73
|
+
post_install_message:
|
74
|
+
rdoc_options: []
|
75
|
+
require_paths:
|
76
|
+
- lib
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ! '>='
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ! '>='
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
requirements: []
|
88
|
+
rubyforge_project:
|
89
|
+
rubygems_version: 2.6.3
|
90
|
+
signing_key:
|
91
|
+
specification_version: 4
|
92
|
+
summary: Gem with a optimization algorithm framework that implements problems, algorithms
|
93
|
+
and operator classes.
|
94
|
+
test_files: []
|