nmatrix 0.0.1
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/.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
|