goalseek 0.1.0 → 0.1.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 +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:
|