rmds 0.2
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.
- 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
|