vns 0.0.7.pre.rc.1

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.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/vns.rb +165 -0
  3. metadata +114 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 119b9c14c04a79df7dacdd5e3f977fc9b67d02f9adc150a47dfbe98c93c697be
4
+ data.tar.gz: ef31b284ece5ac078808b989bce9e26df44b9e638d53d93c5dafd9d390dfca3b
5
+ SHA512:
6
+ metadata.gz: c9cf3f669279b4ce6a2c65239a95a2a5ca597b2902e63804048c10a65412f446d4c427d5a10b3228805ff5cc001e980856e9a928ef11191d1b7b47a78187cf63
7
+ data.tar.gz: ea446c4aae3786a803ed7fc58ee793ae6e49b77fbcf099e095d0e02955a8fc1a34e99f3fcb4080b5c057f7dde9a5fe3952779e3a56ac0728f4e6e54b9cf7f3c5
@@ -0,0 +1,165 @@
1
+ module VNS
2
+ require 'person'
3
+ require 'session'
4
+ require 'active_support/all'
5
+
6
+ class VNS
7
+ attr_reader :people, :sessions, :preferences
8
+
9
+ PERTURBATION_COUNT = 100
10
+
11
+ def initialize(people, sessions, preferences, &inspection)
12
+ @people = people.map.with_index { |person, i| Person.new(i, person) }
13
+ @sessions = sessions.map.with_index { |session, i| Session.new(i, session) }
14
+ @preferences = preferences
15
+ @inspection = inspection
16
+ end
17
+
18
+ def run
19
+ initial_groups = people.in_groups(sessions.count).map(&:compact)
20
+ initial_solution = sessions.zip(initial_groups).to_h
21
+ @solution = global_optimize(initial_solution)
22
+
23
+ self
24
+ end
25
+
26
+ def print
27
+ puts "Target function: #{target_function(@solution)}\n"
28
+ @solution.each do |session, people|
29
+ puts "#{session.name} => #{people.map(&:name).join(', ')}"
30
+ end
31
+
32
+ puts "\nHappiness per person:\n"
33
+ people.each do |person|
34
+ puts "#{person.name} => #{happiness(person)}"
35
+ end
36
+
37
+ nil
38
+ end
39
+
40
+ def target_function(solution = @solution)
41
+ return Float::INFINITY unless solution
42
+
43
+ solution.map do |session, people|
44
+ people.map do |person|
45
+ preferences[person.id][session.id]
46
+ end
47
+ end.flatten.inject(:+)
48
+ end
49
+
50
+ private
51
+
52
+ def global_optimize(initial_solution)
53
+ best_solution = initial_solution
54
+
55
+ first_solution = initial_solution.dup
56
+ local_optimize(first_solution)
57
+
58
+ puts "First solution => #{target_function(first_solution)}"
59
+ best_solution = first_solution
60
+
61
+ PERTURBATION_COUNT.times do |counter|
62
+ temporary_solution = clone(best_solution)
63
+ perturbate(temporary_solution)
64
+ local_optimize(temporary_solution)
65
+
66
+ puts "Perturbation #{counter + 1} => #{target_function(temporary_solution)} vs #{target_function(best_solution)}"
67
+
68
+ if target_function(temporary_solution) < target_function(best_solution)
69
+ puts 'Updated best solution'
70
+ best_solution = temporary_solution
71
+ end
72
+
73
+ progress = (counter + 1) * 1.0 / PERTURBATION_COUNT
74
+ @inspection.call(progress, target_function(best_solution)) if @inspection
75
+ end
76
+
77
+ best_solution
78
+ end
79
+
80
+ def clone(solution)
81
+ solution.map do |k, v|
82
+ [k, v.dup]
83
+ end.to_h
84
+ end
85
+
86
+ def local_optimize(solution)
87
+ shift_optimization(solution)
88
+ swap_optimization(solution)
89
+ end
90
+
91
+ def shift_optimization(solution)
92
+ initial_value = target_function(solution)
93
+
94
+ people.product(sessions).each do |(person, session)|
95
+ original_session = find_session(solution, person)
96
+ shift(solution, session, person)
97
+
98
+ if feasible?(solution) && target_function(solution) < initial_value
99
+ return local_optimize(solution)
100
+ else
101
+ shift(solution, original_session, person)
102
+ end
103
+ end
104
+ end
105
+
106
+ def swap_optimization(solution)
107
+ initial_value = target_function(solution)
108
+
109
+ combinations_of_two(solution).each do |(p1, p2)|
110
+ swap(solution, p1, p2)
111
+ if target_function(solution) < initial_value
112
+ return local_optimize(solution)
113
+ else
114
+ swap(solution, p2, p1)
115
+ end
116
+ end
117
+ end
118
+
119
+ def perturbate(solution)
120
+ 2.times do
121
+ extracted = []
122
+ solution.each do |_, people|
123
+ extracted << people.delete_at(rand(people.length))
124
+ end
125
+
126
+ extracted.shuffle.each_with_index do |person, i|
127
+ solution.values[i] << person
128
+ end
129
+ end
130
+ end
131
+
132
+ def combinations_of_two(solution)
133
+ solution.values
134
+ .combination(2)
135
+ .map { |(a, b)| a.product(b) }
136
+ .flatten(1)
137
+ end
138
+
139
+ def happiness(person)
140
+ preferences[person.id][find_session(@solution, person).id]
141
+ end
142
+
143
+ def feasible?(solution)
144
+ solution.values.all? { |group| group.size <= Session::MAX_ALLOCATION }
145
+ end
146
+
147
+ def swap(solution, person1, person2)
148
+ session1 = find_session(solution, person1)
149
+ session2 = find_session(solution, person2)
150
+ solution[session1].delete(person1)
151
+ solution[session2].delete(person2)
152
+ solution[session1] << person2
153
+ solution[session2] << person1
154
+ end
155
+
156
+ def shift(solution, session, person)
157
+ solution[find_session(solution, person)].delete(person)
158
+ solution[session] << person
159
+ end
160
+
161
+ def find_session(solution, person)
162
+ sessions.detect { |s| solution[s].include?(person) }
163
+ end
164
+ end
165
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vns
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.7.pre.rc.1
5
+ platform: ruby
6
+ authors:
7
+ - Manuel Bustillo
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-01-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rubocop
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: bundler
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: A gem to execute Variable Neighborhood Search algorithm in optimization
84
+ problems
85
+ email: mayn13@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - lib/vns.rb
91
+ homepage: https://rubygems.org/exploradoresdemadrid/vns
92
+ licenses:
93
+ - MIT
94
+ metadata: {}
95
+ post_install_message:
96
+ rdoc_options: []
97
+ require_paths:
98
+ - lib
99
+ required_ruby_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">"
107
+ - !ruby/object:Gem::Version
108
+ version: 1.3.1
109
+ requirements: []
110
+ rubygems_version: 3.0.3
111
+ signing_key:
112
+ specification_version: 4
113
+ summary: VNS
114
+ test_files: []