yasannealer 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/examples/sort.rb +35 -0
- data/lib/yasannealer.rb +140 -0
- metadata +45 -0
checksums.yaml
ADDED
@@ -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
|
data/examples/sort.rb
ADDED
@@ -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
|
data/lib/yasannealer.rb
ADDED
@@ -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: []
|