polynomial 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +176 -0
- data/License.txt +20 -0
- data/Manifest.txt +34 -0
- data/README.txt +72 -0
- data/lib/polynomial/chebyshev.rb +95 -0
- data/lib/polynomial/legendre.rb +57 -0
- data/lib/polynomial/multivariate.rb +143 -0
- data/lib/polynomial/ultraspherical.rb +47 -0
- data/lib/polynomial/version.rb +9 -0
- data/lib/polynomial.rb +532 -0
- data/test/test_chebyshev.rb +103 -0
- data/test/test_helper.rb +6 -0
- data/test/test_legendre.rb +53 -0
- data/test/test_multivariate.rb +469 -0
- data/test/test_polynomial.rb +404 -0
- data/test/test_suite.rb +16 -0
- data/test/test_ultraspherical.rb +45 -0
- data/website/index.txt +57 -0
- metadata +127 -0
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
|
data/test/test_helper.rb
ADDED
@@ -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
|