rmds 0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.md +27 -0
- data/README.md +157 -0
- data/Rakefile +77 -0
- data/examples/adaptive_backend.rb +61 -0
- data/examples/extended_metric.rb +82 -0
- data/examples/minimal_metric.rb +52 -0
- data/examples/visualization.rb +75 -0
- data/examples/visualization_cities.rb +84 -0
- data/lib/mds.rb +22 -0
- data/lib/mds/backend.rb +105 -0
- data/lib/mds/interfaces/gsl_interface.rb +140 -0
- data/lib/mds/interfaces/linalg_interface.rb +149 -0
- data/lib/mds/interfaces/stdlib_interface.rb +155 -0
- data/lib/mds/io.rb +29 -0
- data/lib/mds/matrix.rb +258 -0
- data/lib/mds/matrix_interface.rb +358 -0
- data/lib/mds/metric.rb +150 -0
- data/lib/mds/test/bundles/bundle_matrix_interface.rb +199 -0
- data/lib/mds/test/bundles/bundle_metric.rb +82 -0
- data/lib/mds/test/matrix_assertions.rb +54 -0
- data/test/benchmark/benchmark_metric.rb +53 -0
- data/test/test_helper.rb +11 -0
- data/test/unit/dummy_interfaces.rb +20 -0
- data/test/unit/test_backend.rb +44 -0
- data/test/unit/test_gsl_interface.rb +22 -0
- data/test/unit/test_gsl_metric.rb +22 -0
- data/test/unit/test_linalg_interface.rb +22 -0
- data/test/unit/test_linalg_metric.rb +21 -0
- data/test/unit/test_stdlib_interface.rb +22 -0
- data/test/unit/test_stdlib_metric.rb +21 -0
- metadata +101 -0
@@ -0,0 +1,155 @@
|
|
1
|
+
#
|
2
|
+
# RMDS - Ruby Multidimensional Scaling Library
|
3
|
+
# Copyright (c) Christoph Heindl, 2010
|
4
|
+
# http://github.com/cheind/rmds
|
5
|
+
#
|
6
|
+
|
7
|
+
require 'mds/matrix_interface'
|
8
|
+
|
9
|
+
begin
|
10
|
+
require 'rubygems'
|
11
|
+
require 'extendmatrix'
|
12
|
+
rescue LoadError
|
13
|
+
warn "\n**Notice: RubyAdapter requires \'extendmatrix\' which was not found."
|
14
|
+
warn "You can install the extension as follows: \n gem install extendmatrix \n"
|
15
|
+
raise $!
|
16
|
+
end
|
17
|
+
|
18
|
+
module MDS
|
19
|
+
|
20
|
+
#
|
21
|
+
# Matrix interface for Ruby's standard library Matrix class.
|
22
|
+
#
|
23
|
+
# To succesfully use this interface 'extendmatrix' is required.
|
24
|
+
# For more information and installation instructions see
|
25
|
+
# http://github.com/clbustos/extendmatrix
|
26
|
+
#
|
27
|
+
# The algorithm/implementation of the eigen-decomposition is
|
28
|
+
# sub-optimal. It is therefore suitable only for small-scale problems.
|
29
|
+
# Timings are illustrated in the benchmark section of the {file:README}.
|
30
|
+
#
|
31
|
+
# The reason this interface is still included in RMDS is the fact
|
32
|
+
# that it only depends on Ruby code and no native extensions.
|
33
|
+
#
|
34
|
+
# Compatible with 'extendmatrix >= 0.3.1'
|
35
|
+
#
|
36
|
+
class StdlibInterface < MatrixInterface
|
37
|
+
|
38
|
+
#
|
39
|
+
# Create a new matrix with equal elements.
|
40
|
+
#
|
41
|
+
# @param (see MatrixInterface#create)
|
42
|
+
# @return (see MatrixInterface#create)
|
43
|
+
#
|
44
|
+
def StdlibInterface.create(n, m, s)
|
45
|
+
::Matrix.build(n, m, s)
|
46
|
+
end
|
47
|
+
|
48
|
+
#
|
49
|
+
# Return the number of matrix rows
|
50
|
+
#
|
51
|
+
# @param (see MatrixInterface#nrows)
|
52
|
+
# @return (see MatrixInterface#nrows)
|
53
|
+
#
|
54
|
+
def StdlibInterface.nrows(m)
|
55
|
+
m.row_size
|
56
|
+
end
|
57
|
+
|
58
|
+
#
|
59
|
+
# Return the number of matrix columns
|
60
|
+
#
|
61
|
+
# @param (see MatrixInterface#ncols)
|
62
|
+
# @return (see MatrixInterface#ncols)
|
63
|
+
#
|
64
|
+
def StdlibInterface.ncols(m)
|
65
|
+
m.column_size
|
66
|
+
end
|
67
|
+
|
68
|
+
#
|
69
|
+
# Set matrix element.
|
70
|
+
#
|
71
|
+
# @param (see MatrixInterface#set)
|
72
|
+
#
|
73
|
+
def StdlibInterface.set(m, i, j, s)
|
74
|
+
m[i,j] = s
|
75
|
+
end
|
76
|
+
|
77
|
+
#
|
78
|
+
# Get matrix element.
|
79
|
+
#
|
80
|
+
# @param (see MatrixInterface#get)
|
81
|
+
# @return (see MatrixInterface#get)
|
82
|
+
#
|
83
|
+
def StdlibInterface.get(m, i, j)
|
84
|
+
m[i,j]
|
85
|
+
end
|
86
|
+
|
87
|
+
#
|
88
|
+
# Calculate the product of two matrices or
|
89
|
+
# the product of a matrix and a scalar.
|
90
|
+
#
|
91
|
+
# @param (see MatrixInterface#prod)
|
92
|
+
# @return (see MatrixInterface#prod)
|
93
|
+
#
|
94
|
+
def StdlibInterface.prod(m, n)
|
95
|
+
m * n
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
# Transpose a matrix.
|
100
|
+
#
|
101
|
+
# @param (see MatrixInterface#t)
|
102
|
+
# @return (see MatrixInterface#t)
|
103
|
+
#
|
104
|
+
def StdlibInterface.t(m)
|
105
|
+
m.t
|
106
|
+
end
|
107
|
+
|
108
|
+
#
|
109
|
+
# Componentwise addition of two matrices.
|
110
|
+
#
|
111
|
+
# @param (see MatrixInterface#add)
|
112
|
+
# @return (see MatrixInterface#add)
|
113
|
+
#
|
114
|
+
def StdlibInterface.add(m, n)
|
115
|
+
m + n
|
116
|
+
end
|
117
|
+
|
118
|
+
#
|
119
|
+
# Componentwise subtraction of two matrices.
|
120
|
+
#
|
121
|
+
# @param (see MatrixAdapter#sub)
|
122
|
+
# @return (see MatrixAdapter#sub)
|
123
|
+
#
|
124
|
+
def StdlibInterface.sub(m, n)
|
125
|
+
m - n
|
126
|
+
end
|
127
|
+
|
128
|
+
#
|
129
|
+
# Compute the eigen-decomposition of a real symmetric matrix.
|
130
|
+
#
|
131
|
+
# The Ruby version uses Jacobi iterations to calculate the
|
132
|
+
# eigen-decomposition of a matrix. Although comfortable as all
|
133
|
+
# third-party dependencies can be installed via gem, the method is
|
134
|
+
# not suited for matrices bigger than 10x10.
|
135
|
+
#
|
136
|
+
# @param (see MatrixInterface#ed)
|
137
|
+
# @return (see MatrixInterface#ed)
|
138
|
+
#
|
139
|
+
def StdlibInterface.ed(m)
|
140
|
+
eigen_values = m.cJacobiA
|
141
|
+
eigen_vectors = m.cJacobiV
|
142
|
+
|
143
|
+
ranks = (0..(m.row_size-1)).sort{|i,j| eigen_values[j,j] <=> eigen_values[i,i]}
|
144
|
+
|
145
|
+
s_eigen_values = []
|
146
|
+
s_eigen_vectors = []
|
147
|
+
ranks.each do |r|
|
148
|
+
s_eigen_values << eigen_values[r,r]
|
149
|
+
s_eigen_vectors << eigen_vectors.column(r)
|
150
|
+
end
|
151
|
+
[::Matrix.diagonal(*s_eigen_values), ::Matrix.columns(s_eigen_vectors)]
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
end
|
data/lib/mds/io.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#
|
2
|
+
# RMDS - Ruby Multidimensional Scaling Library
|
3
|
+
# Copyright (c) Christoph Heindl, 2010
|
4
|
+
# http://github.com/cheind/rmds
|
5
|
+
#
|
6
|
+
|
7
|
+
module MDS
|
8
|
+
module IO
|
9
|
+
|
10
|
+
#
|
11
|
+
# Read matrix from CSV file
|
12
|
+
# Each feature corresponds to a single row.
|
13
|
+
# All rows are assumed to have an equal column count.
|
14
|
+
#
|
15
|
+
def IO.read_csv(path, sep = ' ')
|
16
|
+
rows = []
|
17
|
+
File.foreach(path) do |line|
|
18
|
+
cells = line.chop.split(sep)
|
19
|
+
if block_given?
|
20
|
+
rows << cells.map{|c| yield c}.compact
|
21
|
+
else
|
22
|
+
rows << cells
|
23
|
+
end
|
24
|
+
end
|
25
|
+
rows
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
data/lib/mds/matrix.rb
ADDED
@@ -0,0 +1,258 @@
|
|
1
|
+
#
|
2
|
+
# RMDS - Ruby Multidimensional Scaling Library
|
3
|
+
# Copyright (c) Christoph Heindl, 2010
|
4
|
+
# http://github.com/cheind/rmds
|
5
|
+
#
|
6
|
+
|
7
|
+
module MDS
|
8
|
+
|
9
|
+
#
|
10
|
+
# A matrix adapter.
|
11
|
+
#
|
12
|
+
# RMDS does not implement matrices or linear algebra routines itself,
|
13
|
+
# instead RMDS offers a non-intrusive adapter architecture to interop
|
14
|
+
# with third party linear algebra packages.
|
15
|
+
#
|
16
|
+
# The RMDS adapter architecture consists of two layers
|
17
|
+
#
|
18
|
+
# * {MDS::MatrixInterface} defines a minimal common interface of
|
19
|
+
# required interop methods.
|
20
|
+
# * {MDS::Matrix} is a matrix adapter that binds to data provided by
|
21
|
+
# a specialized {MDS::MatrixInterface} class and carries out all
|
22
|
+
# computations through methods defined in {MDS::MatrixInterface}.
|
23
|
+
#
|
24
|
+
# @see MDS::MatrixInterface
|
25
|
+
#
|
26
|
+
class Matrix
|
27
|
+
# Wrapped matrix
|
28
|
+
attr_reader :m
|
29
|
+
alias :matrix :m
|
30
|
+
|
31
|
+
#
|
32
|
+
# Initialize from matrix instance and interface class.
|
33
|
+
#
|
34
|
+
# @param matrix the matrix to be wrapped
|
35
|
+
#
|
36
|
+
def initialize(matrix)
|
37
|
+
@m = matrix
|
38
|
+
end
|
39
|
+
|
40
|
+
#
|
41
|
+
# Create a new matrix setting all elements to equal values.
|
42
|
+
#
|
43
|
+
# @param (see MatrixInterface#create)
|
44
|
+
# @return [Matrix] the newly created matrix.
|
45
|
+
#
|
46
|
+
def Matrix.create(n, m, s)
|
47
|
+
Matrix.new(Backend.active.create(n, m, s))
|
48
|
+
end
|
49
|
+
|
50
|
+
#
|
51
|
+
# Create a new matrix and assign each element as the
|
52
|
+
# result of invoking the given block.
|
53
|
+
#
|
54
|
+
# @param (see MatrixInterface#create_block)
|
55
|
+
# @return [Matrix] the newly created matrix.
|
56
|
+
#
|
57
|
+
def Matrix.create_block(n, m, &block)
|
58
|
+
Matrix.new(Backend.active.create_block(n, m, &block))
|
59
|
+
end
|
60
|
+
|
61
|
+
#
|
62
|
+
# Create a new matrix from uniform random distribution.
|
63
|
+
#
|
64
|
+
# @param (see MatrixInterface#create_random)
|
65
|
+
# @return [Matrix] the newly created matrix.
|
66
|
+
#
|
67
|
+
def Matrix.create_random(n, m, smin = -1.0, smax = 1.0)
|
68
|
+
Matrix.new(Backend.active.create_random(n, m, smin, smax))
|
69
|
+
end
|
70
|
+
|
71
|
+
#
|
72
|
+
# Create a new identity matrix.
|
73
|
+
#
|
74
|
+
# @param (see MatrixInterface#create_identity)
|
75
|
+
# @return [Matrix] the newly created matrix.
|
76
|
+
#
|
77
|
+
def Matrix.create_identity(n)
|
78
|
+
Matrix.new(Backend.active.create_identity(n))
|
79
|
+
end
|
80
|
+
|
81
|
+
#
|
82
|
+
# Create a new diagonal matrix.
|
83
|
+
#
|
84
|
+
# @param (see MatrixInterface#create_diagonal)
|
85
|
+
# @return [Matrix] the newly created matrix.
|
86
|
+
#
|
87
|
+
def Matrix.create_diagonal(*elements)
|
88
|
+
Matrix.new(Backend.active.create_diagonal(*elements))
|
89
|
+
end
|
90
|
+
|
91
|
+
#
|
92
|
+
# Create matrix from rows.
|
93
|
+
#
|
94
|
+
# @param (see MatrixInterface#create_rows)
|
95
|
+
# @return [Matrix] the newly created matrix.
|
96
|
+
#
|
97
|
+
def Matrix.create_rows(*rows)
|
98
|
+
Matrix.new(Backend.active.create_rows(*rows))
|
99
|
+
end
|
100
|
+
|
101
|
+
#
|
102
|
+
# Return the number of matrix rows
|
103
|
+
#
|
104
|
+
# @return number of rows in matrix
|
105
|
+
#
|
106
|
+
def nrows
|
107
|
+
Backend.active.nrows(@m)
|
108
|
+
end
|
109
|
+
|
110
|
+
#
|
111
|
+
# Return the number of matrix columns
|
112
|
+
#
|
113
|
+
# @return number of columns in matrix
|
114
|
+
#
|
115
|
+
def ncols
|
116
|
+
Backend.active.ncols(@m)
|
117
|
+
end
|
118
|
+
|
119
|
+
#
|
120
|
+
# Set value of matrix element.
|
121
|
+
#
|
122
|
+
# @param [Integer] i the i-th row, zero-based indexing
|
123
|
+
# @param [Integer] j the j-th column, zero-based indexing
|
124
|
+
# @param [Float] s scalar value to set
|
125
|
+
#
|
126
|
+
def []=(i, j, s)
|
127
|
+
Backend.active.set(@m, i, j, s)
|
128
|
+
end
|
129
|
+
|
130
|
+
#
|
131
|
+
# Get value of matrix element.
|
132
|
+
#
|
133
|
+
# @param [Integer] i the i-th row, zero-based indexing
|
134
|
+
# @param [Integer] j the j-th column, zero-based indexing
|
135
|
+
# @return [Float] value of element
|
136
|
+
#
|
137
|
+
def [](i,j)
|
138
|
+
Backend.active.get(@m, i, j)
|
139
|
+
end
|
140
|
+
|
141
|
+
#
|
142
|
+
# Calculate the product of two matrices or
|
143
|
+
# the product of a matrix and a scalar.
|
144
|
+
#
|
145
|
+
# @param [Matrix, Float] other matrix to multiply with, or scalar
|
146
|
+
# @return [Matrix] the matrix.
|
147
|
+
#
|
148
|
+
def *(other)
|
149
|
+
is_ma = other.instance_of?(Matrix)
|
150
|
+
Matrix.new(
|
151
|
+
Backend.active.prod(@m, is_ma ? other.matrix : other)
|
152
|
+
)
|
153
|
+
end
|
154
|
+
|
155
|
+
#
|
156
|
+
# Componentwise addition of two matrices.
|
157
|
+
#
|
158
|
+
# @param [Matrix] other matrix to add to this matrix
|
159
|
+
# @return [Matrix] the matrix.
|
160
|
+
#
|
161
|
+
def +(other)
|
162
|
+
Matrix.new(Backend.active.add(@m, other.m))
|
163
|
+
end
|
164
|
+
|
165
|
+
#
|
166
|
+
# Componentwise subtraction of two matrices.
|
167
|
+
#
|
168
|
+
# @param [Matrix] other matrix to subtract from this matrix.
|
169
|
+
# @return [Matrix] the matrix.
|
170
|
+
#
|
171
|
+
def -(other)
|
172
|
+
Matrix.new(Backend.active.sub(@m, other.m))
|
173
|
+
end
|
174
|
+
|
175
|
+
#
|
176
|
+
# Transpose a matrix.
|
177
|
+
#
|
178
|
+
# @return [Matrix] the transposed matrix.
|
179
|
+
#
|
180
|
+
def t
|
181
|
+
Matrix.new(Backend.active.t(@m))
|
182
|
+
end
|
183
|
+
|
184
|
+
#
|
185
|
+
# Compute the eigen-decomposition of a real symmetric matrix.
|
186
|
+
#
|
187
|
+
# The eigen-decomposition consists of a diagonal matrix +D+ containing
|
188
|
+
# the eigen values and a square matrix +N+ having the normalized eigenvectors
|
189
|
+
# in columns.
|
190
|
+
#
|
191
|
+
# @return [Array] the array containing the matrix of eigen-values and eigen-vector
|
192
|
+
#
|
193
|
+
def ed
|
194
|
+
Backend.active.ed(@m).map {|m| Matrix.new(m) }
|
195
|
+
end
|
196
|
+
|
197
|
+
#
|
198
|
+
# Retrieve the diagonal elements as an array.
|
199
|
+
#
|
200
|
+
# @return [Array] diagonal elements as array.
|
201
|
+
#
|
202
|
+
def diagonals
|
203
|
+
Backend.active.diagonals(@m)
|
204
|
+
end
|
205
|
+
|
206
|
+
#
|
207
|
+
# Calculate the sum of diagonal matrix elements.
|
208
|
+
#
|
209
|
+
# @return [Float] trace of matrix
|
210
|
+
#
|
211
|
+
def trace
|
212
|
+
Backend.active.trace(@m)
|
213
|
+
end
|
214
|
+
|
215
|
+
#
|
216
|
+
# Calculate minor matrix.
|
217
|
+
#
|
218
|
+
# @param [Range] row_range row range
|
219
|
+
# @param [Range] col_range column range
|
220
|
+
# @return [Matrix] minor matrix
|
221
|
+
#
|
222
|
+
def minor(row_range, col_range)
|
223
|
+
Matrix.new(
|
224
|
+
Backend.active.minor(@m, row_range, col_range)
|
225
|
+
)
|
226
|
+
end
|
227
|
+
|
228
|
+
#
|
229
|
+
# Returns matrix as array of columns.
|
230
|
+
#
|
231
|
+
# @return [Array<Array>] the array of columns where each column is an array
|
232
|
+
#
|
233
|
+
def columns
|
234
|
+
Backend.active.columns(@m)
|
235
|
+
end
|
236
|
+
|
237
|
+
#
|
238
|
+
# Returns matrix as array of rows.
|
239
|
+
#
|
240
|
+
# @return [Array<Array>] the array of rows where each row is an array
|
241
|
+
#
|
242
|
+
def rows
|
243
|
+
Backend.active.rows(@m)
|
244
|
+
end
|
245
|
+
|
246
|
+
#
|
247
|
+
# Convert to string.
|
248
|
+
#
|
249
|
+
# Invokes #to_s from wrapped matrix.
|
250
|
+
#
|
251
|
+
# @return wrapped matrix as string.
|
252
|
+
#
|
253
|
+
def to_s
|
254
|
+
Backend.active.to_s(@m)
|
255
|
+
end
|
256
|
+
|
257
|
+
end
|
258
|
+
end
|
@@ -0,0 +1,358 @@
|
|
1
|
+
#
|
2
|
+
# RMDS - Ruby Multidimensional Scaling Library
|
3
|
+
# Copyright (c) Christoph Heindl, 2010
|
4
|
+
# http://github.com/cheind/rmds
|
5
|
+
#
|
6
|
+
|
7
|
+
module MDS
|
8
|
+
|
9
|
+
#
|
10
|
+
# Provides a common interface to matrix operations.
|
11
|
+
#
|
12
|
+
# RMDS does not implement any linear algebra routine itself,
|
13
|
+
# but rather provides a non intrusive mechanism to plugin third party
|
14
|
+
# linear algebra packages. {MDS::MatrixInterface} defines a minimal
|
15
|
+
# set of required methods to be implemented for any linear algebra packages
|
16
|
+
# which are to be used with RMDS.
|
17
|
+
#
|
18
|
+
# Making linear algebra backends compatible with RMDS is easy:
|
19
|
+
# simply subclass from {MDS::MatrixInterface} and implement all abstract
|
20
|
+
# methods. Not all of {MDS::MatrixInterface} methods are abstract, some
|
21
|
+
# come with a default implementation. If your linear algebra package
|
22
|
+
# does better at some of those methods, you should override them in your
|
23
|
+
# interface subclass.
|
24
|
+
#
|
25
|
+
# RMDS helps you in testing your matrix interfaces through test bundles
|
26
|
+
# that ship with RMDS. Each test bundle contains a set of tests that work
|
27
|
+
# independently of the matrix interface chosen. The following file unit
|
28
|
+
# tests a matrix interface.
|
29
|
+
#
|
30
|
+
# require 'test/unit'
|
31
|
+
# require 'mds/test/bundles/bundle_matrix_interface.rb'
|
32
|
+
# require 'mds/interfaces/linalg_interface'
|
33
|
+
#
|
34
|
+
# class TestLinalgInteface < Test::Unit::TestCase
|
35
|
+
# include MDS::Test::BundleMatrixInterface
|
36
|
+
#
|
37
|
+
# def setup
|
38
|
+
# MDS::Backend.push_active(MDS::LinalgInterface)
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# def teardown
|
42
|
+
# MDS::Backend.pop_active
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# Finally, tell RMDS to use your matrix interface by setting the
|
48
|
+
# default matrix interface, like so
|
49
|
+
#
|
50
|
+
# # Set active interface
|
51
|
+
# MDS::Backend.active = YourMatrixInterface
|
52
|
+
#
|
53
|
+
# # Push onto interface stack and set active interface
|
54
|
+
# MDS::Backend.push_active(YourMatrixInterface)
|
55
|
+
#
|
56
|
+
# # Restore the previously active interface
|
57
|
+
# MDS::Backend.pop_active
|
58
|
+
#
|
59
|
+
# @see MDS::Matrix, MDS::Backend
|
60
|
+
class MatrixInterface
|
61
|
+
|
62
|
+
#
|
63
|
+
# Record creation of a subclass of this class at the MDS::Backend
|
64
|
+
#
|
65
|
+
def MatrixInterface.inherited(subclass)
|
66
|
+
MDS::Backend.add(subclass)
|
67
|
+
end
|
68
|
+
|
69
|
+
#---------------------------------------
|
70
|
+
# Required
|
71
|
+
# Abstract methods
|
72
|
+
#---------------------------------------
|
73
|
+
|
74
|
+
#
|
75
|
+
# Create a new matrix having all elements equal values.
|
76
|
+
#
|
77
|
+
# @param [Integer] n the number of rows
|
78
|
+
# @param [Integer] m the number of columns
|
79
|
+
# @param [Float] s the scalar value of each matrix component
|
80
|
+
# @return the newly created matrix.
|
81
|
+
# @abstract
|
82
|
+
#
|
83
|
+
def MatrixInterface.create(n, m, s)
|
84
|
+
raise NotImplementedError
|
85
|
+
end
|
86
|
+
|
87
|
+
#
|
88
|
+
# Return the number of matrix rows
|
89
|
+
#
|
90
|
+
# @param m the matrix
|
91
|
+
# @return number of rows in matrix
|
92
|
+
# @abstract
|
93
|
+
#
|
94
|
+
def MatrixInterface.nrows(m)
|
95
|
+
raise NotImplementedError
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
# Return the number of matrix columns
|
100
|
+
#
|
101
|
+
# @param m the matrix
|
102
|
+
# @return number of columns in matrix
|
103
|
+
# @abstract
|
104
|
+
#
|
105
|
+
def MatrixInterface.ncols(m)
|
106
|
+
raise NotImplementedError
|
107
|
+
end
|
108
|
+
|
109
|
+
#
|
110
|
+
# Set value of matrix element.
|
111
|
+
#
|
112
|
+
# @param m the matrix
|
113
|
+
# @param [Integer] i the i-th row, zero-based indexing
|
114
|
+
# @param [Integer] j the j-th column, zero-based indexing
|
115
|
+
# @param [Float] s scalar value to set
|
116
|
+
# @abstract
|
117
|
+
def MatrixInterface.set(m, i, j, s)
|
118
|
+
raise NotImplementedError
|
119
|
+
end
|
120
|
+
|
121
|
+
#
|
122
|
+
# Get value of matrix element.
|
123
|
+
#
|
124
|
+
# @param m the matrix
|
125
|
+
# @param [Integer] i the i-th row, zero-based indexing
|
126
|
+
# @param [Integer] j the j-th column, zero-based indexing
|
127
|
+
# @return [Float] value of element
|
128
|
+
# @abstract
|
129
|
+
#
|
130
|
+
def MatrixInterface.get(m, i, j)
|
131
|
+
raise NotImplementedError
|
132
|
+
end
|
133
|
+
|
134
|
+
#
|
135
|
+
# Calculate the product of two matrices or
|
136
|
+
# the product of a matrix and a scalar.
|
137
|
+
#
|
138
|
+
# @param m first matrix
|
139
|
+
# @param n second matrix or scalar
|
140
|
+
# @return the matrix product as newly allocated matrix
|
141
|
+
# @abstract
|
142
|
+
#
|
143
|
+
def MatrixInterface.prod(m, n)
|
144
|
+
raise NotImplementedError
|
145
|
+
end
|
146
|
+
|
147
|
+
#
|
148
|
+
# Componentwise addition of two matrices.
|
149
|
+
#
|
150
|
+
# @param m first matrix
|
151
|
+
# @param n second matrix
|
152
|
+
# @return the matrix addition as newly allocated matrix
|
153
|
+
# @abstract
|
154
|
+
def MatrixInterface.add(m, n)
|
155
|
+
raise NotImplementedError
|
156
|
+
end
|
157
|
+
|
158
|
+
#
|
159
|
+
# Componentwise subtraction of two matrices.
|
160
|
+
#
|
161
|
+
# @param m first matrix
|
162
|
+
# @param n second matrix
|
163
|
+
# @return the matrix subtraction as newly allocated matrix
|
164
|
+
# @abstract
|
165
|
+
#
|
166
|
+
def MatrixInterface.sub(m, n)
|
167
|
+
raise NotImplementedError
|
168
|
+
end
|
169
|
+
|
170
|
+
#
|
171
|
+
# Transpose a matrix.
|
172
|
+
#
|
173
|
+
# @param m the matrix to transpose
|
174
|
+
# @return the transposed matrix as newly allocated matrix
|
175
|
+
# @abstract
|
176
|
+
#
|
177
|
+
def MatrixInterface.t(m)
|
178
|
+
raise NotImplementedError
|
179
|
+
end
|
180
|
+
|
181
|
+
#
|
182
|
+
# Compute the eigen-decomposition of a real symmetric matrix.
|
183
|
+
#
|
184
|
+
# The eigen-decomposition consists of a diagonal matrix +D+ containing
|
185
|
+
# the eigen values and a square matrix +N+ having the normalized eigenvectors
|
186
|
+
# in columns. It is assumed that the result of MatrixInterface#ed yields the
|
187
|
+
# matrices as an array and that the eigen-vectors and values are sorted in
|
188
|
+
# descending order of importance.
|
189
|
+
#
|
190
|
+
# @param m the matrix to decompose
|
191
|
+
# @return [Array] the array containing the matrix of eigen-values and eigen-vector
|
192
|
+
# @abstract
|
193
|
+
#
|
194
|
+
def MatrixInterface.ed(m)
|
195
|
+
raise NotImplementedError
|
196
|
+
end
|
197
|
+
|
198
|
+
#---------------------------------------
|
199
|
+
# Optional
|
200
|
+
# Methods having default implementations.
|
201
|
+
#---------------------------------------
|
202
|
+
|
203
|
+
#
|
204
|
+
# Create a new matrix and assign each element as the
|
205
|
+
# result of invoking the given block.
|
206
|
+
#
|
207
|
+
# @param [Integer] n the number of rows
|
208
|
+
# @param [Integer] m the number of columns.
|
209
|
+
# @return the newly created matrix.
|
210
|
+
#
|
211
|
+
def MatrixInterface.create_block(n, m, &block)
|
212
|
+
mat = self.create(n, m, 0.0)
|
213
|
+
for i in 0..self.nrows(mat)-1
|
214
|
+
for j in 0..self.ncols(mat)-1
|
215
|
+
self.set(mat, i, j, block.call(i,j))
|
216
|
+
end
|
217
|
+
end
|
218
|
+
mat
|
219
|
+
end
|
220
|
+
|
221
|
+
#
|
222
|
+
# Create a new matrix with uniform random elements.
|
223
|
+
#
|
224
|
+
# @param [Integer] n the number of rows
|
225
|
+
# @param [Integer] m the number of columns
|
226
|
+
# @param [Float] smin the minimum element value (inclusive).
|
227
|
+
# @param [Float] smax the maximum element value (inclusive).
|
228
|
+
# @return the newly created matrix.
|
229
|
+
#
|
230
|
+
def MatrixInterface.create_random(n, m, smin = -1.0, smax = 1.0)
|
231
|
+
range = smax - smin
|
232
|
+
self.create_block(n, m) do |i,j|
|
233
|
+
smin + range*rand()
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
#
|
238
|
+
# Create a new identity matrix.
|
239
|
+
#
|
240
|
+
# @param [Integer] n matrix dimension
|
241
|
+
# @return the newly created matrix.
|
242
|
+
#
|
243
|
+
def MatrixInterface.create_identity(n)
|
244
|
+
self.create_diagonal(*[1.0]*n)
|
245
|
+
end
|
246
|
+
|
247
|
+
#
|
248
|
+
# Create a new diagonal matrix.
|
249
|
+
#
|
250
|
+
# @param *elements the diagonal elements
|
251
|
+
# @return the newly created matrix
|
252
|
+
#
|
253
|
+
def MatrixInterface.create_diagonal(*elements)
|
254
|
+
n = elements.length
|
255
|
+
m = self.create(n, n, 0.0)
|
256
|
+
for i in 0..self.nrows(m)-1
|
257
|
+
self.set(m, i, i, elements[i])
|
258
|
+
end
|
259
|
+
m
|
260
|
+
end
|
261
|
+
|
262
|
+
#
|
263
|
+
# Create matrix from rows.
|
264
|
+
#
|
265
|
+
# @param [Array] rows the rows
|
266
|
+
# @return the newly created matrix
|
267
|
+
#
|
268
|
+
def MatrixInterface.create_rows(*rows)
|
269
|
+
nrows = rows.length
|
270
|
+
|
271
|
+
ncols = rows.first.length
|
272
|
+
self.create_block(nrows, ncols) do |i,j|
|
273
|
+
rows[i][j]
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
#
|
278
|
+
# Retrieve the diagonal elements as an array.
|
279
|
+
#
|
280
|
+
# The number of diagonals of an NxM matrix is
|
281
|
+
# defined as min(N,M).
|
282
|
+
#
|
283
|
+
# @param m the matrix
|
284
|
+
# @return diagonals of matrix as array.
|
285
|
+
#
|
286
|
+
def MatrixInterface.diagonals(m)
|
287
|
+
size = [self.nrows(m), self.ncols(m)].min
|
288
|
+
(0..size-1).map{|i| self.get(m,i,i)}
|
289
|
+
end
|
290
|
+
|
291
|
+
#
|
292
|
+
# Calculate the sum of diagonal matrix elements.
|
293
|
+
#
|
294
|
+
# @param m the matrix
|
295
|
+
# @return trace of matrix
|
296
|
+
#
|
297
|
+
def MatrixInterface.trace(m)
|
298
|
+
self.diagonals(m).inject(0) {|sum,e| sum += e}
|
299
|
+
end
|
300
|
+
|
301
|
+
#
|
302
|
+
# Returns matrix as array of columns.
|
303
|
+
#
|
304
|
+
# @return [Array<Array>] the array of columns where each column is an array
|
305
|
+
#
|
306
|
+
def MatrixInterface.columns(m)
|
307
|
+
a = Array.new(self.ncols(m)) {Array.new(self.nrows(m))}
|
308
|
+
for i in 0..self.nrows(m)-1 do
|
309
|
+
for j in 0..self.ncols(m)-1 do
|
310
|
+
a[j][i] = self.get(m, i, j)
|
311
|
+
end
|
312
|
+
end
|
313
|
+
a
|
314
|
+
end
|
315
|
+
|
316
|
+
#
|
317
|
+
# Returns matrix as array of rows.
|
318
|
+
#
|
319
|
+
# @return [Array<Array>] the array of rows where each rows is an array
|
320
|
+
#
|
321
|
+
def MatrixInterface.rows(m)
|
322
|
+
a = Array.new(self.nrows(m)) {Array.new(self.ncols(m))}
|
323
|
+
for i in 0..self.nrows(m)-1 do
|
324
|
+
for j in 0..self.ncols(m)-1 do
|
325
|
+
a[i][j] = self.get(m, i, j)
|
326
|
+
end
|
327
|
+
end
|
328
|
+
a
|
329
|
+
end
|
330
|
+
|
331
|
+
#
|
332
|
+
# Calculate minor matrix.
|
333
|
+
#
|
334
|
+
# @param m matrix to calculate minor from
|
335
|
+
# @param [Range] row_range linear row range with step size equal to one.
|
336
|
+
# @param [Range] col_range linear column range with step size equal to one.
|
337
|
+
#
|
338
|
+
def MatrixInterface.minor(m, row_range, col_range)
|
339
|
+
nrows = (row_range.last - row_range.first) + 1
|
340
|
+
ncols = (col_range.last - col_range.first) + 1
|
341
|
+
self.create_block(nrows, ncols) do |i,j|
|
342
|
+
self.get(m, i + row_range.first, j + col_range.first)
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
#
|
347
|
+
# Convert to string.
|
348
|
+
#
|
349
|
+
# Invokes #to_s from wrapped matrix.
|
350
|
+
#
|
351
|
+
# @return wrapped matrix as string.
|
352
|
+
#
|
353
|
+
def to_s(m)
|
354
|
+
m.to_s
|
355
|
+
end
|
356
|
+
|
357
|
+
end
|
358
|
+
end
|