appmath 0.0.1

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