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