forked from josdejong/mathjs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmatrixmarket.js
293 lines (280 loc) · 8.75 KB
/
matrixmarket.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
const fs = require('fs')
const typed = require('typed-function')
const math = require('../lib/bundleAny')
const Spa = math.Spa
const DenseMatrix = math.DenseMatrix
const SparseMatrix = math.SparseMatrix
const FibonacciHeap = math.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. (https://math.nist.gov/MatrixMarket/)
*/
const _import = typed('importMatrix', {
Array: function (files) {
return Promise.all(files.map(file => _import(file)))
},
string: function (file) {
const input = fs.createReadStream(file)
return _importFromStream(input)
}
})
module.exports = {
import: _import
}