polynomial 0.7.0

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