goalseek 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +16 -1
- data/lib/goalseek.rb +1 -79
- data/lib/goalseek/linear_search.rb +77 -0
- data/lib/goalseek/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ef1c7fc4ff85d9cb3a5033fa6f9b18c856f60bd5
|
4
|
+
data.tar.gz: f7e85129e6e10ddc37b0e67c56dec035503922e1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 68a6ac63ae127ec11ac432ef84f4ee67cf5d99b45c48c70c28effc703d4fdd49f2cfbffe9931201f8fa7e6f5bd44d9d407d9a8cbae5535a985b22b09f5f7dabe
|
7
|
+
data.tar.gz: db959a50069beae15e00a9c8cd633b630894f136e9fd8f8ffaad25ad2f5b6e29209fc7c94c55f29a4b69a503638b7c9fa2819ddc6e79856e11df53fe2696c00b
|
data/README.md
CHANGED
@@ -30,7 +30,22 @@ Or install it yourself as:
|
|
30
30
|
|
31
31
|
## Usage
|
32
32
|
|
33
|
-
|
33
|
+
This is a simple example of a linear search on a qubic function.
|
34
|
+
|
35
|
+
Bounds are automatically determined if they are within -10^10 to 10^10. Iterations are limited to 1000 by default.
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
begin
|
39
|
+
goalseeker = GoalSeek::LinearSearch.new(Proc.new() { |x| x ** 3 });
|
40
|
+
result = goalseeker.seek(27) # returns 3.0
|
41
|
+
rescue GoalSeek::InvalidBoundError
|
42
|
+
puts "Function does not cross target value within interval (-10^10, 10^10)"
|
43
|
+
rescue GoalSeek::InvalidFunctionError
|
44
|
+
puts "Provided block is not callable"
|
45
|
+
end
|
46
|
+
```
|
47
|
+
|
48
|
+
For more examples, please have a look at the specs.
|
34
49
|
|
35
50
|
## Development
|
36
51
|
|
data/lib/goalseek.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'goalseek/version'
|
2
|
+
require 'goalseek/linear_search'
|
2
3
|
|
3
4
|
module GoalSeek
|
4
5
|
class InvalidBoundError < Exception
|
@@ -6,83 +7,4 @@ module GoalSeek
|
|
6
7
|
|
7
8
|
class InvalidFunctionError < Exception
|
8
9
|
end
|
9
|
-
|
10
|
-
class GoalSeek
|
11
|
-
|
12
|
-
# block is an f(x) function
|
13
|
-
def initialize(f)
|
14
|
-
@f = f
|
15
|
-
end
|
16
|
-
|
17
|
-
def seek(goal, options = {})
|
18
|
-
@goal = goal.to_f
|
19
|
-
|
20
|
-
options = {
|
21
|
-
tolerance: 0,
|
22
|
-
max_iterations: 1000
|
23
|
-
}.merge(options)
|
24
|
-
|
25
|
-
min_max = get_min_max(options[:lower_bound], options[:upper_bound])
|
26
|
-
lower, upper = min_max[0], min_max[1]
|
27
|
-
|
28
|
-
iterate(lower, upper, options[:tolerance], options[:max_iterations])
|
29
|
-
end
|
30
|
-
|
31
|
-
def iterate(lower, upper, tolerance, max_iterations, iterations = 1)
|
32
|
-
new_bound = (lower + upper) / 2
|
33
|
-
|
34
|
-
begin
|
35
|
-
new_value = @f.call(new_bound)
|
36
|
-
rescue NoMethodError
|
37
|
-
raise InvalidFunctionError
|
38
|
-
end
|
39
|
-
|
40
|
-
if iterations == max_iterations || new_value === @goal
|
41
|
-
return new_bound
|
42
|
-
elsif new_value < @goal
|
43
|
-
return iterate(new_bound, upper, tolerance, max_iterations, iterations + 1)
|
44
|
-
elsif new_value > @goal
|
45
|
-
return iterate(lower, new_bound, tolerance, max_iterations, iterations + 1)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def get_min_max(a, b)
|
50
|
-
x = nil, y = nil
|
51
|
-
|
52
|
-
# Use given bounds if present
|
53
|
-
if a && b
|
54
|
-
x = a.to_f
|
55
|
-
y = b.to_f
|
56
|
-
|
57
|
-
if check_bounds(x, y)
|
58
|
-
[x, y]
|
59
|
-
elsif check_bounds(y, x)
|
60
|
-
[y, x]
|
61
|
-
else
|
62
|
-
raise InvalidBoundError
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
# Try exponential bound expansion from 0 to 10^10
|
67
|
-
(0..10).each do |i|
|
68
|
-
if check_bounds(0.0, 10.0 ** i)
|
69
|
-
return [0.0, 10.0 ** i]
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
# Try exponential bound expansion from -x to x
|
74
|
-
(0..10).each do |i|
|
75
|
-
if check_bounds(-10.0 ** i, 10.0 ** i)
|
76
|
-
return [-10.0 ** i, 10.0 ** i]
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
# Fall back to raising if no working bound could be found
|
81
|
-
raise InvalidBoundError
|
82
|
-
end
|
83
|
-
|
84
|
-
def check_bounds(x, y)
|
85
|
-
(@f.call(x) < @goal) && (@f.call(y) > @goal) || (@f.call(y) < @goal) && (@f.call(x) > @goal)
|
86
|
-
end
|
87
|
-
end
|
88
10
|
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module GoalSeek
|
2
|
+
class LinearSearch
|
3
|
+
# block is an f(x) function
|
4
|
+
def initialize(f)
|
5
|
+
@f = f
|
6
|
+
end
|
7
|
+
|
8
|
+
def seek(goal, options = {})
|
9
|
+
@goal = goal.to_f
|
10
|
+
|
11
|
+
options = {
|
12
|
+
tolerance: 0,
|
13
|
+
max_iterations: 1000
|
14
|
+
}.merge(options)
|
15
|
+
|
16
|
+
min_max = get_min_max(options[:lower_bound], options[:upper_bound])
|
17
|
+
lower, upper = min_max[0], min_max[1]
|
18
|
+
|
19
|
+
iterate(lower, upper, options[:tolerance], options[:max_iterations])
|
20
|
+
end
|
21
|
+
|
22
|
+
def iterate(lower, upper, tolerance, max_iterations, iterations = 1)
|
23
|
+
new_bound = (lower + upper) / 2
|
24
|
+
|
25
|
+
begin
|
26
|
+
new_value = @f.call(new_bound)
|
27
|
+
rescue NoMethodError
|
28
|
+
raise InvalidFunctionError
|
29
|
+
end
|
30
|
+
|
31
|
+
if iterations == max_iterations || new_value === @goal
|
32
|
+
return new_bound
|
33
|
+
elsif new_value < @goal
|
34
|
+
return iterate(new_bound, upper, tolerance, max_iterations, iterations + 1)
|
35
|
+
elsif new_value > @goal
|
36
|
+
return iterate(lower, new_bound, tolerance, max_iterations, iterations + 1)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def get_min_max(a, b)
|
41
|
+
# Use given bounds if present
|
42
|
+
if a && b
|
43
|
+
x = a.to_f
|
44
|
+
y = b.to_f
|
45
|
+
|
46
|
+
if check_bounds(x, y)
|
47
|
+
[x, y]
|
48
|
+
elsif check_bounds(y, x)
|
49
|
+
[y, x]
|
50
|
+
else
|
51
|
+
raise InvalidBoundError
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Try exponential bound expansion from 0 to 10^10
|
56
|
+
(0..10).each do |i|
|
57
|
+
if check_bounds(0.0, 10.0 ** i)
|
58
|
+
return [0.0, 10.0 ** i]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Try exponential bound expansion from -x to x
|
63
|
+
(0..10).each do |i|
|
64
|
+
if check_bounds(-10.0 ** i, 10.0 ** i)
|
65
|
+
return [-10.0 ** i, 10.0 ** i]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Fall back to raising if no working bound could be found
|
70
|
+
raise InvalidBoundError
|
71
|
+
end
|
72
|
+
|
73
|
+
def check_bounds(x, y)
|
74
|
+
(@f.call(x) < @goal) && (@f.call(y) > @goal) || (@f.call(y) < @goal) && (@f.call(x) > @goal)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/lib/goalseek/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: goalseek
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pascal Ehlert
|
@@ -72,6 +72,7 @@ files:
|
|
72
72
|
- bin/setup
|
73
73
|
- goalseek.gemspec
|
74
74
|
- lib/goalseek.rb
|
75
|
+
- lib/goalseek/linear_search.rb
|
75
76
|
- lib/goalseek/version.rb
|
76
77
|
homepage: https://github.com/pehlert/goalseek
|
77
78
|
licenses:
|