stick 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/CHANGES +7 -0
  2. data/COPYING +344 -0
  3. data/README +110 -0
  4. data/lib/stick/constants.rb +3 -0
  5. data/lib/stick/constants/cgs.rb +151 -0
  6. data/lib/stick/constants/mks.rb +158 -0
  7. data/lib/stick/constants/number.rb +33 -0
  8. data/lib/stick/constants/typeless_cgs.rb +141 -0
  9. data/lib/stick/constants/typeless_mks.rb +142 -0
  10. data/lib/stick/currency.rb +8 -0
  11. data/lib/stick/mapcar.rb +61 -0
  12. data/lib/stick/matrix.rb +1022 -0
  13. data/lib/stick/quaternion.rb +562 -0
  14. data/lib/stick/times.rb +441 -0
  15. data/lib/stick/units.rb +112 -0
  16. data/lib/stick/units/base.rb +980 -0
  17. data/lib/stick/units/currency.rb +159 -0
  18. data/lib/stick/units/data/binary/base.rb +4 -0
  19. data/lib/stick/units/data/cex.rb +5 -0
  20. data/lib/stick/units/data/currency-default.rb +5 -0
  21. data/lib/stick/units/data/currency-standard.rb +2 -0
  22. data/lib/stick/units/data/currency/base.rb +89 -0
  23. data/lib/stick/units/data/iec.rb +5 -0
  24. data/lib/stick/units/data/iec_binary/base.rb +6 -0
  25. data/lib/stick/units/data/si.rb +7 -0
  26. data/lib/stick/units/data/si/base.rb +9 -0
  27. data/lib/stick/units/data/si/derived.rb +26 -0
  28. data/lib/stick/units/data/si/extra.rb +22 -0
  29. data/lib/stick/units/data/uk.rb +10 -0
  30. data/lib/stick/units/data/uk/base.rb +22 -0
  31. data/lib/stick/units/data/units-default.rb +11 -0
  32. data/lib/stick/units/data/units-standard.rb +5 -0
  33. data/lib/stick/units/data/us.rb +10 -0
  34. data/lib/stick/units/data/us/base.rb +23 -0
  35. data/lib/stick/units/data/xmethods.rb +5 -0
  36. data/lib/stick/units/data/xmethods/cached.rb +84 -0
  37. data/lib/stick/units/data/xmethods/mapping.rb +87 -0
  38. data/lib/stick/units/loaders.rb +98 -0
  39. data/lib/stick/units/units.rb +109 -0
  40. data/meta/MANIFEST +76 -0
  41. data/meta/ROLLRC +2 -0
  42. data/meta/icli.yaml +16 -0
  43. data/meta/project.yaml +18 -0
  44. data/task/clobber/package +10 -0
  45. data/task/publish +57 -0
  46. data/task/release +10 -0
  47. data/task/setup +1616 -0
  48. data/task/test +25 -0
  49. data/test/spec_matrix.rb +342 -0
  50. data/test/test_currency.rb +26 -0
  51. data/test/test_matrix.rb +359 -0
  52. data/test/test_units.rb +205 -0
  53. data/work/TODO +20 -0
  54. data/work/bytes.rb +231 -0
  55. data/work/multipliers.rb +195 -0
  56. metadata +138 -0
@@ -0,0 +1,142 @@
1
+ # Title:
2
+ #
3
+ # Unitless MKS Constants
4
+ #
5
+ # Copyright:
6
+ #
7
+ # Copyright (C) 2003 Daniel Carrera, Brian Gough
8
+ #
9
+ # License:
10
+ #
11
+ # GNU General Public License
12
+ #
13
+ # This program is free software; you can redistribute it and/or modify
14
+ # it under the terms of the GNU General Public License as published by
15
+ # the Free Software Foundation; either version 2 of the License, or (at
16
+ # your option) any later version.
17
+ #
18
+ # This program is distributed in the hope that it will be useful, but
19
+ # WITHOUT ANY WARRANTY; without even the implied warranty of
20
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21
+ # General Public License for more details.
22
+ #
23
+ # You should have received a copy of the GNU General Public License
24
+ # along with this program; if not, write to the Free Software
25
+ # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26
+ #
27
+ # Authors:
28
+ #
29
+ # - Daniel Carrera
30
+ # - Brian Gough
31
+ # - Thomas Sawyer
32
+
33
+ #
34
+ module Stick
35
+ module Constants
36
+ module Typeless
37
+ # Unitless constants in the MKS system (meters, kg, sec)
38
+
39
+ module MKS
40
+ SPEED_OF_LIGHT = 2.99792458e8 # m / s
41
+ GRAVITATIONAL_CONSTANT = 6.673e-11 # m^3 / kg s^2
42
+ PLANCKS_CONSTANT_H = 6.62606876e-34 # kg m^2 / s
43
+ PLANCKS_CONSTANT_HBAR = 1.05457159642e-34 # kg m^2 / s
44
+ VACUUM_PERMEABILITY = 1.25663706144e-6 # kg m / A^2 s^2
45
+ ASTRONOMICAL_UNIT = 1.49597870691e11 # m
46
+ LIGHT_YEAR = 9.46053620707e15 # m
47
+ PARSEC = 3.08567758135e16 # m
48
+ GRAV_ACCEL = 9.80665e0 # m / s^2
49
+ ELECTRON_VOLT = 1.602176462e-19 # kg m^2 / s^2
50
+ MASS_ELECTRON = 9.10938188e-31 # kg
51
+ MASS_MUON = 1.88353109e-28 # kg
52
+ MASS_PROTON = 1.67262158e-27 # kg
53
+ MASS_NEUTRON = 1.67492716e-27 # kg
54
+ RYDBERG = 2.17987190389e-18 # kg m^2 / s^2
55
+ BOLTZMANN = 1.3806503e-23 # kg m^2 / K s^2
56
+ BOHR_MAGNETON = 9.27400899e-24 # A m^2
57
+ NUCLEAR_MAGNETON = 5.05078317e-27 # A m^2
58
+ ELECTRON_MAGNETIC_MOMENT = 9.28476362e-24 # A m^2
59
+ PROTON_MAGNETIC_MOMENT = 1.410606633e-26 # A m^2
60
+ MOLAR_GAS = 8.314472e0 # kg m^2 / K mol s^2
61
+ STANDARD_GAS_VOLUME = 2.2710981e-2 # m^3 / mol
62
+ MINUTE = 6e1 # s
63
+ HOUR = 3.6e3 # s
64
+ DAY = 8.64e4 # s
65
+ WEEK = 6.048e5 # s
66
+ INCH = 2.54e-2 # m
67
+ FOOT = 3.048e-1 # m
68
+ YARD = 9.144e-1 # m
69
+ MILE = 1.609344e3 # m
70
+ NAUTICAL_MILE = 1.852e3 # m
71
+ FATHOM = 1.8288e0 # m
72
+ MIL = 2.54e-5 # m
73
+ POINT = 3.52777777778e-4 # m
74
+ TEXPOINT = 3.51459803515e-4 # m
75
+ MICRON = 1e-6 # m
76
+ ANGSTROM = 1e-10 # m
77
+ HECTARE = 1e4 # m^2
78
+ ACRE = 4.04685642241e3 # m^2
79
+ BARN = 1e-28 # m^2
80
+ LITER = 1e-3 # m^3
81
+ US_GALLON = 3.78541178402e-3 # m^3
82
+ QUART = 9.46352946004e-4 # m^3
83
+ PINT = 4.73176473002e-4 # m^3
84
+ CUP = 2.36588236501e-4 # m^3
85
+ FLUID_OUNCE = 2.95735295626e-5 # m^3
86
+ TABLESPOON = 1.47867647813e-5 # m^3
87
+ TEASPOON = 4.92892159375e-6 # m^3
88
+ CANADIAN_GALLON = 4.54609e-3 # m^3
89
+ UK_GALLON = 4.546092e-3 # m^3
90
+ MILES_PER_HOUR = 4.4704e-1 # m / s
91
+ KILOMETERS_PER_HOUR = 2.77777777778e-1 # m / s
92
+ KNOT = 5.14444444444e-1 # m / s
93
+ POUND_MASS = 4.5359237e-1 # kg
94
+ OUNCE_MASS = 2.8349523125e-2 # kg
95
+ TON = 9.0718474e2 # kg
96
+ METRIC_TON = 1e3 # kg
97
+ UK_TON = 1.0160469088e3 # kg
98
+ TROY_OUNCE = 3.1103475e-2 # kg
99
+ CARAT = 2e-4 # kg
100
+ UNIFIED_ATOMIC_MASS = 1.66053873e-27 # kg
101
+ ATOMIC_MASS = 1.66053873e-27 # kg
102
+ GRAM_FORCE = 9.80665e-3 # kg m / s^2
103
+ POUND_FORCE = 4.44822161526e0 # kg m / s^2
104
+ KILOPOUND_FORCE = 4.44822161526e3 # kg m / s^2
105
+ POUNDAL = 1.38255e-1 # kg m / s^2
106
+ CALORIE = 4.1868e0 # kg m^2 / s^2
107
+ BTU = 1.05505585262e3 # kg m^2 / s^2
108
+ THERM = 1.05506e8 # kg m^2 / s^2
109
+ HORSEPOWER = 7.457e2 # kg m^2 / s^3
110
+ BAR = 1e5 # kg / m s^2
111
+ STD_ATMOSPHERE = 1.01325e5 # kg / m s^2
112
+ TORR = 1.33322368421e2 # kg / m s^2
113
+ METER_OF_MERCURY = 1.33322368421e5 # kg / m s^2
114
+ INCH_OF_MERCURY = 3.38638815789e3 # kg / m s^2
115
+ INCH_OF_WATER = 2.490889e2 # kg / m s^2
116
+ PSI = 6.89475729317e3 # kg / m s^2
117
+ POISE = 1e-1 # kg m^-1 s^-1
118
+ STOKES = 1e-4 # m^2 / s
119
+ FARADAY = 9.6485341472e4 # A s / mol
120
+ ELECTRON_CHARGE = 1.602176462e-19 # A s
121
+ GAUSS = 1e-4 # kg / A s^2
122
+ STILB = 1e4 # cd / m^2
123
+ LUMEN = 1e0 # cd sr
124
+ LUX = 1e0 # cd sr / m^2
125
+ PHOT = 1e4 # cd sr / m^2
126
+ FOOTCANDLE = 1.076e1 # cd sr / m^2
127
+ LAMBERT = 1e4 # cd sr / m^2
128
+ FOOTLAMBERT = 1.07639104e1 # cd sr / m^2
129
+ CURIE = 3.7e10 # 1 / s
130
+ ROENTGEN = 2.58e-4 # A s / kg
131
+ RAD = 1e-2 # m^2 / s^2
132
+ SOLAR_MASS = 1.98892e30 # kg
133
+ BOHR_RADIUS = 5.291772083e-11 # m
134
+ VACUUM_PERMITTIVITY = 8.854187817e-12 # A^2 s^4 / kg m^3
135
+ NEWTON = 1e0 # kg m / s^2
136
+ DYNE = 1e-5 # kg m / s^2
137
+ JOULE = 1e0 # kg m^2 / s^2
138
+ ERG = 1e-7 # kg m^2 / s^2
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,8 @@
1
+ require 'stick/units/currency'
2
+
3
+ module Stick
4
+ # Load conversion units.
5
+ class Converter
6
+ require("currency-standard")
7
+ end
8
+ end
@@ -0,0 +1,61 @@
1
+ # = TITLE:
2
+ #
3
+ # Mapcar
4
+ #
5
+ # AUTHOR:
6
+ #
7
+ # Cosmin Bonchis
8
+ #
9
+ # NOTES:
10
+ #
11
+ # Google Summer of Code 2007 project for Ruby Central Inc.
12
+
13
+ require 'generator'
14
+ #
15
+ # Non-recursive mapcar (works on all Enumerables)
16
+ #
17
+ def mapcar(*enums)
18
+ generators = enums.collect { |e| Generator.new(e) }
19
+ result = []
20
+ while true
21
+ begin
22
+ params = generators.collect { |g| g.current; g.next }
23
+ rescue EOFError
24
+ return result
25
+ end
26
+ result << yield(*params)
27
+ end
28
+ end
29
+
30
+ def map(*enums)
31
+ generators = []; enums.each { |e| generators << Generator.new(e) }
32
+ while true
33
+ begin
34
+ params = []; generators.each { |g| g.current; params << g.next }
35
+ rescue EOFError
36
+ return
37
+ end
38
+ yield(*params)
39
+ end
40
+ end
41
+
42
+ class Array
43
+ def Array.map(n, *arrays)
44
+ len = arrays.length
45
+ if n == 0
46
+ n = arrays[0].length
47
+ 1.upto(arrays.length - 1) do |i|
48
+ al = arrays[i].length
49
+ n = al if al < n
50
+ end
51
+ end
52
+ 0.upto(n - 1) do |i|
53
+ params = []
54
+ 0.upto(len - 1) do |arr|
55
+ params << arrays[arr][i]
56
+ end
57
+ yield(*params)
58
+ end
59
+ end
60
+ end
61
+
@@ -0,0 +1,1022 @@
1
+ # = TITLE:
2
+ #
3
+ # Matrix Extensions
4
+ #
5
+ # AUTHOR:
6
+ #
7
+ # Cosmin Bonchis
8
+ #
9
+ # NOTES:
10
+ #
11
+ # Google Summer of Code 2007 project for Ruby Central Inc.
12
+
13
+ require 'rational'
14
+ require 'matrix'
15
+
16
+ require 'stick/mapcar'
17
+
18
+
19
+ class Vector
20
+ include Enumerable
21
+
22
+ module Norm
23
+ def Norm.sqnorm(obj, p)
24
+ sum = 0
25
+ obj.each{|x| sum += x ** p}
26
+ sum
27
+ end
28
+ end
29
+
30
+ alias :length :size
31
+ alias :index :[]
32
+ #
33
+ # Returns the value of an index vector or
34
+ # a Vector with the values of a range
35
+ # v = Vector[1, 2, 3, 4]
36
+ # v[0] => 1
37
+ # v[0..2] => Vector[1, 2, 3]
38
+ #
39
+ def [](i)
40
+ case i
41
+ when Range
42
+ Vector[*to_a.slice(i)]
43
+ else
44
+ index(i)
45
+ end
46
+ end
47
+
48
+ #
49
+ # Sets a vector value/(range of values) with a new value/(values from a vector)
50
+ # v = Vector[1, 2, 3]
51
+ # v[2] = 9 => Vector[1, 2, 9]
52
+ # v[1..2] = Vector[9, 9, 9, 9, 9] => v: Vector[1, 9, 9]
53
+ #
54
+ def []=(i, v)
55
+ case i
56
+ when Range
57
+ (self.size..i.begin - 1).each{|e| self[e] = 0} # self.size must be in the first place because the size of self can be modified
58
+ [v.size, i.entries.size].min.times {|e| self[e + i.begin] = v[e]}
59
+ (v.size + i.begin .. i.end).each {|e| self[e] = 0}
60
+ else
61
+ @elements[i]=v
62
+ end
63
+ end
64
+
65
+ class << self
66
+ #
67
+ # Returns a concatenated Vector
68
+ #
69
+ def concat(*args)
70
+ v = []
71
+ args.each{|x| v += x.to_a}
72
+ Vector[*v]
73
+ end
74
+ end
75
+
76
+ #
77
+ # Changes the elements of vector and returns a Vector
78
+ #
79
+ def collect!
80
+ els = @elements.collect! {|v| yield(v)}
81
+ Vector.elements(els, false)
82
+ end
83
+
84
+ #
85
+ # Iterates the elements of a vector
86
+ #
87
+ def each
88
+ (0...size).each {|i| yield(self[i])}
89
+ nil
90
+ end
91
+
92
+ #
93
+ # Returns the maximum element of a vector
94
+ #
95
+ def max
96
+ to_a.max
97
+ end
98
+
99
+ #
100
+ # Returns the minimum element of a vector
101
+ #
102
+ def min
103
+ to_a.min
104
+ end
105
+
106
+ #
107
+ # Returns the p-norm of a vector
108
+ #
109
+ def norm(p = 2)
110
+ Norm.sqnorm(self, p) ** (Float(1)/p)
111
+ end
112
+
113
+ #
114
+ # Returns the infinite-norm
115
+ #
116
+ def norm_inf
117
+ [min.abs, max.abs].max
118
+ end
119
+
120
+ #
121
+ # Returns a slice of vector
122
+ #
123
+ def slice(*args)
124
+ Vector[*to_a.slice(*args)]
125
+ end
126
+
127
+ def slice_set(v, b, e)
128
+ for i in b..e
129
+ self[i] = v[i-b]
130
+ end
131
+ end
132
+
133
+ #
134
+ # Sets a slice of vector
135
+ #
136
+ def slice=(args)
137
+ case args[1]
138
+ when Range
139
+ slice_set(args[0], args[1].begin, args[1].last)
140
+ else
141
+ slice_set(args[0], args[1], args[2])
142
+ end
143
+ end
144
+
145
+ #
146
+ # Return the vector divided by a scalar
147
+ #
148
+ def /(c)
149
+ map {|e| e.quo(c)}
150
+ end
151
+
152
+ #
153
+ # Return the matrix column coresponding to the vector transpose
154
+ #
155
+ def transpose
156
+ Matrix[self.to_a]
157
+ end
158
+
159
+ alias :t :transpose
160
+
161
+ #
162
+ # Computes the Householder vector (MC, Golub, p. 210, algorithm 5.1.1)
163
+ #
164
+ def house
165
+ s = self[1..length-1]
166
+ sigma = s.inner_product(s)
167
+ v = clone; v[0] = 1
168
+ if sigma == 0
169
+ beta = 0
170
+ else
171
+ mu = Math.sqrt(self[0] ** 2 + sigma)
172
+ if self[0] <= 0
173
+ v[0] = self[0] - mu
174
+ else
175
+ v[0] = - sigma.quo(self[0] + mu)
176
+ end
177
+ v2 = v[0] ** 2
178
+ beta = 2 * v2.quo(sigma + v2)
179
+ v /= v[0]
180
+ end
181
+ return v, beta
182
+ end
183
+
184
+ #
185
+ #Projection operator
186
+ #(http://en.wikipedia.org/wiki/Gram-Schmidt_process#The_Gram.E2.80.93Schmidt_process)
187
+ #
188
+ def proj(v)
189
+ vp = v.inner_product(self)
190
+ vp = Float vp if vp.is_a?(Integer)
191
+ self * (vp / inner_product(self))
192
+ end
193
+
194
+ #
195
+ # Return the vector normalized
196
+ #
197
+ def normalize
198
+ self / self.norm
199
+ end
200
+
201
+ #
202
+ # Stabilized Gram-Schmidt process
203
+ # (http://en.wikipedia.org/wiki/Gram-Schmidt_process#Algorithm)
204
+ #
205
+ def Vector.gram_schmidt(*vectors)
206
+ v = vectors.clone
207
+ for j in 0...v.size
208
+ for i in 0..j-1
209
+ v[j] -= v[i] * v[j].inner_product(v[i])
210
+ end
211
+ v[j] /= v[j].norm
212
+ end
213
+ v
214
+ end
215
+ end
216
+
217
+ class Matrix
218
+ include Enumerable
219
+ public_class_method :new
220
+
221
+ attr_reader :rows, :wrap
222
+ @wrap = nil
223
+
224
+ def initialize(*argv)
225
+ return initialize_old(*argv) if argv[0].is_a?(Symbol)
226
+ n, m, val = argv; val = 0 if not val
227
+ f = (block_given?)? lambda {|i,j| yield(i, j)} : lambda {|i,j| val}
228
+ init_rows((0...n).collect {|i| (0...m).collect {|j| f.call(i,j)}}, true)
229
+ end
230
+
231
+ #
232
+ # For invoking a method
233
+ # in Ruby1.8 is working 'send' and
234
+ # in Ruby1.9 is working 'funcall'
235
+ #
236
+ def initialize_old(init_method, *argv)
237
+ if RUBY_VERSION < "1.9.0"
238
+ self.send(init_method, *argv) # in Ruby1.8
239
+ else
240
+ self.funcall(init_method, *argv) # in Ruby1.9
241
+ end
242
+ end
243
+
244
+ alias :ids :[]
245
+ #
246
+ # Return a value or a vector/matrix of values depending
247
+ # if the indexes are ranges or not
248
+ # m = Matrix.new(4, 3){|i, j| i * 3 + j}
249
+ # m: 0 1 2
250
+ # 3 4 5
251
+ # 6 7 8
252
+ # 9 10 11
253
+ # m[1, 2] => 5
254
+ # m[3,1..2] => Vector[10, 11]
255
+ # m[0..1, 0..2] => Matrix[[0, 1, 2], [3, 4, 5]]
256
+ #
257
+ def [](i, j)
258
+ case i
259
+ when Range
260
+ case j
261
+ when Range
262
+ Matrix[*i.collect{|l| self.row(l)[j].to_a}]
263
+ else
264
+ column(j)[i]
265
+ end
266
+ else
267
+ case j
268
+ when Range
269
+ row(i)[j]
270
+ else
271
+ ids(i, j)
272
+ end
273
+ end
274
+ end
275
+
276
+ #
277
+ # Set the values of a matrix
278
+ # m = Matrix.new(3, 3){|i, j| i * 3 + j}
279
+ # m: 0 1 2
280
+ # 3 4 5
281
+ # 6 7 8
282
+ # m[1, 2] = 9 => Matrix[[0, 1, 2], [3, 4, 9], [6, 7, 8]]
283
+ # m[2,1..2] = Vector[8, 8] => Matrix[[0, 1, 2], [3, 8, 8], [6, 7, 8]]
284
+ # m[0..1, 0..1] = Matrix[[0, 0, 0],[0, 0, 0]]
285
+ # => Matrix[[0, 0, 2], [0, 0, 8], [6, 7, 8]]
286
+ #
287
+ def []=(i, j, v)
288
+ case i
289
+ when Range
290
+ if i.entries.size == 1
291
+ self[i.begin, j] = (v.is_a?(Matrix) ? v.row(0) : v)
292
+ else
293
+ case j
294
+ when Range
295
+ if j.entries.size == 1
296
+ self[i, j.begin] = (v.is_a?(Matrix) ? v.column(0) : v)
297
+ else
298
+ i.each{|l| self.row= l, v.row(l - i.begin), j}
299
+ end
300
+ else
301
+ self.column= j, v, i
302
+ end
303
+ end
304
+ else
305
+ case j
306
+ when Range
307
+ if j.entries.size == 1
308
+ self[i, j.begin] = (v.is_a?(Vector) ? v[0] : v)
309
+ else
310
+ self.row= i, v, j
311
+ end
312
+ else
313
+ @rows[i][j] = (v.is_a?(Vector) ? v[0] : v)
314
+
315
+ end
316
+ end
317
+ end
318
+
319
+ #
320
+ # Return a clone matrix
321
+ #
322
+ def clone
323
+ super
324
+ end
325
+
326
+ def initialize_copy(orig)
327
+ init_rows(orig.rows, true)
328
+ self.wrap=(orig.wrap)
329
+ end
330
+
331
+
332
+ class << self
333
+ #
334
+ # Creates a matrix with the given matrices as diagonal blocks
335
+ #
336
+ def diag(*args)
337
+ dsize = 0
338
+ sizes = args.collect{|e| x = (e.is_a?(Matrix)) ? e.row_size : 1; dsize += x; x}
339
+ m = Matrix.zero(dsize)
340
+ count = 0
341
+
342
+ sizes.size.times{|i|
343
+ range = count..(count+sizes[i]-1)
344
+ m[range, range] = args[i]
345
+ count += sizes[i]
346
+ }
347
+ m
348
+ end
349
+
350
+ #
351
+ # Tests if all the elements of two matrix are equal in delta
352
+ #
353
+ def equal_in_delta?(m0, m1, delta = 1.0e-10)
354
+ delta = delta.abs
355
+ mapcar(m0, m1){|x, y| return false if (x < y - delta or x > y + delta) }
356
+ true
357
+ end
358
+
359
+ #
360
+ # Tests if all the diagonal elements of two matrix are equal in delta
361
+ #
362
+ def diag_in_delta?(m1, m0, eps = 1.0e-10)
363
+ n = m1.row_size
364
+ return false if n != m0.row_size or m1.column_size != m0.column_size
365
+ n.times{|i|
366
+ return false if (m1[i,i]-m0[i,i]).abs > eps
367
+ }
368
+ true
369
+ end
370
+ end
371
+
372
+ #
373
+ # Returns the matrix divided by a scalar
374
+ #
375
+ def quo(v)
376
+ map {|e| e.quo(v)}
377
+ end
378
+
379
+ #
380
+ # quo seems always desirable
381
+ #
382
+ alias :/ :quo
383
+
384
+ #
385
+ # Set de values of a matrix and the value of wrap property
386
+ #
387
+ def set(m)
388
+ 0.upto(m.row_size - 1) do |i|
389
+ 0.upto(m.column_size - 1) do |j|
390
+ self[i, j] = m[i, j]
391
+ end
392
+ end
393
+ self.wrap = m.wrap
394
+ end
395
+
396
+ def wraplate(ijwrap = "")
397
+ "class << self
398
+ def [](i, j)
399
+ #{ijwrap}; @rows[i][j]
400
+ end
401
+
402
+ def []=(i, j, v)
403
+ #{ijwrap}; @rows[i][j] = v
404
+ end
405
+ end"
406
+ end
407
+
408
+ #
409
+ # Set wrap feature of a matrix
410
+ #
411
+ def wrap=(mode = :torus)
412
+ case mode
413
+ when :torus then eval(wraplate("i %= row_size; j %= column_size"))
414
+ when :h_cylinder then eval(wraplate("i %= row_size"))
415
+ when :v_cylinder then eval(wraplate("j %= column_size"))
416
+ when :nil then eval(wraplate)
417
+ end
418
+ @wrap = mode
419
+ end
420
+
421
+ #
422
+ # Returns the maximum length of column elements
423
+ #
424
+ def max_len_column(j)
425
+ column_collect(j) {|x| x.to_s.length}.max
426
+ end
427
+
428
+ #
429
+ # Returns a list with the maximum lengths
430
+ #
431
+ def cols_len
432
+ (0...column_size).collect {|j| max_len_column(j)}
433
+ end
434
+
435
+ #
436
+ # Returns a string for nice printing matrix
437
+ #
438
+ def to_s(mode = :pretty, len_col = 3)
439
+ return super if empty?
440
+ if mode == :pretty
441
+ clen = cols_len
442
+ to_a.collect {|r| mapcar(r, clen) {|x, l| format("%#{l}s ",x.to_s)} << "\n"}.join("")
443
+ else
444
+ i = 0; s = ""; cs = column_size
445
+ each do |e|
446
+ i = (i + 1) % cs
447
+ s += format("%#{len_col}s ", e.to_s)
448
+ s += "\n" if i == 0
449
+ end
450
+ s
451
+ end
452
+ end
453
+
454
+ #
455
+ # Iterate the elements of a matrix
456
+ #
457
+ def each
458
+ @rows.each {|x| x.each {|e| yield(e)}}
459
+ nil
460
+ end
461
+
462
+ #
463
+ # a hided module of Matrix
464
+ module MMatrix
465
+ def MMatrix.default_block(block)
466
+ block ? lambda { |i| block.call(i) } : lambda {|i| i }
467
+ end
468
+
469
+ #
470
+ # Returns:
471
+ # 1) the index of row/column and
472
+ # 2) the values Vector for changing the row/column and
473
+ # 3) the range of changes
474
+ #
475
+ def MMatrix.id_vect_range(args, l)
476
+ i = args[0] # the column(/the row) to be change
477
+ vect = args[1] # the values vector
478
+
479
+ case args.size
480
+ when 3 then range = args[2] # the range of the elements to be change
481
+ when 4 then range = args[2]..args[3] #the range by borders
482
+ else range = 0...l
483
+ end
484
+ return i, vect, range
485
+ end
486
+
487
+ end
488
+
489
+ #
490
+ # Returns an array with the elements collected from the row "i".
491
+ # When a block is given, the elements of that vector are iterated.
492
+ #
493
+ def row_collect(i, &block)
494
+ f = MMatrix.default_block(block)
495
+ @rows[i].collect {|e| f.call(e)}
496
+ end
497
+
498
+ #
499
+ # Returns row vector number "i" like Matrix.row as a Vector.
500
+ # When the block is given, the elements of row "i" are modified
501
+ #
502
+ def row!(i)
503
+ if block_given?
504
+ @rows[i].collect! {|e| yield e }
505
+ else
506
+ Vector.elements(@rows[i], false)
507
+ end
508
+ end
509
+ alias :row_collect! :row!
510
+
511
+ #
512
+ # Returns an array with the elements collected from the column "j".
513
+ # When a block is given, the elements of that vector are iterated.
514
+ #
515
+ def column_collect(j, &block)
516
+ f = MMatrix.default_block(block)
517
+ (0...row_size).collect {|r| f.call(self[r, j])}
518
+ end
519
+
520
+ #
521
+ # Returns column vector number "j" as a Vector.
522
+ # When the block is given, the elements of column "j" are mmodified
523
+ #
524
+ def column!(j)
525
+ if block_given?
526
+ (0...row_size).collect { |i| @rows[i][j] = yield @rows[i][j] }
527
+ else
528
+ column(j)
529
+ end
530
+ end
531
+ alias :column_collect! :column!
532
+
533
+ #
534
+ # Set a certain column with the values of a Vector
535
+ # m = Matrix.new(3, 3){|i, j| i * 3 + j + 1}
536
+ # m.column= 1, Vector[1, 1, 1], 1..2
537
+ # m => 1 2 3
538
+ # 4 1 6
539
+ # 7 1 9
540
+ #
541
+ def column=(args)
542
+ m = row_size
543
+ c, v, r = MMatrix.id_vect_range(args, m)
544
+ (m..r.begin - 1).each{|i| self[i, c] = 0}
545
+ [v.size, r.entries.size].min.times{|i| self[i + r.begin, c] = v[i]}
546
+ ((v.size + r.begin)..r.entries.last).each {|i| self[i, c] = 0}
547
+ end
548
+
549
+ #
550
+ # Set a certain row with the values of a Vector
551
+ # m = Matrix.new(3, 3){|i, j| i * 3 + j + 1}
552
+ # m.row= 0, Vector[0, 0], 1..2
553
+ # m => 1 0 0
554
+ # 4 5 6
555
+ # 7 8 9
556
+ #
557
+ def row=(args)
558
+ i, val, range = MMatrix.id_vect_range(args, column_size)
559
+ row!(i)[range] = val
560
+ end
561
+
562
+ def norm(p = 2)
563
+ Vector::Norm.sqnorm(self, p) ** (Float(1)/p)
564
+ end
565
+
566
+ def norm_frobenius
567
+ norm
568
+ end
569
+ alias :normF :norm_frobenius
570
+
571
+ #
572
+ # Tests if the matrix is empty or not
573
+ #
574
+ def empty?
575
+ @rows.empty? if @rows
576
+ end
577
+
578
+ #
579
+ # Returns the row/s of matrix as a Matrix
580
+ #
581
+ def row2matrix(r)
582
+ a = self.send(:row, r).to_a
583
+ if r.is_a?(Range) and r.entries.size > 1
584
+ return Matrix[*a]
585
+ else
586
+ return Matrix[a]
587
+ end
588
+ end
589
+
590
+ #
591
+ # Returns the colomn/s of matrix as a Matrix
592
+ #
593
+ def column2matrix(c)
594
+ a = self.send(:column, c).to_a
595
+ if c.is_a?(Range) and c.entries.size > 1
596
+ return Matrix[*a]
597
+ else
598
+ return Matrix[*a.collect{|x| [x]}]
599
+ end
600
+ end
601
+
602
+ module LU
603
+ #
604
+ # Return the Gauss vector, MC, Golub, 3.2.1 Gauss Transformation, p94
605
+ #
606
+ def LU.gauss_vector(mat, k)
607
+ t = mat.column2matrix(k)
608
+ tk = t[k, 0]
609
+ (0..k).each{|i| t[i, 0] = 0}
610
+ return t if tk == 0
611
+ (k+1...mat.row_size).each{|i| t[i, 0] = t[i, 0].to_f / tk}
612
+ t
613
+ end
614
+
615
+ #
616
+ # Return the Gauss transformation matrix: M_k = I - tau * e_k^T
617
+ #
618
+ def LU.gauss(mat, k)
619
+ i = Matrix.I(mat.column_size)
620
+ tau = gauss_vector(mat, k)
621
+ e = i.row2matrix(k)
622
+ i - tau * e
623
+ end
624
+
625
+ #
626
+ # LU factorization: A = LU
627
+ # where L is lower triangular and U is upper triangular
628
+ #
629
+ def LU.factorization(mat)
630
+ u = mat.clone
631
+ n = u.column_size
632
+ i = Matrix.I(n)
633
+ l = i.clone
634
+ (n-1).times {|k|
635
+ mk = gauss(u, k)
636
+ u = mk * u # M_{n-1} * ... * M_1 * A = U
637
+ l += i - mk # L = M_1^{-1} * ... * M_{n-1}^{-1} = I + sum_{k=1}^{n-1} tau * e
638
+ }
639
+ return l, u
640
+ end
641
+ end
642
+
643
+ #
644
+ # Return the upper triangular matrix of LU factorization
645
+ # M_{n-1} * ... * M_1 * A = U
646
+ #
647
+ def U
648
+ LU.factorization(self)[1]
649
+ end
650
+
651
+ #
652
+ # Return the lower triangular matrix of LU factorization
653
+ # L = M_1^{-1} * ... * M_{n-1}^{-1} = I + sum_{k=1}^{n-1} tau * e
654
+ #
655
+ def L
656
+ LU.factorization(self)[0]
657
+ end
658
+
659
+ module Householder
660
+ #
661
+ # a QR factorization that uses Householder transformation
662
+ # Q^T * A = R
663
+ # MC, Golub & van Loan, pg 224, 5.2.1 Householder QR
664
+ #
665
+ def Householder.QR(mat)
666
+ h = []
667
+ a = mat.clone
668
+ m = a.row_size
669
+ n = a.column_size
670
+ n.times{|j|
671
+ v, beta = a[j..m - 1, j].house
672
+
673
+ h[j] = Matrix.diag(Matrix.I(j), Matrix.I(m-j)- beta * (v * v.t))
674
+
675
+ a[j..m-1, j..n-1] = (Matrix.I(m-j) - beta * (v * v.t)) * a[j..m-1, j..n-1]
676
+ a[(j+1)..m-1,j] = v[2..(m-j)] if j < m - 1 }
677
+ h
678
+ end
679
+
680
+ #
681
+ # From the essential part of Householder vector
682
+ # it returns the coresponding upper(U_j)/lower(V_j) matrix
683
+ #
684
+ def Householder.bidiagUV(essential, dim, beta)
685
+ v = Vector.concat(Vector[1], essential)
686
+ dimv = v.size
687
+ Matrix.diag(Matrix.I(dim - dimv), Matrix.I(dimv) - beta * (v * v.t) )
688
+ end
689
+
690
+ #
691
+ # Householder Bidiagonalization algorithm. MC, Golub, pg 252, Algorithm 5.4.2
692
+ # Returns the matrices U_B and V_B such that: U_B^T * A * V_B = B,
693
+ # where B is upper bidiagonal.
694
+ #
695
+ def Householder.bidiag(mat)
696
+ a = mat.clone
697
+ m = a.row_size
698
+ n = a.column_size
699
+ ub = Matrix.I(m)
700
+ vb = Matrix.I(n)
701
+ n.times{|j|
702
+ v, beta = a[j..m-1,j].house
703
+ a[j..m-1, j..n-1] = (Matrix.I(m-j) - beta * (v * v.t)) * a[j..m-1, j..n-1]
704
+ a[j+1..m-1, j] = v[1..(m-j-1)]
705
+ ub *= bidiagUV(a[j+1..m-1,j], m, beta) #Ub = U_1 * U_2 * ... * U_n
706
+ if j < n - 2
707
+ v, beta = (a[j, j+1..n-1]).house
708
+ a[j..m-1, j+1..n-1] = a[j..m-1, j+1..n-1] * (Matrix.I(n-j-1) - beta * (v * v.t))
709
+ a[j, j+2..n-1] = v[1..n-j-2]
710
+ vb *= bidiagUV(a[j, j+2..n-1], n, beta) #Vb = V_1 * U_2 * ... * V_n-2
711
+ end }
712
+ return ub, vb
713
+ end
714
+
715
+ #
716
+ #Householder Reduction to Hessenberg Form
717
+ #
718
+ def Householder.toHessenberg(mat)
719
+ h = mat.clone
720
+ n = h.row_size
721
+ u0 = Matrix.I(n)
722
+ for k in (0...n - 2)
723
+ v, beta = h[k+1..n-1, k].house #the householder matrice part
724
+ houseV = Matrix.I(n-k-1) - beta * (v * v.t)
725
+ u0 *= Matrix.diag(Matrix.I(k+1), houseV)
726
+ h[k+1..n-1, k..n-1] = houseV * h[k+1..n-1, k..n-1]
727
+ h[0..n-1, k+1..n-1] = h[0..n-1, k+1..n-1] * houseV
728
+ end
729
+ return h, u0
730
+ end
731
+
732
+
733
+ end #end of Householder module
734
+
735
+ #
736
+ # Returns the upper bidiagonal matrix obtained with Householder Bidiagonalization algorithm
737
+ #
738
+ def bidiagonal
739
+ ub, vb = Householder.bidiag(self)
740
+ ub.t * self * vb
741
+ end
742
+
743
+ #
744
+ # Returns the orthogonal matrix Q of Householder QR factorization
745
+ # where Q = H_1 * H_2 * H_3 * ... * H_n,
746
+ #
747
+ def houseQ
748
+ h = Householder.QR(self)
749
+ q = h[0]
750
+ (1...h.size).each{|i| q *= h[i]}
751
+ q
752
+ end
753
+
754
+ #
755
+ # Returns the matrix R of Householder QR factorization
756
+ # R = H_n * H_n-1 * ... * H_1 * A is an upper triangular matrix
757
+ #
758
+ def houseR
759
+ h = Householder.QR(self)
760
+ r = self.clone
761
+ h.size.times{|i| r = h[i] * r}
762
+ r
763
+ end
764
+
765
+ #
766
+ # Modified Gram Schmidt QR factorization (MC, Golub, p. 232)
767
+ # A = Q_1 * R_1
768
+ #
769
+ def gram_schmidt
770
+ a = clone
771
+ n = column_size
772
+ m = row_size
773
+ q = Matrix.new(m, n){0}
774
+ r = Matrix.zero(n)
775
+ for k in 0...n
776
+ r[k,k] = a[0...m, k].norm
777
+ q[0...m, k] = a[0...m, k] / r[k, k]
778
+ for j in (k+1)...n
779
+ r[k, j] = q[0...m, k].t * a[0...m, j]
780
+ a[0...m, j] -= q[0...m, k] * r[k, j]
781
+ end
782
+ end
783
+ return q, r
784
+ end
785
+
786
+ #
787
+ # Returns the Q_1 matrix of Modified Gram Schmidt algorithm
788
+ # Q_1 has orthonormal columns
789
+ #
790
+ def gram_schmidtQ
791
+ gram_schmidt[0]
792
+ end
793
+
794
+ #
795
+ # Returns the R_1 upper triangular matrix of Modified Gram Schmidt algorithm
796
+ #
797
+ def gram_schmidtR
798
+ gram_schmidt[1]
799
+ end
800
+
801
+
802
+ module Givens
803
+ #
804
+ # Returns the values "c and s" of a Given rotation
805
+ # MC, Golub, pg 216, Alghorithm 5.1.3
806
+ #
807
+ def Givens.givens(a, b)
808
+ if b == 0
809
+ c = 0; s = 0
810
+ else
811
+ if b.abs > a.abs
812
+ tau = Float(-a)/b; s = 1/Math.sqrt(1+tau**2); c = s * tau
813
+ else
814
+ tau = Float(-b)/a; c = 1/Math.sqrt(1+tau**2); s = c * tau
815
+ end
816
+ end
817
+ return c, s
818
+ end
819
+
820
+ #
821
+ # a QR factorization using Givens rotation
822
+ # Computes the upper triangular matrix R and the orthogonal matrix Q
823
+ # where Q^t A = R (MC, Golub, p227 algorithm 5.2.2)
824
+ #
825
+ def Givens.QR(mat)
826
+ r = mat.clone
827
+ m = r.row_size
828
+ n = r.column_size
829
+ q = Matrix.I(m)
830
+ n.times{|j|
831
+ m-1.downto(j+1){|i|
832
+ c, s = givens(r[i - 1, j], r[i, j])
833
+ qt = Matrix.I(m); qt[i-1..i, i-1..i] = Matrix[[c, s],[-s, c]]
834
+ q *= qt
835
+ r[i-1..i, j..n-1] = Matrix[[c, -s],[s, c]] * r[i-1..i, j..n-1]}}
836
+ return r, q
837
+ end
838
+
839
+ end
840
+
841
+ #
842
+ # Returns the upper triunghiular matrix R of a Givens QR factorization
843
+ #
844
+ def givensR
845
+ Givens.QR(self)[0]
846
+ end
847
+
848
+ #
849
+ # Returns the orthogonal matrix Q of Givens QR factorization.
850
+ # Q = G_1 * ... * G_t where G_j is the j'th Givens rotation
851
+ # and 't' is the total number of rotations
852
+ #
853
+ def givensQ
854
+ Givens.QR(self)[1]
855
+ end
856
+
857
+ module Hessenberg
858
+ #
859
+ # the matrix must be an upper R^(n x n) Hessenberg matrix
860
+ #
861
+ def Hessenberg.QR(mat)
862
+ r = mat.clone
863
+ n = r.row_size
864
+ q = Matrix.I(n)
865
+ for j in (0...n-1)
866
+ c, s = Givens.givens(r[j,j], r[j+1, j])
867
+ cs = Matrix[[c, s], [-s, c]]
868
+ q *= Matrix.diag(Matrix.I(j), cs, Matrix.I(n - j - 2))
869
+ r[j..j+1, j..n-1] = cs.t * r[j..j+1, j..n-1]
870
+ end
871
+ return q, r
872
+ end
873
+ end
874
+
875
+ #
876
+ # Returns the orthogonal matrix Q of Hessenberg QR factorization
877
+ # Q = G_1 *...* G_(n-1) where G_j is the Givens rotation G_j = G(j, j+1, omega_j)
878
+ #
879
+ def hessenbergQ
880
+ Hessenberg.QR(self)[0]
881
+ end
882
+
883
+ #
884
+ # Returns the upper triunghiular matrix R of a Hessenberg QR factorization
885
+ #
886
+ def hessenbergR
887
+ Hessenberg.QR(self)[1]
888
+ end
889
+
890
+ #
891
+ # Return an upper Hessenberg matrix obtained with Householder reduction to Hessenberg Form algorithm
892
+ #
893
+ def hessenberg_form_H
894
+ Householder.toHessenberg(self)[0]
895
+ end
896
+
897
+ #
898
+ # The real Schur decomposition.
899
+ # The eigenvalues are aproximated in diagonal elements of the real Schur decomposition matrix
900
+ #
901
+ def realSchur(eps = 1.0e-10, steps = 100)
902
+ h = self.hessenberg_form_H
903
+ h1 = Matrix[]
904
+ i = 0
905
+ loop do
906
+ h1 = h.hessenbergR * h.hessenbergQ
907
+ break if Matrix.diag_in_delta?(h1, h, eps) or steps <= 0
908
+ h = h1.clone
909
+ steps -= 1
910
+ i += 1
911
+ end
912
+ h1
913
+ end
914
+
915
+
916
+ module Jacobi
917
+ #
918
+ # Returns the nurm of the off-diagonal element
919
+ #
920
+ def Jacobi.off(a)
921
+ n = a.row_size
922
+ sum = 0
923
+ n.times{|i| n.times{|j| sum += a[i, j]**2 if j != i}}
924
+ Math.sqrt(sum)
925
+ end
926
+
927
+ #
928
+ # Returns the index pair (p, q) with 1<= p < q <= n and A[p, q] is the maximum in absolute value
929
+ #
930
+ def Jacobi.max(a)
931
+ n = a.row_size
932
+ max = 0
933
+ p = 0
934
+ q = 0
935
+ n.times{|i|
936
+ ((i+1)...n).each{|j|
937
+ val = a[i, j].abs
938
+ if val > max
939
+ max = val
940
+ p = i
941
+ q = j
942
+ end }}
943
+ return p, q
944
+ end
945
+
946
+ #
947
+ # Compute the cosine-sine pair (c, s) for the element A[p, q]
948
+ #
949
+ def Jacobi.sym_schur2(a, p, q)
950
+ if a[p, q] != 0
951
+ tau = Float(a[q, q] - a[p, p])/(2 * a[p, q])
952
+ if tau >= 0
953
+ t = 1./(tau + Math.sqrt(1 + tau ** 2))
954
+ else
955
+ t = -1./(-tau + Math.sqrt(1 + tau ** 2))
956
+ end
957
+ c = 1./Math.sqrt(1 + t ** 2)
958
+ s = t * c
959
+ else
960
+ c = 1
961
+ s = 0
962
+ end
963
+ return c, s
964
+ end
965
+
966
+ #
967
+ # Returns the Jacobi rotation matrix
968
+ #
969
+ def Jacobi.J(p, q, c, s, n)
970
+ j = Matrix.I(n)
971
+ j[p,p] = c; j[p, q] = s
972
+ j[q,p] = -s; j[q, q] = c
973
+ j
974
+ end
975
+ end
976
+
977
+ #
978
+ # Classical Jacobi 8.4.3 Golub & van Loan
979
+ #
980
+ def cJacobi(tol = 1.0e-10)
981
+ a = self.clone
982
+ n = row_size
983
+ v = Matrix.I(n)
984
+ eps = tol * a.normF
985
+ while Jacobi.off(a) > eps
986
+ p, q = Jacobi.max(a)
987
+ c, s = Jacobi.sym_schur2(a, p, q)
988
+ #print "\np:#{p} q:#{q} c:#{c} s:#{s}\n"
989
+ j = Jacobi.J(p, q, c, s, n)
990
+ a = j.t * a * j
991
+ v = v * j
992
+ end
993
+ return a, v
994
+ end
995
+
996
+ #
997
+ # Returns the aproximation matrix computed with Classical Jacobi algorithm.
998
+ # The aproximate eigenvalues values are in the diagonal of the matrix A.
999
+ #
1000
+ def cJacobiA(tol = 1.0e-10)
1001
+ cJacobi(tol)[0]
1002
+ end
1003
+
1004
+ #
1005
+ # Returns a Vector with the eigenvalues aproximated values.
1006
+ # The eigenvalues are computed with the Classic Jacobi Algorithm.
1007
+ #
1008
+ def eigenvaluesJacobi
1009
+ a = cJacobiA
1010
+ Vector[*(0...row_size).collect{|i| a[i, i]}]
1011
+ end
1012
+
1013
+ #
1014
+ # Returns the orthogonal matrix obtained with the Jacobi eigenvalue algorithm.
1015
+ # The columns of V are the eigenvector.
1016
+ #
1017
+ def cJacobiV(tol = 1.0e-10)
1018
+ cJacobi(tol)[1]
1019
+ end
1020
+ end
1021
+
1022
+