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