mhl 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 3e0490b924de2e42d7fd0935673abd7d232b07ed
4
- data.tar.gz: 51076e97078fc49710f9b6d86dc236e09cac5d99
2
+ SHA256:
3
+ metadata.gz: 36fae082a93b3e25f96b3157a62e8c6d5d95e63379fc6790a2628abcc1c7d754
4
+ data.tar.gz: 23a0d3730c592f7209d95db64f4843fef6d9e220c76ecf68240dfd0d0d917d63
5
5
  SHA512:
6
- metadata.gz: a1aac62bdff38e7c43d0bd58c06703ccf0df63a365b9c9e004b8f1a2a8f2cba59d2772a6e37c2dbba2c928e3ce5dffad6fd8b7c258c40979789a09217fdde090
7
- data.tar.gz: 383e9ce734b17fbb47f843ff91e487b53dfed05a1df65bb70a09678b61eb98948b542eff2f3b45f9435d030ae5f7cbc816dc12ac2ba4447ad86e704469a1db97
6
+ metadata.gz: eb0309f4908366b7940ad7d0b160a9aac5a2e2b7c2fe20affaf8176e3f5859bb695ed5182d273c6ebd382325fbd97984d945d9d8063dd2dc47428353ec8572b7
7
+ data.tar.gz: f2ed2cb5eb6f0e00dd0ec4d22153510c19b77746e14278664146ebcc2bb04e9b30a7f061c0c0bc6dfbc395dd164b9ce55ab862cb9d5e51940f42cc8a01b0cf84
@@ -0,0 +1,12 @@
1
+ {
2
+ "*": {"make": "bundle exec rake test"},
3
+ "*.rb": {"path": "lib"},
4
+ "lib/mhl/*.rb": {
5
+ "type": "source",
6
+ "alternate": "test/mhl/{}_test.rb"
7
+ },
8
+ "test/mhl/*_test.rb": {
9
+ "type": "test",
10
+ "alternate": "lib/mhl/{}.rb"
11
+ }
12
+ }
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.3
4
+ - jruby
5
+ - rbx-3
6
+ script: bundle exec rake test
data/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # ruby-mhl - A Ruby metaheuristics library
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/mhl.svg)](https://badge.fury.io/rb/mhl)
4
+ [![Build Status](https://travis-ci.org/mtortonesi/ruby-mhl.png?branch=master)](https://travis-ci.org/mtortonesi/ruby-mhl)
5
+ [![Code Climate](https://codeclimate.com/github/mtortonesi/ruby-mhl.png)](https://codeclimate.com/github/mtortonesi/ruby-mhl)
6
+
3
7
  ruby-mhl is a scientific library that provides a fairly large array of advanced
4
8
  computational intelligence methods for continuous optimization solutions.
5
9
 
@@ -9,26 +13,19 @@ Particle Swarm Optimization (constrained PSO, quantum-inspired PSO, and a
9
13
  multi-swarm version of quantum-inspired PSO), extended with adaptation
10
14
  mechanisms to provide support for dynamic optimization problems.
11
15
 
12
- ruby-mhl was designed for _high duty_ target functions, whose evaluation
13
- typically involves one or more simulation runs, possibly defined on very
14
- complex domains (or search spaces), and implemented in JRuby for performance
15
- reasons. To this end, ruby-mhl automatically takes advantage of the parallelism
16
- provided by the processor.
16
+ ruby-mhl was designed for _heavy duty_ target functions, whose evaluation
17
+ typically involves one or more long simulation runs, possibly defined on very
18
+ complex domains (or search spaces). To this end, ruby-mhl allows to evaluate
19
+ the target function in a concurrent fashion, automatically taking advantage of
20
+ the full amount of parallelism offered by the processor if the application is
21
+ running on a Ruby interpreter that does not have a GIL, such as JRuby.
22
+ (ruby-mhl will work with any Ruby interpreter, but the adoption of JRuby is
23
+ strongly recommended for performance reasons.)
17
24
 
18
25
 
19
26
  ## Installation
20
27
 
21
- To install ruby-mhl you first have to install Java and JRuby. This is a system
22
- dependent step, so I won't show you how to do it. However, if you are on Linux
23
- or OS X I recommend you to use [rbenv](https://github.com/rbenv/rbenv) to
24
- install and manage your Ruby installations.
25
-
26
- Once you have JRuby installed, you need to install bundler:
27
-
28
- gem install bundler
29
-
30
-
31
- ### Stable version
28
+ ### Stable version (recommended)
32
29
 
33
30
  You can get the stable version of ruby-mhl by installing the mhl gem from
34
31
  RubyGems:
@@ -74,16 +71,16 @@ x<sub>2</sub><sup>2</sup>_ equation with a genetic algorithm:
74
71
  require 'mhl'
75
72
 
76
73
  solver = MHL::GeneticAlgorithmSolver.new(
77
- :population_size => 80,
78
- :genotype_space_type => :integer,
79
- :mutation_probability => 0.5,
80
- :recombination_probability => 0.5,
81
- :genotype_space_conf => {
82
- :dimensions => 2,
83
- :recombination_type => :intermediate,
84
- :random_func => lambda { Array.new(2) { rand(100) } }
74
+ population_size: 80,
75
+ genotype_space_type: :integer,
76
+ mutation_probability: 0.5,
77
+ recombination_probability: 0.5,
78
+ genotype_space_conf: {
79
+ dimensions: 2,
80
+ recombination_type: :intermediate,
81
+ random_func: lambda { Array.new(2) { rand(100) } }
85
82
  },
86
- :exit_condition => lambda {|generation,best| best[:fitness] == 0}
83
+ exit_condition: lambda {|generation,best| best[:fitness] == 0}
87
84
  )
88
85
  solver.solve(Proc.new{|x| -(x[0] ** 2 + x[1] ** 2) })
89
86
  ```
@@ -148,12 +145,12 @@ x<sub>2</sub><sup>2</sup>_ equation with PSO:
148
145
  require 'mhl'
149
146
 
150
147
  solver = MHL::ParticleSwarmOptimizationSolver.new(
151
- :swarm_size => 40, # 40 is the default swarm size
152
- :constraints => {
153
- :min => [ -100, -100 ],
154
- :max => [ 100, 100 ],
148
+ swarm_size: 40, # 40 is the default swarm size
149
+ constraints: {
150
+ min: [ -100, -100 ],
151
+ max: [ 100, 100 ],
155
152
  },
156
- :exit_condition => lambda {|iteration,best| best[:height].abs < 0.001 },
153
+ exit_condition: lambda {|iteration,best| best[:height].abs < 0.001 },
157
154
  )
158
155
  solver.solve(Proc.new{|x| -(x[0] ** 2 + x[1] ** 2) })
159
156
  ```
@@ -198,12 +195,12 @@ x<sub>2</sub><sup>2</sup>_ equation with PSO:
198
195
  require 'mhl'
199
196
 
200
197
  solver = MHL::QuantumPSOSolver.new(
201
- :swarm_size => 40, # 40 is the default swarm size
202
- :constraints => {
203
- :min => [ -100, -100 ],
204
- :max => [ 100, 100 ],
198
+ swarm_size: 40, # 40 is the default swarm size
199
+ constraints: {
200
+ min: [ -100, -100 ],
201
+ max: [ 100, 100 ],
205
202
  },
206
- :exit_condition => lambda {|iteration,best| best[:height].abs < 0.001 },
203
+ exit_condition: lambda {|iteration,best| best[:height].abs < 0.001 },
207
204
  )
208
205
  solver.solve(Proc.new{|x| -(x[0] ** 2 + x[1] ** 2) })
209
206
  ```
@@ -240,17 +237,32 @@ IFIP/IEEE International Workshop on Business-driven IT Management (BDIM 2013),
240
237
  If you are interested in ruby-mhl, please consider reading and citing them.
241
238
 
242
239
 
240
+ ## Acknowledgements
241
+
242
+ The research work that led to the development of ruby-mhl was supported in part by
243
+ the [DICET - INMOTO - ORganization of Cultural HEritage for Smart
244
+ Tourism and Real-time Accessibility (OR.C.HE.S.T.R.A.)](http://www.ponrec.it/open-data/progetti/scheda-progetto?ProgettoID=5835)
245
+ project, funded by the Italian Ministry of University and Research on Axis II
246
+ of the National operative programme (PON) for Research and Competitiveness
247
+ 2007-13 within the call 'Smart Cities and Communities and Social Innovation'
248
+ (D.D. n.84/Ric., 2 March 2012).
249
+
250
+
243
251
  ## References
244
252
 
245
- [SUN11] Jun Sun, Choi-Hong Lai, Xiao-Jun Wu, "Particle Swarm Optimisation:
246
- Classical and Quantum Perspectives", CRC Press, 2011.
253
+ [MUHLENBEIN93] H. Mühlenbein, D. Schlierkamp-Voosen, "Predictive Models for the
254
+ Breeder Genetic Algorithm - I. Continuous Parameter Optimization", Journal of
255
+ Evolutionary Computation, Vol. 1, No. 1, pp. 25-49, March 1993.
256
+
257
+ [SUN11] J. Sun, C.-H. Lai, X.-J. Wu, "Particle Swarm Optimisation: Classical
258
+ and Quantum Perspectives", CRC Press, 2011.
247
259
 
248
260
  [CLERC02] M. Clerc, J. Kennedy, "The particle swarm - explosion,
249
261
  stability, and convergence in a multidimensional complex space", IEEE
250
262
  Transactions on Evolutionary Computation, Vol. 6, No. 1, pp. 58-73,
251
263
  2002, DOI: 10.1109/4235.985692
252
264
 
253
- [BLACKWELL04] Tim Blackwell, Jürgen Branke, "Multi-swarm Optimization in
265
+ [BLACKWELL04] T. Blackwell, J. Branke, "Multi-swarm Optimization in
254
266
  Dynamic Environments", Applications of Evolutionary Computing, pp. 489-500,
255
267
  Springer, 2004. DOI: 10.1007/978-3-540-24653-4\_50
256
268
 
@@ -260,3 +272,11 @@ Intelligence, Vol. 25, No. 4, pp. 527-542, 2013. DOI: 10.1080/0952813X.2013.7823
260
272
 
261
273
  [CLERC12] M. Clerc, "Standard Particle Swarm Optimisation - From 2006 to 2011",
262
274
  available at: [http://clerc.maurice.free.fr/pso/SPSO\_descriptions.pdf](http://clerc.maurice.free.fr/pso/SPSO_descriptions.pdf)
275
+
276
+ [LUKE15] S. Luke, "Essentials of Metaheuristics", 2nd Edition, available at:
277
+ [https://cs.gmu.edu/~sean/book/metaheuristics/](https://cs.gmu.edu/~sean/book/metaheuristics/),
278
+ Online Version 2.2, October 2015.
279
+
280
+ [DEEP07] K. Deep, M. Thakur, "A new mutation operator for real coded genetic
281
+ algorithms", Applied Mathematics and Computation, Vol. 193, No. 1, pp. 211-230,
282
+ October 2007.
data/Rakefile CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'bundler/gem_tasks'
2
2
 
3
+ require 'dotenv/tasks'
3
4
  require 'rake/testtask'
4
5
 
5
6
  Rake::TestTask.new do |t|
@@ -7,3 +8,5 @@ Rake::TestTask.new do |t|
7
8
  t.test_files = Dir.glob('test/**/*_test.rb').sort
8
9
  t.verbose = true
9
10
  end
11
+
12
+ task :test => :dotenv
data/TODO ADDED
@@ -0,0 +1,24 @@
1
+ IMPORTANT
2
+
3
+ * improve tournament selection (with default value of k=3)
4
+ * improve real-valued array chromosome representation support for GA
5
+ * implement measure of population diversity
6
+ * survey methods for increasing population diversity
7
+ * improve implementation of continuous optimization
8
+ - can we introduce the notion of "mutation intensity" in GAs?
9
+ - what about PSO variants?
10
+ * implement support for easy definition of memetic algorithms
11
+ * implement SPSO (in PSO movement update, do not use the global best as informant but use
12
+ "links" from neighboring particles)
13
+ * improve documentation
14
+ * add specs
15
+
16
+
17
+ MAYBE
18
+
19
+ * in random initialization, try using Sobol sequences (or non-linear simplex
20
+ method or plowing method)?
21
+ * implement CLONALG?
22
+ * implement CMA-ES?
23
+ * implement biased random key genetic algorithms?
24
+ * implement more rigorous constraint management according to CoelloCoello11?
@@ -0,0 +1,2 @@
1
+ BEGIN { print "Generation,Value" }
2
+ /> gen*/ { print $3 $NF }
@@ -0,0 +1,2 @@
1
+ BEGIN { print "Iteration,Value" }
2
+ /> iter*/ { print $3 $NF }
@@ -8,11 +8,13 @@ module MHL
8
8
  # traditional PSO (with inertia), swarms
9
9
  DEFAULT_CHARGED_TO_NEUTRAL_RATIO = 1.0
10
10
 
11
- def initialize(size, initial_positions, initial_velocities, params={})
11
+ def initialize(size:, initial_positions:, initial_velocities:,
12
+ charged_to_neutral_ratio: nil, alpha: nil, c1: nil, c2: nil,
13
+ chi: nil, constraints: nil, logger: nil)
12
14
  @size = size
13
15
 
14
16
  # retrieve ratio between charged (QPSO) and neutral (constrained PSO) particles
15
- ratio = (params[:charged_to_neutral_ratio] || DEFAULT_CHARGED_TO_NEUTRAL_RATIO).to_f
17
+ ratio = (charged_to_neutral_ratio || DEFAULT_CHARGED_TO_NEUTRAL_RATIO).to_f
16
18
  unless ratio > 0.0
17
19
  raise ArgumentError, 'Parameter :charged_to_neutral_ratio should be a real greater than zero!'
18
20
  end
@@ -36,28 +38,29 @@ module MHL
36
38
  @iteration = 1
37
39
 
38
40
  # define procedure to get dynamic value for alpha
39
- @get_alpha = if params.has_key? :alpha and params[:alpha].respond_to? :call
40
- params[:alpha]
41
+ @get_alpha = if alpha and alpha.respond_to? :call
42
+ alpha
41
43
  else
42
- ->(it) { (params[:alpha] || DEFAULT_ALPHA).to_f }
44
+ ->(it) { (alpha || DEFAULT_ALPHA).to_f }
43
45
  end
44
46
 
45
47
  # get values for parameters C1 and C2
46
- @c1 = (params[:c1] || DEFAULT_C1).to_f
47
- @c2 = (params[:c1] || DEFAULT_C2).to_f
48
+ @c1 = (c1 || DEFAULT_C1).to_f
49
+ @c2 = (c2 || DEFAULT_C2).to_f
48
50
 
49
51
  # define procedure to get dynamic value for chi
50
- @get_chi = if params.has_key? :chi and params[:chi].respond_to? :call
51
- params[:chi]
52
+ @get_chi = if chi and chi.respond_to? :call
53
+ chi
52
54
  else
53
- ->(it) { (params[:chi] || DEFAULT_CHI).to_f }
55
+ ->(it) { (chi || DEFAULT_CHI).to_f }
54
56
  end
55
57
 
56
- if params.has_key? :constraints
57
- puts "ChargedSwarm called w/ constraints: #{params[:constraints]}"
58
- end
58
+ @constraints = constraints
59
+ @logger = logger
59
60
 
60
- @constraints = params[:constraints]
61
+ if @constraints and @logger
62
+ @logger.info "ChargedSwarm called w/ constraints: #{@constraints}"
63
+ end
61
64
  end
62
65
 
63
66
  def mutate
@@ -3,7 +3,8 @@ require 'erv'
3
3
  require 'logger'
4
4
 
5
5
  require 'mhl/bitstring_genotype_space'
6
- require 'mhl/integer_genotype_space'
6
+ require 'mhl/integer_vector_genotype_space'
7
+ require 'mhl/real_vector_genotype_space'
7
8
 
8
9
 
9
10
  module MHL
@@ -18,16 +19,36 @@ module MHL
18
19
  raise ArgumentError, 'Even population size required!'
19
20
  end
20
21
 
22
+ @exit_condition = opts[:exit_condition]
23
+ @start_population = opts[:genotype_space_conf][:start_population]
24
+
25
+ @controller = opts[:controller]
26
+
27
+ case opts[:logger]
28
+ when :stdout
29
+ @logger = Logger.new(STDOUT)
30
+ when :stderr
31
+ @logger = Logger.new(STDERR)
32
+ else
33
+ @logger = opts[:logger]
34
+ end
35
+
36
+ @quiet = opts[:quiet]
37
+
38
+ if @logger
39
+ @logger.level = (opts[:log_level] or :warn)
40
+ end
41
+
21
42
  # perform genotype space-specific configuration
22
43
  case opts[:genotype_space_type]
23
44
  when :integer
24
- @genotype_space = IntegerVectorGenotypeSpace.new(opts[:genotype_space_conf])
45
+ @genotype_space = IntegerVectorGenotypeSpace.new(opts[:genotype_space_conf], @logger)
25
46
 
26
47
  begin
27
48
  @mutation_probability = opts[:mutation_probability].to_f
28
49
  @mutation_rv = \
29
- ERV::RandomVariable.new(:distribution => :geometric,
30
- :probability_of_success => @mutation_probability)
50
+ ERV::RandomVariable.new(distribution: :geometric,
51
+ args: { probability_of_success: @mutation_probability })
31
52
  rescue
32
53
  raise ArgumentError, 'Mutation probability configuration is wrong.'
33
54
  end
@@ -35,43 +56,35 @@ module MHL
35
56
  begin
36
57
  p_r = opts[:recombination_probability].to_f
37
58
  @recombination_rv = \
38
- ERV::RandomVariable.new(:distribution => :uniform,
39
- :min_value => -p_r,
40
- :max_value => 1.0 + p_r)
59
+ ERV::RandomVariable.new(distribution: :uniform,
60
+ args: { min_value: -p_r, max_value: 1.0 + p_r })
41
61
  rescue
42
62
  raise ArgumentError, 'Recombination probability configuration is wrong.'
43
63
  end
44
64
 
45
- when :bitstring
46
- @genotype_space = BitstringGenotypeSpace.new(opts[:genotype_space_conf])
47
- @recombination_rv = ERV::RandomVariable.new(:distribution => :uniform, :max_value => 1.0)
48
- @mutation_rv = ERV::RandomVariable.new(:distribution => :uniform, :max_value => 1.0)
49
-
50
- else
51
- raise ArgumentError, 'Only integer and bitstring genotype representations are supported!'
52
- end
53
-
54
- @exit_condition = opts[:exit_condition]
55
- @start_population = opts[:genotype_space_conf][:start_population]
65
+ when :real
66
+ @genotype_space = RealVectorGenotypeSpace.new(opts[:genotype_space_conf], @logger)
56
67
 
57
- @controller = opts[:controller]
68
+ # we have no mutation probability related parameters
69
+ @mutation_rv = ERV::RandomVariable.new(distribution: :uniform, args: { max_value: 1.0 })
58
70
 
59
- @pool = Concurrent::FixedThreadPool.new(Concurrent::processor_count * 4)
71
+ begin
72
+ p_r = opts[:recombination_probability].to_f
73
+ @recombination_rv = \
74
+ ERV::RandomVariable.new(distribution: :uniform,
75
+ args: { min_value: -p_r, max_value: 1.0 + p_r })
76
+ rescue
77
+ raise ArgumentError, 'Recombination probability configuration is wrong.'
78
+ end
60
79
 
61
- case opts[:logger]
62
- when :stdout
63
- @logger = Logger.new(STDOUT)
64
- when :stderr
65
- @logger = Logger.new(STDERR)
80
+ when :bitstring
81
+ @genotype_space = BitstringGenotypeSpace.new(opts[:genotype_space_conf])
82
+ @recombination_rv = ERV::RandomVariable.new(distribution: :uniform, args: { max_value: 1.0 })
83
+ @mutation_rv = ERV::RandomVariable.new(distribution: :uniform, args: { max_value: 1.0 })
66
84
  else
67
- @logger = opts[:logger]
85
+ raise ArgumentError, 'Only integer and bitstring genotype representations are supported!'
68
86
  end
69
87
 
70
- @quiet = opts[:quiet]
71
-
72
- if @logger
73
- @logger.level = (opts[:log_level] or Logger::WARN)
74
- end
75
88
  end
76
89
 
77
90
  def mutation_probability=(new_mp)
@@ -80,8 +93,8 @@ module MHL
80
93
  end
81
94
  @mutation_probability = new_mp
82
95
  @mutation_rv = \
83
- ERV::RandomVariable.new(:distribution => :geometric,
84
- :probability_of_success => @mutation_probability)
96
+ ERV::RandomVariable.new(distribution: :geometric,
97
+ args: { :probability_of_success => @mutation_probability })
85
98
  end
86
99
 
87
100
  # This is the method that solves the optimization problem
@@ -89,16 +102,16 @@ module MHL
89
102
  # Parameter func is supposed to be a method (or a Proc, a lambda, or any callable
90
103
  # object) that accepts the genotype as argument (that is, the set of
91
104
  # parameters) and returns the phenotype (that is, the function result)
92
- def solve(func)
105
+ def solve(func, params={})
93
106
  # setup population
94
107
  if @start_population.nil?
95
108
  population = Array.new(@population_size) do
96
109
  # generate random genotype according to the chromosome type
97
- { :genotype => @genotype_space.get_random }
110
+ { genotype: @genotype_space.get_random }
98
111
  end
99
112
  else
100
113
  population = @start_population.map do |x|
101
- { :genotype => x }
114
+ { genotype: x }
102
115
  end
103
116
  end
104
117
 
@@ -113,34 +126,45 @@ module MHL
113
126
  gen += 1
114
127
  @logger.info("GA - Starting generation #{gen}") if @logger
115
128
 
116
- # create latch to control program termination
117
- latch = Concurrent::CountDownLatch.new(@population_size)
118
-
119
129
  # assess fitness for every member of the population
120
- population.each do |s|
121
- @pool.post do
122
- # do we need to syncronize this call through population_mutex?
123
- # probably not.
124
- ret = func.call(s[:genotype])
125
-
126
- # protect write access to population struct using mutex
127
- population_mutex.synchronize do
128
- s[:fitness] = ret
130
+ if params[:concurrent]
131
+ # the function to optimize is thread safe: call it multiple times in
132
+ # a concurrent fashion
133
+ # to this end, we use the high level promise-based construct
134
+ # recommended by the authors of ruby's (fantastic) concurrent gem
135
+ promises = population.map do |member|
136
+ Concurrent::Promise.execute do
137
+ # evaluate target function
138
+ # do we need to syncronize this call through population_mutex?
139
+ # probably not.
140
+ ret = func.call(member[:genotype])
141
+
142
+ # protect write access to population struct using mutex
143
+ population_mutex.synchronize do
144
+ # update fitness
145
+ member[:fitness] = ret
146
+ end
129
147
  end
148
+ end
130
149
 
131
- # update latch
132
- latch.count_down
150
+ # wait for all the spawned threads to finish
151
+ promises.map(&:wait)
152
+ else
153
+ # the function to optimize is not thread safe: call it multiple times
154
+ # in a sequential fashion
155
+ population.each do |member|
156
+ # evaluate target function
157
+ ret = func.call(member[:genotype])
158
+ # update fitness
159
+ member[:fitness] = ret
133
160
  end
134
161
  end
135
162
 
136
- # wait for all the threads to terminate
137
- latch.wait
138
-
139
163
  # find fittest member
140
164
  population_best = population.max_by {|x| x[:fitness] }
141
165
 
142
166
  # print results
143
- puts "> gen #{gen}, best: #{population_best[:genotype]}, #{population_best[:fitness]}" unless @quiet
167
+ @logger.info "> gen #{gen}, best: #{population_best[:genotype]}, #{population_best[:fitness]}" unless @quiet
144
168
 
145
169
  # calculate overall best
146
170
  if overall_best.nil?