nmatrix 0.1.0.rc5 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +0 -1
- data/Gemfile +0 -2
- data/History.txt +39 -4
- data/LICENSE.txt +3 -1
- data/Manifest.txt +2 -0
- data/README.rdoc +6 -14
- data/Rakefile +4 -1
- data/ext/nmatrix/data/data.cpp +1 -1
- data/ext/nmatrix/data/data.h +2 -1
- data/ext/nmatrix/data/rational.h +230 -226
- data/ext/nmatrix/extconf.rb +7 -4
- data/ext/nmatrix/math.cpp +259 -172
- data/ext/nmatrix/math/getri.h +2 -2
- data/ext/nmatrix/math/math.h +1 -1
- data/ext/nmatrix/ruby_constants.cpp +0 -1
- data/ext/nmatrix/ruby_nmatrix.c +55 -32
- data/ext/nmatrix/storage/dense/dense.cpp +1 -0
- data/ext/nmatrix/storage/yale/yale.cpp +12 -14
- data/ext/nmatrix/ttable_helper.rb +0 -1
- data/lib/nmatrix.rb +5 -0
- data/lib/nmatrix/homogeneous.rb +98 -0
- data/lib/nmatrix/io/fortran_format.rb +135 -0
- data/lib/nmatrix/io/harwell_boeing.rb +220 -0
- data/lib/nmatrix/io/market.rb +18 -8
- data/lib/nmatrix/io/mat5_reader.rb +16 -111
- data/lib/nmatrix/io/mat_reader.rb +3 -5
- data/lib/nmatrix/io/point_cloud.rb +27 -28
- data/lib/nmatrix/lapack.rb +3 -1
- data/lib/nmatrix/math.rb +112 -43
- data/lib/nmatrix/monkeys.rb +67 -11
- data/lib/nmatrix/nmatrix.rb +56 -33
- data/lib/nmatrix/rspec.rb +2 -2
- data/lib/nmatrix/shortcuts.rb +42 -25
- data/lib/nmatrix/version.rb +4 -4
- data/nmatrix.gemspec +4 -3
- data/spec/03_nmatrix_monkeys_spec.rb +72 -0
- data/spec/blas_spec.rb +4 -0
- data/spec/homogeneous_spec.rb +12 -4
- data/spec/io/fortran_format_spec.rb +88 -0
- data/spec/io/harwell_boeing_spec.rb +98 -0
- data/spec/io/test.rua +9 -0
- data/spec/math_spec.rb +51 -9
- metadata +38 -9
data/lib/nmatrix/nmatrix.rb
CHANGED
@@ -30,31 +30,39 @@
|
|
30
30
|
|
31
31
|
require_relative './lapack.rb'
|
32
32
|
require_relative './yale_functions.rb'
|
33
|
+
require_relative './monkeys'
|
33
34
|
|
35
|
+
# NMatrix is a matrix class that supports both multidimensional arrays
|
36
|
+
# (`:dense` stype) and sparse storage (`:list` or `:yale` stypes) and 13 data
|
37
|
+
# types, including complex and rational numbers, various integer and
|
38
|
+
# floating-point sizes and ruby objects.
|
34
39
|
class NMatrix
|
35
|
-
|
36
|
-
# Read and write extensions for NMatrix. These are only loaded when needed.
|
37
|
-
#
|
40
|
+
# Read and write extensions for NMatrix.
|
38
41
|
module IO
|
42
|
+
extend AutoloadPatch
|
43
|
+
|
44
|
+
# Reader (and eventually writer) of Matlab .mat files.
|
45
|
+
#
|
46
|
+
# The .mat file format is documented in the following link:
|
47
|
+
# * http://www.mathworks.com/help/pdf_doc/matlab/matfile_format.pdf
|
39
48
|
module Matlab
|
49
|
+
extend AutoloadPatch
|
50
|
+
|
40
51
|
class << self
|
41
|
-
|
52
|
+
# call-seq:
|
53
|
+
# load(mat_file_path) -> NMatrix
|
54
|
+
# load_mat(mat_file_path) -> NMatrix
|
55
|
+
#
|
56
|
+
# Load a .mat file and return a NMatrix corresponding to it.
|
57
|
+
def load_mat(file_path)
|
42
58
|
NMatrix::IO::Matlab::Mat5Reader.new(File.open(file_path, "rb+")).to_ruby
|
43
59
|
end
|
44
60
|
alias :load :load_mat
|
45
61
|
end
|
46
|
-
|
47
|
-
# FIXME: Remove autoloads
|
48
|
-
autoload :MatReader, 'nmatrix/io/mat_reader'
|
49
|
-
autoload :Mat5Reader, 'nmatrix/io/mat5_reader'
|
50
62
|
end
|
51
|
-
|
52
|
-
autoload :Market, 'nmatrix/io/market.rb'
|
53
|
-
autoload :PointCloud, 'nmatrix/io/point_cloud.rb'
|
54
63
|
end
|
55
64
|
|
56
65
|
class << self
|
57
|
-
#
|
58
66
|
# call-seq:
|
59
67
|
# load_matlab_file(path) -> Mat5Reader
|
60
68
|
#
|
@@ -62,12 +70,10 @@ class NMatrix
|
|
62
70
|
# - +file_path+ -> The path to a version 5 .mat file.
|
63
71
|
# * *Returns* :
|
64
72
|
# - A Mat5Reader object.
|
65
|
-
#
|
66
73
|
def load_matlab_file(file_path)
|
67
74
|
NMatrix::IO::Mat5Reader.new(File.open(file_path, 'rb')).to_ruby
|
68
75
|
end
|
69
76
|
|
70
|
-
#
|
71
77
|
# call-seq:
|
72
78
|
# load_pcd_file(path) -> PointCloudReader::MetaReader
|
73
79
|
#
|
@@ -75,12 +81,10 @@ class NMatrix
|
|
75
81
|
# - +file_path+ -> The path to a PCL PCD file.
|
76
82
|
# * *Returns* :
|
77
83
|
# - A PointCloudReader::MetaReader object with the matrix stored in its +matrix+ property
|
78
|
-
#
|
79
84
|
def load_pcd_file(file_path)
|
80
85
|
NMatrix::IO::PointCloudReader::MetaReader.new(file_path)
|
81
86
|
end
|
82
87
|
|
83
|
-
#
|
84
88
|
# Calculate the size of an NMatrix of a given shape.
|
85
89
|
def size(shape)
|
86
90
|
shape = [shape,shape] unless shape.is_a?(Array)
|
@@ -128,8 +132,6 @@ class NMatrix
|
|
128
132
|
end
|
129
133
|
end
|
130
134
|
end
|
131
|
-
#alias :pp :pretty_print
|
132
|
-
|
133
135
|
|
134
136
|
#
|
135
137
|
# call-seq:
|
@@ -238,7 +240,6 @@ class NMatrix
|
|
238
240
|
end
|
239
241
|
|
240
242
|
|
241
|
-
##
|
242
243
|
# call-seq:
|
243
244
|
# integer_dtype?() -> Boolean
|
244
245
|
#
|
@@ -248,6 +249,26 @@ class NMatrix
|
|
248
249
|
[:byte, :int8, :int16, :int32, :int64].include?(self.dtype)
|
249
250
|
end
|
250
251
|
|
252
|
+
##
|
253
|
+
# call-seq:
|
254
|
+
# complex_dtype?() -> Boolean
|
255
|
+
#
|
256
|
+
# Checks if dtype is a complex type
|
257
|
+
#
|
258
|
+
def complex_dtype?
|
259
|
+
[:complex64, :complex128].include?(self.dtype)
|
260
|
+
end
|
261
|
+
|
262
|
+
##
|
263
|
+
# call-seq:
|
264
|
+
# complex_dtype?() -> Boolean
|
265
|
+
#
|
266
|
+
# Checks if dtype is a rational type
|
267
|
+
#
|
268
|
+
def rational_dtype?
|
269
|
+
[:rational32, :rational64, :rational128].include?(self.dtype)
|
270
|
+
end
|
271
|
+
|
251
272
|
|
252
273
|
#
|
253
274
|
# call-seq:
|
@@ -352,7 +373,7 @@ class NMatrix
|
|
352
373
|
#
|
353
374
|
# See @row (dimension = 0), @column (dimension = 1)
|
354
375
|
def rank(shape_idx, rank_idx, meth = :copy)
|
355
|
-
|
376
|
+
|
356
377
|
if shape_idx > (self.dim-1)
|
357
378
|
raise(RangeError, "#rank call was out of bounds")
|
358
379
|
end
|
@@ -428,7 +449,6 @@ class NMatrix
|
|
428
449
|
end
|
429
450
|
t = reshape_clone_structure(newer_shape)
|
430
451
|
left_params = [:*]*newer_shape.size
|
431
|
-
puts(left_params)
|
432
452
|
right_params = [:*]*self.shape.size
|
433
453
|
t[*left_params] = self[*right_params]
|
434
454
|
t
|
@@ -512,7 +532,6 @@ class NMatrix
|
|
512
532
|
end
|
513
533
|
|
514
534
|
|
515
|
-
#
|
516
535
|
# call-seq:
|
517
536
|
# matrix1.concat(*m2) -> NMatrix
|
518
537
|
# matrix1.concat(*m2, rank) -> NMatrix
|
@@ -520,20 +539,21 @@ class NMatrix
|
|
520
539
|
# matrix1.vconcat(*m2) -> NMatrix
|
521
540
|
# matrix1.dconcat(*m3) -> NMatrix
|
522
541
|
#
|
523
|
-
# Joins two matrices together into a new larger matrix. Attempts to determine
|
524
|
-
# on by looking for the first common element
|
525
|
-
#
|
542
|
+
# Joins two matrices together into a new larger matrix. Attempts to determine
|
543
|
+
# which direction to concatenate on by looking for the first common element
|
544
|
+
# of the matrix +shape+ in reverse. In other words, concatenating two columns
|
545
|
+
# together without supplying +rank+ will glue them into an n x 2 matrix.
|
526
546
|
#
|
527
|
-
# You can also use hconcat, vconcat, and dconcat for the first three ranks.
|
528
|
-
# rank argument is provided.
|
547
|
+
# You can also use hconcat, vconcat, and dconcat for the first three ranks.
|
548
|
+
# concat performs an hconcat when no rank argument is provided.
|
529
549
|
#
|
530
550
|
# The two matrices must have the same +dim+.
|
531
551
|
#
|
532
552
|
# * *Arguments* :
|
533
553
|
# - +matrices+ -> one or more matrices
|
534
|
-
# - +rank+ -> Fixnum (for rank); alternatively, may use :row, :column, or
|
535
|
-
#
|
536
|
-
def concat
|
554
|
+
# - +rank+ -> Fixnum (for rank); alternatively, may use :row, :column, or
|
555
|
+
# :layer for 0, 1, 2, respectively
|
556
|
+
def concat(*matrices)
|
537
557
|
rank = nil
|
538
558
|
rank = matrices.pop unless matrices.last.is_a?(NMatrix)
|
539
559
|
|
@@ -587,15 +607,18 @@ class NMatrix
|
|
587
607
|
n
|
588
608
|
end
|
589
609
|
|
590
|
-
|
610
|
+
# Horizontal concatenation with +matrices+.
|
611
|
+
def hconcat(*matrices)
|
591
612
|
concat(*matrices, :column)
|
592
613
|
end
|
593
614
|
|
594
|
-
|
615
|
+
# Vertical concatenation with +matrices+.
|
616
|
+
def vconcat(*matrices)
|
595
617
|
concat(*matrices, :row)
|
596
618
|
end
|
597
619
|
|
598
|
-
|
620
|
+
# Depth concatenation with +matrices+.
|
621
|
+
def dconcat(*matrices)
|
599
622
|
concat(*matrices, :layer)
|
600
623
|
end
|
601
624
|
|
data/lib/nmatrix/rspec.rb
CHANGED
@@ -29,7 +29,7 @@
|
|
29
29
|
require 'rspec'
|
30
30
|
|
31
31
|
# Amend RSpec to allow #be_within for matrices.
|
32
|
-
module RSpec::Matchers::BuiltIn
|
32
|
+
module RSpec::Matchers::BuiltIn
|
33
33
|
class BeWithin
|
34
34
|
|
35
35
|
def of(expected)
|
@@ -72,4 +72,4 @@ module RSpec::Matchers::BuiltIn #:nodoc:
|
|
72
72
|
end
|
73
73
|
|
74
74
|
end
|
75
|
-
end
|
75
|
+
end
|
data/lib/nmatrix/shortcuts.rb
CHANGED
@@ -34,29 +34,40 @@
|
|
34
34
|
|
35
35
|
class NMatrix
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
#
|
40
37
|
# call-seq:
|
41
|
-
# dense? -> true or false
|
42
|
-
# list? -> true or false
|
43
|
-
# yale? -> true or false
|
44
|
-
#
|
45
|
-
# Shortcut functions for quickly determining a matrix's stype.
|
38
|
+
# m.dense? -> true or false
|
46
39
|
#
|
40
|
+
# Determine if +m+ is a dense matrix.
|
47
41
|
def dense?; return stype == :dense; end
|
48
|
-
def yale?; return stype == :yale; end
|
49
|
-
def list?; return stype == :list; end
|
50
42
|
|
43
|
+
# call-seq:
|
44
|
+
# m.yale? -> true or false
|
45
|
+
#
|
46
|
+
# Determine if +m+ is a Yale matrix.
|
47
|
+
def yale?; return stype == :yale; end
|
48
|
+
|
49
|
+
# call-seq:
|
50
|
+
# m.list? -> true or false
|
51
|
+
#
|
52
|
+
# Determine if +m+ is a list-of-lists matrix.
|
53
|
+
def list?; return stype == :list; end
|
51
54
|
|
52
55
|
class << self
|
53
|
-
#
|
54
56
|
# call-seq:
|
55
|
-
# NMatrix[
|
57
|
+
# NMatrix[Numeric, ..., Numeric, dtype: Symbol] -> NMatrix
|
58
|
+
# NMatrix[Array, dtype: Symbol] -> NMatrix
|
59
|
+
#
|
60
|
+
# The default value for +dtype+ is guessed from the first parameter. For example:
|
61
|
+
# NMatrix[1.0, 2.0].dtype # => :float64
|
62
|
+
# NMatrix[1r, 2r].dtype # => :rational64
|
56
63
|
#
|
57
|
-
#
|
64
|
+
# But this is just a *guess*. If the other values can't be converted to
|
65
|
+
# this dtype, a +TypeError+ will be raised.
|
66
|
+
#
|
67
|
+
# You can use the +N+ constant in this way:
|
58
68
|
# N = NMatrix
|
59
69
|
# N[1, 2, 3]
|
70
|
+
#
|
60
71
|
# NMatrix needs to have a succinct way to create a matrix by specifying the
|
61
72
|
# components directly. This is very useful for using it as an advanced
|
62
73
|
# calculator, it is useful for learning how to use, for testing language
|
@@ -68,12 +79,16 @@ class NMatrix
|
|
68
79
|
#
|
69
80
|
# Examples:
|
70
81
|
#
|
71
|
-
# a =
|
82
|
+
# a = N[ 1,2,3,4 ] => 1 2 3 4
|
72
83
|
#
|
73
|
-
# a =
|
84
|
+
# a = N[ 1,2,3,4, :int32 ] => 1 2 3 4
|
74
85
|
#
|
75
|
-
# a =
|
76
|
-
#
|
86
|
+
# a = N[ [1,2,3], [3,4,5] ] => 1.0 2.0 3.0
|
87
|
+
# 3.0 4.0 5.0
|
88
|
+
#
|
89
|
+
# a = N[ 3,6,9 ].transpose => 3
|
90
|
+
# 6
|
91
|
+
# 9
|
77
92
|
#
|
78
93
|
# SYNTAX COMPARISON:
|
79
94
|
#
|
@@ -83,7 +98,6 @@ class NMatrix
|
|
83
98
|
#
|
84
99
|
# SciRuby: a = NMatrix[ [1,2,3], [4,5,6] ]
|
85
100
|
# Ruby array: a = [ [1,2,3], [4,5,6] ]
|
86
|
-
#
|
87
101
|
def [](*params)
|
88
102
|
options = params.last.is_a?(Hash) ? params.pop : {}
|
89
103
|
|
@@ -159,7 +173,6 @@ class NMatrix
|
|
159
173
|
NMatrix.new(shape, 1, {:dtype => :float64, :default => 1}.merge(opts))
|
160
174
|
end
|
161
175
|
|
162
|
-
##
|
163
176
|
# call-seq:
|
164
177
|
# ones_like(nm) -> NMatrix
|
165
178
|
#
|
@@ -173,7 +186,6 @@ class NMatrix
|
|
173
186
|
NMatrix.ones(nm.shape, dtype: nm.dtype, stype: nm.stype, capacity: nm.capacity, default: 1)
|
174
187
|
end
|
175
188
|
|
176
|
-
##
|
177
189
|
# call-seq:
|
178
190
|
# zeros_like(nm) -> NMatrix
|
179
191
|
#
|
@@ -355,7 +367,7 @@ class NMatrix
|
|
355
367
|
end
|
356
368
|
end
|
357
369
|
|
358
|
-
module NVector
|
370
|
+
module NVector #:nodoc:
|
359
371
|
|
360
372
|
class << self
|
361
373
|
#
|
@@ -648,14 +660,19 @@ module NVector
|
|
648
660
|
end
|
649
661
|
|
650
662
|
|
651
|
-
#
|
663
|
+
# This constant is intended as a simple constructor for NMatrix meant for
|
664
|
+
# experimenting.
|
665
|
+
#
|
652
666
|
# Examples:
|
653
667
|
#
|
654
|
-
# a = N[ 1,2,3,4 ] => 1
|
668
|
+
# a = N[ 1,2,3,4 ] => 1 2 3 4
|
655
669
|
#
|
656
670
|
# a = N[ 1,2,3,4, :int32 ] => 1 2 3 4
|
657
671
|
#
|
658
|
-
# a = N[ [1,2,3], [3,4,5] ] => 1
|
659
|
-
# 3
|
672
|
+
# a = N[ [1,2,3], [3,4,5] ] => 1 2 3
|
673
|
+
# 3 4 5
|
660
674
|
#
|
675
|
+
# a = N[ 3,6,9 ].transpose => 3
|
676
|
+
# 6
|
677
|
+
# 9
|
661
678
|
N = NMatrix
|
data/lib/nmatrix/version.rb
CHANGED
@@ -26,14 +26,14 @@ class NMatrix
|
|
26
26
|
# Note that the format of the VERSION string is needed for NMatrix
|
27
27
|
# native IO. If you change the format, please make sure that native
|
28
28
|
# IO can still understand NMatrix::VERSION.
|
29
|
-
|
30
|
-
module VERSION
|
29
|
+
module VERSION #:nodoc:
|
31
30
|
MAJOR = 0
|
32
31
|
MINOR = 1
|
33
32
|
TINY = 0
|
34
|
-
PRE = "rc5"
|
33
|
+
# PRE = "rc5"
|
35
34
|
|
36
|
-
STRING = [MAJOR, MINOR, TINY
|
35
|
+
STRING = [MAJOR, MINOR, TINY].compact.join(".")
|
36
|
+
#STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
data/nmatrix.gemspec
CHANGED
@@ -6,12 +6,12 @@ require 'nmatrix/version'
|
|
6
6
|
Gem::Specification.new do |gem|
|
7
7
|
gem.name = "nmatrix"
|
8
8
|
gem.version = NMatrix::VERSION::STRING
|
9
|
-
gem.summary = "NMatrix is
|
10
|
-
gem.description = "NMatrix is
|
9
|
+
gem.summary = "NMatrix is a linear algebra library for Ruby"
|
10
|
+
gem.description = "NMatrix is a linear algebra library for Ruby, written mostly in C and C++."
|
11
11
|
gem.homepage = 'http://sciruby.com'
|
12
12
|
gem.authors = ['John Woods', 'Chris Wailes', 'Aleksey Timin']
|
13
13
|
gem.email = ['john.o.woods@gmail.com']
|
14
|
-
gem.license = 'BSD
|
14
|
+
gem.license = 'BSD 3-clause'
|
15
15
|
gem.post_install_message = <<-EOF
|
16
16
|
***********************************************************
|
17
17
|
Welcome to SciRuby: Tools for Scientific Computing in Ruby!
|
@@ -44,6 +44,7 @@ EOF
|
|
44
44
|
gem.required_ruby_version = '>= 1.9'
|
45
45
|
|
46
46
|
gem.add_dependency 'rdoc', '~>4.0', '>=4.0.1'
|
47
|
+
gem.add_dependency 'packable', '~> 1.3', '>= 1.3.5'
|
47
48
|
gem.add_development_dependency 'rake', '~>10.3'
|
48
49
|
gem.add_development_dependency 'bundler', '~>1.6'
|
49
50
|
gem.add_development_dependency 'rspec', '~>2.14'
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe NMatrix do
|
4
|
+
describe "#to_a" do
|
5
|
+
it "creates an Array with the same dimensions" do
|
6
|
+
n = NMatrix.seq([3,2])
|
7
|
+
expect(n.to_a).to eq([[0, 1], [2, 3], [4, 5]])
|
8
|
+
end
|
9
|
+
|
10
|
+
it "creates an Array with the proper element type" do
|
11
|
+
n = NMatrix.seq([3,2], dtype: :float64)
|
12
|
+
expect(n.to_a).to eq([[0.0, 1.0], [2.0, 3.0], [4.0, 5.0]])
|
13
|
+
end
|
14
|
+
|
15
|
+
it "properly interprets list matrices" do
|
16
|
+
n = NMatrix.seq([3,2], stype: :list)
|
17
|
+
expect(n.to_a).to eq([[0, 1], [2, 3], [4, 5]])
|
18
|
+
end
|
19
|
+
|
20
|
+
it "properly interprets yale matrices" do
|
21
|
+
n = NMatrix.seq([3,2], stype: :yale)
|
22
|
+
expect(n.to_a).to eq([[0, 1], [2, 3], [4, 5]])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe Array do
|
28
|
+
describe "#to_nm" do
|
29
|
+
# [0, 1, 2, 3, 4, 5]
|
30
|
+
let(:a) {(0..5).to_a}
|
31
|
+
|
32
|
+
it "uses a given shape and type" do
|
33
|
+
expect(a.to_nm([3,2]).dtype).to eq :int64
|
34
|
+
expect(a.to_nm([3,2])).to eq(NMatrix.seq([3,2]))
|
35
|
+
end
|
36
|
+
|
37
|
+
it "guesses dtype based on first element" do
|
38
|
+
a[0] = 0.0
|
39
|
+
expect(a.to_nm([3,2]).dtype).to eq :float64
|
40
|
+
end
|
41
|
+
|
42
|
+
it "defaults to dtype :object if necessary" do
|
43
|
+
a = %w(this is an array of strings)
|
44
|
+
expect(a.to_nm([3,2]).dtype).to eq :object
|
45
|
+
expect(a.to_nm([3,2])).to eq(NMatrix.new([3,2], a, dtype: :object))
|
46
|
+
end
|
47
|
+
|
48
|
+
it "attempts to intuit the shape of the Array" do
|
49
|
+
a = [[0, 1], [2, 3], [4, 5]]
|
50
|
+
expect(a.to_nm).to eq(NMatrix.new([3,2], a.flatten))
|
51
|
+
expect(a.to_nm.dtype).to eq :int64
|
52
|
+
end
|
53
|
+
|
54
|
+
it "creates an object Array for inconsistent dimensions" do
|
55
|
+
a = [[0, 1, 2], [3], [4, 5]]
|
56
|
+
expect(a.to_nm).to eq(NMatrix.new([3], a, dtype: :object))
|
57
|
+
expect(a.to_nm.dtype).to eq :object
|
58
|
+
end
|
59
|
+
|
60
|
+
it "intuits shape of Array into multiple dimensions" do
|
61
|
+
a = [[[0], [1]], [[2], [3]], [[4], [5]]]
|
62
|
+
expect(a.to_nm).to eq(NMatrix.new([3,1,2], a.flatten))
|
63
|
+
expect(a).to eq(a.to_nm.to_a)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "is reflective with NMatrix#to_a" do
|
67
|
+
a = [[0, 1, 2], [3], [4, 5]]
|
68
|
+
expect(a).to eq(a.to_nm.to_a)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|