|
1 | 1 | module GnuplotRB
|
2 | 2 | ##
|
3 |
| - # ====== Overview |
4 |
| - # Fit given data with function. Covered in |
5 |
| - # {fit notebook}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/fitting_data.ipynb]. |
6 |
| - # ====== Arguments |
7 |
| - # * *data* - method accepts the same sources as Dataset.new |
8 |
| - # and Dataset object |
9 |
| - # * *:function* - function to fit data with. Default 'a2*x*x+a1*x+a0' |
10 |
| - # * *:initials* - initial values for coefficients used in fitting. |
11 |
| - # Default: {a2: 1, a1: 1, a0: 1} |
12 |
| - # * *:term_options* - terminal options that should be setted to terminal before fit. |
13 |
| - # You can see them in Plot's documentation (or even better in gnuplot doc). |
14 |
| - # Most useful here are ranges (xrange, yrange etc) and fit option which tunes fit parameters |
15 |
| - # (see {gnuplot doc}[http://www.gnuplot.info/docs_5.0/gnuplot.pdf] p. 122). |
16 |
| - # * *options* - options passed to Gnuplot's fit such as *using*. They are covered in |
17 |
| - # {gnuplot doc}[http://www.gnuplot.info/docs_5.0/gnuplot.pdf](pp. 69-74) |
18 |
| - # ====== Return value |
19 |
| - # Fit returns hash of 4 elements: |
20 |
| - # * *:formula_ds* - dataset with best fit curve as data |
21 |
| - # * *:coefficients* - hash of calculated coefficients. So if you gave {via: [:a, :b, :c]} or |
22 |
| - # {initials: {a: 1, b: 1, c: 1} } it will return hash with keys :a, :b, :c and its values |
23 |
| - # * *:deltas* - Gnuplot calculates possible deltas for coefficients during fitting and |
24 |
| - # *deltas* hash contains this deltas |
25 |
| - # * *:data* - pointer to Datablock with given data |
26 |
| - # ====== Examples |
27 |
| - # fit(some_data, function: 'exp(a/x)', initials: {a: 10}, term_option: { xrange: 1..100 }) |
28 |
| - # fit(some_dataset, using: '1:2:3') |
29 |
| - def fit(data, function: 'a2*x*x+a1*x+a0', initials: { a2: 1, a1: 1, a0: 1 }, term_options: {}, **options) |
30 |
| - dataset = data.is_a?(Dataset) ? Dataset.new(data.data) : Dataset.new(data) |
31 |
| - opts_str = OptionHandling.ruby_class_to_gnuplot(options) |
32 |
| - output = gnuplot_fit(function, dataset, opts_str, initials, term_options) |
33 |
| - res = parse_output(initials.keys, function, output) |
34 |
| - { |
35 |
| - formula_ds: Dataset.new(res[2], title: 'Fit formula'), |
36 |
| - coefficients: res[0], |
37 |
| - deltas: res[1], |
38 |
| - data: dataset |
39 |
| - } |
40 |
| - end |
41 |
| - |
42 |
| - ## |
43 |
| - # ====== Overview |
44 |
| - # Shortcut for fit with polynomial. Degree here is max power of *x* in polynomial. |
45 |
| - # ====== Arguments |
46 |
| - # * *data* - method accepts the same sources as Dataset.new and Dataset object |
47 |
| - # * *:degree* - degree of polynomial |
48 |
| - # * *options* - all of this options will be passed to *#fit* so you |
49 |
| - # can set here any *term_options*. If you pass here *:initials* hash, it |
50 |
| - # will be merged into default initals hash (all values are 1). |
51 |
| - # ====== Return value |
52 |
| - # See the same section for #fit. |
53 |
| - # ====== Examples |
54 |
| - # fit_poly(some_data, degree: 5, initials: { a4: 10, a2: -1 }, term_option: { xrange: 1..100 }) |
55 |
| - # #=> The same as: |
56 |
| - # #=> fit( |
57 |
| - # #=> some_data, |
58 |
| - # #=> function: 'a5*x**5 + a4*x**4 + ... + a0*x**0', |
59 |
| - # #=> initals: {a5: 1, a4: 10, a3: 1, a2: -1, a1: 1, a0: 1}, |
60 |
| - # #=> term_option: { xrange: 1..100 } |
61 |
| - # #=> ) |
62 |
| - def fit_poly(data, degree: 2, **options) |
63 |
| - sum_count = degree + 1 |
64 |
| - initials = {} |
65 |
| - sum_count.times { |i| initials["a#{i}".to_sym] = 1 } |
66 |
| - options[:initials] = initials.merge(options[:initials] || {}) |
67 |
| - function = sum_count.times.map { |i| "a#{i}*x**#{i}" }.join(' + ') |
68 |
| - fit(data, **options, function: function) |
69 |
| - end |
| 3 | + # Contains methods relating to Gnuplot's fit function. |
| 4 | + module Fit |
| 5 | + ## |
| 6 | + # ====== Overview |
| 7 | + # Fit given data with function. Covered in |
| 8 | + # {fit notebook}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/fitting_data.ipynb]. |
| 9 | + # ====== Arguments |
| 10 | + # * *data* - method accepts the same sources as Dataset.new |
| 11 | + # and Dataset object |
| 12 | + # * *:function* - function to fit data with. Default 'a2*x*x+a1*x+a0' |
| 13 | + # * *:initials* - initial values for coefficients used in fitting. |
| 14 | + # Default: {a2: 1, a1: 1, a0: 1} |
| 15 | + # * *:term_options* - terminal options that should be setted to terminal before fit. |
| 16 | + # You can see them in Plot's documentation (or even better in gnuplot doc). |
| 17 | + # Most useful here are ranges (xrange, yrange etc) and fit option which tunes fit parameters |
| 18 | + # (see {gnuplot doc}[http://www.gnuplot.info/docs_5.0/gnuplot.pdf] p. 122). |
| 19 | + # * *options* - options passed to Gnuplot's fit such as *using*. They are covered in |
| 20 | + # {gnuplot doc}[http://www.gnuplot.info/docs_5.0/gnuplot.pdf](pp. 69-74) |
| 21 | + # ====== Return value |
| 22 | + # Fit returns hash of 4 elements: |
| 23 | + # * *:formula_ds* - dataset with best fit curve as data |
| 24 | + # * *:coefficients* - hash of calculated coefficients. So if you gave {via: [:a, :b, :c]} or |
| 25 | + # {initials: {a: 1, b: 1, c: 1} } it will return hash with keys :a, :b, :c and its values |
| 26 | + # * *:deltas* - Gnuplot calculates possible deltas for coefficients during fitting and |
| 27 | + # *deltas* hash contains this deltas |
| 28 | + # * *:data* - pointer to Datablock with given data |
| 29 | + # ====== Examples |
| 30 | + # fit(some_data, function: 'exp(a/x)', initials: {a: 10}, term_option: { xrange: 1..100 }) |
| 31 | + # fit(some_dataset, using: '1:2:3') |
| 32 | + def fit(data, function: 'a2*x*x+a1*x+a0', initials: { a2: 1, a1: 1, a0: 1 }, term_options: {}, **options) |
| 33 | + dataset = data.is_a?(Dataset) ? Dataset.new(data.data) : Dataset.new(data) |
| 34 | + opts_str = OptionHandling.ruby_class_to_gnuplot(options) |
| 35 | + output = gnuplot_fit(function, dataset, opts_str, initials, term_options) |
| 36 | + res = parse_output(initials.keys, function, output) |
| 37 | + { |
| 38 | + formula_ds: Dataset.new(res[2], title: 'Fit formula'), |
| 39 | + coefficients: res[0], |
| 40 | + deltas: res[1], |
| 41 | + data: dataset |
| 42 | + } |
| 43 | + end |
70 | 44 |
|
71 |
| - ## |
72 |
| - # :method: fit_<function> |
73 |
| - # :call-seq: |
74 |
| - # fit_exp(data, **options) -> Hash |
75 |
| - # fit_log(data, **options) -> Hash |
76 |
| - # fit_sin(data, **options) -> Hash |
77 |
| - # |
78 |
| - # ====== Overview |
79 |
| - # Shortcuts for fitting with several math functions (exp, log, sin). |
80 |
| - # ====== Arguments |
81 |
| - # * *data* - method accepts the same sources as Dataset.new and Dataset object |
82 |
| - # * *options* - all of this options will be passed to *#fit* so you |
83 |
| - # can set here any *term_options*. If you pass here *:initials* hash, it |
84 |
| - # will be merged into default initals hash { yoffset: 0.1, xoffset: 0.1, yscale: 1, xscale: 1 } |
85 |
| - # ====== Return value |
86 |
| - # See the same section for #fit. |
87 |
| - # ====== Examples |
88 |
| - # fit_exp(some_data, initials: { yoffset: -11 }, term_option: { xrange: 1..100 }) |
89 |
| - # #=> The same as: |
90 |
| - # #=> fit( |
91 |
| - # #=> some_data, |
92 |
| - # #=> function: 'yscale * (yoffset + exp((x - xoffset) / xscale))', |
93 |
| - # #=> initals: { yoffset: -11, xoffset: 0.1, yscale: 1, xscale: 1 }, |
94 |
| - # #=> term_option: { xrange: 1..100 } |
95 |
| - # #=> ) |
96 |
| - # fit_log(...) |
97 |
| - # fit_sin(...) |
98 |
| - %w(exp log sin).map do |fname| |
99 |
| - define_method("fit_#{fname}".to_sym) do |data, **options| |
100 |
| - options[:initials] = { |
101 |
| - yoffset: 0.1, |
102 |
| - xoffset: 0.1, |
103 |
| - yscale: 1, |
104 |
| - xscale: 1 |
105 |
| - }.merge(options[:initials] || {}) |
106 |
| - function = "yscale * (yoffset + #{fname} ((x - xoffset) / xscale))" |
| 45 | + ## |
| 46 | + # ====== Overview |
| 47 | + # Shortcut for fit with polynomial. Degree here is max power of *x* in polynomial. |
| 48 | + # ====== Arguments |
| 49 | + # * *data* - method accepts the same sources as Dataset.new and Dataset object |
| 50 | + # * *:degree* - degree of polynomial |
| 51 | + # * *options* - all of this options will be passed to *#fit* so you |
| 52 | + # can set here any *term_options*. If you pass here *:initials* hash, it |
| 53 | + # will be merged into default initals hash (all values are 1). |
| 54 | + # ====== Return value |
| 55 | + # See the same section for #fit. |
| 56 | + # ====== Examples |
| 57 | + # fit_poly(some_data, degree: 5, initials: { a4: 10, a2: -1 }, term_option: { xrange: 1..100 }) |
| 58 | + # #=> The same as: |
| 59 | + # #=> fit( |
| 60 | + # #=> some_data, |
| 61 | + # #=> function: 'a5*x**5 + a4*x**4 + ... + a0*x**0', |
| 62 | + # #=> initals: {a5: 1, a4: 10, a3: 1, a2: -1, a1: 1, a0: 1}, |
| 63 | + # #=> term_option: { xrange: 1..100 } |
| 64 | + # #=> ) |
| 65 | + def fit_poly(data, degree: 2, **options) |
| 66 | + sum_count = degree + 1 |
| 67 | + initials = {} |
| 68 | + sum_count.times { |i| initials["a#{i}".to_sym] = 1 } |
| 69 | + options[:initials] = initials.merge(options[:initials] || {}) |
| 70 | + function = sum_count.times.map { |i| "a#{i}*x**#{i}" }.join(' + ') |
107 | 71 | fit(data, **options, function: function)
|
108 | 72 | end
|
109 |
| - end |
110 |
| - |
111 |
| - private |
112 | 73 |
|
113 |
| - ## |
114 |
| - # It takes some time to produce output so here we need |
115 |
| - # to wait for it. |
116 |
| - def wait_for_output(term, variables) |
117 |
| - # now we should catch 'error' from terminal: it will contain approximation data |
118 |
| - # but we can get a real error instead of output, so lets wait for limited time |
119 |
| - start = Time.now |
120 |
| - output = '' |
121 |
| - until output_ready?(output, variables) |
122 |
| - begin |
123 |
| - term.check_errors |
124 |
| - rescue GnuplotRB::GnuplotError => e |
125 |
| - output += e.message |
| 74 | + ## |
| 75 | + # :method: fit_<function> |
| 76 | + # :call-seq: |
| 77 | + # fit_exp(data, **options) -> Hash |
| 78 | + # fit_log(data, **options) -> Hash |
| 79 | + # fit_sin(data, **options) -> Hash |
| 80 | + # |
| 81 | + # ====== Overview |
| 82 | + # Shortcuts for fitting with several math functions (exp, log, sin). |
| 83 | + # ====== Arguments |
| 84 | + # * *data* - method accepts the same sources as Dataset.new and Dataset object |
| 85 | + # * *options* - all of this options will be passed to *#fit* so you |
| 86 | + # can set here any *term_options*. If you pass here *:initials* hash, it |
| 87 | + # will be merged into default initals hash { yoffset: 0.1, xoffset: 0.1, yscale: 1, xscale: 1 } |
| 88 | + # ====== Return value |
| 89 | + # See the same section for #fit. |
| 90 | + # ====== Examples |
| 91 | + # fit_exp(some_data, initials: { yoffset: -11 }, term_option: { xrange: 1..100 }) |
| 92 | + # #=> The same as: |
| 93 | + # #=> fit( |
| 94 | + # #=> some_data, |
| 95 | + # #=> function: 'yscale * (yoffset + exp((x - xoffset) / xscale))', |
| 96 | + # #=> initals: { yoffset: -11, xoffset: 0.1, yscale: 1, xscale: 1 }, |
| 97 | + # #=> term_option: { xrange: 1..100 } |
| 98 | + # #=> ) |
| 99 | + # fit_log(...) |
| 100 | + # fit_sin(...) |
| 101 | + %w(exp log sin).map do |fname| |
| 102 | + define_method("fit_#{fname}".to_sym) do |data, **options| |
| 103 | + options[:initials] = { |
| 104 | + yoffset: 0.1, |
| 105 | + xoffset: 0.1, |
| 106 | + yscale: 1, |
| 107 | + xscale: 1 |
| 108 | + }.merge(options[:initials] || {}) |
| 109 | + function = "yscale * (yoffset + #{fname} ((x - xoffset) / xscale))" |
| 110 | + fit(data, **options, function: function) |
126 | 111 | end
|
127 |
| - if Time.now - start > Settings.max_fit_delay |
128 |
| - fail GnuplotError, "Seems like there is an error in gnuplotrb: #{output}" |
| 112 | + end |
| 113 | + |
| 114 | + private |
| 115 | + |
| 116 | + ## |
| 117 | + # It takes some time to produce output so here we need |
| 118 | + # to wait for it. |
| 119 | + def wait_for_output(term, variables) |
| 120 | + # now we should catch 'error' from terminal: it will contain approximation data |
| 121 | + # but we can get a real error instead of output, so lets wait for limited time |
| 122 | + start = Time.now |
| 123 | + output = '' |
| 124 | + until output_ready?(output, variables) |
| 125 | + begin |
| 126 | + term.check_errors |
| 127 | + rescue GnuplotRB::GnuplotError => e |
| 128 | + output += e.message |
| 129 | + end |
| 130 | + if Time.now - start > Settings.max_fit_delay |
| 131 | + fail GnuplotError, "Seems like there is an error in gnuplotrb: #{output}" |
| 132 | + end |
129 | 133 | end
|
| 134 | + output |
130 | 135 | end
|
131 |
| - output |
132 |
| - end |
133 | 136 |
|
134 |
| - ## |
135 |
| - # Check if current output contains all the |
136 |
| - # variables given to fit. |
137 |
| - def output_ready?(output, variables) |
138 |
| - output =~ /Final set .*#{variables.join('.*')}/ |
139 |
| - end |
| 137 | + ## |
| 138 | + # Check if current output contains all the |
| 139 | + # variables given to fit. |
| 140 | + def output_ready?(output, variables) |
| 141 | + output =~ /Final set .*#{variables.join('.*')}/ |
| 142 | + end |
140 | 143 |
|
141 |
| - ## |
142 |
| - # Parse Gnuplot's output to get coefficients and their deltas |
143 |
| - # from it. Also replaces coefficients in given function with |
144 |
| - # exact values. |
145 |
| - def parse_output(variables, function, output) |
146 |
| - plottable_function = " #{function.clone} " |
147 |
| - coefficients = {} |
148 |
| - deltas = {} |
149 |
| - variables.each do |var| |
150 |
| - value, error = output.scan(%r{#{var} *= ([^ ]+) *\+/\- ([^ ]+)})[0] |
151 |
| - plottable_function.gsub!(/#{var}([^0-9a-zA-Z])/) { value + Regexp.last_match(1) } |
152 |
| - coefficients[var] = value.to_f |
153 |
| - deltas[var] = error.to_f |
| 144 | + ## |
| 145 | + # Parse Gnuplot's output to get coefficients and their deltas |
| 146 | + # from it. Also replaces coefficients in given function with |
| 147 | + # exact values. |
| 148 | + def parse_output(variables, function, output) |
| 149 | + plottable_function = " #{function.clone} " |
| 150 | + coefficients = {} |
| 151 | + deltas = {} |
| 152 | + variables.each do |var| |
| 153 | + value, error = output.scan(%r{#{var} *= ([^ ]+) *\+/\- ([^ ]+)})[0] |
| 154 | + plottable_function.gsub!(/#{var}([^0-9a-zA-Z])/) { value + Regexp.last_match(1) } |
| 155 | + coefficients[var] = value.to_f |
| 156 | + deltas[var] = error.to_f |
| 157 | + end |
| 158 | + [coefficients, deltas, plottable_function] |
154 | 159 | end
|
155 |
| - [coefficients, deltas, plottable_function] |
156 |
| - end |
157 | 160 |
|
158 |
| - ## |
159 |
| - # Make fit command and send it to gnuplot |
160 |
| - def gnuplot_fit(function, data, options, initials, term_options) |
161 |
| - variables = initials.keys |
162 |
| - term = Terminal.new |
163 |
| - term.set(term_options) |
164 |
| - initials.each { |var_name, value| term.stream_puts "#{var_name} = #{value}" } |
165 |
| - command = "fit #{function} #{data.to_s(term)} #{options} via #{variables.join(',')}" |
166 |
| - term.stream_puts(command) |
167 |
| - output = wait_for_output(term, variables) |
168 |
| - begin |
169 |
| - term.close |
170 |
| - rescue GnuplotError |
171 |
| - # Nothing interesting here. |
172 |
| - # If we had an error, we never reach this line. |
173 |
| - # Error here may be only additional information |
174 |
| - # such as correlation matrix. |
| 161 | + ## |
| 162 | + # Make fit command and send it to gnuplot |
| 163 | + def gnuplot_fit(function, data, options, initials, term_options) |
| 164 | + variables = initials.keys |
| 165 | + term = Terminal.new |
| 166 | + term.set(term_options) |
| 167 | + initials.each { |var_name, value| term.stream_puts "#{var_name} = #{value}" } |
| 168 | + command = "fit #{function} #{data.to_s(term)} #{options} via #{variables.join(',')}" |
| 169 | + term.stream_puts(command) |
| 170 | + output = wait_for_output(term, variables) |
| 171 | + begin |
| 172 | + term.close |
| 173 | + rescue GnuplotError |
| 174 | + # Nothing interesting here. |
| 175 | + # If we had an error, we never reach this line. |
| 176 | + # Error here may be only additional information |
| 177 | + # such as correlation matrix. |
| 178 | + end |
| 179 | + output |
175 | 180 | end
|
176 |
| - output |
177 | 181 | end
|
178 | 182 | end
|
0 commit comments