lib/nmatrix/io/harwell_boeing.rb
#--
# = NMatrix
#
# A linear algebra library for scientific computation in Ruby.
# NMatrix is part of SciRuby.
#
# NMatrix was originally inspired by and derived from NArray, by
# Masahiro Tanaka: http://narray.rubyforge.org
#
# == Copyright Information
#
# SciRuby is Copyright (c) 2010 - 2016, Ruby Science Foundation
# NMatrix is Copyright (c) 2012 - 2016, John Woods and the Ruby Science Foundation
#
# Please see LICENSE.txt for additional copyright notices.
#
# == Contributing
#
# By contributing source code to SciRuby, you agree to be bound by
# our Contributor Agreement:
#
# * https://github.com/SciRuby/sciruby/wiki/Contributor-Agreement
#
# == io/matlab/harwell_boeing.rb
#
# Harwell Boeing file reader (and eventually writer too).
# => Supports only assembled, non-symmetric, real matrices
# => Data types supported are exponential, floating point and integer
# => Returned NMatrix is of type :float64
#++
require_relative './fortran_format.rb'
class NMatrix
module IO
module HarwellBoeing
class << self
# Loads the contents of a valid Harwell Boeing format file and
# returns an NMatrix object with the values of the file and optionally
# only the header info.
#
# Supports only assembled, non-symmetric, real matrices. File name must
# have matrix type as extension.
#
# Example - test_file.rua
#
# == Arguments
#
# * +file_path+ - Path of the Harwell Boeing file to load.
# * +opts+ - Options for specifying whether you want
# the values and header or only the header.
#
# == Options
#
# * +:header+ - If specified as *true*, will return only the header of
# the HB file.Will return the NMatrix object and
# header as an array if left blank.
#
# == Usage
#
# mat, head = NMatrix::IO::HarwellBoeing.load("test_file.rua")
#
# head = NMatrix::IO::HarwellBoeing.load("test_file.rua", {header: true})
#
# == Alternate Usage
#
# You can specify the file using NMatrix::IO::Reader.new("path/to/file")
# and then call *header* or *values* on the resulting object.
def load file_path, opts={}
hb_obj = NMatrix::IO::HarwellBoeing::Reader.new(file_path)
return hb_obj.header if opts[:header]
[hb_obj.values, hb_obj.header]
end
end
class Reader
def initialize file_name
raise(IOError, "Unsupported file format. Specify file as \
file_name.rua.") if !file_name.match(/.*\.[rR][uU][aA]/)
@file_name = file_name
@header = {}
@body = nil
end
def header
return @header if !@header.empty?
@file = File.open @file_name, "r"
line = @file.gets
@header[:title] = line[0...72].strip
@header[:key] = line[72...80].strip
line = @file.gets
@header[:totcrd] = line[0...14] .strip.to_i
@header[:ptrcrd] = line[14...28].strip.to_i
@header[:indcrd] = line[28...42].strip.to_i
@header[:valcrd] = line[42...56].strip.to_i
@header[:rhscrd] = line[56...70].strip.to_i
raise(IOError, "Right hand sides not supported.") \
if @header[:rhscrd] > 0
line = @file.gets
@header[:mxtype] = line[0...3]
raise(IOError, "Currently supports only real, assembled, unsymmetric \
matrices.") if !@header[:mxtype].match(/RUA/)
@header[:nrow] = line[13...28].strip.to_i
@header[:ncol] = line[28...42].strip.to_i
@header[:nnzero] = line[42...56].strip.to_i
@header[:neltvl] = line[56...70].strip.to_i
line = @file.gets
fortran_reader = NMatrix::IO::FortranFormat::Reader
@header[:ptrfmt] = fortran_reader.new(line[0...16].strip) .parse
@header[:indfmt] = fortran_reader.new(line[16...32].strip).parse
@header[:valfmt] = fortran_reader.new(line[32...52].strip).parse
@header[:rhsfmt] = fortran_reader.new(line[52...72].strip).parse
@header
end
def values
@header = header if @header.empty?
@file.lineno = 5 if @file.lineno != 5
@matrix = NMatrix.new([ @header[:nrow], @header[:ncol] ],
0, dtype: :float64)
read_column_pointers
read_row_indices
read_values
@file.close
assemble_matrix
@matrix
end
private
def read_column_pointers
@col_ptrs = []
pointer_lines = @header[:ptrcrd]
pointers_per_line = @header[:ptrfmt][:repeat]
pointer_width = @header[:ptrfmt][:field_width]
@col_ptrs = read_numbers :to_i, pointer_lines, pointers_per_line,
pointer_width
@col_ptrs.map! {|c| c -= 1}
end
def read_row_indices
@row_indices = []
row_lines = @header[:indcrd]
indices_per_line = @header[:indfmt][:repeat]
row_width = @header[:indfmt][:field_width]
@row_indices = read_numbers :to_i, row_lines, indices_per_line,
row_width
@row_indices.map! {|r| r -= 1}
end
def read_values
@vals = []
value_lines = @header[:valcrd]
values_per_line = @header[:valfmt][:repeat]
value_width = @header[:valfmt][:field_width]
@vals = read_numbers :to_f, value_lines, values_per_line,
value_width
end
def read_numbers to_dtype, num_of_lines, numbers_per_line, number_width
data = []
num_of_lines.times do
line = @file.gets
index = 0
numbers_per_line.times do
delimiter = index + number_width
data << line[index...delimiter].strip.send(to_dtype)
break if line.length <= delimiter
index += number_width
end
end
data
end
def assemble_matrix
col = 0
@col_ptrs[0..-2].each_index do |index|
@col_ptrs[index].upto(@col_ptrs[index+1] - 1) do |row_ptr|
row = @row_indices[row_ptr]
@matrix[row, col] = @vals[row_ptr]
end
col += 1
end
end
end
end
end
end