appmath 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,118 @@
1
+ =begin rdoc
2
+ ruby
3
+
4
+ Ulrich Mutze, www.ulrichmutze.de
5
+
6
+ 2008-12-09
7
+
8
+ Defines the module AppMath::Basics.
9
+
10
+ Requires files rnum and float_ext.
11
+
12
+ The intention is that a statement
13
+ require 'appmath_basics.rb'
14
+ allows us to use in the following a programming style in which the concept
15
+ of real numbers is handled in a flexibel manner so that switching from
16
+ standard type Float to arbitrary precision type R can be achieved
17
+ by a single statement of the type
18
+ R.prec = 0 ( for using Float)
19
+ or
20
+ R.prec = 100 ( for using R with 100 decimal places )
21
+ All modules and classes to be defined furtheron within the module
22
+ AppMath
23
+ will require appmath_basics.rb and will use real numbers in the manner
24
+ indicated above.
25
+ Copyright (C) 2008 Ulrich Mutze
26
+
27
+ This program is free software: you can redistribute it and/or modify
28
+ it under the terms of the GNU General Public License as published by
29
+ the Free Software Foundation, either version 3 of the License, or
30
+ (at your option) any later version.
31
+
32
+ This program is distributed in the hope that it will be useful,
33
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
34
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35
+ GNU General Public License for more details.
36
+
37
+ You should have received a copy of the GNU General Public License
38
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
39
+ =end
40
+
41
+ require File.join(File.dirname(__FILE__), 'rnum')
42
+ require File.join(File.dirname(__FILE__), 'float_ext')
43
+ #require 'rnum'
44
+ #require 'float_ext'
45
+
46
+ # A Module which collects concepts of applied mathematics and of physics.
47
+ module AppMath
48
+
49
+ # A Module which collects simple utility functions.
50
+ module Basics
51
+
52
+ # Returns the larger of the two numbers a and b.
53
+ def Basics.sup(a, b)
54
+ a <= b ? b : a
55
+ end
56
+
57
+ # Returns the smaller of the two numbers a and b.
58
+ def Basics.inf(a, b)
59
+ a < b ? a : b
60
+ end
61
+
62
+ # Returns the sign of a as a real number.
63
+ def Basics.sign(a)
64
+ if a > R.c0
65
+ R.c1
66
+ elsif a < R.c0
67
+ -R.c1
68
+ else
69
+ R.c0
70
+ end
71
+ end
72
+
73
+ # Returns 'a with the sign of b'. Programming idiom frequently used in
74
+ # the 'Numerical Recipes in C' by Press et al.
75
+ def Basics.sign2(a, b)
76
+ b >= R.c0 ? a.abs : -a.abs
77
+ end
78
+
79
+ # Returns the nearest number to x, which up to sign and power of 10
80
+ # is one of the numbers in the array c in the function
81
+ # body. For instance,
82
+ # c = [1,2,5,7.5,10]
83
+ # In a sense, here cutting is a more drastic version of rounding.
84
+ # It will be used in order to create reasonable axis subdivsion in
85
+ # graphical representation of data.
86
+ def Basics.cut(x)
87
+ fail "x is not a number" if x.nan?
88
+ fail "x is not finite" if x.infinite?
89
+ c = [R.c1,R.c2,R.c5,R.c(7.5),R.c10]
90
+ return R.c0 if x.zero?
91
+ s = Basics.sign(x)
92
+ y1 = x.abs
93
+ ylog = y1.log10
94
+ ylogInt = ylog.floor
95
+ yMantisse = ylog - ylogInt
96
+ y = R.c10 ** yMantisse
97
+ fail "Basics.cut(): !(1 <= y)" if !(R.c1 <= y)
98
+ fail "Basics.cut(): !(y < 10)" if !(y < R.c10)
99
+ i = 0
100
+ while y > c[i]
101
+ i += 1
102
+ end
103
+ cd = c.length
104
+ fail "unexpected failure in function Basics.cut" if i < 0 || i >= cd
105
+ yu = c[i] # yu is the smallest c[k] which is larger or equal to y
106
+ if i==0
107
+ yf = yu
108
+ else
109
+ fail "unexpected failure in function Basics.cut" if (i-1) < 0 || (i-1) >= cd
110
+ yl = c[i-1]
111
+ yf = yu-y <y -yl ? yu : yl # now yf is the c[k] which is nearest to y
112
+ end
113
+ return yf.ldexp(ylogInt) * s # sign and exponent are restored
114
+ end
115
+
116
+ end # Basics
117
+
118
+ end # module AppMath
@@ -0,0 +1,615 @@
1
+ =begin rdoc
2
+ ruby
3
+
4
+ Ulrich Mutze www.ulrichmutze.de
5
+
6
+ Started 2008-12-03 by modifying cpmc.h and cpmc.cpp
7
+
8
+ Defining a class C of complex numbers.
9
+
10
+ Defines the class AppMath::C.
11
+
12
+ Requires file appmath_basics.
13
+
14
+ Copyright (C) 2008 Ulrich Mutze
15
+
16
+ This program is free software: you can redistribute it and/or modify
17
+ it under the terms of the GNU General Public License as published by
18
+ the Free Software Foundation, either version 3 of the License, or
19
+ (at your option) any later version.
20
+
21
+ This program is distributed in the hope that it will be useful,
22
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
23
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24
+ GNU General Public License for more details.
25
+
26
+ You should have received a copy of the GNU General Public License
27
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
28
+ =end
29
+
30
+ require File.join(File.dirname(__FILE__), 'appmath_basics')
31
+ #require 'appmath_basics'
32
+
33
+ module AppMath
34
+
35
+ # Class of arbitrary precision complex numbers. The underlying real numbers may be Float
36
+ # or R.
37
+ class C < Numeric
38
+
39
+ attr_reader :re, :im
40
+
41
+ def initialize(*arg)
42
+ n = arg.size
43
+ case n
44
+ when 0
45
+ @re = R.c0
46
+ @im = R.c0
47
+ when 1
48
+ a0 = arg[0]
49
+ if a0.integer? || a0.real?
50
+ @re = R.c a0
51
+ @im = R.c0
52
+ elsif a0.complex?
53
+ @re = R.c a0.re
54
+ @im = R.c a0.im
55
+ else
56
+ fail "can't construct a C from this argument"
57
+ end
58
+ when 2
59
+ a0 = R.c arg[0]; a1 = R.c arg[1]
60
+ @re = a0
61
+ @im = a1
62
+ else
63
+ fail "can't construct a C from more than two arguments"
64
+ end
65
+ end
66
+
67
+ def clone; C.new(@re,@im); end
68
+
69
+ # The constant 0.
70
+ def C.zero
71
+ C.new
72
+ end
73
+
74
+ # The constant 1.
75
+ def C.one
76
+ C.new(R.c1,R.c0)
77
+ end
78
+ # The constant i
79
+ def C.i
80
+ C.new(R.c0,R.c1)
81
+ end
82
+
83
+ # Random value (sine-floor random generator).
84
+ #
85
+ # Chaotic function from the integers into the
86
+ # subset [0,1] x [0,1] of C
87
+
88
+ def C.ran(anInteger)
89
+ ai = anInteger.to_i * 2
90
+ x1 = R.ran(ai)
91
+ x2 = R.ran(ai + 1)
92
+ C.new(x1,x2)
93
+ end
94
+
95
+ # Test object.
96
+ #
97
+ # Needed for automatic tests of arithmetic relations. Intended
98
+ # to give numbers which rapidly change sign and order of
99
+ # magnitude when the argument grows regularly e.g.
100
+ # as in 1,2,3,... . However, suitibility as a random generator is
101
+ # not the focus. If the second argument is 'true', the result
102
+ # is multplied by a number << 1 in order to prevent the result
103
+ # from overloading the exponential function.
104
+
105
+ def C.tob(anInteger, small = false)
106
+ ai = anInteger.to_i * 2
107
+ x1 = R.tob(ai,small)
108
+ x2 = R.tob(ai + 1,small)
109
+ C.new(x1,x2)
110
+ end
111
+
112
+ # Unary minus operator. It returns the C-object -self.
113
+ def -@; C.new(-@re, -@im); end
114
+
115
+ # Unary plus operator. It returns the C-object self.
116
+ def +@; self; end
117
+
118
+ # (Complex) conjugation, no effect on real numbers.
119
+ # Supports the unified treatment of real and complex numbers.
120
+ def conj; C.new(@re, -@im); end
121
+
122
+ # Returns self times i.
123
+ def ti; self * C.i; end
124
+
125
+ # Returns self divided by i.
126
+ def dbi; self * C.new(0,-1); end
127
+
128
+ # Returns the absolute value of self.
129
+ def abs; @re.hypot(@im); end
130
+
131
+ # Returns the absolute value squared of self.
132
+ def abs2; @re * @re + @im * @im; end
133
+
134
+ # Returns the argument (i.e. the polar angle) of self.
135
+ def arg; @re.arg(@im); end
136
+
137
+ # Redefining coerce from Numeric.
138
+ # This allows writing 1 + C.new(137) instead of C.new(137) + 1
139
+ # or C.new(137) + R.c1.
140
+ #-- Notice that the order in the resulting array is essential for
141
+ # correct functionality.
142
+ def coerce(a)
143
+ [ C.new(a), self]
144
+ end
145
+
146
+ # The order relation is here lexicographic ordering based on the
147
+ # agreement that re is the 'first letter' and im the 'second letter'
148
+ # of 'the word'. Needed only for book-keeping purposes.
149
+ def <=> (a)
150
+ cr = @re <=> a.re
151
+ return cr unless cr.zero?
152
+ ci = @im <=> a.im
153
+ return ci unless ci.zero?
154
+ return 0
155
+ end
156
+
157
+ # Returns 'true' if self equals zero.
158
+ def zero?; @re.zero? && @im.zero?; end
159
+
160
+ # Returns 'true' if self is 'not a number' (NaN).
161
+ def nan?; @re.nan? || @im.nan?; end
162
+
163
+ # Returns 'true' if the real art or the iaginary part of self is
164
+ # infinite.
165
+ def infinite?; @re.infinite? || @im.infinite?; end
166
+
167
+ # Since R is not Fixnum or Bignum we return 'false'. In scientific
168
+ # computation there may be the need to use various types of
169
+ # 'real number types' but there should always a clear-cut distinction
170
+ # between integer types and real types.
171
+ def integer?; false; end
172
+
173
+ # Supports the unified treatment of real and complex numbers.
174
+ def real?; false; end
175
+
176
+ # Supports the unified treatment of real and complex numbers.
177
+ def complex?; true; end
178
+
179
+ # Returns the C-object self + a.
180
+ def +(a)
181
+ if a.integer? || a.real?
182
+ b = R.c a
183
+ C.new(@re + b, @im)
184
+ elsif a.complex?
185
+ C.new(@re + a.re, @im + a.im)
186
+ else
187
+ fail "cannot add this argument to a complex number"
188
+ end
189
+ end
190
+
191
+ # Returns the C-object self - a.
192
+ def -(a)
193
+ if a.integer? || a.real?
194
+ b = R.c a
195
+ C.new(@re - b, @im)
196
+ elsif a.complex?
197
+ C.new(@re - a.re, @im - a.im)
198
+ else
199
+ fail "cannot subtract this argument from a complex number"
200
+ end
201
+
202
+ end
203
+
204
+ # Returns the C-object self * a.
205
+ def *(a)
206
+ if a.integer? || a.real?
207
+ b = R.c a
208
+ C.new(@re * b, @im * b)
209
+ elsif a.complex?
210
+ C.new(@re * a.re - @im * a.im , @re * a.im + @im * a.re )
211
+ else
212
+ fail "cannot multiply a complex number with this argument"
213
+ end
214
+ end
215
+
216
+ # Returns the C-object self / a.
217
+ def /(a)
218
+ if a.integer? || a.real?
219
+ b = R.c a
220
+ C.new(@re / b, @im / b)
221
+ elsif a.complex?
222
+ r2 = a.abs2
223
+ C.new((@re * a.re + @im * a.im)/r2 , (@im * a.re - @re * a.im)/r2 )
224
+ else
225
+ fail "cannot divide a complex number by this argument"
226
+ end
227
+ end
228
+
229
+ # Exponential function.
230
+ def exp; C.new(@im.cos, @im.sin) * @re.exp; end
231
+
232
+ # Exponential function of the argument multiplied by C.i
233
+ def expi; (self * C.i).exp; end
234
+
235
+
236
+ # Natural logarithm.
237
+ def log; C.new(abs.log,arg); end
238
+
239
+ # Returns the a-th power of self. A may be integer, real, or
240
+ # complex. The result is always complex.
241
+ def **(a)
242
+ return C.nan if nan?
243
+ if a.integer?
244
+ if a.zero?
245
+ C.one
246
+ elsif a == 1
247
+ self
248
+ elsif a == -1
249
+ inv
250
+ else
251
+ b = a.abs
252
+ res = self
253
+ for i in 1...b
254
+ res *= self
255
+ end
256
+ if a < 0
257
+ res = res.inv
258
+ end
259
+ res
260
+ end
261
+ elsif a.real?
262
+ b = C.new(a)
263
+ (log * b).exp
264
+ elsif a.complex?
265
+ (log * a).exp
266
+ else
267
+ fail "Argument not acceptable as an exponent"
268
+ end
269
+ end
270
+
271
+ # Returns the zero-element which belongs to the same class than self
272
+ def to_0; C.zero; end
273
+
274
+ # Returns the unit-element which belongs to the same class than self
275
+ def to_1; C.one; end
276
+
277
+ # Returns the inverse 1/self.
278
+ def inv
279
+ C.one / self
280
+ end
281
+
282
+ # Returns 'true' iff self == C(0,0)
283
+ def zero?; @re.zero? && @im.zero?; end
284
+
285
+ # The pseudo_inverse of zero is zero, and equal to the inverse for
286
+ # all other arguments.
287
+ def pseudo_inv
288
+ zero? ? C.zero : C.one / self
289
+ end
290
+
291
+ # For the return value res we have res.int? true and (self - res).abs <= 0.5
292
+ def round(n)
293
+ u = @re.round(n)
294
+ v = @im.round(n)
295
+ C.new(u,v)
296
+ end
297
+
298
+ # Returns the square root of self.
299
+ def sqrt
300
+ self ** R.i2
301
+ end
302
+
303
+ # Returns a kind of relative distance between self and aR.
304
+ # The return value varies from 0 to 1, where 1 means maximum dissimilarity
305
+ # of the arguments.
306
+ # Such a function is needed for testing the validity of arithmetic laws,
307
+ # which, due to numerical noise, should not be expected to be fulfilled
308
+ # exactly.
309
+ def dis(aC)
310
+ a = abs
311
+ b = aC.abs
312
+ d = (self - aC).abs
313
+ s = a + b
314
+ return R.c0 if s.zero?
315
+ d1 = d/s
316
+ Basics.inf(d,d1)
317
+ end
318
+
319
+ # Conversion to String.
320
+ def to_s; "C(#{@re}, #{@im})"; end
321
+
322
+ # Printing the value together with a label
323
+ def prn(name)
324
+ puts "#{name} = " + to_s
325
+ end
326
+ #=begin
327
+
328
+ # Sine.
329
+ def sin
330
+ (expi - (-self).expi) * C.new(0,-R.i2)
331
+ end
332
+
333
+ # Cosine.
334
+ def cos
335
+ (expi + (-self).expi) * R.i2
336
+ end
337
+
338
+ # Tangent.
339
+ def tan
340
+ sin / cos
341
+ end
342
+
343
+ # Cotangent.
344
+ def cot
345
+ cos / sin
346
+ end
347
+
348
+ # Hyperbolic sine.
349
+ def sinh; (exp - (-self).exp) * R.i2; end
350
+
351
+ # Hyperbolic cosine.
352
+ def cosh; (exp + (-self).exp) * R.i2; end
353
+
354
+ # Hyperbolic tangent.
355
+ def tanh
356
+ s = exp - (-self).exp
357
+ c = exp + (-self).exp
358
+ s/c
359
+ end
360
+
361
+ # Hyperbolic cotangent.
362
+ def coth
363
+ s = exp - (-self).exp
364
+ c = exp + (-self).exp
365
+ c/s
366
+ end
367
+
368
+ # Inverse hyperbolic sine.
369
+ def asinh
370
+ ((self * self + C.one).sqrt + self).log
371
+ end
372
+
373
+ # Inverse hyperbolic cosine.
374
+ def acosh
375
+ ((self * self - C.one).sqrt + self).log
376
+ end
377
+
378
+ # Inverse hyperbolic tangent.
379
+ def atanh
380
+ ((C.one + self)/(C.one - self)).log * R.i2
381
+ end
382
+
383
+ # Inverse hyperbolic cotangent.
384
+ def acoth
385
+ ((self + C.one)/(self - C.one)).log * R.i2
386
+ end
387
+
388
+ # Inverse sine.
389
+ def asin
390
+ ti.asinh.dbi
391
+ end
392
+
393
+ # Inverse cosine.
394
+ def acos
395
+ acosh.dbi
396
+ end
397
+
398
+ # Inverse tangent.
399
+ def atan
400
+ ti.atanh.dbi
401
+ end
402
+
403
+ # Inverse cotangent.
404
+ def acot
405
+ ti.acoth.ti
406
+ end
407
+
408
+ # Consistency test for class C
409
+ # This is intended to keep the class consistent despite of modifications.
410
+ # The first argument influences the numbers which are selected for the
411
+ # test. Returned is a sum of numbers each of which should be numerical
412
+ # noise and so the result has to be << 1 if the test is to indicate
413
+ # success.
414
+ # For instance, on my system
415
+ #
416
+ # Doing C.test(n = 137, verbose = false) for R.dig = 100:
417
+ # *************************************************
418
+ # class of s is AppMath::R
419
+ # class of s is AppMath::R .
420
+ # The error sum s is 0.95701879151814897746312007872622225589589551941
421
+ # 73186692168823486932509515793972625242699350133964052E-98 .
422
+ # It should be close to 0.
423
+ # Computation time was 1.062 seconds.
424
+
425
+ def C.test(n0, verbose = false )
426
+ puts "Doing C.test(n = #{n0}, verbose = #{verbose})" +
427
+ " for R.dig = #{R.dig}:"
428
+ puts "*************************************************"
429
+ t1 = Time.now
430
+ small = true # otherwise not all inverse function tests work well
431
+ s = R.c0
432
+ puts "class of s is " + s.class.to_s
433
+ i = n0
434
+ a = C.tob(i,small)
435
+ i += 1
436
+ b = C.tob(i,small)
437
+ i += 1
438
+ c = C.tob(i,small)
439
+ i += 1
440
+
441
+ if verbose
442
+ a.prn("a")
443
+ b.prn("b")
444
+ c.prn("c")
445
+ end
446
+
447
+ r = 2 + a
448
+ l = a + 2
449
+ ds = r.dis(l)
450
+ puts "coerce 2 + a: ds = " + ds.to_s if verbose
451
+ s += ds
452
+
453
+ r = a + 1.234
454
+ l = a + R.c(1.234)
455
+ ds = r.dis(l)
456
+ puts "coerce a + float: ds = " + ds.to_s if verbose
457
+ s += ds
458
+
459
+ r = (a + b) * c
460
+ l = a * c + b * c
461
+
462
+ ds = r.dis(l)
463
+ puts "Distributive law for +: ds = " + ds.to_s if verbose
464
+ s += ds
465
+
466
+ r = (a - b) * c
467
+ l = a * c - b * c
468
+ ds = r.dis(l)
469
+ puts "Distributive law for -: ds = " + ds.to_s if verbose
470
+ s += ds
471
+
472
+ r = (a * b) * c
473
+ l = b * (c * a)
474
+ ds = r.dis(l)
475
+ puts "Multiplication: ds = " + ds.to_s if verbose
476
+ s += ds
477
+
478
+ r = (a * b) / c
479
+ l = (a / c) * b
480
+ ds = r.dis(l)
481
+ puts "Division: ds = " + ds.to_s if verbose
482
+ s += ds
483
+
484
+ r = C.one
485
+ l = a * a.inv
486
+ ds = r.dis(l)
487
+ puts "inv: ds = " + ds.to_s if verbose
488
+ s += ds
489
+
490
+ r = 1/a
491
+ l = a.inv
492
+ ds = r.dis(l)
493
+ puts "inv and 1/x: ds = " + ds.to_s if verbose
494
+ s += ds
495
+
496
+ r = b
497
+ l = -(-b)
498
+ ds = r.dis(l)
499
+ puts "Unary minus is idempotent: ds = " + ds.to_s if verbose
500
+ s += ds
501
+ x = -a
502
+ y = x + a
503
+ r = y
504
+ l = C.zero
505
+ ds = r.dis(l)
506
+ puts "Unary -: ds = " + ds.to_s if verbose
507
+ s += ds
508
+
509
+ l = a
510
+ x = a.sqrt
511
+ r = x * x
512
+ s = r.dis(l)
513
+ puts "square root: ds = " + ds.to_s if verbose
514
+ s += ds
515
+
516
+ n = 11
517
+ l = a ** n
518
+ r = a ** C.new(n)
519
+ ds = r.dis(l)
520
+ puts "power with integer exponent: ds = " + ds.to_s if verbose
521
+ s += ds
522
+
523
+ n = -7
524
+ l = a ** n
525
+ r = a ** C.new(n)
526
+ ds = r.dis(l)
527
+ puts "power with negative integer exponent: ds = " + ds.to_s if verbose
528
+ s += ds
529
+
530
+ l = -C.one
531
+ r = (C.i * R.pi).exp
532
+ ds = r.dis(l)
533
+ puts "Euler's relation: ds = " + ds.to_s if verbose
534
+ s += ds
535
+
536
+ l = a.sin * b.cos + a.cos * b.sin
537
+ r = (a + b).sin
538
+ ds = r.dis(l)
539
+ puts "Addition theorem for sin: ds = " + ds.to_s if verbose
540
+ s += ds
541
+
542
+ l = a.exp * b.exp
543
+ r = (a + b).exp
544
+ ds = r.dis(l)
545
+ puts "Addition theorem for exp: ds = " + ds.to_s if verbose
546
+ s += ds
547
+
548
+ l = b.exp
549
+ r = l.log.exp
550
+ ds = r.dis(l)
551
+ puts "exp and log: ds = " + ds.to_s if verbose
552
+ s += ds
553
+
554
+ l = c.sin
555
+ r = l.asin.sin
556
+ ds = r.dis(l)
557
+ puts "sin and asin: ds = " + ds.to_s if verbose
558
+ s += ds
559
+
560
+ l = b.cos
561
+ r = l.acos.cos
562
+ ds = r.dis(l)
563
+ puts "cos and acos: ds = " + ds.to_s if verbose
564
+ s += ds
565
+
566
+ l = a.tan
567
+ r = l.atan.tan
568
+ ds = r.dis(l)
569
+ puts "tan and atan: ds = " + ds.to_s if verbose
570
+ s += ds
571
+
572
+ l = a.cot
573
+ r = l.acot.cot
574
+ ds = r.dis(l)
575
+ puts "cot and acot: ds = " + ds.to_s if verbose
576
+ s += ds
577
+
578
+ l = c.sinh
579
+ r = l.asinh.sinh
580
+ ds = r.dis(l)
581
+ puts "sinh and asinh: ds = " + ds.to_s if verbose
582
+ s += ds
583
+
584
+ l = a.cosh
585
+ r = l.acosh.cosh
586
+ ds = r.dis(l)
587
+ puts "cosh and acosh: ds = " + ds.to_s if verbose
588
+ s += ds
589
+
590
+ l = b.tanh
591
+ r = l.atanh.tanh
592
+ ds = r.dis(l)
593
+ puts "tanh and atanh: ds = " + ds.to_s if verbose
594
+ s += ds
595
+
596
+ l = a.coth
597
+ r = l.acoth.coth
598
+ ds = r.dis(l)
599
+ puts "coth and acoth: ds = " + ds.to_s if verbose
600
+ s += ds
601
+
602
+ t2 = Time.now
603
+ puts "class of s is " + s.class.to_s + " ."
604
+ puts "The error sum s is " + s.to_s + " ."
605
+ puts "It should be close to 0."
606
+ puts "Computation time was #{t2-t1} seconds."
607
+ s
608
+ end
609
+
610
+ protected :coerce
611
+
612
+ end # class C
613
+
614
+ end # module AppMath
615
+