gnuplotrb 0.3.1 → 0.3.2

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.
@@ -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