yasannealer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. checksums.yaml +7 -0
  2. data/examples/sort.rb +35 -0
  3. data/lib/yasannealer.rb +140 -0
  4. metadata +45 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9b49789d429d6f1eefb4b192f87157b3a83e0390
4
+ data.tar.gz: 6cb0e9fe97e7ec584801f858377e28f35dab7239
5
+ SHA512:
6
+ metadata.gz: a60d6f77fcb311e6100e82e8d6f2984b98249137065c37cf00567ba913653121b77fd6327846f2f3144755705ad15db10297c91e53d2582f760c6a1da8e147d1
7
+ data.tar.gz: 88f8d57548c78241440efbd810ce0922e369321892556a17507f2eeb449b468cc4a135838a4aba9441ab06df4ef733339ad7c849ea6d2993cd15ecad22c9111e
@@ -0,0 +1,35 @@
1
+ require 'yasannealer'
2
+
3
+ class Array
4
+ def swap!(a,b)
5
+ self[a], self[b] = self[b], self[a]
6
+ self
7
+ end
8
+ end
9
+
10
+ class SortAnnealer < Yasannealer
11
+ attr_accessor :sorted_array
12
+
13
+ def initialize(arr, opts)
14
+ self.sorted_array = arr.sort
15
+
16
+ super(arr, opts)
17
+ end
18
+
19
+ def energy
20
+ self.state.zip(self.sorted_array).count{|pair| pair.first != pair.last}
21
+ end
22
+
23
+ def move
24
+ index_a = rand(self.state.size)
25
+ index_b = rand(self.state.size)
26
+
27
+ self.state.swap!(index_a, index_b)
28
+ end
29
+ end
30
+
31
+ def test
32
+ init_state = (1..300).to_a.shuffle
33
+
34
+ annealer = SortAnnealer.new(init_state, {tmin: 0.001, tmax: 60.0, steps: 1000000, updates: 60})
35
+ end
@@ -0,0 +1,140 @@
1
+ # Returns x rounded to n significant figures.
2
+ def round_figures(x, n)
3
+ x #round(x, int(n - math.ceil(math.log10(abs(x)))))
4
+ end
5
+
6
+ # Returns time in seconds as a string formatted HHHH:MM:SS.
7
+ def time_string(seconds)
8
+ Time.at(seconds).utc.strftime("%H:%M:%S")
9
+ end
10
+
11
+ class Yasannealer
12
+ # Performs simulated annealing by calling functions to calculate
13
+ # energy and make moves on a state. The temperature schedule for
14
+ # annealing may be provided manually or estimated automatically.
15
+
16
+ attr_accessor :tmax, :tmin, :steps, :updates, :state,
17
+ :start
18
+
19
+ MESS = "SYSTEM ERROR: method missing"
20
+
21
+ def initialize(initial_state, opts = {})
22
+ self.tmin = opts[:tmin] || 0.001
23
+ self.tmax = opts[:tmax] || 60.0
24
+ self.steps = opts[:steps] || 1000000
25
+ self.updates = opts[:updates] || 60
26
+
27
+ self.state = initial_state.dup
28
+ end
29
+
30
+ def move; raise MESS; end
31
+ def energy; raise MESS; end
32
+
33
+ # Prints the current temperature, energy, acceptance rate,
34
+ # improvement rate, elapsed time, and remaining time.
35
+
36
+ # The acceptance rate indicates the percentage of moves since the last
37
+ # update that were accepted by the Metropolis algorithm. It includes
38
+ # moves that decreased the energy, moves that left the energy
39
+ # unchanged, and moves that increased the energy yet were reached by
40
+ # thermal excitation.
41
+
42
+ # The improvement rate indicates the percentage of moves since the
43
+ # last update that strictly decreased the energy. At high
44
+ # temperatures it will include both moves that improved the overall
45
+ # state and moves that simply undid previously accepted moves that
46
+ #increased the energy by thermal excititation. At low temperatures
47
+ # it will tend toward zero as the moves that can decrease the energy
48
+ # are exhausted and moves that would increase the energy are no longer
49
+ # thermally accessible.
50
+ def update(step, t, e, acceptance, improvement)
51
+ elapsed = Time.now - self.start
52
+ if step == 0
53
+ puts(' Temperature Energy Accept Improve Elapsed Remaining')
54
+ puts("\r%12.2f %12.2f %s " % [t, e, time_string(elapsed).to_s] )
55
+ else
56
+ remain = (self.steps - step) * (elapsed.to_f / step)
57
+ puts("\r%12.2f %12.2f %7.2f%% %7.2f%% %s %s" % [t, e, 100.0 * acceptance, 100.0 * improvement, time_string(elapsed), time_string(remain)])
58
+ end
59
+ end
60
+
61
+ # Minimizes the energy of a system by simulated annealing.
62
+ # Parameters
63
+ # state : an initial arrangement of the system
64
+ #
65
+ # Returns
66
+ # (state, energy): the best state and energy found.
67
+ def anneal
68
+ step = 0
69
+ self.start = Time.now
70
+
71
+ # Precompute factor for exponential cooling from Tmax to Tmin
72
+ if self.tmin <= 0.0
73
+ raise 'Exponential cooling requires a minimum temperature greater than zero.'
74
+ end
75
+ t_factor = -Math.log(self.tmax.to_f / self.tmin)
76
+
77
+ # Note initial state
78
+ t = self.tmax
79
+ e = self.energy
80
+
81
+ prev_state = self.state.dup
82
+ prev_energy = e
83
+ best_state = self.state.dup
84
+ best_energy = e
85
+
86
+ trials, accepts, improves = 0, 0, 0
87
+ if self.updates > 0
88
+ update_wavelength = self.steps / self.updates
89
+ self.update(step, t, e, nil, nil)
90
+ end
91
+
92
+ # Attempt moves to new states
93
+ while step < self.steps
94
+ step += 1
95
+ t = self.tmax * Math.exp(t_factor * step / self.steps)
96
+ self.move
97
+ e = self.energy
98
+ dE = e - prev_energy
99
+ trials += 1
100
+
101
+ if dE > 0.0 and Math.exp(-dE / t) < rand()
102
+ # Restore previous state
103
+ self.state = prev_state.dup
104
+ e = prev_energy
105
+ else
106
+ # Accept new state and compare to best state
107
+ accepts += 1
108
+ if dE < 0.0
109
+ improves += 1
110
+ end
111
+
112
+ prev_state = self.state.dup
113
+ prev_energy = e
114
+
115
+ if e < best_energy
116
+ best_state = self.state.dup
117
+ best_energy = e
118
+ end
119
+ end
120
+
121
+ if self.updates > 1
122
+ if step / update_wavelength > (step - 1) / update_wavelength
123
+ self.update(step, t, e, accepts.to_f / trials, improves.to_f / trials)
124
+ trials, accepts, improves = 0, 0, 0
125
+ end
126
+ end
127
+
128
+ break if best_energy == 0.0
129
+ end
130
+
131
+ # line break after progress output
132
+ puts
133
+
134
+ self.state = best_state.dup
135
+
136
+ # Return best state and energy
137
+ return [best_state, best_energy]
138
+
139
+ end
140
+ end
metadata ADDED
@@ -0,0 +1,45 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: yasannealer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Adam Lučanský
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-11-28 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: ''
14
+ email: adamlucansky@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - examples/sort.rb
20
+ - lib/yasannealer.rb
21
+ homepage: http://github.com/lucansky/yasannealer
22
+ licenses:
23
+ - ISC
24
+ metadata: {}
25
+ post_install_message:
26
+ rdoc_options: []
27
+ require_paths:
28
+ - lib
29
+ required_ruby_version: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ required_rubygems_version: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ requirements: []
40
+ rubyforge_project:
41
+ rubygems_version: 2.4.5.1
42
+ signing_key:
43
+ specification_version: 4
44
+ summary: Yet another simulated annealer. Based on Python's perrygeo/simanneal.
45
+ test_files: []