gnuplotrb 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
- ##
2
- # Methods to take data for GnuplotRB plots.
3
- class String
4
- # @return [String] data converted to Gnuplot format
5
- alias_method :to_gnuplot_points, :clone
6
- end
1
+ ##
2
+ # Methods to take data for GnuplotRB plots.
3
+ class String
4
+ # @return [String] data converted to Gnuplot format
5
+ alias_method :to_gnuplot_points, :clone
6
+ end
data/lib/gnuplotrb/fit.rb CHANGED
@@ -1,204 +1,204 @@
1
- module GnuplotRB
2
- ##
3
- # Contains methods relating to Gnuplot's fit function. Covered in
4
- # {fit notebook}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/fitting_data.ipynb].
5
- #
6
- # You can also see original gnuplot's fit in
7
- # {gnuplot doc}[http://www.gnuplot.info/docs_5.0/gnuplot.pdf] p. 122.
8
- module Fit
9
- ##
10
- # Fit given data with function.
11
- #
12
- # Fit waits for output from gnuplot Settings.max_fit_delay and throw exception if gets nothing.
13
- # One can change this value in order to wait longer (if huge datasets is fitted).
14
- #
15
- # @param data [#to_gnuplot_points] method accepts the same sources as Dataset.new
16
- # and Dataset object
17
- # @param :function [String] function to fit data with
18
- # @param :initials [Hash] initial values for coefficients used in fitting
19
- # @param :term_options [Hash] terminal options that should be setted to terminal before fit.
20
- # You can see them in Plot's documentation (or even better in gnuplot doc)
21
- # Most useful here are ranges (xrange, yrange etc) and fit option which tunes fit parameters
22
- # (see {gnuplot doc}[http://www.gnuplot.info/docs_5.0/gnuplot.pdf] p. 122)
23
- # @param options [Hash] options passed to Gnuplot's fit such as *using*. They are covered in
24
- # {gnuplot doc}[http://www.gnuplot.info/docs_5.0/gnuplot.pdf] (pp. 69-74)
25
- #
26
- # @return [Hash] hash with four elements:
27
- # - :formula_ds - dataset with best fit curve as data
28
- # - :coefficients - hash of calculated coefficients. So if you gave
29
- # ``{ initials: {a: 1, b: 1, c: 1} }`` it will return hash with keys :a, :b, :c and its values
30
- # - :deltas - Gnuplot calculates possible deltas for coefficients during fitting and
31
- # deltas hash contains this deltas
32
- # - :data - pointer to Datablock with given data
33
- # @example
34
- # fit(some_data, function: 'exp(a/x)', initials: {a: 10}, term_option: { xrange: 1..100 })
35
- # fit(some_dataset, using: '1:2:3')
36
- def fit(data, function: 'a2*x*x+a1*x+a0', initials: { a2: 1, a1: 1, a0: 1 }, term_options: {}, **options)
37
- dataset = data.is_a?(Dataset) ? Dataset.new(data.data) : Dataset.new(data)
38
- opts_str = OptionHandling.ruby_class_to_gnuplot(options)
39
- output = gnuplot_fit(function, dataset, opts_str, initials, term_options)
40
- res = parse_output(initials.keys, function, output)
41
- {
42
- formula_ds: Dataset.new(res[2], title: 'Fit formula'),
43
- coefficients: res[0],
44
- deltas: res[1],
45
- data: dataset
46
- }
47
- end
48
-
49
- ##
50
- # Shortcut for fit with polynomial. Degree here is max power of *x* in polynomial.
51
- #
52
- # @param data [#to_gnuplot_points] method accepts the same sources as Dataset.new
53
- # and Dataset object
54
- # @param :degree [Integer] degree of polynomial
55
- # @param options [Hash] all of this options will be passed to #fit so you
56
- # can set here any options listed in its docs. If you pass here :initials hash, it
57
- # will be merged into default initals hash. Formula by default is
58
- # "xn*x**n + ... + x0*x**0", initials by default "{ an: 1, ..., a0: 1 }"
59
- #
60
- # @return [Hash] hash with four elements:
61
- # - :formula_ds - dataset with best fit curve as data
62
- # - :coefficients - hash of calculated coefficients. So for degree = 3
63
- # it will return hash with keys :a3, :a2, :a1, :a0 and calculated values
64
- # - :deltas - Gnuplot calculates possible deltas for coefficients during fitting and
65
- # deltas hash contains this deltas
66
- # - :data - pointer to Datablock with given data
67
- # @example
68
- # fit_poly(some_data, degree: 5, initials: { a4: 10, a2: -1 }, term_option: { xrange: 1..100 })
69
- # #=> The same as:
70
- # #=> fit(
71
- # #=> some_data,
72
- # #=> function: 'a5*x**5 + a4*x**4 + ... + a0*x**0',
73
- # #=> initals: {a5: 1, a4: 10, a3: 1, a2: -1, a1: 1, a0: 1},
74
- # #=> term_option: { xrange: 1..100 }
75
- # #=> )
76
- def fit_poly(data, degree: 2, **options)
77
- sum_count = degree + 1
78
- initials = {}
79
- sum_count.times { |i| initials["a#{i}".to_sym] = 1 }
80
- options[:initials] = initials.merge(options[:initials] || {})
81
- function = sum_count.times.map { |i| "a#{i}*x**#{i}" }.join(' + ')
82
- fit(data, **options, function: function)
83
- end
84
-
85
- ##
86
- # @!method fit_exp(data, **options)
87
- # @!method fit_log(data, **options)
88
- # @!method fit_sin(data, **options)
89
- #
90
- # Shortcuts for fitting with several math functions (exp, log, sin).
91
- #
92
- # @param data [#to_gnuplot_points] method accepts the same sources as Dataset.new
93
- # and Dataset object
94
- # @param options [Hash] all of this options will be passed to #fit so you
95
- # can set here any options listed in its docs. If you pass here :initials hash, it
96
- # will be merged into default initals hash. Formula by default is
97
- # "yscale * (yoffset + #{function name}((x - xoffset) / xscale))", initials by default
98
- # "{ yoffset: 0.1, xoffset: 0.1, yscale: 1, xscale: 1 }"
99
- #
100
- # @return [Hash] hash with four elements:
101
- # - :formula_ds - dataset with best fit curve as data
102
- # - :coefficients - hash of calculated coefficients. So for this case
103
- # it will return hash with keys :yoffset, :xoffset, :yscale, :xscale and calculated values
104
- # - :deltas - Gnuplot calculates possible deltas for coefficients during fitting and
105
- # deltas hash contains this deltas
106
- # - :data - pointer to Datablock with given data
107
- #
108
- # @example
109
- # fit_exp(some_data, initials: { yoffset: -11 }, term_option: { xrange: 1..100 })
110
- # #=> The same as:
111
- # #=> fit(
112
- # #=> some_data,
113
- # #=> function: 'yscale * (yoffset + exp((x - xoffset) / xscale))',
114
- # #=> initals: { yoffset: -11, xoffset: 0.1, yscale: 1, xscale: 1 },
115
- # #=> term_option: { xrange: 1..100 }
116
- # #=> )
117
- # fit_log(...)
118
- # fit_sin(...)
119
- %w(exp log sin).map do |fname|
120
- define_method("fit_#{fname}".to_sym) do |data, **options|
121
- options[:initials] = {
122
- yoffset: 0.1,
123
- xoffset: 0.1,
124
- yscale: 1,
125
- xscale: 1
126
- }.merge(options[:initials] || {})
127
- function = "yscale * (yoffset + #{fname} ((x - xoffset) / xscale))"
128
- fit(data, **options, function: function)
129
- end
130
- end
131
-
132
- private
133
-
134
- ##
135
- # It takes some time to produce output so here we need
136
- # to wait for it.
137
- #
138
- # Max time to wait is stored in Settings.max_fit_delay, so one
139
- # can change it in order to wait longer.
140
- def wait_for_output(term, variables)
141
- # now we should catch 'error' from terminal: it will contain approximation data
142
- # but we can get a real error instead of output, so lets wait for limited time
143
- start = Time.now
144
- output = ''
145
- until output_ready?(output, variables)
146
- begin
147
- term.check_errors(raw: true)
148
- rescue GnuplotRB::GnuplotError => e
149
- output += e.message
150
- end
151
- if Time.now - start > Settings.max_fit_delay
152
- fail GnuplotError, "Seems like there is an error in gnuplotrb: #{output}"
153
- end
154
- end
155
- output
156
- end
157
-
158
- ##
159
- # Check if current output contains all the
160
- # variables given to fit.
161
- def output_ready?(output, variables)
162
- output =~ /Final set .*#{variables.join('.*')}/
163
- end
164
-
165
- ##
166
- # Parse Gnuplot's output to get coefficients and their deltas
167
- # from it. Also replaces coefficients in given function with
168
- # exact values.
169
- def parse_output(variables, function, output)
170
- plottable_function = " #{function.clone} "
171
- coefficients = {}
172
- deltas = {}
173
- variables.each do |var|
174
- value, error = output.scan(%r{#{var} *= ([^ ]+) *\+/\- ([^ ]+)})[0]
175
- plottable_function.gsub!(/#{var}([^0-9a-zA-Z])/) { value + Regexp.last_match(1) }
176
- coefficients[var] = value.to_f
177
- deltas[var] = error.to_f
178
- end
179
- [coefficients, deltas, plottable_function]
180
- end
181
-
182
- ##
183
- # Make fit command and send it to gnuplot
184
- def gnuplot_fit(function, data, options, initials, term_options)
185
- variables = initials.keys
186
- term = Terminal.new
187
- term.set(term_options)
188
- initials.each { |var_name, value| term.stream_puts "#{var_name} = #{value}" }
189
- command = "fit #{function} #{data.to_s(term, without_options: true)} " \
190
- "#{options} via #{variables.join(',')}"
191
- term.stream_puts(command)
192
- output = wait_for_output(term, variables)
193
- begin
194
- term.close
195
- rescue GnuplotError
196
- # Nothing interesting here.
197
- # If we had an error, we never reach this line.
198
- # Error here may be only additional information
199
- # such as correlation matrix.
200
- end
201
- output
202
- end
203
- end
204
- end
1
+ module GnuplotRB
2
+ ##
3
+ # Contains methods relating to Gnuplot's fit function. Covered in
4
+ # {fit notebook}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/fitting_data.ipynb].
5
+ #
6
+ # You can also see original gnuplot's fit in
7
+ # {gnuplot doc}[http://www.gnuplot.info/docs_5.0/gnuplot.pdf] p. 122.
8
+ module Fit
9
+ ##
10
+ # Fit given data with function.
11
+ #
12
+ # Fit waits for output from gnuplot Settings.max_fit_delay and throw exception if gets nothing.
13
+ # One can change this value in order to wait longer (if huge datasets is fitted).
14
+ #
15
+ # @param data [#to_gnuplot_points] method accepts the same sources as Dataset.new
16
+ # and Dataset object
17
+ # @param :function [String] function to fit data with
18
+ # @param :initials [Hash] initial values for coefficients used in fitting
19
+ # @param :term_options [Hash] terminal options that should be setted to terminal before fit.
20
+ # You can see them in Plot's documentation (or even better in gnuplot doc)
21
+ # Most useful here are ranges (xrange, yrange etc) and fit option which tunes fit parameters
22
+ # (see {gnuplot doc}[http://www.gnuplot.info/docs_5.0/gnuplot.pdf] p. 122)
23
+ # @param options [Hash] options passed to Gnuplot's fit such as *using*. They are covered in
24
+ # {gnuplot doc}[http://www.gnuplot.info/docs_5.0/gnuplot.pdf] (pp. 69-74)
25
+ #
26
+ # @return [Hash] hash with four elements:
27
+ # - :formula_ds - dataset with best fit curve as data
28
+ # - :coefficients - hash of calculated coefficients. So if you gave
29
+ # ``{ initials: {a: 1, b: 1, c: 1} }`` it will return hash with keys :a, :b, :c and its values
30
+ # - :deltas - Gnuplot calculates possible deltas for coefficients during fitting and
31
+ # deltas hash contains this deltas
32
+ # - :data - pointer to Datablock with given data
33
+ # @example
34
+ # fit(some_data, function: 'exp(a/x)', initials: {a: 10}, term_option: { xrange: 1..100 })
35
+ # fit(some_dataset, using: '1:2:3')
36
+ def fit(data, function: 'a2*x*x+a1*x+a0', initials: { a2: 1, a1: 1, a0: 1 }, term_options: {}, **options)
37
+ dataset = data.is_a?(Dataset) ? Dataset.new(data.data) : Dataset.new(data)
38
+ opts_str = OptionHandling.ruby_class_to_gnuplot(options)
39
+ output = gnuplot_fit(function, dataset, opts_str, initials, term_options)
40
+ res = parse_output(initials.keys, function, output)
41
+ {
42
+ formula_ds: Dataset.new(res[2], title: 'Fit formula'),
43
+ coefficients: res[0],
44
+ deltas: res[1],
45
+ data: dataset
46
+ }
47
+ end
48
+
49
+ ##
50
+ # Shortcut for fit with polynomial. Degree here is max power of *x* in polynomial.
51
+ #
52
+ # @param data [#to_gnuplot_points] method accepts the same sources as Dataset.new
53
+ # and Dataset object
54
+ # @param :degree [Integer] degree of polynomial
55
+ # @param options [Hash] all of this options will be passed to #fit so you
56
+ # can set here any options listed in its docs. If you pass here :initials hash, it
57
+ # will be merged into default initals hash. Formula by default is
58
+ # "xn*x**n + ... + x0*x**0", initials by default "{ an: 1, ..., a0: 1 }"
59
+ #
60
+ # @return [Hash] hash with four elements:
61
+ # - :formula_ds - dataset with best fit curve as data
62
+ # - :coefficients - hash of calculated coefficients. So for degree = 3
63
+ # it will return hash with keys :a3, :a2, :a1, :a0 and calculated values
64
+ # - :deltas - Gnuplot calculates possible deltas for coefficients during fitting and
65
+ # deltas hash contains this deltas
66
+ # - :data - pointer to Datablock with given data
67
+ # @example
68
+ # fit_poly(some_data, degree: 5, initials: { a4: 10, a2: -1 }, term_option: { xrange: 1..100 })
69
+ # #=> The same as:
70
+ # #=> fit(
71
+ # #=> some_data,
72
+ # #=> function: 'a5*x**5 + a4*x**4 + ... + a0*x**0',
73
+ # #=> initals: {a5: 1, a4: 10, a3: 1, a2: -1, a1: 1, a0: 1},
74
+ # #=> term_option: { xrange: 1..100 }
75
+ # #=> )
76
+ def fit_poly(data, degree: 2, **options)
77
+ sum_count = degree + 1
78
+ initials = {}
79
+ sum_count.times { |i| initials["a#{i}".to_sym] = 1 }
80
+ options[:initials] = initials.merge(options[:initials] || {})
81
+ function = sum_count.times.map { |i| "a#{i}*x**#{i}" }.join(' + ')
82
+ fit(data, **options, function: function)
83
+ end
84
+
85
+ ##
86
+ # @!method fit_exp(data, **options)
87
+ # @!method fit_log(data, **options)
88
+ # @!method fit_sin(data, **options)
89
+ #
90
+ # Shortcuts for fitting with several math functions (exp, log, sin).
91
+ #
92
+ # @param data [#to_gnuplot_points] method accepts the same sources as Dataset.new
93
+ # and Dataset object
94
+ # @param options [Hash] all of this options will be passed to #fit so you
95
+ # can set here any options listed in its docs. If you pass here :initials hash, it
96
+ # will be merged into default initals hash. Formula by default is
97
+ # "yscale * (yoffset + #{function name}((x - xoffset) / xscale))", initials by default
98
+ # "{ yoffset: 0.1, xoffset: 0.1, yscale: 1, xscale: 1 }"
99
+ #
100
+ # @return [Hash] hash with four elements:
101
+ # - :formula_ds - dataset with best fit curve as data
102
+ # - :coefficients - hash of calculated coefficients. So for this case
103
+ # it will return hash with keys :yoffset, :xoffset, :yscale, :xscale and calculated values
104
+ # - :deltas - Gnuplot calculates possible deltas for coefficients during fitting and
105
+ # deltas hash contains this deltas
106
+ # - :data - pointer to Datablock with given data
107
+ #
108
+ # @example
109
+ # fit_exp(some_data, initials: { yoffset: -11 }, term_option: { xrange: 1..100 })
110
+ # #=> The same as:
111
+ # #=> fit(
112
+ # #=> some_data,
113
+ # #=> function: 'yscale * (yoffset + exp((x - xoffset) / xscale))',
114
+ # #=> initals: { yoffset: -11, xoffset: 0.1, yscale: 1, xscale: 1 },
115
+ # #=> term_option: { xrange: 1..100 }
116
+ # #=> )
117
+ # fit_log(...)
118
+ # fit_sin(...)
119
+ %w(exp log sin).map do |fname|
120
+ define_method("fit_#{fname}".to_sym) do |data, **options|
121
+ options[:initials] = {
122
+ yoffset: 0.1,
123
+ xoffset: 0.1,
124
+ yscale: 1,
125
+ xscale: 1
126
+ }.merge(options[:initials] || {})
127
+ function = "yscale * (yoffset + #{fname} ((x - xoffset) / xscale))"
128
+ fit(data, **options, function: function)
129
+ end
130
+ end
131
+
132
+ private
133
+
134
+ ##
135
+ # It takes some time to produce output so here we need
136
+ # to wait for it.
137
+ #
138
+ # Max time to wait is stored in Settings.max_fit_delay, so one
139
+ # can change it in order to wait longer.
140
+ def wait_for_output(term, variables)
141
+ # now we should catch 'error' from terminal: it will contain approximation data
142
+ # but we can get a real error instead of output, so lets wait for limited time
143
+ start = Time.now
144
+ output = ''
145
+ until output_ready?(output, variables)
146
+ begin
147
+ term.check_errors(raw: true)
148
+ rescue GnuplotRB::GnuplotError => e
149
+ output += e.message
150
+ end
151
+ if Time.now - start > Settings.max_fit_delay
152
+ fail GnuplotError, "Seems like there is an error in gnuplotrb: #{output}"
153
+ end
154
+ end
155
+ output
156
+ end
157
+
158
+ ##
159
+ # Check if current output contains all the
160
+ # variables given to fit.
161
+ def output_ready?(output, variables)
162
+ output =~ /Final set .*#{variables.join('.*')}/
163
+ end
164
+
165
+ ##
166
+ # Parse Gnuplot's output to get coefficients and their deltas
167
+ # from it. Also replaces coefficients in given function with
168
+ # exact values.
169
+ def parse_output(variables, function, output)
170
+ plottable_function = " #{function.clone} "
171
+ coefficients = {}
172
+ deltas = {}
173
+ variables.each do |var|
174
+ value, error = output.scan(%r{#{var} *= ([^ ]+) *\+/\- ([^ ]+)})[0]
175
+ plottable_function.gsub!(/#{var}([^0-9a-zA-Z])/) { value + Regexp.last_match(1) }
176
+ coefficients[var] = value.to_f
177
+ deltas[var] = error.to_f
178
+ end
179
+ [coefficients, deltas, plottable_function]
180
+ end
181
+
182
+ ##
183
+ # Make fit command and send it to gnuplot
184
+ def gnuplot_fit(function, data, options, initials, term_options)
185
+ variables = initials.keys
186
+ term = Terminal.new
187
+ term.set(term_options)
188
+ initials.each { |var_name, value| term.stream_puts "#{var_name} = #{value}" }
189
+ command = "fit #{function} #{data.to_s(term, without_options: true)} " \
190
+ "#{options} via #{variables.join(',')}"
191
+ term.stream_puts(command)
192
+ output = wait_for_output(term, variables)
193
+ begin
194
+ term.close
195
+ rescue GnuplotError
196
+ # Nothing interesting here.
197
+ # If we had an error, we never reach this line.
198
+ # Error here may be only additional information
199
+ # such as correlation matrix.
200
+ end
201
+ output
202
+ end
203
+ end
204
+ end
@@ -1,48 +1,48 @@
1
- module GnuplotRB
2
- ##
3
- # Just a new error name
4
- class GnuplotError < ArgumentError
5
- end
6
-
7
- ##
8
- # Mixin for classes that need to run subprocess and
9
- # handle errors from its stderr.
10
- module ErrorHandling
11
- ##
12
- # Check if there were errors in previous commands.
13
- # Throws GnuplotError in case of any errors.
14
- def check_errors(raw: false)
15
- return if @err_array.empty?
16
- command = ''
17
- rest = ''
18
- @semaphore.synchronize do
19
- command = @err_array.first
20
- rest = @err_array[1..-1].join('; ')
21
- @err_array.clear
22
- end
23
- message = if raw
24
- "#{command};#{rest}}"
25
- else
26
- "Error in previous command (\"#{command}\"): \"#{rest}\""
27
- end
28
- fail GnuplotError, message
29
- end
30
-
31
- private
32
-
33
- ##
34
- # Start new thread that will read stderr given as stream
35
- # and add errors into @err_array.
36
- def handle_stderr(stream)
37
- @err_array = []
38
- # synchronize access to @err_array
39
- @semaphore = Mutex.new
40
- Thread.new do
41
- until (line = stream.gets).nil?
42
- line.strip!
43
- @semaphore.synchronize { @err_array << line if line.size > 3 }
44
- end
45
- end
46
- end
47
- end
48
- end
1
+ module GnuplotRB
2
+ ##
3
+ # Just a new error name
4
+ class GnuplotError < ArgumentError
5
+ end
6
+
7
+ ##
8
+ # Mixin for classes that need to run subprocess and
9
+ # handle errors from its stderr.
10
+ module ErrorHandling
11
+ ##
12
+ # Check if there were errors in previous commands.
13
+ # Throws GnuplotError in case of any errors.
14
+ def check_errors(raw: false)
15
+ return if @err_array.empty?
16
+ command = ''
17
+ rest = ''
18
+ @semaphore.synchronize do
19
+ command = @err_array.first
20
+ rest = @err_array[1..-1].join('; ')
21
+ @err_array.clear
22
+ end
23
+ message = if raw
24
+ "#{command};#{rest}}"
25
+ else
26
+ "Error in previous command (\"#{command}\"): \"#{rest}\""
27
+ end
28
+ fail GnuplotError, message
29
+ end
30
+
31
+ private
32
+
33
+ ##
34
+ # Start new thread that will read stderr given as stream
35
+ # and add errors into @err_array.
36
+ def handle_stderr(stream)
37
+ @err_array = []
38
+ # synchronize access to @err_array
39
+ @semaphore = Mutex.new
40
+ Thread.new do
41
+ until (line = stream.gets).nil?
42
+ line.strip!
43
+ @semaphore.synchronize { @err_array << line if line.size > 3 }
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end