nmatrix 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +23 -0
- data/.gemtest +0 -0
- data/Gemfile +7 -0
- data/History.txt +6 -0
- data/LICENSE.txt +21 -0
- data/Manifest.txt +51 -0
- data/README.rdoc +63 -0
- data/Rakefile +154 -0
- data/ext/nmatrix/cblas.c +150 -0
- data/ext/nmatrix/dense.c +307 -0
- data/ext/nmatrix/dense/blas_header.template.c +52 -0
- data/ext/nmatrix/dense/elementwise.template.c +107 -0
- data/ext/nmatrix/dense/gemm.template.c +159 -0
- data/ext/nmatrix/dense/gemv.template.c +130 -0
- data/ext/nmatrix/dense/rationalmath.template.c +68 -0
- data/ext/nmatrix/depend +18 -0
- data/ext/nmatrix/extconf.rb +143 -0
- data/ext/nmatrix/generator.rb +594 -0
- data/ext/nmatrix/generator/syntax_tree.rb +481 -0
- data/ext/nmatrix/list.c +774 -0
- data/ext/nmatrix/nmatrix.c +1977 -0
- data/ext/nmatrix/nmatrix.h +912 -0
- data/ext/nmatrix/rational.c +98 -0
- data/ext/nmatrix/yale.c +726 -0
- data/ext/nmatrix/yale/complexmath.template.c +71 -0
- data/ext/nmatrix/yale/elementwise.template.c +46 -0
- data/ext/nmatrix/yale/elementwise_op.template.c +73 -0
- data/ext/nmatrix/yale/numbmm.template.c +94 -0
- data/ext/nmatrix/yale/smmp1.template.c +21 -0
- data/ext/nmatrix/yale/smmp1_header.template.c +38 -0
- data/ext/nmatrix/yale/smmp2.template.c +43 -0
- data/ext/nmatrix/yale/smmp2_header.template.c +46 -0
- data/ext/nmatrix/yale/sort_columns.template.c +56 -0
- data/ext/nmatrix/yale/symbmm.template.c +54 -0
- data/ext/nmatrix/yale/transp.template.c +68 -0
- data/lib/array.rb +67 -0
- data/lib/nmatrix.rb +263 -0
- data/lib/string.rb +65 -0
- data/spec/nmatrix_spec.rb +395 -0
- data/spec/nmatrix_yale_spec.rb +239 -0
- data/spec/nvector_spec.rb +43 -0
- data/spec/syntax_tree_spec.rb +46 -0
- metadata +150 -0
@@ -0,0 +1,54 @@
|
|
1
|
+
|
2
|
+
// Symbolic matrix multiply c=a*b
|
3
|
+
void %%TYPE_ABBREV%%_symbmm_(y_size_t n, y_size_t m, YALE_PARAM A, YALE_PARAM B, YALE_PARAM C)
|
4
|
+
{
|
5
|
+
u_%%TYPE%% mask[m];
|
6
|
+
u_%%TYPE%% i, j, k, kk, jj, minmn, ndnz = n; /* Local variables */
|
7
|
+
|
8
|
+
u_%%TYPE%% *ia = (u_%%TYPE%%*)(A.ia),
|
9
|
+
*ja = (u_%%TYPE%%*)(A.ja),
|
10
|
+
*ib = (u_%%TYPE%%*)(B.ia),
|
11
|
+
*jb = (u_%%TYPE%%*)(B.ja),
|
12
|
+
*ic = (u_%%TYPE%%*)(C.ia);
|
13
|
+
|
14
|
+
for (j = 0; j < m; ++j)
|
15
|
+
mask[j] = U%%TYPE_MAX%%;
|
16
|
+
|
17
|
+
if (C.diag) ic[0] = n+1;
|
18
|
+
else ic[0] = 0;
|
19
|
+
|
20
|
+
minmn = SMMP_MIN(m,n);
|
21
|
+
|
22
|
+
for (i = 0; i < n; ++i) { // MAIN LOOP: through rows
|
23
|
+
|
24
|
+
for (jj = ia[i]; jj <= ia[i+1]; ++jj) { // merge row lists, walking through columns in each row
|
25
|
+
|
26
|
+
// j <- column index given by JA[jj], or handle diagonal.
|
27
|
+
if (jj == ia[i+1]) { // Don't really do it the last time -- just handle diagonals in a new yale matrix.
|
28
|
+
if (!A.diag || i >= minmn) continue;
|
29
|
+
j = i;
|
30
|
+
} else j = ja[jj];
|
31
|
+
|
32
|
+
for (kk = ib[j]; kk <= ib[j+1]; ++kk) { // Now walk through columns of row J in matrix B.
|
33
|
+
if (kk == ib[j+1]) {
|
34
|
+
if (!B.diag || j >= minmn) continue;
|
35
|
+
k = j;
|
36
|
+
} else k = jb[kk];
|
37
|
+
|
38
|
+
if (mask[k] != i) {
|
39
|
+
mask[k] = i;
|
40
|
+
++ndnz;
|
41
|
+
}
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
if (C.diag && !mask[i]) --ndnz;
|
46
|
+
|
47
|
+
ic[i+1] = ndnz;
|
48
|
+
}
|
49
|
+
} /* symbmm_ */
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
|
54
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
|
2
|
+
void %%INT_ABBREV%%_%%TYPE_ABBREV%%_transp_(y_size_t n, y_size_t m, YALE_PARAM A, YALE_PARAM B, bool move)
|
3
|
+
{
|
4
|
+
u_%%INT%% i, j, index;
|
5
|
+
|
6
|
+
u_%%INT%% *ia = (u_%%INT%%*)(A.ia),
|
7
|
+
*ja = (u_%%INT%%*)(A.ja),
|
8
|
+
*ib = (u_%%INT%%*)(B.ia),
|
9
|
+
*jb = (u_%%INT%%*)(B.ja);
|
10
|
+
%%TYPE%% *a = A.a,
|
11
|
+
*b = B.a;
|
12
|
+
|
13
|
+
// Clear B
|
14
|
+
for (i = 0; i < m+1; ++i)
|
15
|
+
ib[i] = 0;
|
16
|
+
if (move) {
|
17
|
+
for (i = 0; i < m+1; ++i) {
|
18
|
+
%%TYPE b[i] = 0%%
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
if (A.diag) ib[0] = m + 1;
|
23
|
+
else ib[0] = 0;
|
24
|
+
|
25
|
+
/* count indices for each column */
|
26
|
+
|
27
|
+
for (i = 0; i < n; ++i) {
|
28
|
+
for (j = ia[i]; j < ia[i+1]; ++j)
|
29
|
+
++(ib[ja[j]+1]);
|
30
|
+
}
|
31
|
+
|
32
|
+
for (i = 0; i < m; ++i)
|
33
|
+
ib[i+1] = ib[i] + ib[i+1];
|
34
|
+
|
35
|
+
/* now make jb */
|
36
|
+
|
37
|
+
for (i = 0; i < n; ++i) {
|
38
|
+
|
39
|
+
for (j = ia[i]; j < ia[i+1]; ++j) {
|
40
|
+
index = ja[j];
|
41
|
+
jb[ib[index]] = i;
|
42
|
+
|
43
|
+
if (move)
|
44
|
+
%%TYPE b[ib[index]] = a[j]%%
|
45
|
+
|
46
|
+
++(ib[index]);
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
/* now fixup ib */
|
51
|
+
|
52
|
+
for (i = m; i >= 1; --i)
|
53
|
+
ib[i] = ib[i-1];
|
54
|
+
|
55
|
+
|
56
|
+
if (A.diag) {
|
57
|
+
if (move) {
|
58
|
+
j = SMMP_MIN(n,m);
|
59
|
+
|
60
|
+
for (i = 0; i < j; ++i)
|
61
|
+
%%TYPE b[i] = a[i]%%
|
62
|
+
}
|
63
|
+
ib[0] = m + 1;
|
64
|
+
|
65
|
+
} else {
|
66
|
+
ib[0] = 0;
|
67
|
+
}
|
68
|
+
}
|
data/lib/array.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# = NMatrix
|
2
|
+
#
|
3
|
+
# A linear algebra library for scientific computation in Ruby.
|
4
|
+
# NMatrix is part of SciRuby.
|
5
|
+
#
|
6
|
+
# NMatrix was originally inspired by and derived from NArray, by
|
7
|
+
# Masahiro Tanaka: http://narray.rubyforge.org
|
8
|
+
#
|
9
|
+
# == Copyright Information
|
10
|
+
#
|
11
|
+
# SciRuby is Copyright (c) 2010 - 2012, Ruby Science Foundation
|
12
|
+
# NMatrix is Copyright (c) 2012, Ruby Science Foundation
|
13
|
+
#
|
14
|
+
# Please see LICENSE.txt for additional copyright notices.
|
15
|
+
#
|
16
|
+
# == Contributing
|
17
|
+
#
|
18
|
+
# By contributing source code to SciRuby, you agree to be bound by
|
19
|
+
# our Contributor Agreement:
|
20
|
+
#
|
21
|
+
# * https://github.com/SciRuby/sciruby/wiki/Contributor-Agreement
|
22
|
+
#
|
23
|
+
# == array.rb
|
24
|
+
#
|
25
|
+
# Ruby core extensions for NMatrix.
|
26
|
+
|
27
|
+
class Array
|
28
|
+
# Convert a Ruby Array to an NMatrix.
|
29
|
+
#
|
30
|
+
# You must provide a shape for the matrix as the first argument.
|
31
|
+
#
|
32
|
+
# == Arguments:
|
33
|
+
# <tt>shape</tt> :: Array describing matrix dimensions (or Fixnum for square) -- REQUIRED!
|
34
|
+
# <tt>dtype</tt> :: Override data type (e.g., to store a Float as :float32 instead of :float64) -- optional.
|
35
|
+
# <tt>stype</tt> :: Optional storage type (defaults to :dense)
|
36
|
+
def to_nm *args
|
37
|
+
pos = 0
|
38
|
+
|
39
|
+
shape = args[pos]; pos += 1
|
40
|
+
|
41
|
+
dtype = begin
|
42
|
+
if pos >= args.size
|
43
|
+
# TODO: Upcasting.
|
44
|
+
if self[0].is_a?(Fixnum)
|
45
|
+
:int64
|
46
|
+
elsif self[0].is_a?(Float)
|
47
|
+
:float64
|
48
|
+
elsif self[0].is_a?(Rational)
|
49
|
+
:rational128
|
50
|
+
elsif self[0].is_a?(Complex)
|
51
|
+
:complex128
|
52
|
+
end.tap { pos += 1 }
|
53
|
+
else
|
54
|
+
args[pos].tap { pos += 1 }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
stype = args[pos].is_a?(Symbol) ? args[pos].tap { pos += 1} : :dense
|
59
|
+
|
60
|
+
|
61
|
+
if stype == :dense
|
62
|
+
NMatrix.new(stype, shape, self, dtype)
|
63
|
+
else
|
64
|
+
NMatrix.new(:dense, shape, self, dtype).cast(:stype, dtype)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/nmatrix.rb
ADDED
@@ -0,0 +1,263 @@
|
|
1
|
+
# = NMatrix
|
2
|
+
#
|
3
|
+
# A linear algebra library for scientific computation in Ruby.
|
4
|
+
# NMatrix is part of SciRuby.
|
5
|
+
#
|
6
|
+
# NMatrix was originally inspired by and derived from NArray, by
|
7
|
+
# Masahiro Tanaka: http://narray.rubyforge.org
|
8
|
+
#
|
9
|
+
# == Copyright Information
|
10
|
+
#
|
11
|
+
# SciRuby is Copyright (c) 2010 - 2012, Ruby Science Foundation
|
12
|
+
# NMatrix is Copyright (c) 2012, Ruby Science Foundation
|
13
|
+
#
|
14
|
+
# Please see LICENSE.txt for additional copyright notices.
|
15
|
+
#
|
16
|
+
# == Contributing
|
17
|
+
#
|
18
|
+
# By contributing source code to SciRuby, you agree to be bound by
|
19
|
+
# our Contributor Agreement:
|
20
|
+
#
|
21
|
+
# * https://github.com/SciRuby/sciruby/wiki/Contributor-Agreement
|
22
|
+
#
|
23
|
+
# == nmatrix.rb
|
24
|
+
#
|
25
|
+
# This file loads the C extension for NMatrix, and adds a few
|
26
|
+
# additional pieces of functionality (e.g., inspect, pretty_print).
|
27
|
+
# Also provided is NVector, which represents a rank-1 NMatrix in
|
28
|
+
# vector operations.
|
29
|
+
|
30
|
+
# For some reason nmatrix.so ends up in a different place during gem build
|
31
|
+
if File.exist? "lib/nmatrix/nmatrix.so"
|
32
|
+
require File.join(File.dirname(__FILE__), "nmatrix/nmatrix.so") # development
|
33
|
+
else
|
34
|
+
require File.join(File.dirname(__FILE__), "nmatrix.so") # gem
|
35
|
+
end
|
36
|
+
require File.join(File.dirname(__FILE__), "array.rb") # Load Array extensions
|
37
|
+
|
38
|
+
|
39
|
+
class NMatrix
|
40
|
+
VERSION = '0.0.1'
|
41
|
+
|
42
|
+
#def inspect
|
43
|
+
#
|
44
|
+
#end
|
45
|
+
|
46
|
+
# TODO: Make this actually pretty.
|
47
|
+
def pretty_print
|
48
|
+
raise(NotImplementedError, "can only print rank 2 matrices") unless rank == 2
|
49
|
+
(0...shape[0]).each do |i|
|
50
|
+
arr = []
|
51
|
+
(0...shape[1]).each do |j|
|
52
|
+
arr << (self[i,j].nil? ? "nil" : self[i,j])
|
53
|
+
end
|
54
|
+
puts arr.join(" ")
|
55
|
+
end
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
def inspect
|
61
|
+
original_inspect = super
|
62
|
+
original_inspect = original_inspect[0...original_inspect.size-1]
|
63
|
+
original_inspect + inspect_helper.join(" ") + ">"
|
64
|
+
end
|
65
|
+
|
66
|
+
def __yale_ary__to_s(sym)
|
67
|
+
ary = self.send("__yale_#{sym.to_s}__".to_sym)
|
68
|
+
"[" + ary.collect { |a| a.nil? ? "nil" : a }.join(',') + "]"
|
69
|
+
end
|
70
|
+
|
71
|
+
class << self
|
72
|
+
|
73
|
+
# Helper function for loading a file in the first sparse format given here:
|
74
|
+
# http://math.nist.gov/MatrixMarket/formats.html
|
75
|
+
#
|
76
|
+
# Override type specifier (e.g., 'real') using :read_with => :to_f (or any other string-to-numeric conversion
|
77
|
+
# function), and with :dtype => :float32 or :dtype => :int8 to force storage in a lesser type.
|
78
|
+
def load_matrix_matrix_coordinate_file filename, options = {}
|
79
|
+
f = File.new(filename, "r")
|
80
|
+
|
81
|
+
func = options.has_key?(:read_with) ? options[:read_with] : nil
|
82
|
+
dtype = options.has_key?(:dtype) ? options[:dtype] : nil
|
83
|
+
|
84
|
+
line = f.gets
|
85
|
+
raise(IOError, "incorrect file type specifier") unless line =~ /^%%MatrixMarket\ matrix\ coordinate/
|
86
|
+
spec = line.split
|
87
|
+
case spec[3]
|
88
|
+
when 'real'
|
89
|
+
func ||= :to_f
|
90
|
+
dtype ||= :float64
|
91
|
+
when 'integer'
|
92
|
+
func ||= :to_i
|
93
|
+
dtype ||= :int64
|
94
|
+
when 'complex'
|
95
|
+
func ||= :to_complex
|
96
|
+
dtype ||= :complex128
|
97
|
+
when 'rational'
|
98
|
+
func = :to_rational
|
99
|
+
dtype ||= :rational128
|
100
|
+
else
|
101
|
+
raise ArgumentError, "Unrecognized dtype"
|
102
|
+
end unless !func.nil? && !dtype.nil?
|
103
|
+
|
104
|
+
line = f.gets
|
105
|
+
while line =~ /^%/
|
106
|
+
line = f.gets
|
107
|
+
end
|
108
|
+
|
109
|
+
rows, cols, entries = line.split.collect { |x| x.to_i }
|
110
|
+
|
111
|
+
matrix = NMatrix.new(:yale, [rows, cols], entries, dtype)
|
112
|
+
|
113
|
+
entries.times do
|
114
|
+
i,j,v = line.split
|
115
|
+
matrix[i.to_i-1,j.to_i-1] = v.send(func)
|
116
|
+
end
|
117
|
+
|
118
|
+
matrix
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
def cblas_gemm a, b, c=nil, alpha=1.0, beta=0.0, transpose_a=false, transpose_b=false, m=nil, n=nil, k=nil, lda=nil, ldb=nil, ldc=nil
|
123
|
+
raise(ArgumentError, "expected dense NMatrices as first two arguments") unless a.is_a?(NMatrix) && b.is_a?(NMatrix) && a.stype == :dense && b.stype == :dense
|
124
|
+
raise(ArgumentError, "expected nil or dense NMatrix as third argument") unless c.nil? || (c.is_a?(NMatrix) && c.stype == :dense)
|
125
|
+
raise(ArgumentError, "NMatrix dtype mismatch") unless a.dtype == b.dtype && (c.nil? ? true : a.dtype == c.dtype)
|
126
|
+
|
127
|
+
# First, set m, n, and k, which depend on whether we're taking the transpose of a and b.
|
128
|
+
if c.nil?
|
129
|
+
if transpose_a # either :transpose or :complex_conjugate
|
130
|
+
m ||= a.shape[1]
|
131
|
+
k ||= a.shape[0]
|
132
|
+
else # no transpose
|
133
|
+
m ||= a.shape[0]
|
134
|
+
k ||= a.shape[1]
|
135
|
+
end
|
136
|
+
n ||= transpose_b ? b.shape[0] : b.shape[1]
|
137
|
+
c = NMatrix.new([m, n], a.dtype)
|
138
|
+
else
|
139
|
+
m ||= c.shape[0]
|
140
|
+
n ||= c.shape[1]
|
141
|
+
k ||= transpose_a ? a.shape[0] : a.shape[1]
|
142
|
+
end
|
143
|
+
|
144
|
+
# I think these are independent of whether or not a transpose occurs.
|
145
|
+
lda ||= a.shape[1]
|
146
|
+
ldb ||= b.shape[1]
|
147
|
+
ldc ||= c.shape[1]
|
148
|
+
|
149
|
+
if a.dtype == :complex64 || a.dtype == :complex128 # NM_COMPLEX64 and NM_COMPLEX128 both require complex alpha and beta
|
150
|
+
alpha = Complex.new(1.0, 0.0) if alpha == 1.0
|
151
|
+
beta = Complex.new(0.0, 0.0) if beta == 0.0
|
152
|
+
end
|
153
|
+
|
154
|
+
# For argument descriptions, see: http://www.netlib.org/blas/dgemm.f
|
155
|
+
NMatrix.__cblas_gemm__(transpose_a, transpose_b, m, n, k, alpha, a, lda, b, ldb, beta, c, ldc)
|
156
|
+
|
157
|
+
return c
|
158
|
+
end
|
159
|
+
|
160
|
+
|
161
|
+
def cblas_gemv a, x, y=nil, alpha=1.0, beta=0.0, transpose_a=false, m=nil, n=nil, lda=nil, incx=nil, incy=nil
|
162
|
+
m ||= transpose_a ? a.shape[1] : a.shape[0]
|
163
|
+
n ||= transpose_a ? a.shape[0] : a.shape[1]
|
164
|
+
|
165
|
+
lda ||= a.shape[1]
|
166
|
+
incx ||= 1
|
167
|
+
incy ||= 1
|
168
|
+
|
169
|
+
if a.dtype == :complex64 || a.dtype == :complex128 # NM_COMPLEX64 and NM_COMPLEX128 both require complex alpha and beta
|
170
|
+
alpha = Complex.new(1.0, 0.0) if alpha == 1.0
|
171
|
+
beta = Complex.new(0.0, 0.0) if beta == 0.0
|
172
|
+
end
|
173
|
+
|
174
|
+
NMatrix.__cblas_gemv__(transpose_a, m, n, alpha, a, lda, x, incx, beta, y, incy)
|
175
|
+
|
176
|
+
return y
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
protected
|
181
|
+
def inspect_helper
|
182
|
+
ary = []
|
183
|
+
ary << "shape:[#{shape.join(',')}]" << "dtype:#{dtype}" << "stype:#{stype}"
|
184
|
+
|
185
|
+
if stype == :yale
|
186
|
+
ary << "capacity:#{capacity}" << "ija:#{__yale_ary__to_s(:ija)}" << "ia:#{__yale_ary__to_s(:ia)}" <<
|
187
|
+
"ja:#{__yale_ary__to_s(:ja)}" << "a:#{__yale_ary__to_s(:a)}" << "d:#{__yale_ary__to_s(:d)}" <<
|
188
|
+
"lu:#{__yale_ary__to_s(:lu)}" << "yale_size:#{__yale_size__}"
|
189
|
+
end
|
190
|
+
|
191
|
+
ary
|
192
|
+
end
|
193
|
+
|
194
|
+
end
|
195
|
+
|
196
|
+
|
197
|
+
|
198
|
+
# This is a specific type of NMatrix in which only one dimension is not 1. Although it is stored as a rank-2, n x 1,
|
199
|
+
# matrix, it acts as a rank-1 vector of size n. If the @orientation flag is set to :row, it is stored as 1 x n instead
|
200
|
+
# of n x 1.
|
201
|
+
class NVector < NMatrix
|
202
|
+
def initialize length, *args
|
203
|
+
super :dense, [length,1], *args
|
204
|
+
end
|
205
|
+
|
206
|
+
# Orientation defaults to column (e.g., [3,1] is a column of length 3). It may also be row, e.g., for [1,5].
|
207
|
+
def orientation
|
208
|
+
defined?(@orientation) && !@orientation.nil? ? @orientation : :column
|
209
|
+
end
|
210
|
+
|
211
|
+
def transpose
|
212
|
+
t = super
|
213
|
+
t.send :eval, "@orientation = @orientation == :row ? :column : :row"
|
214
|
+
t
|
215
|
+
end
|
216
|
+
|
217
|
+
def transpose!
|
218
|
+
super
|
219
|
+
@orientation = @orientation == :row ? :column : :row
|
220
|
+
self
|
221
|
+
end
|
222
|
+
|
223
|
+
def multiply m
|
224
|
+
v = super(m)
|
225
|
+
v.send :eval, "@orientation = @orientation == :row ? :column : :row"
|
226
|
+
v
|
227
|
+
end
|
228
|
+
|
229
|
+
def multiply! m
|
230
|
+
super
|
231
|
+
@orientation = @orientation == :row ? :column : :row
|
232
|
+
self
|
233
|
+
end
|
234
|
+
|
235
|
+
def [] i
|
236
|
+
@orientation == :row ? super(0,i) : super(i,0)
|
237
|
+
end
|
238
|
+
|
239
|
+
def []= i,val
|
240
|
+
@orientation == :row ? super(0,i,val) : super(i,0,val)
|
241
|
+
end
|
242
|
+
|
243
|
+
def rank; 1; end
|
244
|
+
|
245
|
+
# TODO: Make this actually pretty.
|
246
|
+
def pretty_print
|
247
|
+
dim = @orientation == :row ? 1 : 0
|
248
|
+
arr = []
|
249
|
+
(0...shape[dim]).each do |i|
|
250
|
+
arr << self[i]
|
251
|
+
end
|
252
|
+
puts arr.join(" ")
|
253
|
+
nil
|
254
|
+
end
|
255
|
+
|
256
|
+
protected
|
257
|
+
def inspect_helper
|
258
|
+
ary = super
|
259
|
+
ary << "orientation:#{(@orientation || 'column').to_s}"
|
260
|
+
ary
|
261
|
+
end
|
262
|
+
|
263
|
+
end
|