mirror of
https://github.com/josdejong/mathjs.git
synced 2026-01-18 14:59:29 +00:00
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.
293 lines
8.6 KiB
JavaScript
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
|
|
}
|