mathjs/tools/matrixmarket.js
Harry Sarson 74ff86fd4b Store uncompressed matricies and drop dev dep on tar and on q. (#1316)
git handles compression for us so compressing the matricies we use to test
is unnessessary. Additionally, handling the compression made the
code significantly more complex and required pulling in extra packages
from npm.

This commit updates the matrix market code to use uncompressed files.
I have also replaced use of the promise libary 'q' with built in
promises.
2018-11-10 11:40:28 +01:00

293 lines
8.6 KiB
JavaScript

'use strict'
import math from '../src/main'
const fs = require('fs')
const typed = require('typed-function')
const Spa = math.type.Spa
const DenseMatrix = math.type.DenseMatrix
const SparseMatrix = math.type.SparseMatrix
const FibonacciHeap = math.type.FibonacciHeap
const _importFromStream = function (stream) {
return new Promise(function (resolve, reject) {
// header regex
const headerRegex = /%%MatrixMarket ([a-zA-Z]+) ([a-zA-Z]+) ([a-zA-Z]+) ([a-zA-Z]+)/
const coordinateHeaderRegex = /(\d+) (\d+) (\d+)/
const coordinateDataRegex = /(\d+) (\d+) (.*)/
const coordinatePatternRegex = /(\d+) (\d+)/
const arrayHeaderRegex = /(\d+) (\d+)/
const arrayDataRegex = /(\d+)/
// Matrix Market supported formats
const typecodes = ['matrix']
const formats = ['coordinate', 'array']
const datatypes = ['real', 'pattern']
const qualifiers = ['general', 'symmetric']
// matrix data
let mm = null
let buffer = ''
const readHeader = function (line) {
// check line is a header
const matches = line.match(headerRegex)
if (matches !== null) {
// get matches values
const typecode = matches[1]
const format = matches[2]
const datatype = matches[3]
const qualifier = matches[4]
// check typecode
if (typecodes.indexOf(typecode) === -1) {
// typecode not supported
reject(new Error('Matrix Market type code is not supported: ' + typecode))
// close stream
stream.close()
}
// check format
if (formats.indexOf(format) === -1) {
// typecode not supported
reject(new Error('Matrix Market format is not supported: ' + format))
// close stream
stream.close()
}
// check datatype
if (datatypes.indexOf(datatype) === -1) {
// typecode not supported
reject(new Error('Matrix Market datatype is not supported: ' + datatype))
// close stream
stream.close()
}
if (qualifiers.indexOf(qualifier) === -1) {
// typecode not supported
reject(new Error('Matrix Market qualifier is not supported: ' + qualifier))
// close stream
stream.close()
}
// initialize matrix market structure
mm = {
typecode: typecode,
format: format,
datatype: datatype,
qualifier: qualifier,
data: null
}
} else {
// invalid header
reject(new Error('Invalid file header: ' + line))
// close stream
stream.close()
}
}
const readStructure = function (line) {
// vars
let matches
// check matrix format
switch (mm.format) {
case 'coordinate':
// rows columns entries
matches = line.match(coordinateHeaderRegex)
if (matches !== null) {
// read structure
mm.rows = parseInt(matches[1])
mm.columns = parseInt(matches[2])
mm.entries = parseInt(matches[3])
// initialize data
mm.data = new FibonacciHeap()
}
break
case 'array':
matches = line.match(arrayHeaderRegex)
if (matches !== null) {
// read structure
mm.rows = parseInt(matches[1])
mm.columns = parseInt(matches[2])
// initialize data
mm.data = []
}
break
}
}
const readValue = function (text) {
// check datatype
switch (mm.datatype) {
case 'real':
return parseFloat(text)
case 'pattern':
return 1
}
}
const readData = function (line) {
// vars
let matches
// check matrix format
switch (mm.format) {
case 'coordinate':
// regex to use
const rx = mm.datatype !== 'pattern' ? coordinateDataRegex : coordinatePatternRegex
// check data line is correct
matches = line.match(rx)
if (matches !== null) {
// row, columns, value
const r = parseInt(matches[1]) - 1
const c = parseInt(matches[2]) - 1
const v = readValue(matches.length === 4 ? matches[3] : null)
// insert entry
mm.data.insert(c, { i: r, j: c, v: v })
// check matrix is simmetric
if (mm.qualifier === 'symmetric' && c !== r) {
// insert entry
mm.data.insert(r, { i: c, j: r, v: v })
}
}
break
case 'array':
// check data line is correct
matches = line.match(arrayDataRegex)
if (matches !== null) {
// get values in row
const values = []
for (let j = 1; j < matches.length; j++) { values.push(readValue(matches[j])) }
// push entry
mm.data.push(values)
}
break
}
}
const processLine = function (line) {
// check this is the first line
if (mm !== null) {
// skip all comments
if (line.charAt(0) !== '%') {
// check data is ready to be processed
if (mm.data !== null) {
// it is a data row
readData(line)
} else {
// read matrix structure
readStructure(line)
}
}
} else {
// read header, initialize data
readHeader(line)
}
}
stream.on('data', function (chunk) {
// concatenate chunk
buffer += chunk
// eol
let index = buffer.indexOf('\n')
// process lines
while (index !== -1) {
// extract line
const line = buffer.substr(0, index)
// process line
processLine(line.trim())
// update buffer
buffer = buffer.length > index ? buffer.substr(index + 1) : ''
// next line
index = buffer.indexOf('\n')
}
})
stream.on('end', function () {
// check mm
if (mm !== null) {
// process matrix format
switch (mm.format) {
case 'coordinate':
// CCS structure
const values = mm.datatype !== 'pattern' ? [] : undefined
const index = []
const ptr = []
const datatype = mm.datatype === 'real' ? 'number' : undefined
// mm data & pointer
const d = mm.data
let p = -1
let spa = new Spa(mm.rows)
// push value
const pushValue = function (i, v) {
// push row
index.push(i)
// check there is a value (pattern matrix)
if (values) { values.push(v) }
}
// extract node (column sorted)
let n = d.extractMinimum()
// loop all nodes
while (n !== null) {
// check column changed
if (p !== n.key) {
// process sparse accumulator
spa.forEach(0, mm.rows, pushValue)
// process columns from p + 1 to n.j
for (let j = p + 1; j <= n.key; j++) {
// ptr update
ptr.push(index.length)
}
// create sparse accumulator
spa = new Spa(mm.rows)
// reset p
p = n.key
}
// store value in spa
spa.set(n.value.i, n.value.v)
// extract node
n = d.extractMinimum()
}
// process sparse accumulator
spa.forEach(0, mm.rows, pushValue)
// ptr update
ptr.push(index.length)
// resolve promise
resolve(new SparseMatrix({
values: values,
index: index,
ptr: ptr,
size: [mm.rows, mm.columns],
datatype: datatype
}))
break
case 'array':
// resolve promise
console.log(mm.data)
resolve(new DenseMatrix({
data: mm.data,
size: [mm.rows, mm.columns]
}))
break
}
}
})
stream.on('error', function (e) {
// reject promise
reject(new Error(e))
})
})
}
/**
* Imports a Matrix Market matrix from the filesystem. (http://math.nist.gov/MatrixMarket/)
*/
const _import = typed('importMatrix', {
'Array': function (files) {
return Promise.all(files.map(file => _import(file)))
},
'string': function (file) {
let input = fs.createReadStream(file)
return _importFromStream(input)
}
})
module.exports = {
import: _import
}