mhl 0.2.0 → 0.3.0

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 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?