stick 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+