appmath 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,282 @@
1
+ =begin rdoc
2
+ ruby
3
+
4
+ Ulrich Mutze, www.ulrichutze.de
5
+
6
+ Started 2008-11-01 by modifying cpminterval.h and cpminterval.cpp.
7
+
8
+ Defines class AppMath::Iv.
9
+
10
+ Requires file appmath_basics.
11
+
12
+ Copyright (C) 2008 Ulrich Mutze
13
+
14
+ This program is free software: you can redistribute it and/or modify
15
+ it under the terms of the GNU General Public License as published by
16
+ the Free Software Foundation, either version 3 of the License, or
17
+ (at your option) any later version.
18
+
19
+ This program is distributed in the hope that it will be useful,
20
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
21
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
+ GNU General Public License for more details.
23
+
24
+ You should have received a copy of the GNU General Public License
25
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
26
+ =end
27
+
28
+ require File.join(File.dirname(__FILE__), 'appmath_basics')
29
+ #require 'appmath_basics.rb'
30
+
31
+ module AppMath
32
+
33
+ =begin rdoc
34
+ Class of real closed intervals.
35
+ In most applications of intervals it is an advantage if single real
36
+ values can be considered as intervals of length zero. This is possible
37
+ only for closed intervals, and this is the rational for defining those
38
+ in preference to open intervals.
39
+ With the attributes @low and @upp, the values x which belong to interval
40
+ are characterized by the formula
41
+ @low <= x <= @upp .
42
+ Hence the interval is empty (void) iff @low > @upp.
43
+ =end
44
+
45
+ class Iv
46
+
47
+ attr_reader :low, :upp
48
+
49
+ # Allowed are 0, 1, 2 arguments, which all have to be convertible to R.
50
+ # For no argument the empty interval is created. For one argument, an
51
+ # second argument 0 is understood. For two arguments, these are the
52
+ # boundaries of the interval and the order on input does not matter:
53
+ # iv1 = Iv.new(2,3); iv2 = Iv.new(3,2)
54
+ # are thus the same, non-empty, interval, which in mathematical notation
55
+ # would be
56
+ # [2,3]
57
+ # Finally
58
+ # iv3 = Iv.new
59
+ # is an empty interval.
60
+ def initialize(*arg)
61
+ n = arg.size
62
+ case n
63
+ when 0
64
+ @low = R.c1
65
+ @upp = R.c0 # notice: epmpty since @low > @upp
66
+ when 1
67
+ x = R.c arg[0]
68
+ zero = R.c0
69
+ if x < zero
70
+ @low = x
71
+ @upp = zero
72
+ else
73
+ @upp = x
74
+ @low = zero
75
+ end
76
+ when 2
77
+ x = R.c arg[0]
78
+ y = R.c arg[1]
79
+ if x < y
80
+ @low = x
81
+ @upp = y
82
+ else
83
+ @low = y
84
+ @upp = x
85
+ end
86
+ else
87
+ fail "Iv.new takes 0 or 1 or 2 arguments, but not " + n.to_s
88
+ end # case n
89
+ end
90
+
91
+ =begin rdoc
92
+ using max and min of an array for defining an interval.
93
+ The values of the array components are then known to belong to
94
+ to a closed interval. The smallest such interval is what
95
+ his method returns.
96
+ =end
97
+ def Iv.from_array(anArray)
98
+ n = anArray.size
99
+ case n
100
+ when 0
101
+ Iv.new
102
+ when 1
103
+ a0 = anArray[0]
104
+ Iv.new(a0)
105
+ else
106
+ x0 = anArray[0]
107
+ x1 = x0
108
+ anArray.each{ |x|
109
+ if x < x0
110
+ x0 = x
111
+ elsif x > x1
112
+ x1 = x
113
+ else
114
+ end
115
+ }
116
+ Iv.new(x0,x1)
117
+ end
118
+ end
119
+
120
+ # Returns an ordered equidistant array of n numbers, where the first one
121
+ # is @low and the last one is @upp. The positive integer n is made from
122
+ # the argument a in a way which depends on whether this argument is integer
123
+ # or not. In the first case it is taken as n and in the second case, the
124
+ # argment is taken as a proposal for the lattice spacing. The actual
125
+ # lattice spacing will be chosen as a fraction of size.
126
+ def to_array(a)
127
+ return nil if empty?
128
+ if a.integer? # then we interpret the argument as an intended number
129
+ # of lattice points
130
+ n = a
131
+ else # then we interprete the argument as an intended lattice spacing
132
+ d = a.abs
133
+ fail "can't build a lattice with spacing 0" if d.zero?
134
+ n = (size/d).round + 1
135
+ end
136
+ fail "number of lattice points must be larger than 1" if n < 2
137
+ res = Array.new
138
+ res << @low
139
+ if n > 2
140
+ na = n - 2
141
+ d = size / (n-1)
142
+ x = @low
143
+ for i in 1..na
144
+ x += d
145
+ res << x
146
+ end
147
+ end
148
+ res << @upp
149
+ end
150
+
151
+ # Returns true iff the interval is empty
152
+ def empty?; @low > @upp; end
153
+
154
+ # Returns the lower boundary if self is not empty
155
+ # and nil else.
156
+ def inf
157
+ return nil if empty?
158
+ @low
159
+ end
160
+
161
+ # Returns the upper boundary if self is not empty
162
+ # and nil else.
163
+ def sup
164
+ return nil if empty?
165
+ @upp
166
+ end
167
+
168
+ # Returns the midpoint of the interval.
169
+ def center
170
+ nil if empty?
171
+ (@low + @upp) * R.i2
172
+ end
173
+
174
+ # Returns the length of the interval, 0 for the empty one
175
+ def size
176
+ s = @upp - @low
177
+ s >= R.c0 ? s : R.c0
178
+ end
179
+
180
+ # Indicator function. Returns true if the point x belongs to self
181
+ # and false else.
182
+ def ind(x)
183
+ return false if empty?
184
+ return false if x > @upp
185
+ return false if x < @low
186
+ true
187
+ end
188
+
189
+ # Metrical indicator function. Returns the distance of x from the set
190
+ # self if x is outside of self. If it is inside, the return value is
191
+ # minus the distance to the complement of self (which is not an
192
+ # interval but a well-defined set).
193
+ def met_ind(x)
194
+ return nil if empty?
195
+ y = R.c x
196
+ dc = (y - center).abs
197
+ dc - size * R.i2
198
+ end
199
+
200
+ # Returns a real number, which is self's lower end for p == 0.0, self's
201
+ # center for p == 0.5, and self's upper end for p == 1.0 .
202
+ def put(p)
203
+ inf + size * p
204
+ end
205
+
206
+ # minimum closed interval that contains the union
207
+ # join or l.u.b. (lowest upper bound) in lattice terminology
208
+ def |(anIv)
209
+ return anIv if empty?
210
+ return self if anIv.empty?
211
+ Iv.from_array [@low, @upp, anIv.low, anIv.upp]
212
+ end
213
+
214
+ #shifting by a
215
+ def +(a); Iv.new(@low + a,@upp + a); end
216
+
217
+ # multiplying size by a, while preserving the center
218
+ def *(a)
219
+ Iv.new(center-(center-@low)*a,center+(@upp-center)*a)
220
+ end
221
+
222
+ # section, intersection
223
+ # meet or g.l.b (greatest lower bound) in lattice terminology
224
+ def &(anIv)
225
+ return Iv.new if empty? || anIv.empty?
226
+ return Iv.new if @upp < anIv.low || @low > anIv.upp
227
+ amin = Basics.sup(@low, anIv.low)
228
+ amax = Basics.inf(@upp, anIv.upp)
229
+ Iv.new(amin, amax)
230
+ end
231
+
232
+ def to_s
233
+ "Iv (" + @low.to_s + "," + @upp.to_s + ")"
234
+ end
235
+
236
+ # The function creates a suitable axis sub-division for data ranging
237
+ # from @low to @upp. Let res be the return value of the function. Then
238
+ # res[0] is a proposal for the difference between adjacent axis tics
239
+ # and res[1] is an array of the values to which the proposed tics belong.
240
+ # Thus res[1].first <= @low and res[1].last >= @upp. All numbers are chosen
241
+ # such that they are simple when written down in normal scientific
242
+ # notation and the intention is to simulate the considerations that
243
+ # determine the axis subdivision of reasonable manually created diagrams.
244
+ # The argument of the function is a proposal for the number of tics to be
245
+ # used. Values from 5 to 10 are reasonable. To have a simple logic, we
246
+ # simply enforce that the interval between tics is a simple number.
247
+ # The initial and the final number of the axis division is chosen as
248
+ # an integer multiple of this inter-tic interval.
249
+
250
+ def axis_division(anPosInteger)
251
+ fail "can't divide an empty interval" if empty?
252
+ a = @low
253
+ b = @upp
254
+ n = anPosInteger.abs
255
+ n += 1 if n == 0
256
+ d = size/n
257
+ d_ = Basics.cut(d) # this is the essential point
258
+ fail "Zero division in function axis_division" if d_ == 0.0
259
+ d_inv=d_.inv;
260
+ epsilon = R.c 1e-6
261
+ k_b=(b*d_inv - epsilon).ceil
262
+ # without the epsilon correction it depends on roundoff
263
+ # errors whether b_ becomes too large
264
+ k_a=(a*d_inv + epsilon).floor
265
+ # without the epsilon correction it depends on roundoff
266
+ # errors whether a_ becomes too small
267
+ b_=k_b * d_
268
+ b__ = (k_b - 0.5) * d_
269
+ a_=k_a * d_
270
+ res=Array.new
271
+ while a_ < b__
272
+ res << a_
273
+ a_ += d_
274
+ end
275
+ res << b_ # we know the last item exactly, and should
276
+ # not spoil it by arithmetic errors
277
+ [ d_ , res ]
278
+ end
279
+
280
+ end # class Iv
281
+
282
+ end # AppMath
@@ -0,0 +1,162 @@
1
+ =begin rdoc
2
+ ruby
3
+
4
+ Ulrich Mutze www.ulrichmutze.de
5
+
6
+ 2008-12-07
7
+
8
+ Defines classes AppMath::R2 and AppMath::Kep2D.
9
+
10
+ Requires file appmath_basics.
11
+
12
+ Copyright (C) 2008 Ulrich Mutze
13
+
14
+ This program is free software: you can redistribute it and/or modify
15
+ it under the terms of the GNU General Public License as published by
16
+ the Free Software Foundation, either version 3 of the License, or
17
+ (at your option) any later version.
18
+
19
+ This program is distributed in the hope that it will be useful,
20
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
21
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
+ GNU General Public License for more details.
23
+
24
+ You should have received a copy of the GNU General Public License
25
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
26
+ =end
27
+
28
+ require File.join(File.dirname(__FILE__), 'appmath_basics')
29
+ #require 'appmath_basics.rb'
30
+
31
+ module AppMath
32
+
33
+ ###################### classes R2 and Kep2D ##############################
34
+
35
+ # Real vector space of two dimensions
36
+ class R2
37
+
38
+ attr :x, true
39
+ attr :y, true
40
+
41
+ def initialize(x,y)
42
+ @x = x
43
+ @y = y
44
+ end
45
+
46
+ def clone
47
+ R.new(x,y)
48
+ end
49
+
50
+ def +(p)
51
+ R2.new(x + p.x, y + p.y)
52
+ end
53
+
54
+ def -(p)
55
+ R2.new(x - p.x, y - p.y)
56
+ end
57
+
58
+ def *(s)
59
+ R2.new(x * s, y * s)
60
+ end
61
+
62
+ def -@
63
+ R2.new(-x, -y)
64
+ end
65
+
66
+ def to_s
67
+ res = "R2(" + x.to_s + ", " + y.to_s + ")"
68
+ end
69
+
70
+ def abs
71
+ x.hypot(y)
72
+ end
73
+
74
+ # abs squared
75
+ def abs2
76
+ x * x + y * y
77
+ end
78
+
79
+ # scalar product
80
+ def spr(p)
81
+ x * p.x + y * p.y
82
+ end
83
+
84
+ # unit vector
85
+ def uv
86
+ r = abs
87
+ if r.zero?
88
+ clone
89
+ else
90
+ ri = r.inv
91
+ self * ri
92
+ end
93
+ end
94
+
95
+ end
96
+
97
+ # Kepler problem in 2 dimensions
98
+ # mass of the test particle is 1.
99
+ # space-fixed central mass times constant of gravity is @g
100
+ class Kep2D
101
+
102
+ def initialize(x,v,g)
103
+ @t = R.c0
104
+ @x = x
105
+ @v = v
106
+ @g = g
107
+ end
108
+
109
+ # acceleration
110
+ def acc
111
+ r = @x.abs
112
+ k = -@g * r**-3
113
+ @x * k
114
+ end
115
+
116
+ # time step of the direct midpoint integrator, highly symmetric !
117
+ def step!(dt)
118
+ h = dt * 0.5
119
+ @t += h
120
+ @x += @v * h
121
+ @v += acc * dt
122
+ @x += @v * h
123
+ @t += h
124
+ end
125
+
126
+ def to_s
127
+ res = "x = " + @x.to_s + "\n" +
128
+ "v = " + @v.to_s + "\n" +
129
+ "t = " + @t.to_s
130
+ end
131
+
132
+ def get_x
133
+ @x.x
134
+ end
135
+
136
+ def get_y
137
+ @x.y
138
+ end
139
+
140
+ def get_t
141
+ @t
142
+ end
143
+
144
+ # total energy
145
+ def energy
146
+ @v.abs2 * 0.5 - @g/@x.abs
147
+ end
148
+
149
+ # angular momentum
150
+ def ang_mom
151
+ @x.x * @v.y - @x.y * @v.x
152
+ end
153
+
154
+ # Runge-Lenz vector
155
+ def lenz
156
+ @x * @v.abs2 - @x * @v.spr(@x) - @x.uv * @g
157
+ end
158
+
159
+ end # class Kep2D
160
+
161
+ end # AppMath
162
+
@@ -0,0 +1,1309 @@
1
+ =begin rdoc
2
+ ruby
3
+
4
+ Ulrich Mutze, www.urichmutze.de
5
+
6
+ Linear algebra of vectors and matrices
7
+
8
+ Defines classes AppMath::Vec and AppMath::Mat.
9
+
10
+ Requires files appmath_basics and random.
11
+
12
+ 2008-12-03
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 File.join(File.dirname(__FILE__), 'random')
32
+ #require 'appmath_basics'
33
+ #require 'random'
34
+
35
+ module AppMath
36
+
37
+ # Vector space of arbitrary dimension.
38
+ # The intended usage is that the components
39
+ # of a vector are all either real or complex.
40
+ # Since
41
+ # x = Vec.new(anyArray); x[1] = anyObject
42
+ # works, there is no guaranty for type-uniformity
43
+ # of the components of a vector.
44
+
45
+ class Vec
46
+ include Enumerable
47
+ include Comparable
48
+
49
+ attr :x, true
50
+
51
+ # Returns the 'dimension' of the vector, i.e. the number of its
52
+ # components.
53
+ def dim; @x.size; end
54
+
55
+ # These are the 3 mehods to generate a vector via 'new'
56
+ # a = Vec.new(anArray)
57
+ # b = Vec.new(aVec)
58
+ # c = Vec.new(aPositiveInteger, aRealOrComplex)
59
+ def initialize(*arg)
60
+ case arg.size
61
+ when 1
62
+ a0 = arg[0]
63
+ if a0.is_a?(Array)
64
+ @x = Array.new(a0)
65
+ # @x = a0 # seems to work but can't be safe
66
+ elsif a0.is_a?(Vec)
67
+ @x = Array.new(a0.x)
68
+ else
69
+ fail "object can't be used to build a vector"
70
+ end
71
+ when 2
72
+ n = arg[0]
73
+ fail "first argument has to be an integer" unless n.integer?
74
+ fail "first argument must be non-negative" unless n >= 0
75
+ @x = Array.new(n,arg[1])
76
+ end
77
+ end
78
+
79
+ # Returns an independent copy of self.
80
+ def clone
81
+ Vec.new(self)
82
+ end
83
+
84
+ # Test object.
85
+ #
86
+ # Returns a Vec res such that res.dim == n. Vector res depends rather
87
+ # chaotically on the integer argument i. If the last argument is
88
+ # 'false' res will have R-typed components, and C-typed components else.
89
+ def Vec.tob(n,i,complex=false)
90
+ vi = complex ? C.tob(i) : R.tob(i)
91
+ res=Vec.new(n, vi)
92
+ if complex
93
+ rg1 = Ran.new(-vi.re,vi.re)
94
+ rg2 = Ran.new(-vi.im,vi.im)
95
+ for j in 1..res.dim
96
+ res[j] = C.new(rg1.ran,rg2.ran)
97
+ end
98
+ else
99
+ rg = Ran.new(-vi,vi)
100
+ for j in 1..res.dim
101
+ res[j] = rg.ran
102
+ end
103
+ end
104
+ res
105
+ end
106
+
107
+ # Gives the pseudoinverse of the vector self.
108
+ # This means that all components get inverted except those that are
109
+ # close to zero in comparison to the component with the largest
110
+ # absolute value.
111
+ # For small components c ( |c| ~ acc * sup |self[i]| ) a continuous
112
+ # transition between the inverse and zero becomes operational.
113
+
114
+ def pseudo_inv(acc=0)
115
+ n = dim
116
+ fail "dim = 0" if n.zero?
117
+ res = clone
118
+ if acc.zero? # most common case, thus first and without ordering
119
+ # overhead
120
+ for i in 1..n
121
+ si = self[i]
122
+ res[i] = si.zero? ? si.to_0 : si.inv
123
+ end
124
+ else
125
+ arr = @x.clone
126
+ arr.each{ |v| v = v.abs }
127
+ arr.sort!
128
+ a_max = arr.last
129
+ eta = a_max * acc
130
+ eta *= 0.5
131
+ eta *= eta
132
+ for i in 1..n
133
+ si = self[i]
134
+ ni = si * si + eta
135
+ res[i] = si / ni
136
+ end
137
+ end
138
+ res
139
+ end
140
+
141
+ # Valid indexes start with 1 not with 0.
142
+ # Read access to the components also works via indexes such as
143
+ # y = x[3]
144
+ def [](i)
145
+ @x[i-1]
146
+ end
147
+
148
+ # Valid indexes start with 1 not with 0.
149
+ # Write access to the components also works via indexes such as
150
+ # x[1] = 3.14
151
+ def []=(i,a)
152
+ @x[i-1] = a
153
+ end
154
+
155
+ # Returns self + v, where v is a Vec
156
+ def +(v)
157
+ fail "object can't be added to a Vec" unless v.is_a?(Vec)
158
+ fail "dimension mismatch" unless dim == v.dim
159
+ res = clone
160
+ for i in 1..dim
161
+ res[i] += v[i]
162
+ end
163
+ res
164
+ end
165
+
166
+ # Returns self - v , where v is a Vec
167
+ def -(v)
168
+ fail "object can't be subtracted from a Vec" unless v.is_a?(Vec)
169
+ fail "dimension mismatch" unless dim == v.dim
170
+ res = clone
171
+ for i in 1..dim
172
+ res[i] -= v[i]
173
+ end
174
+ res
175
+ end
176
+
177
+ # Returns self * s, where s has the same type as the components of self.
178
+ def *(s)
179
+ res = clone
180
+ for i in 1..dim
181
+ res[i] *= s
182
+ end
183
+ res
184
+ end
185
+
186
+ # Returns -self.
187
+ def -@
188
+ res = clone
189
+ for i in 1..dim
190
+ res[i] = -res[i]
191
+ end
192
+ res
193
+ end
194
+
195
+ # Returns a string which consists of a list of the strings which
196
+ # represent the components.
197
+ def to_s
198
+ res = "\n Vec"
199
+ for i in 0...dim
200
+ res += "\n " + x[i].to_s
201
+ end
202
+ res + "\n end Vec"
203
+ end
204
+
205
+ # Prints the content of self and naming the output.
206
+ def prn(name)
207
+ for i in 1..dim
208
+ puts " #{name}[#{i}] = " + self[i].to_s
209
+ end
210
+ end
211
+
212
+ # The order relation is here lexicographic ordering of lists.
213
+ # Needed only for book-keeping purposes.
214
+ # Defines the functionality of self as a Comparable.
215
+ def <=> (v)
216
+ d1 = dim; d2 = v.dim
217
+ if d1 < d2
218
+ return -1
219
+ elsif d1 > d2
220
+ return 1
221
+ else
222
+ for i in 0...d1
223
+ ci = x[i] <=> v.x[i]
224
+ return ci unless ci == 0
225
+ end
226
+ end
227
+ return 0
228
+ end
229
+
230
+ # Defines the functionality of self as an Enumerable.
231
+ def each
232
+ @x.each{ |c| yield c}
233
+ end
234
+
235
+ # Returns the scalar product (self|v). The complex
236
+ # conjugation (which acts trivially on R) affects here the
237
+ # first factor. This is the convention preferred in physics.
238
+ def spr(v)
239
+ fail "dimension mismatch" unless dim == v.dim
240
+ return nil if dim.zero?
241
+ s = self[1].conj * v[1]
242
+ for i in 2..dim
243
+ s += self[i].conj * v[i]
244
+ end
245
+ s
246
+ end
247
+
248
+ # Returns a 'modified scalar product' in which no
249
+ # complex conjugation is involved.
250
+ def convolution(v)
251
+ fail "dimension mismatch" unless dim == v.dim
252
+ return nil if dim.zero?
253
+ s = self[1] * v[1]
254
+ for i in 2..dim
255
+ s += self[i] * v[i]
256
+ end
257
+ s
258
+ end
259
+
260
+ # Returns the square of absolute value of self.
261
+ def abs2
262
+ spr(self)
263
+ end
264
+
265
+ # Returns the absolute value of self. This is also
266
+ # known as the L2-norm.
267
+ def abs
268
+ if complex?
269
+ abs2.re.sqrt
270
+ else
271
+ abs2.sqrt
272
+ end
273
+ end
274
+
275
+ # Returns a unit vector which has the same direction as self,
276
+ # (or self if this is the zero-vector).
277
+ def uv
278
+ r = abs
279
+ if r.zero?
280
+ clone
281
+ else
282
+ ri = r.inv
283
+ self * ri
284
+ end
285
+ end
286
+
287
+ # Returns a relative distance between self and v.
288
+ def dis(v)
289
+ a = abs
290
+ b = v.abs
291
+ d = (self - v).abs
292
+ s = a + b
293
+ return R.c0 if s.zero?
294
+ d1 = d/s
295
+ Basics.inf(d,d1)
296
+ end
297
+
298
+ # Returns 'true' if the first component is complex. Notice
299
+ # that the normal usage of Vec is to have all components
300
+ # of the same type.
301
+ def complex?
302
+ return nil if dim.zero?
303
+ @x[0].complex?
304
+ end
305
+
306
+ # Consistency test of class Vec.
307
+ def Vec.test(n0, verbose = true , complex = false)
308
+ puts "Doing Vec.test( n = #{n0}, verbose = #{verbose}, " +
309
+ "complex = #{complex}) for R.prec = #{R.prec}:"
310
+ puts "*************************************************"
311
+
312
+ t1 = Time.now
313
+ s = R.c0
314
+ puts "class of s is " + s.class.to_s
315
+ i = n0
316
+ a = Vec.tob(n0, i, complex)
317
+ i += 1
318
+ b = Vec.tob(n0, i, complex)
319
+ i += 1
320
+ c = Vec.tob(n0, i, complex)
321
+ i += 1
322
+ s1 = complex ? C.ran(i) : R.ran(i)
323
+ i += 1
324
+ s2 = complex ? C.ran(i) : R.ran(i)
325
+
326
+ r = (a + b) + c
327
+ l = a + (b + c)
328
+ ds = r.dis(l)
329
+ puts "associativity: ds = " + ds.to_s if verbose
330
+ s += ds
331
+
332
+ r = (a - b) + c
333
+ l = a - (b - c)
334
+ ds = r.dis(l)
335
+ puts "associativity 2: ds = " + ds.to_s if verbose
336
+ s += ds
337
+
338
+ r = (a + b) * s1
339
+ l = a * s1 + b * s1
340
+ ds = r.dis(l)
341
+ puts "distributivity: ds = " + ds.to_s if verbose
342
+ s += ds
343
+
344
+ r = c * (s1*s2)
345
+ l = (c * s1) * s2
346
+ ds = r.dis(l)
347
+ puts "distributivity of multiplication by scalars: ds = " + ds.to_s if verbose
348
+ s += ds
349
+
350
+ r = a
351
+ l = -(-a)
352
+ ds = r.dis(l)
353
+ puts "idempotency of unary minus: ds = " + ds.to_s if verbose
354
+ s += ds
355
+
356
+ r = (a + b).spr(c)
357
+ l = a.spr(c) + b.spr(c)
358
+ ds = r.dis(l)
359
+ puts "distributivity of spr: ds = " + ds.to_s if verbose
360
+ s += ds
361
+
362
+ t2 = Time.now
363
+
364
+ if verbose
365
+ puts
366
+ a.prn("a")
367
+ puts
368
+ b.prn("b")
369
+ puts
370
+ c.prn("c")
371
+ puts
372
+ s1.prn("s1")
373
+ puts
374
+ s2.prn("s2")
375
+ end
376
+
377
+ puts "class of s is " + s.class.to_s + " ."
378
+ puts "The error sum s is " + s.to_s + " ."
379
+ puts "It should be close to 0."
380
+ puts "Computation time was " + (t2-t1).to_s
381
+ s
382
+ end
383
+
384
+ end # Vec
385
+
386
+ # Matrix space of arbitrary dimension.
387
+ # The intended usage is that the elements
388
+ # of a matrix are all either real or complex.
389
+ # Since one is allowed to change any matrix element into
390
+ # any object there is no guaranty for type-uniformity
391
+ # of the elements of a matrix.
392
+
393
+ class Mat
394
+ include Enumerable
395
+ include Comparable
396
+ attr :x, true
397
+
398
+ # Returns the 'dimension' of the matrix, i.e. the number of its
399
+ # row-vectors. This thus is m for a 'm times n - matrix'.
400
+ def dim; @x.size; end
401
+
402
+ # Let self be a (m,n)-matrix (also called a m times n matrix)
403
+ # then dim1 == m
404
+ def dim1; @x.size; end
405
+
406
+ # Let self be a (m,n)-matrix (also called a m times n matrix)
407
+ # then dim2 == n
408
+ def dim2
409
+ return 0 if dim.zero?
410
+ self[1].dim
411
+ end
412
+
413
+ # These are the 5 mehods to generate a matrix via 'new'
414
+ # m1 = Mat.new(aMat)
415
+ # m2 = Mat.new(anArrayOfVec)
416
+ # m3 = Mat.new(aVec)
417
+ # m4 = Mat.new(aPositiveInteger, aRealOrComplex)
418
+ # m5 = Mat.new(aPositiveInteger, aPositiveInteger, aRealOrComplex)
419
+ # Here, m1 is a copy of aMat, m2 is a matrix which has as row
420
+ # vectors, the components of anArrayOfVec. If these vectors have
421
+ # not all he same dimension, failure results; m3 is a square matrix
422
+ # in which only the main diagonal may have non-zero elements,
423
+ # and in which ths diagonal is given as aVec; m4 is a square matrix
424
+ # with the dimension given by the first argument, and with all matrix
425
+ # elements equal to the second argment; m5 is a rectangular matrix
426
+ # with dim1 and dim2 given by the first and the second argument, and
427
+ # with all matrix elements equal to the third argument.
428
+
429
+ def initialize(*arg)
430
+ case arg.size
431
+ when 0
432
+ @x = Array.new
433
+ when 1 # ok if this is a Matrix or an array of Vectors
434
+ a0 = arg[0]
435
+ if a0.is_a?(Mat)
436
+ @x = Array.new(a0.x)
437
+ elsif a0.is_a?(Array)
438
+ n = a0.size
439
+ if n.zero?
440
+ @x = Array.new
441
+ else
442
+ misfit = 0
443
+ a0.each{|c|
444
+ misfit += 1 unless c.is_a?(Vec)
445
+ }
446
+ fail "input must consist of Vec-objects" unless misfit.zero?
447
+ misfit2 = 0
448
+ d2 = a0[1].dim
449
+ a0.each{|c|
450
+ misfit2 += 1 unless c.dim == d2
451
+ }
452
+ fail "input Vec-objects must agree in dimension" unless
453
+ misfit.zero?
454
+ @x = a0.clone
455
+ end
456
+ elsif a0.is_a?(Vec) # make a diagonal matrix
457
+ n = a0.dim
458
+ if n.zero?
459
+ @x = Array.new
460
+ else
461
+ c = a0[1].to_0
462
+ vc = Vec.new(n,c)
463
+ @x = Array.new(n,vc)
464
+ for i in 1..n
465
+ s!(i,i,a0[i])
466
+ end
467
+ end
468
+ else
469
+ fail "no reasonable construction available for this argument"
470
+ end
471
+ when 2 # make a square matrix, the diagonal filled with one element
472
+ # (all others zero)
473
+ n = arg[0]
474
+ a = arg[1]
475
+ zero = a.to_0
476
+ vc = Vec.new(n,zero)
477
+ @x = Array.new(n,vc)
478
+ for i in 1..n
479
+ vi = Vec.new(vc)
480
+ vi[i] = a
481
+ @x[i-1] = vi
482
+ end
483
+ when 3 # make rectangular matrix filled with one element
484
+ n1 = arg[0]
485
+ fail "first argument must be integer" unless n1.integer?
486
+ n2 = arg[1]
487
+ fail "second argument must be integer" unless n2.integer?
488
+ a = arg[2]
489
+ vc = Vec.new(n2,a)
490
+ @x = Array.new(n1,vc)
491
+ else
492
+ fail "no construction for more than 3 arguments"
493
+ end
494
+ end
495
+
496
+ # Returns an independent copy of self.
497
+ def clone
498
+ Mat.new(self)
499
+ end
500
+
501
+ # Generates a test object, here a n times n matrix with random
502
+ # elements. This object depends rather chaotically on the
503
+ # integer parameter i.
504
+ # If the last argument is 'false' the test matrix will have R-typed
505
+ # elements, and C-typed elements else.
506
+ def Mat.tob(n,i, complex = false)
507
+ if complex
508
+ ri = C.tob(i)
509
+ zero = ri.to_0
510
+ res=Mat.new(n, n, zero)
511
+ rg1 = Ran.new(-ri.re,ri.re)
512
+ rg2 = Ran.new(-ri.im,ri.im)
513
+ for j in 1..n
514
+ for k in 1..n
515
+ yjk = C.new(rg1.ran,rg2.ran)
516
+ res.s!(j,k,yjk)
517
+ end
518
+ end
519
+ res
520
+ else
521
+ ri = R.tob(i)
522
+ zero = ri.to_0
523
+ res=Mat.new(n, n, zero)
524
+ rg = Ran.new(-ri,ri)
525
+ for j in 1..n
526
+ for k in 1..n
527
+ yjk = rg.ran
528
+ res.s!(j,k,yjk)
529
+ end
530
+ end
531
+ res
532
+ end
533
+ end
534
+
535
+ # Singular value decomposition. Slightly modified fom Press et al.
536
+ # Only needed as a algorithmic tool. The method for the end-user
537
+ # is method pseudo_inv.
538
+ def Mat.svdcmp(a, w, v)
539
+
540
+ m = a.dim1; n = a.dim2
541
+ fail "svdcmp: bad frame of a" unless m >= n
542
+ fail "svdcmp: bad frame of w" unless n == w.dim
543
+ fail "svdcmp: bad frame of v" unless v.dim1 == n && v.dim2 == n
544
+ fail "svdcmp: dim = 0 as input" if m.zero? || n.zero?
545
+
546
+ iter_max=40
547
+ a11 = a[1][1]
548
+ zero =a11.to_0
549
+ one = a11.to_1
550
+ two = one + one
551
+ rv1 = Vec.new(n,zero)
552
+ g = zero; scale = zero; anorm = zero
553
+ for i in 1..n
554
+ l = i + 1
555
+ rv1[i] = scale * g
556
+ g = zero; s = zero; scale = zero
557
+ if i <= m
558
+ for k in i..m; scale += a[k][i].abs; end
559
+ if scale.nonzero?
560
+ for k in i..m
561
+ aki = a[k][i]
562
+ aki /= scale
563
+ a.s!(k,i,aki)
564
+ s += aki * aki
565
+ end
566
+ f = a[i][i]
567
+ g = - Basics.sign2(s.sqrt,f)
568
+ h = f * g - s
569
+ a.s!(i,i,f - g)
570
+ for j in l..n
571
+ s = zero
572
+ for k in i..m; s += a[k][i] * a[k][j]; end
573
+ f = s/h
574
+ for k in i..m
575
+ akj = a[k][j]
576
+ akj += f * a[k][i]
577
+ a.s!(k,j,akj)
578
+ end
579
+ end # for j in l..n
580
+ for k in i..m
581
+ aki = a[k][i]
582
+ aki *= scale
583
+ a.s!(k,i,aki)
584
+ end
585
+ end # scale != zero
586
+ end # i <= m
587
+ w[i] = scale * g
588
+ g = zero; s = R.c0; scale = zero
589
+ if i <= m && i != n
590
+ for k in l..n; scale += a[i][k].abs; end
591
+ if scale.nonzero?
592
+ for k in l..n
593
+ aik = a[i][k]
594
+ aik /= scale
595
+ a.s!(i,k,aik)
596
+ s += aik * aik
597
+ end
598
+ f = a[i][l]
599
+ g = - Basics.sign2(s.sqrt,f)
600
+ h = f * g - s
601
+ a.s!(i,l,f - g)
602
+ for k in l..n; rv1[k] = a[i][k]/h ; end
603
+ for j in l..m
604
+ s = zero
605
+ for k in l..n; s += a[j][k] * a[i][k]; end
606
+ for k in l..n
607
+ ajk = a[j][k]
608
+ ajk += s * rv1[k]
609
+ a.s!(j,k,ajk)
610
+ end
611
+ end # for j in l..m
612
+ for k in l..n; aik = a[i][k]; aik *= scale; a.s!(i,k,aik); end
613
+ end # if scale != zero
614
+ end # if i <= m && i != n
615
+ anorm = Basics.sup(anorm,w[i].abs + rv1[i].abs)
616
+ end # for i in 1..n
617
+ i = n
618
+ while i >= 1
619
+ if i < n
620
+ if g.nonzero?
621
+ for j in l..n; v.s!(j,i, (a[i][j]/a[i][l])/g); end
622
+ for j in l..n
623
+ s = zero
624
+ for k in l..n; s += a[i][k] * v[k][j]; end
625
+ for k in l..n
626
+ vkj =v[k][j]
627
+ vkj += s * v[k][i]
628
+ v.s!(k,j,vkj)
629
+ end
630
+ end # for j in l..n
631
+ end # if g.notzero!
632
+ for j in l..n; v.s!(i,j,zero); v.s!(j,i,zero); end
633
+ end # if i< n
634
+ v.s!(i,i,one)
635
+ g = rv1[i]
636
+ l = i
637
+ i -= 1
638
+ end # while i >= 1
639
+
640
+ i = Basics.inf(m,n)
641
+ while i >= 1
642
+ l = i + 1
643
+ g = w[i]
644
+ for j in l..n; a.s!(i,j,zero); end
645
+ if g.nonzero?
646
+ g = one/g
647
+ for j in l..n
648
+ s = zero
649
+ for k in l..m; s += a[k][i] * a[k][j]; end
650
+ f = (s/a[i][i]) * g
651
+ for k in i..m
652
+ akj = a[k][j]; akj += f * a[k][i]; a.s!(k,j,akj)
653
+ end
654
+ end # for j in l..n
655
+ for j in i..m; aji = a[j][i]; aji *= g; a.s!(j,i,aji); end
656
+ else
657
+ for j in i..m; a.s!(j,i,zero); end
658
+ end # if g.nonzero?
659
+ aii = a[i][i]; aii += one; a.s!(i,i,aii)
660
+ i -= 1
661
+ end # while i >= 1
662
+
663
+ k = n
664
+ while k >=1
665
+ for its in 1..iter_max
666
+ flag = 1
667
+ l = k
668
+ while l >= 1
669
+ nm = l - 1
670
+ if rv1[l].abs + anorm == anorm
671
+ flag = 0
672
+ break
673
+ end # if rv1[l].abs + anorm == anorm
674
+ break if w[nm].abs + anorm == anorm
675
+ l -= 1
676
+ end # while l >= 1
677
+ if flag.nonzero?
678
+ c = zero
679
+ s = one
680
+ for i in l..k
681
+ f = s * rv1[i]
682
+ rv1[i] = c * rv1[i]
683
+ break if f.abs + anorm == anorm
684
+ g = w[i]
685
+ h = f.hypot(g)
686
+ w[i] = h
687
+ h = one/h
688
+ c = g * h
689
+ s = -f * h
690
+ for j in 1..m
691
+ y = a[j][nm]; z = a[j][i];
692
+ a.s!(j,nm,y*c+z*s); a.s!(j,i,z*c-y*s)
693
+ end # for j in 1..m
694
+ end # for i in l..k
695
+ end # if flag.nonzero?
696
+ z = w[k]
697
+ if l == k
698
+ if z < zero
699
+ w[k] = -z
700
+ for j in 1..n; v.s!(j,k,-v[j][k]); end
701
+ end # if z < zero
702
+ break
703
+ end # if l == k
704
+ fail "no convergence in #{iter_max} iterations" if its == iter_max
705
+ x = w[l]; nm = k - 1; y = w[nm]; g = rv1[nm]; h = rv1[k]
706
+ f = ((y-z) * (y+z) + (g-h) * (g+h))/(h*y*two)
707
+ g = f.hypot(one)
708
+ f = ((x-z)*(x+z)+h*((y/(f+Basics.sign2(g,f)))-h))/x;
709
+ c = one; s = one
710
+ for j in l..nm
711
+ i = j + 1; g = rv1[i]; y =w[i]; h = s * g; g = c * g
712
+ z = f.hypot(h)
713
+ rv1[j] = z
714
+ c = f/z
715
+ s = h/z
716
+ f = x*c+g*s; g = g*c-x*s; h=y*s; y *= c;
717
+ for jj in 1..n
718
+ x=v[jj][j]; z=v[jj][i];
719
+ v.s!(jj,j,x*c+z*s); v.s!(jj,i,z*c-x*s)
720
+ end # for jj in 1..n
721
+ z = f.hypot(h)
722
+ w[j] = z
723
+ if z.nonzero?
724
+ z = one/z; c = f * z; s = h * z
725
+ end # if z.nonzero?
726
+ f=c*g+s*y; x=c*y-s*g;
727
+ for jj in 1..m
728
+ y=a[jj][j]; z=a[jj][i]
729
+ a.s!(jj,j,y*c+z*s); a.s!(jj,i,z*c-y*s)
730
+ end # for jj in 1..m
731
+ end # for j in l..nm
732
+ rv1[l] = zero
733
+ rv1[k] = f
734
+ w[k] = x
735
+ end # for its in 1..iter_max
736
+ k -= 1
737
+ end # while k >=1
738
+ end
739
+
740
+ # Reading row vectors. Valid indexes are 1,...,dim1.
741
+ def [](i)
742
+ @x[i-1]
743
+ end
744
+
745
+ # Setting row vectors. Valid indexes are 1,...,dim1.
746
+ # Notice that setting matrix elements via [][]= is not permanent.
747
+ # For setting matrix elements the method s! is provided.
748
+ def []=(i,a)
749
+ @x[i-1] = a
750
+ end
751
+
752
+ # The 's' stands for 'set (value)' and the '!' this is a method by which
753
+ # self changes (non-constant or mutating method).
754
+
755
+ def s!(i,j,a)
756
+ si = Vec.new(self[i]) # don't change the row-vector self[i]
757
+ # itself. Such changes are subject to subtle side effects.
758
+ # Worcing on a copy is safe.
759
+ si[j] = a # changing si here is normal syntax
760
+ self[i] = si # this is OK, of course
761
+ # self[i] = Vec.new(si) would work too, but would cause more work
762
+ end
763
+
764
+ # Returns a string which consists of a list of the strings which
765
+ # represent the row-vectors.
766
+ def to_s
767
+ res = "\n Mat"
768
+ for i in 0...dim
769
+ res += "\n " + x[i].to_s
770
+ end
771
+ res + "\n end Mat"
772
+ end
773
+
774
+ # Prints the content of self and naming the output.
775
+ def prn(name)
776
+ for i in 1..dim1
777
+ for j in 1..dim2
778
+ puts " #{name}[#{i}][#{j}] = " + self[i][j].to_s
779
+ end
780
+ end
781
+ end
782
+
783
+ # Unary minus operator. Returns - self.
784
+ def -@
785
+ res = clone
786
+ for i in 1..dim
787
+ res[i] = -res[i]
788
+ end
789
+ res
790
+ end
791
+
792
+ # Returns self + v, where v is a Mat
793
+ def +(v)
794
+ fail "Object can't be added to a Mat." unless v.is_a?(Mat)
795
+ fail "Dimension mismatch." unless dim == v.dim
796
+ res = clone
797
+ for i in 1..dim
798
+ res[i] += v[i]
799
+ end
800
+ res
801
+ end
802
+
803
+ # Returns self - v , where v is a Mat
804
+ def -(v)
805
+ fail "Object can't be subtracted from a Mat." unless v.is_a?(Mat)
806
+ fail "Dimension mismatch." unless dim == v.dim
807
+ res = clone
808
+ for i in 1..dim
809
+ res[i] -= v[i]
810
+ end
811
+ res
812
+ end
813
+
814
+ # Returns the transposed of self.
815
+ def trp # implementation without s!
816
+ d1 = dim1
817
+ d2 = dim2
818
+ fail "dim1 == 0" if d1.zero?
819
+ fail "dim2 == 0" if d2.zero?
820
+ zero = @x[0][0].to_0
821
+ # self has d1 row-vectors of length d2.
822
+ # The result of transposing has d2 row-vectors of length d1.
823
+ v = Array.new(d2)
824
+ for j in 0...d2
825
+ vj = Vec.new(d1,zero)
826
+ for i in 0...d1
827
+ vj.x[i] = @x[i].x[j]
828
+ end
829
+ v[j] = vj
830
+ end
831
+ Mat.new(v)
832
+ end
833
+
834
+ # Returns the Hermitian conjugate of self.
835
+ def conj
836
+ d1 = dim1
837
+ d2 = dim2
838
+ fail "dim1 == 0" if d1.zero?
839
+ fail "dim2 == 0" if d2.zero?
840
+ zero = @x[0].x[0].to_0
841
+ res = Mat.new(d2,d1,zero)
842
+ for i in 0...d1
843
+ for j in 0...d2
844
+ sij = @x[i].x[j]
845
+ res.s!(j+1,i+1,sij.conj)
846
+ end
847
+ end
848
+ res
849
+ end
850
+
851
+
852
+ # 'to zero' Returns a matrix with he same dimensions
853
+ # as self, but with all matrix elements set to zero.
854
+
855
+ def to_0
856
+ d1 = dim1
857
+ d2 = dim2
858
+ fail "dim1 == 0" if d1.zero?
859
+ fail "dim2 == 0" if d2.zero?
860
+ zero = @x[0].x[0].to_0
861
+ Mat.new(d2,d1,zero)
862
+ end
863
+
864
+ # 'to one'. Returns a matrix with he same dimensions
865
+ # as self, but with all matrix elements set to 1.
866
+ def to_1
867
+ d1 = dim1
868
+ d2 = dim2
869
+ fail "dim1 == 0" if d1.zero?
870
+ fail "dim2 == 0" if d2.zero?
871
+ fail "dim1 != dim2" unless d1 == d2
872
+ unit = @x[0].x[0].to_1
873
+ diag = Vec.new(d1,unit)
874
+ Mat.new(diag)
875
+ end
876
+
877
+ =begin
878
+ # Multiplication of a Mat with either a Mat, Vec, or Numeric
879
+ def *(v)
880
+ d1 = dim1
881
+ fail "dim1 == 0" if d1.zero?
882
+ d2 = dim2
883
+ fail "dim2 == 0" if d2.zero?
884
+ zero=@x[0].x[0].to_0
885
+ if v.is_a?(Mat)
886
+ d3 = v.dim1
887
+ fail "dimenson mismatch" unless d3 == d2
888
+ d4 = v.dim2
889
+ fail "dim4 == 0" if d4.zero?
890
+ res = Mat.new(d1,d4,zero)
891
+ for i in 0...d1
892
+ ip = i + 1
893
+ for j in 0...d4
894
+ vij = zero
895
+ jp = j + 1
896
+ for k in 0...d2
897
+ vij += @x[i].x[k] * v.x[k].x[j]
898
+ end
899
+ res.s!(ip, jp, vij)
900
+ end
901
+ end
902
+ elsif v.is_a?(Vec)
903
+ d3 = v.dim
904
+ fail "dimenson mismatch" unless d3 == d2
905
+ res = Vec.new(d1,zero)
906
+ for i in 0...d1
907
+ vi = zero
908
+ for j in 0...d2
909
+ vi += @x[i].x[j] * v.x[j]
910
+ end
911
+ res.x[i] = vi
912
+ end
913
+ elsif v.is_a?(Numeric) # multiplication with scalar
914
+ res = clone
915
+ for i in 1..d1
916
+ res[i] *= v
917
+ end
918
+ else
919
+ fail "can't multiply with this object"
920
+ end
921
+ res
922
+ end
923
+ =end
924
+
925
+ # Multiplication of a Mat with either a Mat, Vec, or Numeric
926
+ def *(v)
927
+ d1 = dim1
928
+ fail "dim1 == 0" if d1.zero?
929
+ d2 = dim2
930
+ fail "dim2 == 0" if d2.zero?
931
+ zero=@x[0].x[0].to_0
932
+ if v.is_a?(Mat)
933
+ d3 = v.dim1
934
+ fail "dimenson mismatch" unless d3 == d2
935
+ d4 = v.dim2
936
+ fail "dim4 == 0" if d4.zero?
937
+ # we better produce the d1 row-vectors in turn
938
+ a = Array.new(d1)
939
+ for i in 0...d1
940
+ vi = Vec.new(d4,zero)
941
+ for j in 0...d4
942
+ vij = zero
943
+ for k in 0...d2
944
+ vij += @x[i].x[k] * v.x[k].x[j]
945
+ end
946
+ vi.x[j] = vij
947
+ end
948
+ a[i] = vi
949
+ end
950
+ res = Mat.new(a)
951
+ elsif v.is_a?(Vec)
952
+ d3 = v.dim
953
+ fail "dimenson mismatch" unless d3 == d2
954
+ res = Vec.new(d1,zero)
955
+ for i in 0...d1
956
+ vi = zero
957
+ for j in 0...d2
958
+ vi += @x[i].x[j] * v.x[j]
959
+ end
960
+ res.x[i] = vi
961
+ end
962
+ elsif v.is_a?(Numeric) # multiplication with scalar
963
+ res = clone
964
+ for i in 1..d1
965
+ res[i] *= v
966
+ end
967
+ else
968
+ fail "can't multiply with this object"
969
+ end
970
+ res
971
+ end
972
+
973
+ # The order relation is here lexicographic ordering of lists.
974
+ # Needed only for book-keeping purposes.
975
+ def <=> (v)
976
+ d1 = dim; d2 = v.dim
977
+ if d1 < d2
978
+ return -1
979
+ elsif d1 > d2
980
+ return 1
981
+ else
982
+ for i in 1..d1
983
+ ci = self[i] <=> v[i]
984
+ return ci unless ci == 0
985
+ end
986
+ end
987
+ return 0
988
+ end
989
+
990
+ def each
991
+ @x.each{ |c| yield c}
992
+ end
993
+
994
+ # Scalar product of matrices.
995
+ def spr(v)
996
+ fail "dimension mismatch" unless dim == v.dim
997
+ return nil if dim.zero?
998
+ s = self[1].spr(v[1])
999
+ for i in 2..dim
1000
+ s += self[i].spr(v[i])
1001
+ end
1002
+ s
1003
+ end
1004
+
1005
+ # Square of absolute value.
1006
+ def abs2
1007
+ spr(self)
1008
+ end
1009
+
1010
+ # Absolute value, always real
1011
+ def abs
1012
+ if complex?
1013
+ abs2.re.sqrt
1014
+ else
1015
+ abs2.sqrt
1016
+ end
1017
+ end
1018
+
1019
+ # Relative distance between matrices
1020
+ def dis(v)
1021
+ a = abs
1022
+ b = v.abs
1023
+ d = (self - v).abs
1024
+ s = a + b
1025
+ return R.c0 if s.zero?
1026
+ d1 = d/s
1027
+ d < d1 ? d : d1
1028
+ end
1029
+
1030
+ def square?
1031
+ dim1 == dim2
1032
+ end
1033
+
1034
+ def complex?
1035
+ return nil if dim.zero?
1036
+ @x[0].complex?
1037
+ end
1038
+
1039
+ # Returns the pseudo-inverse (als known as Penrose inverse) of self.
1040
+ # If the argument acc is not zero, the discontinous treatment of singular
1041
+ # values near zero is replaced by a continuous one.
1042
+ # Notice that the pseudo inverse always exists, and that the pseudo-
1043
+ # inverse of a (m,n)-matrix is a (n,m)-matrix.
1044
+ # If the argument acc is not zero, the pseudo-inverse is the first
1045
+ # component of a 4-array res, which also contains the the
1046
+ # intermediary quantities a, w, v resulting from the call
1047
+ # Mat.svdcmp(a,w,v). a == res[1], w == res[2], v == res[3].
1048
+ # Especially the list w of the original singular values is thus made
1049
+ # accessible, so that one can judge whether their processing controlled
1050
+ # by the parameter acc was reasonable.
1051
+ # The pseudo_inverse is the most useful and stable mehod to solve
1052
+ # linear equations: Let a be a (m,n)-matrix, and b a m-vector.
1053
+ # The equation
1054
+ # a * x = b (i)
1055
+ # determines a n-vector x as
1056
+ # x = a.pseudo_inv * b
1057
+ # which is a solution of (i) if there is one. If there are many solutions
1058
+ # it is the one of minimum absolute value, and if there is no solution
1059
+ # it comes closest to be a solution: It minimizes the defect
1060
+ # (a * x - b).abs
1061
+ # Simply great! No need for LU-decompositions any more.
1062
+
1063
+ def pseudo_inv(acc = 0)
1064
+ if complex?
1065
+ fail "Pseudo inverse not yet impemented for complex matrices"
1066
+ end
1067
+ m = dim1
1068
+ fail "dim1 == 0" if m.zero?
1069
+ n=dim2
1070
+ fail "dim2 == 0" if n.zero?
1071
+ rightframe = m >= n
1072
+ a = clone
1073
+ a = a.trp if rightframe == false
1074
+ m = a.dim1; n = a.dim2
1075
+ zero = a[1][1].to_0
1076
+ v = Mat.new(n,n,zero)
1077
+ w = Vec.new(n,zero)
1078
+ vr = Mat.new(n,m,zero) # sic
1079
+ Mat.svdcmp(a,w,v)
1080
+ wi = w.pseudo_inv(acc) # w does not come out orderd
1081
+ for i in 1..n
1082
+ for j in 1..m
1083
+ sum = zero
1084
+ for k in 1..n
1085
+ sum += v[i][k] * wi[k] * a[j][k]
1086
+ end
1087
+ vr.s!(i,j,sum)
1088
+ end
1089
+ end
1090
+ vr = vr.trp if rightframe == false
1091
+ if acc.zero?
1092
+ vr
1093
+ else
1094
+ [vr,a,w,v]
1095
+ end
1096
+ end
1097
+
1098
+ # In most cases ('up to a subst of measure zero') the pseudo-inverse
1099
+ # is also the inverse.
1100
+ def inv
1101
+ pseudo_inv
1102
+ end
1103
+
1104
+ # Consistency test of class Mat.
1105
+ def Mat.test(n0, verbose = true, complex = false )
1106
+ puts "Doing Mat.test( n = #{n0}, verbose = #{verbose}," +
1107
+ " complex = #{complex} ) for R.prec = #{R.prec}:"
1108
+ puts "******************************************************"
1109
+ t1 = Time.now
1110
+ s = R.c0
1111
+ puts "class of s is " + s.class.to_s
1112
+ i = n0 + 137
1113
+ a = Mat.tob(n0, i, complex)
1114
+ i += 1
1115
+ b = Mat.tob(n0, i, complex)
1116
+ i += 1
1117
+ c = Mat.tob(n0, i, complex)
1118
+ x = Vec.tob(n0, i, complex)
1119
+ i += 1
1120
+ y = Vec.tob(n0, i, complex)
1121
+ i += 1
1122
+ s1 = complex ? C.ran(i) : R.ran(i)
1123
+ i += 1
1124
+ s2 = complex ? C.ran(i) : R.ran(i)
1125
+
1126
+ unit = a.to_1
1127
+
1128
+ a0 = a.clone
1129
+ b0 = b.clone
1130
+ c0 = c.clone
1131
+
1132
+ abc0 = a0.abs + b0.abs + c0.abs
1133
+
1134
+ ac = a.clone
1135
+ a = b
1136
+ r = a
1137
+ l = b
1138
+ ds = r.dis(l)
1139
+ puts "assignment of variables: ds = " + ds.to_s if verbose
1140
+ s += ds
1141
+
1142
+ a = ac
1143
+ r = a
1144
+ l = ac
1145
+ ds = r.dis(l)
1146
+ puts "assignment of variables 2: ds = " + ds.to_s if verbose
1147
+ s += ds
1148
+
1149
+ r = (a + b) + c
1150
+ l = a + (b + c)
1151
+ ds = r.dis(l)
1152
+ puts "associativity of +: ds = " + ds.to_s if verbose
1153
+ s += ds
1154
+
1155
+ r = (a - b) + c
1156
+ l = a - (b - c)
1157
+ ds = r.dis(l)
1158
+ puts "associativity of -: ds = " + ds.to_s if verbose
1159
+ s += ds
1160
+
1161
+ r = (a * b) * c
1162
+ l = a * (b * c)
1163
+ ds = r.dis(l)
1164
+ puts "associativity of *: ds = " + ds.to_s if verbose
1165
+ s += ds
1166
+
1167
+ r = (a + b) * s1
1168
+ l = a * s1 + b * s1
1169
+ ds = r.dis(l)
1170
+ puts "distributivity of multiplication by scalars: ds = " +
1171
+ ds.to_s if verbose
1172
+ s += ds
1173
+
1174
+ r = c * (s1*s2)
1175
+ l = (c * s1) * s2
1176
+ ds = r.dis(l)
1177
+ puts "distributivity of multiplication by scalars: ds = " +
1178
+ ds.to_s if verbose
1179
+ s += ds
1180
+
1181
+ r = a
1182
+ l = -(-a)
1183
+ ds = r.dis(l)
1184
+ puts "idempotency of unary minus: ds = " + ds.to_s if verbose
1185
+ s += ds
1186
+
1187
+ r = (a + b).spr(c)
1188
+ l = a.spr(c) + b.spr(c)
1189
+ ds = r.dis(l)
1190
+ puts "distributivity of spr: ds = " + ds.to_s if verbose
1191
+ s += ds
1192
+
1193
+ r = (a + b) * c
1194
+ l = a * c + b * c
1195
+ ds = r.dis(l)
1196
+ puts "distributivity of matrix multiplication: ds = " +
1197
+ ds.to_s if verbose
1198
+ s += ds
1199
+
1200
+ r = (a * b) * x
1201
+ l = a * (b * x)
1202
+ ds = r.dis(l)
1203
+ puts "action on vectors 1: ds = " + ds.to_s if verbose
1204
+ s += ds
1205
+
1206
+ r = (a + b) * x
1207
+ l = a * x + b * x
1208
+ ds = r.dis(l)
1209
+ puts "action on vectors 2: ds = " + ds.to_s if verbose
1210
+ s += ds
1211
+
1212
+ r = b * (x + y)
1213
+ l = b * x + b * y
1214
+ ds = r.dis(l)
1215
+ puts "action on vectors 3: ds = " + ds.to_s if verbose
1216
+ s += ds
1217
+
1218
+ r = c * (x * s1)
1219
+ l = (c * s1) * x
1220
+ ds = r.dis(l)
1221
+ puts "action on vectors 4: ds = " + ds.to_s if verbose
1222
+ s += ds
1223
+
1224
+ if complex == false
1225
+ r = unit
1226
+ l = a * a.pseudo_inv
1227
+ ds = r.dis(l)
1228
+ puts "pseudo inverse is right inverse: ds = " + ds.to_s if verbose
1229
+ s += ds
1230
+
1231
+ r = unit
1232
+ l = a.pseudo_inv * a
1233
+ ds = r.dis(l)
1234
+ puts "pseudo inverse is left inverse: ds = " + ds.to_s if verbose
1235
+ s += ds
1236
+ else
1237
+ puts "test of pseudo inverse left out, since not implemented for complex"
1238
+ end
1239
+
1240
+ aMem = a.clone
1241
+ bMem = b.clone
1242
+ cMem = c.clone
1243
+
1244
+ # Testing the access functions, under harsh conditions with
1245
+ # inserting double transposition
1246
+
1247
+ for i in 1..a.dim1
1248
+ for j in 1..a.dim2
1249
+ b.s!(i,j,a[i][j]) # copies a to b
1250
+ end
1251
+ end
1252
+
1253
+ l = a
1254
+ r = a.trp.trp
1255
+ ds = r.dis(l)
1256
+ puts "test of double transposition: ds = " + ds.to_s if verbose
1257
+ s += ds
1258
+
1259
+ for i in 1..b.dim1
1260
+ for j in 1..b.dim2
1261
+ c.s!(i,j,b[i][j]) # copies b to c
1262
+ end
1263
+ end
1264
+ # Finally c should have the content of a
1265
+
1266
+ r = a
1267
+ l = c
1268
+ ds = r.dis(l)
1269
+ puts "test of access functions: ds = " + ds.to_s if verbose
1270
+ s += ds
1271
+
1272
+ a = aMem; b = bMem; c = cMem
1273
+
1274
+ abc = a.abs + b.abs + c.abs
1275
+
1276
+ l = abc
1277
+ r = abc0
1278
+ ds = r.dis(l)
1279
+ puts "heavy test of assignment: ds = " + ds.to_s if verbose
1280
+
1281
+ t2 = Time.now
1282
+
1283
+ if verbose
1284
+ puts
1285
+ a.prn("a")
1286
+ puts
1287
+ b.prn("b")
1288
+ puts
1289
+ c.prn("c")
1290
+ puts
1291
+ s1.prn("s1")
1292
+ puts
1293
+ s2.prn("s2")
1294
+ puts
1295
+ x.prn("x")
1296
+ puts
1297
+ y.prn("y")
1298
+ end
1299
+
1300
+ puts "class of s is " + s.class.to_s + " ."
1301
+ puts "The error sum s is " + s.to_s + " ."
1302
+ puts "It should be close to 0."
1303
+ puts "Computation time was " + (t2-t1).to_s
1304
+ s
1305
+ end
1306
+
1307
+ end # Mat
1308
+
1309
+ end # module AppMath