yasannealer 0.0.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 (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: []