polynomial 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/polynomial.rb ADDED
@@ -0,0 +1,532 @@
1
+ begin
2
+ require 'handy_hash'
3
+ rescue LoadError
4
+ unless defined? HandyHash
5
+ $stderr.puts 'HandyHash not be loaded, abbreviations not enabled'
6
+ HandyHash = Hash
7
+ Hash.class_eval { alias merge_abbrv merge }
8
+ end
9
+ end
10
+
11
+ # Polynomials on a single variable.
12
+ #
13
+ class Polynomial
14
+
15
+ attr_reader :coefs
16
+
17
+ class << self
18
+ alias [] new
19
+ end
20
+
21
+ def dup
22
+ Marshal.load(Marshal.dump(self))
23
+ end
24
+
25
+ # Creates a new polynomial with provided coefficients, which are interpreted
26
+ # as corresponding to increasing powers of _x_. Alternatively, a hash
27
+ # of power=>coefficients may be supplied, as well as the polynomial
28
+ # degree plus a block to compute each coefficient from correspoding degree.
29
+ # If a string, optionally followed by a arguments hash, is supplied,
30
+ # from_string is called.
31
+ #
32
+ # Examples:
33
+ # Polynomial.new(1, 2).to_s #=> "1 + 2*x"
34
+ # Polynomial.new(1, Complex(2,3)).to_s #=> "1 + (2+3i)*x"
35
+ # Polynomial.new(3) {|n| n+1 }.to_s #=> "1 + 2*x + 3*x**2 + 4*x**3"
36
+ # Polynomial[3, 4, 5].to_s #=> "3 + 4*x + 5x**2"
37
+ # Polynomial[1 => 2, 3 => 4].to_s #=> "2*x + 4x**3"
38
+ # Polynomial.new('x^2-1', :power_symbol=>'^').to_s #=> "-1 + x**2"
39
+ #
40
+ def initialize(*coefs)
41
+ case coefs[0]
42
+ when Integer
43
+ if block_given?
44
+ coefs = 0.upto(coefs[0]).map {|degree| yield(degree) }
45
+ end
46
+ when Hash
47
+ coefs = self.class.coefs_from_pow_coefs(coefs[0])
48
+ when String
49
+ coefs = self.class.coefs_from_string(coefs[0], coefs[1] || {})
50
+ else
51
+ coefs.flatten!
52
+ if coefs.empty?
53
+ raise ArgumentError, 'at least one coefficient should be supplied'
54
+ elsif !coefs.all? {|c| c.is_a? Numeric }
55
+ raise TypeError, 'non-Numeric coefficient supplied'
56
+ end
57
+ end
58
+ @coefs = Polynomial.remove_trailing_zeroes(coefs)
59
+ @coefs.freeze
60
+ end
61
+
62
+ FromStringDefaults = HandyHash[
63
+ :power_symbol => '**',
64
+ :multiplication_symbol => '*',
65
+ :variable_name => 'x',
66
+ ]
67
+ # Creates Polynomial from a String in appropriate format.
68
+ # Coefficients must be integers or decimals (interpreted as floats).
69
+ #
70
+ # Warning: complex coefficients are not currently accepted.
71
+ #
72
+ # Examples:
73
+ # Polynomial.from_string("1 - 7*x**2") == Polynomial.new(1,0,-7) #=> true
74
+ # Polynomial.from_string("3 + 4.1x + 5x^2", :multiplication_symbol=>'', :power_symbol=>'^') == Polynomial.new(3,4.1,5) #=> true
75
+ # Polynomial.from_string("-3*y", :variable_name=>'y') == Polynomial.new(0,-3) #=> true
76
+ # Polynomial.from_string('x^2-1', :power_symbol=>'^').to_s #=> -1 + x**2
77
+ #
78
+ def self.from_string(s, params={})
79
+ Polynomial.new(self.coefs_from_string(s, FromStringDefaults.merge_abbrv(params)))
80
+ end
81
+
82
+ # Degree of the polynomial (i.e., highest not null power of the variable).
83
+ #
84
+ def degree
85
+ @coefs.size-1
86
+ end
87
+
88
+ # Evaluates Polynomial of degree _n_ at point _x_ in O(_n_) time using Horner's rule.
89
+ #
90
+ def substitute(x)
91
+ total = @coefs.last
92
+ @coefs[0..-2].reverse.each do |a|
93
+ total = total * x + a
94
+ end
95
+ total
96
+ end
97
+
98
+ # Returns an array with the 1st, 2nd, ..., +degree+ derivatives.
99
+ # These are the only possibly non null derivatives, subsequent ones would
100
+ # necesseraly be zero.
101
+ #
102
+ def derivatives
103
+ ds = []
104
+ d = self
105
+ begin
106
+ d = d.derivative
107
+ ds << d
108
+ end until d.zero?
109
+ ds
110
+ end
111
+
112
+ def coerce(other)
113
+ case other
114
+ when Numeric
115
+ [Polynomial.new(other), self]
116
+ when Polynomial
117
+ [other, self]
118
+ else
119
+ raise TypeError, "#{other.class} can't be coerced into Polynomial"
120
+ end
121
+ end
122
+
123
+ # Add another Polynomial or Numeric object.
124
+ #
125
+ def +(other)
126
+ case other
127
+ when Numeric
128
+ self + Polynomial.new(other)
129
+ else
130
+ small, big = [self, other].sort
131
+ a = big.coefs.dup
132
+ for n in 0 .. small.degree
133
+ a[n] += small.coefs[n]
134
+ end
135
+ Polynomial.new(a)
136
+ end
137
+ end
138
+
139
+ # Generates a Polynomial object with negated coefficients.
140
+ #
141
+ def -@
142
+ Polynomial.new(coefs.map {|x| -x})
143
+ end
144
+
145
+ # Subtract another Polynomial or Numeric object.
146
+ #
147
+ def -(other)
148
+ self + (-other)
149
+ end
150
+
151
+ # Multiply by another Polynomial or Numeric object.
152
+ # As the straightforward algorithm is used, multipling two polynomials of
153
+ # degree _m_ and _n_ takes O(_m_ _n_) operations. It is well-known, though,
154
+ # that the Fast Fourier Transform may be employed to reduce the time
155
+ # complexity to O(_K_ log _K_), where _K_ = max{_m_, _n_}.
156
+ #
157
+ def *(other)
158
+ case other
159
+ when Numeric
160
+ result_coefs = @coefs.map {|a| a * other}
161
+ else
162
+ result_coefs = [0] * (self.degree + other.degree + 2)
163
+ for m in 0 .. self.degree
164
+ for n in 0 .. other.degree
165
+ result_coefs[m+n] += @coefs[m] * other.coefs[n]
166
+ end
167
+ end
168
+ end
169
+ Polynomial.new(result_coefs)
170
+ end
171
+
172
+ # Divides by +divisor+ (using coefficients' #quo), returning quotient and rest.
173
+ # If dividend and divisor have Intenger or Rational coefficients,
174
+ # then both quotient and rest will have Rational coefficients.
175
+ # If +divisor+ is a polynomial, uses polynomial long division.
176
+ # Otherwise, performs the division direclty on the coefficients.
177
+ #
178
+ def quomod(divisor)
179
+ case divisor
180
+ when Numeric
181
+ new_coefs = @coefs.map {|a| a.quo(divisor) }
182
+ q, r = Polynomial.new(new_coefs), 0
183
+ when Polynomial
184
+ a = self; b = divisor; q = 0; r = self
185
+ (a.degree - b.degree + 1).times do
186
+ dd = r.degree - b.degree
187
+ qqa = r.coefs[-1].quo(b.coefs[-1])
188
+ qq = Polynomial[dd => qqa]
189
+ q += qq
190
+ r -= qq * divisor
191
+ break if r.zero?
192
+ end
193
+ else
194
+ raise ArgumentError, 'divisor should be Numeric or Polynomial'
195
+ end
196
+ [q, r]
197
+ end
198
+
199
+ # Divides by +divisor+ (using coefficients' #quo), which may be a number
200
+ # or another polynomial, which need not be divisible by the former.
201
+ # If dividend and divisor have Intenger or Rational coefficients,
202
+ # then the quotient will have Rational coefficients.
203
+ #
204
+ #
205
+ def quo(divisor)
206
+ quomod(divisor).first
207
+ end
208
+
209
+ # Divides by +divisor+, returing quotient and rest.
210
+ # If +divisor+ is a polynomial, uses polynomial long division.
211
+ # Otherwise, performs the division direclty on the coefficients.
212
+ #
213
+ def divmod(divisor)
214
+ case divisor
215
+ when Numeric
216
+ new_coefs = @coefs.map do |a|
217
+ if divisor.is_a?(Integer)
218
+ qq, rr = a.divmod(divisor)
219
+ if rr.zero? then qq else a / divisor.to_f end
220
+ else
221
+ a / divisor
222
+ end
223
+ end
224
+ q, r = Polynomial.new(new_coefs), 0
225
+ when Polynomial
226
+ a = self; b = divisor; q = 0; r = self
227
+ (a.degree - b.degree + 1).times do
228
+ dd = r.degree - b.degree
229
+ qqa = r.coefs[-1] / (b.coefs[-1].to_f rescue b.coefs[-1]) # rescue for complex numbers
230
+ qq = Polynomial[dd => qqa]
231
+ q += qq
232
+ r -= qq * divisor
233
+ break if r.zero?
234
+ end
235
+ else
236
+ raise ArgumentError, 'divisor should be Numeric or Polynomial'
237
+ end
238
+ [q, r]
239
+ end
240
+
241
+ # Divides polynomial by a number or another polynomial,
242
+ # which need not be divisible by the former.
243
+ #
244
+ def div(other)
245
+ divmod(other).first
246
+ end
247
+ alias / div
248
+
249
+ # Remainder of division by supplied divisor, which may be another polynomial
250
+ # or a number.
251
+ #
252
+ def %(other)
253
+ divmod(other).last
254
+ end
255
+
256
+ # Raises to non-negative integer power.
257
+ #
258
+ def **(n)
259
+ raise ArgumentError, "negative argument" if n < 0
260
+ result = Polynomial[1]
261
+ n.times do
262
+ result *= self
263
+ end
264
+ result
265
+ end
266
+
267
+ # Computes polynomial's indefinite integral (which is itself a polynomial).
268
+ #
269
+ def integral
270
+ a = Array.new(@coefs.size+1)
271
+ a[0] = 0
272
+ @coefs.each.with_index do |coef, n|
273
+ if coef.is_a?(Integer) && coef.modulo(n+1).zero?
274
+ a[n+1] = coef / (n + 1)
275
+ else
276
+ a[n+1] = coef / (n + 1.0)
277
+ end
278
+ end
279
+ Polynomial.new(a)
280
+ end
281
+
282
+ # Computes polynomial's derivative (which is itself a polynomial).
283
+ #
284
+ def derivative
285
+ if degree > 0
286
+ a = Array.new(@coefs.size-1)
287
+ a.each_index { |n| a[n] = (n+1) * @coefs[n+1] }
288
+ else
289
+ a = [0]
290
+ end
291
+ Polynomial.new(a)
292
+ end
293
+
294
+ ToSDefaults = HandyHash[
295
+ :verbose => false,
296
+ :spaced => true,
297
+ :power_symbol => '**',
298
+ :multiplication_symbol => '*',
299
+ :variable_name => 'x',
300
+ :decreasing => false
301
+ ]
302
+ # Generate the string corresponding to the polynomial.
303
+ # If :verbose is false (default), omits zero-coefficiented monomials.
304
+ # If :spaced is true (default), monomials are spaced.
305
+ # If :decreasing is false (default), monomials are present from
306
+ # lowest to greatest degree.
307
+ #
308
+ # Examples:
309
+ # Polynomial[1,-2,3].to_s #=> "1 - 2*x + 3*x**2"
310
+ # Polynomial[1,-2,3].to_s(:spaced=>false) #=> "1-2*x+3*x**2"
311
+ # Polynomial[1,-2,3].to_s(:decreasing=>true) #=> "3*x**2 - 2*x + 1"
312
+ # Polynomial[1,0,3].to_s(:verbose=>true) #=> "1 + 0*x + 3*x**2"
313
+ #
314
+ def to_s(params={})
315
+ params = ToSDefaults.merge_abbrv(params)
316
+ mult = params[:multiplication_symbol]
317
+ pow = params[:power_symbol]
318
+ var = params[:variable_name]
319
+ coefs_index_enumerator = if params[:decreasing]
320
+ @coefs.each.with_index.reverse_each
321
+ else
322
+ @coefs.each.with_index
323
+ end
324
+ result = ''
325
+ coefs_index_enumerator.each do |a,n|
326
+ next if a.zero? && degree > 0 && !params[:verbose]
327
+ result += '+' unless result.empty?
328
+ coef_str = a.to_s
329
+ coef_str = '(' + coef_str + ')' if coef_str[/[+\/]/]
330
+ result += coef_str unless a == 1 && n > 0
331
+ result += "#{mult}" if a != 1 && n > 0
332
+ result += "#{var}" if n >= 1
333
+ result += "#{pow}#{n}" if n >= 2
334
+ end
335
+ result.gsub!(/\+-/,'-')
336
+ result.gsub!(/([^e\(])(\+|-)(.)/,'\1 \2 \3') if params[:spaced]
337
+ result
338
+ end
339
+
340
+ # Converts a zero-degree polynomial to a number, i.e., returns its only
341
+ # coefficient. If degree is positive, an exception is raised.
342
+ #
343
+ def to_num
344
+ if self.degree == 0
345
+ @coefs[0]
346
+ else
347
+ raise ArgumentError, "can't convert Polynomial of positive degree to Numeric"
348
+ end
349
+ end
350
+
351
+ # Returns the only coefficient of a zero-degree polynomial converted to a
352
+ # Float. If degree is positive, an exception is raised.
353
+ #
354
+ def to_f; to_num.to_f; end
355
+
356
+ # Returns the only coefficient of a zero-degree polynomial converted to an
357
+ # Integer. If degree is positive, an exception is raised.
358
+ #
359
+ def to_i; to_num.to_i; end
360
+
361
+ # If EasyPlot can be loaded, plot method is defined.
362
+ begin
363
+ require 'easy_plot'
364
+
365
+ # Plots polynomial using EasyPlot.
366
+ #
367
+ def plot(params={})
368
+ EasyPlot.plot(self.to_s, params)
369
+ end
370
+ rescue LoadError
371
+ $stderr.puts 'EasyPlot could not be loaded, thus plotting convenience methods were not defined.'
372
+ end
373
+
374
+ # Compares with another Polynomial by degrees then coefficients.
375
+ #
376
+ def <=>(other)
377
+ case other
378
+ when Numeric
379
+ [self.degree, @coefs[0]] <=> [0, other]
380
+ when Polynomial
381
+ [self.degree, @coefs] <=> [other.degree, other.coefs]
382
+ else
383
+ raise TypeError, "can't compare #{other.class} to Polynomial"
384
+ end
385
+ end
386
+ include Comparable
387
+
388
+ # Returns true if the other Polynomial has same degree and close-enough
389
+ # (up to delta absolute difference) coefficients. Returns false otherwise.
390
+ #
391
+ def equal_in_delta(other, delta)
392
+ return false unless self.degree == other.degree
393
+ for n in 0 .. degree
394
+ return false unless (@coefs[n] - other.coefs[n]).abs <= delta
395
+ end
396
+ true
397
+ end
398
+
399
+ private
400
+
401
+ def self.remove_trailing_zeroes(ary)
402
+ m = 0
403
+ ary.reverse.each.with_index do |a,n|
404
+ unless a.zero?
405
+ m = n+1
406
+ break
407
+ end
408
+ end
409
+ ary[0..-m]
410
+ end
411
+
412
+ # Converts a power-to-coefficient Hash into the Array of coefficients.
413
+ #
414
+ def self.coefs_from_pow_coefs(hash, params={})
415
+ power_coefs = Hash.new(0).merge(hash)
416
+ (0..power_coefs.keys.max).map {|p| power_coefs[p] }
417
+ end
418
+
419
+ # Extracts the Array of coefficients from a String.
420
+ #
421
+ def self.coefs_from_string(s, params={})
422
+ h = pow_coefs_from_string(s, params)
423
+ coefs_from_pow_coefs(h, params)
424
+ end
425
+
426
+ # Extracts a power-to-coefficient Hash from a String.
427
+ #
428
+ def self.pow_coefs_from_string(s, params={})
429
+ h = Hash.new(0)
430
+ begin
431
+ power, coef, s = parse_term(s, params)
432
+ h[power] += coef
433
+ end until s.strip.empty?
434
+ h
435
+ end
436
+
437
+ # Parses a single polynomial term (i.e., a monomial). Returns an array with
438
+ # degree (power), coeficient and the remainder of the string, which may
439
+ # contains other terms.
440
+ #
441
+ # Example:
442
+ # Polynomial.parse_term('x**2-3') #=> [2, 1, '-3']
443
+ # Polynomial.parse_term('4') #=> [0, 4, '']
444
+ #
445
+ def self.parse_term(string, params={})
446
+ params = FromStringDefaults.merge(params)
447
+ mult = Regexp.escape(params[:multiplication_symbol])
448
+ pow_sym = Regexp.escape(params[:power_symbol])
449
+ var = Regexp.escape(params[:variable_name])
450
+ opt_sign = '(\+|-)?'
451
+ int = '(\d+)'
452
+ decimal = '(\d+(?:\.\d+)?(?:[eE]\-?\d+)?)'
453
+ opt_space = '\s*'
454
+ anything = '(.*)'
455
+ tbp = string.strip # tbp stands for 'to be parsed'
456
+ power, coef = nil, nil # scope reasons
457
+ make_regex = lambda {|core| Regexp.new('^' + core + '$') }
458
+
459
+ # matches terms starting with numbers, possibly signed, such as '1', '2.5', '- 3.3', '3*x', '4*x**2'
460
+ if md = make_regex[opt_sign + opt_space + decimal + anything].match(tbp)
461
+ sn = md[1] ? md[1]+md[2] : md[2]
462
+ coef = Integer(sn) rescue Float(sn)
463
+ tbp = md[-1]
464
+ if md = make_regex[mult + var + anything].match(tbp)
465
+ tbp = md[-1]
466
+ if md = make_regex[pow_sym + int + anything].match(tbp)
467
+ power = Integer(md[1])
468
+ tbp = md[-1]
469
+ else
470
+ power = 1
471
+ end
472
+ else
473
+ power = 0
474
+ end
475
+ # matches terms starting with variable, such as 'x', 'x**2'
476
+ elsif md = make_regex.call(opt_sign + opt_space + var + anything).match(tbp)
477
+ power, coef, tbp = 1, 1, md[-1].strip
478
+ if md = make_regex[pow_sym + int + anything].match(tbp)
479
+ power = Integer(md[1])
480
+ tbp = md[-1]
481
+ end
482
+ end
483
+ unless [power, coef].none? {|val| val.nil? } && (tbp[/^\s*\+|-/] || tbp.empty?)
484
+ raise ArgumentError, "invalid value for Polynomial: \"#{string}\""
485
+ end
486
+ [power, coef, tbp]
487
+ end
488
+
489
+ public
490
+
491
+ Zero = new([0])
492
+ Unity = new([1])
493
+
494
+ # Returns true if and only if the polynomial is null.
495
+ #
496
+ def zero?
497
+ self == Zero
498
+ end
499
+
500
+ # Returns true if and only if the polynomial is unity.
501
+ #
502
+ def unity?
503
+ self == Unity
504
+ end
505
+
506
+ =begin
507
+
508
+ #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
509
+ #OBSOLETE CODE:
510
+ #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
511
+
512
+ # Evaluates Polynomial at n equidistant points: x0, x0 + dx, ... , x0 + n*dx
513
+ # using forward differencing algorithm (approximate if degree > 1)
514
+ #
515
+ # TODO: implement it
516
+ #
517
+ # ref: http://www.cosc.brocku.ca/~cspress/HelloWorld/1999/04-apr/rendering_bezier_forms.html
518
+ # http://www.niksula.cs.hut.fi/~hkankaan/Homepages/bezierfast.html
519
+ #
520
+ def sub_fd(x0, dx, n)
521
+ ds = self.derivatives.map {|d| d.substitute_single(x0)}
522
+
523
+ total = @coefs.last
524
+ @coefs[0..-2].reverse.each do |a|
525
+ total = total * x + a
526
+ end
527
+ total
528
+ end
529
+
530
+ =end
531
+
532
+ end
@@ -0,0 +1,103 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ require 'polynomial/chebyshev'
4
+
5
+ class TestChebyshev < Test::Unit::TestCase
6
+
7
+ @@epsilon = 1e-11
8
+
9
+ @@first_kind_in_out = {
10
+ 0 => [1],
11
+ 1 => [0,1],
12
+ 2 => [-1,0,2],
13
+ 3 => [0,-3,0,4],
14
+ 4 => [1,0,-8,0,8],
15
+ 5 => [0,5,0,-20,0,16],
16
+ 6 => [-1,0,18,0,-48,0,32],
17
+ 7 => [0,-7,0,56,0,-112,0,64],
18
+ 8 => [1,0,-32,0,160,0,-256,0,128],
19
+ 9 => [0,9,0,-120,0,432,0,-576,0,256],
20
+ 10 => [-1,0,50,0,-400,0,1120,0,-1280,0,512],
21
+ 33 => [0, 33, 0, -5984, 0, 323136, 0, -8186112, 0, 118243840, 0, -1083543552, 0, 6723526656, 0, -29455450112, 0, 93564370944, 0, -218864025600, 0, 379364311040, 0, -485826232320, 0, 453437816832, 0, -299708186624, 0, 132875550720, 0, -35433480192, 0, 4294967296],
22
+ 117 => [0, 117, 0, -266916, 0, 182570544, 0, -59396283648, 0, 11252295957760, 0, -1392011303574528, 0, 121069290813456384, 0, -7794556246656811008, 0, 385830534209512144896, 0, -15117336720489657139200, 0, 479723485263538453217280, 0, -12560033068718097684234240, 0, 275483391973883609207537664, 0, -5126659590807429445423464448, 0, 81824517606975721197891747840, 0, -1130410109907982866613455028224, 0, 13624867309572732884712173862912, 0, -144263300924887759955775958548480, 0, 1349923260306156937003597257768960, 0, -11222034120763733781298460334489600, 0, 83262019013081166250511991067115520, 0, -553604830735923944815142850904719360, 0, 3310445048441080558894591593288826880, 0, -17859866348296375411168601454264975360, 0, 87173157176208499030703888050579046400, 0, -385886509100016289042582544437229912064, 0, 1552507116437220831968125998809871286272, 0, -5687298796914802239667747766683972927488, 0, 19000424301472259111471448052605854416896, 0, -57967396173983163390929841516424640593920, 0, 161675185831699489588691754699361401962496, 0, -412590438394874683108059244967545943359488, 0, 964033428172639884569792274299169848426496, 0, -2063232104076405216546475369508671064113152, 0, 4045553145247853365777402685311119733555200, 0, -7267343758706807816833129008945206636052480, 0, 11957379913488674657529090500258399350947840, 0, -18011476770588345970620395780569408752058368, 0, 24819642631241357126979301362698515409534976, 0, -31256154952683046300772375620665446215188480, 0, 35925284272775205069159360126962383242395648, 0, -37624952438486873601670279016307356413722624, 0, 35833288036654165334924075253626053727354880, 0, -30957815272511698038619249189981129550069760, 0, 24190734099562256383599311164796286114201600, 0, -17036893075247264324859685811788153639403520, 0, 10768527086364797273123092668320515998351360, 0, -6077645746391778080239684999813594695598080, 0, 3044044218338837303075374875336190470389760, 0, -1342868403884789080309483041273850259046400, 0, 516937856861788113291412877670567307640832, 0, -171623762110595558648434048859217472782336, 0, 48406702133757721670071141985933133348864, 0, -11369727956650552859202038639616105381888, 0, 2163454860932487122376194916134902824960, 0, -320354331577881795025074562520221483008, 0, 34627427749568765454669880019568820224, 0, -2429994929794299330152272282075004928, 0, 83076749736557242056487941267521536],
23
+ }
24
+
25
+ @@second_kind_in_out = {
26
+ 0 => [1],
27
+ 1 => [0,2],
28
+ 2 => [-1,0,4],
29
+ 3 => [0,-4,0,8],
30
+ 4 => [1,0,-12,0,16],
31
+ 5 => [0,6,0,-32,0,32],
32
+ 6 => [-1,0,24,0,-80,0,64],
33
+ 7 => [0,-8,0,80,0,-192,0,128],
34
+ 8 => [1,0,-40,0,240,0,-448,0,256],
35
+ 9 => [0,10,0,-160,0,672,0,-1024,0,512],
36
+ }
37
+
38
+ def test_first_kind_invalid_degree
39
+ assert_raise(RangeError) { Polynomial::Chebyshev.first_kind(-1) }
40
+ end
41
+
42
+ def my_test_in_out_rand(in_out_hash, method_name)
43
+ keys, size = in_out_hash.keys, in_out_hash.size
44
+ 100.times do
45
+ degree = keys[rand(size)]
46
+ coefs = in_out_hash[degree]
47
+ assert_equal Polynomial[*coefs], Polynomial::Chebyshev.send(method_name, degree)
48
+ end
49
+ end
50
+
51
+ def my_test_in_out_seq(in_out_hash, method_name)
52
+ in_out_hash.each_pair do |degree, coefs|
53
+ assert_equal Polynomial[*coefs], Polynomial::Chebyshev.send(method_name, degree)
54
+ end
55
+ end
56
+
57
+ def test_cached_first_kind
58
+ my_test_in_out_rand(@@first_kind_in_out, :first_kind)
59
+ my_test_in_out_seq(@@first_kind_in_out, :first_kind)
60
+ end
61
+
62
+ def test_uncached_first_kind
63
+ my_test_in_out_rand(@@first_kind_in_out, :uncached_first_kind)
64
+ my_test_in_out_seq(@@first_kind_in_out, :uncached_first_kind)
65
+ end
66
+
67
+ # See {Wikipedia entry}[http://en.wikipedia.org/wiki/Chebyshev_polynomials] for trigonometric definitions.
68
+ #
69
+ def test_first_kind_evaluation
70
+ 9.downto(0) do |n|
71
+ 10.times do
72
+ x = 2*rand-1
73
+ expected = Math.cos(n*Math.acos(x))
74
+ actual = Polynomial::Chebyshev.first_kind(n).substitute(x)
75
+ assert_in_delta expected, actual, @@epsilon
76
+ end
77
+ end
78
+ end
79
+
80
+ def test_cached_second_kind
81
+ my_test_in_out_rand(@@second_kind_in_out, :second_kind)
82
+ my_test_in_out_seq(@@second_kind_in_out, :second_kind)
83
+ end
84
+
85
+ def test_uncached_second_kind
86
+ my_test_in_out_rand(@@second_kind_in_out, :uncached_second_kind)
87
+ my_test_in_out_seq(@@second_kind_in_out, :uncached_second_kind)
88
+ end
89
+
90
+ # See {Wikipedia entry}[http://en.wikipedia.org/wiki/Chebyshev_polynomials] for trigonometric definitions.
91
+ #
92
+ def test_second_kind_evaluation
93
+ 9.downto(0) do |n|
94
+ 10.times do
95
+ theta = 2*Math::PI*rand - Math::PI
96
+ expected = Math.sin((n+1)*theta)/Math.sin(theta)
97
+ actual = Polynomial::Chebyshev.second_kind(n).substitute(Math.cos(theta))
98
+ assert_in_delta expected, actual, @@epsilon
99
+ end
100
+ end
101
+ end
102
+
103
+ end
@@ -0,0 +1,6 @@
1
+ require 'test/unit'
2
+
3
+ require 'rubygems'
4
+ require 'rational'
5
+
6
+ $:.unshift(File.join(File.dirname(__FILE__), '../lib/'))
@@ -0,0 +1,53 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ require 'polynomial/legendre'
4
+
5
+ class TestLegendre < Test::Unit::TestCase
6
+
7
+ @@epsilon = 1e-12
8
+
9
+ @@in_out = {
10
+ 0=>[1],
11
+ 1=>[0, 1],
12
+ 2=>[Rational(-1, 2), Rational(0, 1), Rational(3, 2)],
13
+ 3=>[Rational(0, 1), Rational(-3, 2), Rational(0, 1), Rational(5, 2)],
14
+ 4=>[Rational(3, 8), Rational(0, 1), Rational(-15, 4), Rational(0, 1), Rational(35, 8)],
15
+ 5=>[Rational(0, 1), Rational(15, 8), Rational(0, 1), Rational(-35, 4), Rational(0, 1), Rational(63, 8)],
16
+ 6=>[Rational(-5, 16), Rational(0, 1), Rational(105, 16), Rational(0, 1), Rational(-315, 16), Rational(0, 1), Rational(231, 16)],
17
+ 7=>[Rational(0, 1), Rational(-35, 16), Rational(0, 1), Rational(315, 16), Rational(0, 1), Rational(-693, 16), Rational(0, 1), Rational(429, 16)],
18
+ 8=>[Rational(35, 128), Rational(0, 1), Rational(-315, 32), Rational(0, 1), Rational(3465, 64), Rational(0, 1), Rational(-3003, 32), Rational(0, 1), Rational(6435, 128)],
19
+ 9=>[Rational(0, 1), Rational(315, 128), Rational(0, 1), Rational(-1155, 32), Rational(0, 1), Rational(9009, 64), Rational(0, 1), Rational(-6435, 32), Rational(0, 1), Rational(12155, 128)],
20
+ 10=>[Rational(-63, 256), Rational(0, 1), Rational(3465, 256), Rational(0, 1), Rational(-15015, 128), Rational(0, 1), Rational(45045, 128), Rational(0, 1), Rational(-109395, 256), Rational(0, 1), Rational(46189, 256)]
21
+ }
22
+
23
+ def test_invalid_degree
24
+ assert_raise(RangeError) { Polynomial::Legendre.uncached_legendre(-1) }
25
+ assert_raise(RangeError) { Polynomial::Legendre.cached_legendre(-1) }
26
+ end
27
+
28
+ def my_test_in_out_rand(in_out_hash, method_name)
29
+ keys, size = in_out_hash.keys, in_out_hash.size
30
+ 100.times do
31
+ degree = keys[rand(size)]
32
+ coefs = in_out_hash[degree]
33
+ assert_equal Polynomial[*coefs], Polynomial::Legendre.send(method_name, degree)
34
+ end
35
+ end
36
+
37
+ def my_test_in_out_seq(in_out_hash, method_name)
38
+ in_out_hash.each_pair do |degree, coefs|
39
+ assert_equal Polynomial[*coefs], Polynomial::Legendre.send(method_name, degree)
40
+ end
41
+ end
42
+
43
+ def test_cached
44
+ my_test_in_out_rand(@@in_out, :cached_legendre)
45
+ my_test_in_out_seq(@@in_out, :cached_legendre)
46
+ end
47
+
48
+ def test_uncached
49
+ my_test_in_out_rand(@@in_out, :uncached_legendre)
50
+ my_test_in_out_seq(@@in_out, :uncached_legendre)
51
+ end
52
+
53
+ end