UNPKG

42.6 kBJavaScriptView Raw
1'use strict'
2
3const util = require('../../utils/index')
4const DimensionError = require('../../error/DimensionError')
5
6const array = util.array
7const object = util.object
8const string = util.string
9const number = util.number
10
11const isArray = Array.isArray
12const isNumber = number.isNumber
13const isInteger = number.isInteger
14const isString = string.isString
15
16const validateIndex = array.validateIndex
17
18function factory (type, config, load, typed) {
19 const Matrix = load(require('./Matrix')) // force loading Matrix (do not use via type.Matrix)
20 const equalScalar = load(require('../../function/relational/equalScalar'))
21 const getArrayDataType = load(require('./utils/getArrayDataType'))
22
23 /**
24 * Sparse Matrix implementation. This type implements a Compressed Column Storage format
25 * for sparse matrices.
26 * @class SparseMatrix
27 */
28 function SparseMatrix (data, datatype) {
29 if (!(this instanceof SparseMatrix)) { throw new SyntaxError('Constructor must be called with the new operator') }
30 if (datatype && !isString(datatype)) { throw new Error('Invalid datatype: ' + datatype) }
31
32 if (type.isMatrix(data)) {
33 // create from matrix
34 _createFromMatrix(this, data, datatype)
35 } else if (data && isArray(data.index) && isArray(data.ptr) && isArray(data.size)) {
36 // initialize fields
37 this._values = data.values
38 this._index = data.index
39 this._ptr = data.ptr
40 this._size = data.size
41 this._datatype = datatype || data.datatype
42 } else if (isArray(data)) {
43 // create from array
44 _createFromArray(this, data, datatype)
45 } else if (data) {
46 // unsupported type
47 throw new TypeError('Unsupported type of data (' + util.types.type(data) + ')')
48 } else {
49 // nothing provided
50 this._values = []
51 this._index = []
52 this._ptr = [0]
53 this._size = [0, 0]
54 this._datatype = datatype
55 }
56 }
57
58 function _createFromMatrix (matrix, source, datatype) {
59 // check matrix type
60 if (source.type === 'SparseMatrix') {
61 // clone arrays
62 matrix._values = source._values ? object.clone(source._values) : undefined
63 matrix._index = object.clone(source._index)
64 matrix._ptr = object.clone(source._ptr)
65 matrix._size = object.clone(source._size)
66 matrix._datatype = datatype || source._datatype
67 } else {
68 // build from matrix data
69 _createFromArray(matrix, source.valueOf(), datatype || source._datatype)
70 }
71 }
72
73 function _createFromArray (matrix, data, datatype) {
74 // initialize fields
75 matrix._values = []
76 matrix._index = []
77 matrix._ptr = []
78 matrix._datatype = datatype
79 // discover rows & columns, do not use math.size() to avoid looping array twice
80 const rows = data.length
81 let columns = 0
82
83 // equal signature to use
84 let eq = equalScalar
85 // zero value
86 let zero = 0
87
88 if (isString(datatype)) {
89 // find signature that matches (datatype, datatype)
90 eq = typed.find(equalScalar, [datatype, datatype]) || equalScalar
91 // convert 0 to the same datatype
92 zero = typed.convert(0, datatype)
93 }
94
95 // check we have rows (empty array)
96 if (rows > 0) {
97 // column index
98 let j = 0
99 do {
100 // store pointer to values index
101 matrix._ptr.push(matrix._index.length)
102 // loop rows
103 for (let i = 0; i < rows; i++) {
104 // current row
105 const row = data[i]
106 // check row is an array
107 if (isArray(row)) {
108 // update columns if needed (only on first column)
109 if (j === 0 && columns < row.length) { columns = row.length }
110 // check row has column
111 if (j < row.length) {
112 // value
113 const v = row[j]
114 // check value != 0
115 if (!eq(v, zero)) {
116 // store value
117 matrix._values.push(v)
118 // index
119 matrix._index.push(i)
120 }
121 }
122 } else {
123 // update columns if needed (only on first column)
124 if (j === 0 && columns < 1) { columns = 1 }
125 // check value != 0 (row is a scalar)
126 if (!eq(row, zero)) {
127 // store value
128 matrix._values.push(row)
129 // index
130 matrix._index.push(i)
131 }
132 }
133 }
134 // increment index
135 j++
136 }
137 while (j < columns)
138 }
139 // store number of values in ptr
140 matrix._ptr.push(matrix._index.length)
141 // size
142 matrix._size = [rows, columns]
143 }
144
145 SparseMatrix.prototype = new Matrix()
146
147 /**
148 * Attach type information
149 */
150 SparseMatrix.prototype.type = 'SparseMatrix'
151 SparseMatrix.prototype.isSparseMatrix = true
152
153 /**
154 * Get the matrix type
155 *
156 * Usage:
157 * const matrixType = matrix.getDataType() // retrieves the matrix type
158 *
159 * @memberOf SparseMatrix
160 * @return {string} type information; if multiple types are found from the Matrix, it will return "mixed"
161 */
162 SparseMatrix.prototype.getDataType = function () {
163 return getArrayDataType(this._values)
164 }
165
166 /**
167 * Get the storage format used by the matrix.
168 *
169 * Usage:
170 * const format = matrix.storage() // retrieve storage format
171 *
172 * @memberof SparseMatrix
173 * @return {string} The storage format.
174 */
175 SparseMatrix.prototype.storage = function () {
176 return 'sparse'
177 }
178
179 /**
180 * Get the datatype of the data stored in the matrix.
181 *
182 * Usage:
183 * const format = matrix.datatype() // retrieve matrix datatype
184 *
185 * @memberof SparseMatrix
186 * @return {string} The datatype.
187 */
188 SparseMatrix.prototype.datatype = function () {
189 return this._datatype
190 }
191
192 /**
193 * Create a new SparseMatrix
194 * @memberof SparseMatrix
195 * @param {Array} data
196 * @param {string} [datatype]
197 */
198 SparseMatrix.prototype.create = function (data, datatype) {
199 return new SparseMatrix(data, datatype)
200 }
201
202 /**
203 * Get the matrix density.
204 *
205 * Usage:
206 * const density = matrix.density() // retrieve matrix density
207 *
208 * @memberof SparseMatrix
209 * @return {number} The matrix density.
210 */
211 SparseMatrix.prototype.density = function () {
212 // rows & columns
213 const rows = this._size[0]
214 const columns = this._size[1]
215 // calculate density
216 return rows !== 0 && columns !== 0 ? (this._index.length / (rows * columns)) : 0
217 }
218
219 /**
220 * Get a subset of the matrix, or replace a subset of the matrix.
221 *
222 * Usage:
223 * const subset = matrix.subset(index) // retrieve subset
224 * const value = matrix.subset(index, replacement) // replace subset
225 *
226 * @memberof SparseMatrix
227 * @param {Index} index
228 * @param {Array | Maytrix | *} [replacement]
229 * @param {*} [defaultValue=0] Default value, filled in on new entries when
230 * the matrix is resized. If not provided,
231 * new matrix elements will be filled with zeros.
232 */
233 SparseMatrix.prototype.subset = function (index, replacement, defaultValue) { // check it is a pattern matrix
234 if (!this._values) { throw new Error('Cannot invoke subset on a Pattern only matrix') }
235
236 // check arguments
237 switch (arguments.length) {
238 case 1:
239 return _getsubset(this, index)
240
241 // intentional fall through
242 case 2:
243 case 3:
244 return _setsubset(this, index, replacement, defaultValue)
245
246 default:
247 throw new SyntaxError('Wrong number of arguments')
248 }
249 }
250
251 function _getsubset (matrix, idx) {
252 // check idx
253 if (!type.isIndex(idx)) {
254 throw new TypeError('Invalid index')
255 }
256
257 const isScalar = idx.isScalar()
258 if (isScalar) {
259 // return a scalar
260 return matrix.get(idx.min())
261 }
262 // validate dimensions
263 const size = idx.size()
264 if (size.length !== matrix._size.length) {
265 throw new DimensionError(size.length, matrix._size.length)
266 }
267
268 // vars
269 let i, ii, k, kk
270
271 // validate if any of the ranges in the index is out of range
272 const min = idx.min()
273 const max = idx.max()
274 for (i = 0, ii = matrix._size.length; i < ii; i++) {
275 validateIndex(min[i], matrix._size[i])
276 validateIndex(max[i], matrix._size[i])
277 }
278
279 // matrix arrays
280 const mvalues = matrix._values
281 const mindex = matrix._index
282 const mptr = matrix._ptr
283
284 // rows & columns dimensions for result matrix
285 const rows = idx.dimension(0)
286 const columns = idx.dimension(1)
287
288 // workspace & permutation vector
289 const w = []
290 const pv = []
291
292 // loop rows in resulting matrix
293 rows.forEach(function (i, r) {
294 // update permutation vector
295 pv[i] = r[0]
296 // mark i in workspace
297 w[i] = true
298 })
299
300 // result matrix arrays
301 const values = mvalues ? [] : undefined
302 const index = []
303 const ptr = []
304
305 // loop columns in result matrix
306 columns.forEach(function (j) {
307 // update ptr
308 ptr.push(index.length)
309 // loop values in column j
310 for (k = mptr[j], kk = mptr[j + 1]; k < kk; k++) {
311 // row
312 i = mindex[k]
313 // check row is in result matrix
314 if (w[i] === true) {
315 // push index
316 index.push(pv[i])
317 // check we need to process values
318 if (values) { values.push(mvalues[k]) }
319 }
320 }
321 })
322 // update ptr
323 ptr.push(index.length)
324
325 // return matrix
326 return new SparseMatrix({
327 values: values,
328 index: index,
329 ptr: ptr,
330 size: size,
331 datatype: matrix._datatype
332 })
333 }
334
335 function _setsubset (matrix, index, submatrix, defaultValue) {
336 // check index
337 if (!index || index.isIndex !== true) {
338 throw new TypeError('Invalid index')
339 }
340
341 // get index size and check whether the index contains a single value
342 const iSize = index.size()
343 const isScalar = index.isScalar()
344
345 // calculate the size of the submatrix, and convert it into an Array if needed
346 let sSize
347 if (type.isMatrix(submatrix)) {
348 // submatrix size
349 sSize = submatrix.size()
350 // use array representation
351 submatrix = submatrix.toArray()
352 } else {
353 // get submatrix size (array, scalar)
354 sSize = array.size(submatrix)
355 }
356
357 // check index is a scalar
358 if (isScalar) {
359 // verify submatrix is a scalar
360 if (sSize.length !== 0) {
361 throw new TypeError('Scalar expected')
362 }
363 // set value
364 matrix.set(index.min(), submatrix, defaultValue)
365 } else {
366 // validate dimensions, index size must be one or two dimensions
367 if (iSize.length !== 1 && iSize.length !== 2) {
368 throw new DimensionError(iSize.length, matrix._size.length, '<')
369 }
370
371 // check submatrix and index have the same dimensions
372 if (sSize.length < iSize.length) {
373 // calculate number of missing outer dimensions
374 let i = 0
375 let outer = 0
376 while (iSize[i] === 1 && sSize[i] === 1) {
377 i++
378 }
379 while (iSize[i] === 1) {
380 outer++
381 i++
382 }
383 // unsqueeze both outer and inner dimensions
384 submatrix = array.unsqueeze(submatrix, iSize.length, outer, sSize)
385 }
386
387 // check whether the size of the submatrix matches the index size
388 if (!object.deepEqual(iSize, sSize)) {
389 throw new DimensionError(iSize, sSize, '>')
390 }
391
392 // offsets
393 const x0 = index.min()[0]
394 const y0 = index.min()[1]
395
396 // submatrix rows and columns
397 const m = sSize[0]
398 const n = sSize[1]
399
400 // loop submatrix
401 for (let x = 0; x < m; x++) {
402 // loop columns
403 for (let y = 0; y < n; y++) {
404 // value at i, j
405 const v = submatrix[x][y]
406 // invoke set (zero value will remove entry from matrix)
407 matrix.set([x + x0, y + y0], v, defaultValue)
408 }
409 }
410 }
411 return matrix
412 }
413
414 /**
415 * Get a single element from the matrix.
416 * @memberof SparseMatrix
417 * @param {number[]} index Zero-based index
418 * @return {*} value
419 */
420 SparseMatrix.prototype.get = function (index) {
421 if (!isArray(index)) { throw new TypeError('Array expected') }
422 if (index.length !== this._size.length) { throw new DimensionError(index.length, this._size.length) }
423
424 // check it is a pattern matrix
425 if (!this._values) { throw new Error('Cannot invoke get on a Pattern only matrix') }
426
427 // row and column
428 const i = index[0]
429 const j = index[1]
430
431 // check i, j are valid
432 validateIndex(i, this._size[0])
433 validateIndex(j, this._size[1])
434
435 // find value index
436 const k = _getValueIndex(i, this._ptr[j], this._ptr[j + 1], this._index)
437 // check k is prior to next column k and it is in the correct row
438 if (k < this._ptr[j + 1] && this._index[k] === i) { return this._values[k] }
439
440 return 0
441 }
442
443 /**
444 * Replace a single element in the matrix.
445 * @memberof SparseMatrix
446 * @param {number[]} index Zero-based index
447 * @param {*} value
448 * @param {*} [defaultValue] Default value, filled in on new entries when
449 * the matrix is resized. If not provided,
450 * new matrix elements will be set to zero.
451 * @return {SparseMatrix} self
452 */
453 SparseMatrix.prototype.set = function (index, v, defaultValue) {
454 if (!isArray(index)) { throw new TypeError('Array expected') }
455 if (index.length !== this._size.length) { throw new DimensionError(index.length, this._size.length) }
456
457 // check it is a pattern matrix
458 if (!this._values) { throw new Error('Cannot invoke set on a Pattern only matrix') }
459
460 // row and column
461 const i = index[0]
462 const j = index[1]
463
464 // rows & columns
465 let rows = this._size[0]
466 let columns = this._size[1]
467
468 // equal signature to use
469 let eq = equalScalar
470 // zero value
471 let zero = 0
472
473 if (isString(this._datatype)) {
474 // find signature that matches (datatype, datatype)
475 eq = typed.find(equalScalar, [this._datatype, this._datatype]) || equalScalar
476 // convert 0 to the same datatype
477 zero = typed.convert(0, this._datatype)
478 }
479
480 // check we need to resize matrix
481 if (i > rows - 1 || j > columns - 1) {
482 // resize matrix
483 _resize(this, Math.max(i + 1, rows), Math.max(j + 1, columns), defaultValue)
484 // update rows & columns
485 rows = this._size[0]
486 columns = this._size[1]
487 }
488
489 // check i, j are valid
490 validateIndex(i, rows)
491 validateIndex(j, columns)
492
493 // find value index
494 const k = _getValueIndex(i, this._ptr[j], this._ptr[j + 1], this._index)
495 // check k is prior to next column k and it is in the correct row
496 if (k < this._ptr[j + 1] && this._index[k] === i) {
497 // check value != 0
498 if (!eq(v, zero)) {
499 // update value
500 this._values[k] = v
501 } else {
502 // remove value from matrix
503 _remove(k, j, this._values, this._index, this._ptr)
504 }
505 } else {
506 // insert value @ (i, j)
507 _insert(k, i, j, v, this._values, this._index, this._ptr)
508 }
509
510 return this
511 }
512
513 function _getValueIndex (i, top, bottom, index) {
514 // check row is on the bottom side
515 if (bottom - top === 0) { return bottom }
516 // loop rows [top, bottom[
517 for (let r = top; r < bottom; r++) {
518 // check we found value index
519 if (index[r] === i) { return r }
520 }
521 // we did not find row
522 return top
523 }
524
525 function _remove (k, j, values, index, ptr) {
526 // remove value @ k
527 values.splice(k, 1)
528 index.splice(k, 1)
529 // update pointers
530 for (let x = j + 1; x < ptr.length; x++) { ptr[x]-- }
531 }
532
533 function _insert (k, i, j, v, values, index, ptr) {
534 // insert value
535 values.splice(k, 0, v)
536 // update row for k
537 index.splice(k, 0, i)
538 // update column pointers
539 for (let x = j + 1; x < ptr.length; x++) { ptr[x]++ }
540 }
541
542 /**
543 * Resize the matrix to the given size. Returns a copy of the matrix when
544 * `copy=true`, otherwise return the matrix itself (resize in place).
545 *
546 * @memberof SparseMatrix
547 * @param {number[]} size The new size the matrix should have.
548 * @param {*} [defaultValue=0] Default value, filled in on new entries.
549 * If not provided, the matrix elements will
550 * be filled with zeros.
551 * @param {boolean} [copy] Return a resized copy of the matrix
552 *
553 * @return {Matrix} The resized matrix
554 */
555 SparseMatrix.prototype.resize = function (size, defaultValue, copy) {
556 // validate arguments
557 if (!isArray(size)) { throw new TypeError('Array expected') }
558 if (size.length !== 2) { throw new Error('Only two dimensions matrix are supported') }
559
560 // check sizes
561 size.forEach(function (value) {
562 if (!number.isNumber(value) || !number.isInteger(value) || value < 0) {
563 throw new TypeError('Invalid size, must contain positive integers ' +
564 '(size: ' + string.format(size) + ')')
565 }
566 })
567
568 // matrix to resize
569 const m = copy ? this.clone() : this
570 // resize matrix
571 return _resize(m, size[0], size[1], defaultValue)
572 }
573
574 function _resize (matrix, rows, columns, defaultValue) {
575 // value to insert at the time of growing matrix
576 let value = defaultValue || 0
577
578 // equal signature to use
579 let eq = equalScalar
580 // zero value
581 let zero = 0
582
583 if (isString(matrix._datatype)) {
584 // find signature that matches (datatype, datatype)
585 eq = typed.find(equalScalar, [matrix._datatype, matrix._datatype]) || equalScalar
586 // convert 0 to the same datatype
587 zero = typed.convert(0, matrix._datatype)
588 // convert value to the same datatype
589 value = typed.convert(value, matrix._datatype)
590 }
591
592 // should we insert the value?
593 const ins = !eq(value, zero)
594
595 // old columns and rows
596 const r = matrix._size[0]
597 let c = matrix._size[1]
598
599 let i, j, k
600
601 // check we need to increase columns
602 if (columns > c) {
603 // loop new columns
604 for (j = c; j < columns; j++) {
605 // update matrix._ptr for current column
606 matrix._ptr[j] = matrix._values.length
607 // check we need to insert matrix._values
608 if (ins) {
609 // loop rows
610 for (i = 0; i < r; i++) {
611 // add new matrix._values
612 matrix._values.push(value)
613 // update matrix._index
614 matrix._index.push(i)
615 }
616 }
617 }
618 // store number of matrix._values in matrix._ptr
619 matrix._ptr[columns] = matrix._values.length
620 } else if (columns < c) {
621 // truncate matrix._ptr
622 matrix._ptr.splice(columns + 1, c - columns)
623 // truncate matrix._values and matrix._index
624 matrix._values.splice(matrix._ptr[columns], matrix._values.length)
625 matrix._index.splice(matrix._ptr[columns], matrix._index.length)
626 }
627 // update columns
628 c = columns
629
630 // check we need to increase rows
631 if (rows > r) {
632 // check we have to insert values
633 if (ins) {
634 // inserts
635 let n = 0
636 // loop columns
637 for (j = 0; j < c; j++) {
638 // update matrix._ptr for current column
639 matrix._ptr[j] = matrix._ptr[j] + n
640 // where to insert matrix._values
641 k = matrix._ptr[j + 1] + n
642 // pointer
643 let p = 0
644 // loop new rows, initialize pointer
645 for (i = r; i < rows; i++, p++) {
646 // add value
647 matrix._values.splice(k + p, 0, value)
648 // update matrix._index
649 matrix._index.splice(k + p, 0, i)
650 // increment inserts
651 n++
652 }
653 }
654 // store number of matrix._values in matrix._ptr
655 matrix._ptr[c] = matrix._values.length
656 }
657 } else if (rows < r) {
658 // deletes
659 let d = 0
660 // loop columns
661 for (j = 0; j < c; j++) {
662 // update matrix._ptr for current column
663 matrix._ptr[j] = matrix._ptr[j] - d
664 // where matrix._values start for next column
665 const k0 = matrix._ptr[j]
666 const k1 = matrix._ptr[j + 1] - d
667 // loop matrix._index
668 for (k = k0; k < k1; k++) {
669 // row
670 i = matrix._index[k]
671 // check we need to delete value and matrix._index
672 if (i > rows - 1) {
673 // remove value
674 matrix._values.splice(k, 1)
675 // remove item from matrix._index
676 matrix._index.splice(k, 1)
677 // increase deletes
678 d++
679 }
680 }
681 }
682 // update matrix._ptr for current column
683 matrix._ptr[j] = matrix._values.length
684 }
685 // update matrix._size
686 matrix._size[0] = rows
687 matrix._size[1] = columns
688 // return matrix
689 return matrix
690 }
691
692 /**
693 * Reshape the matrix to the given size. Returns a copy of the matrix when
694 * `copy=true`, otherwise return the matrix itself (reshape in place).
695 *
696 * NOTE: This might be better suited to copy by default, instead of modifying
697 * in place. For now, it operates in place to remain consistent with
698 * resize().
699 *
700 * @memberof SparseMatrix
701 * @param {number[]} size The new size the matrix should have.
702 * @param {boolean} [copy] Return a reshaped copy of the matrix
703 *
704 * @return {Matrix} The reshaped matrix
705 */
706 SparseMatrix.prototype.reshape = function (size, copy) {
707 // validate arguments
708 if (!isArray(size)) { throw new TypeError('Array expected') }
709 if (size.length !== 2) { throw new Error('Sparse matrices can only be reshaped in two dimensions') }
710
711 // check sizes
712 size.forEach(function (value) {
713 if (!number.isNumber(value) || !number.isInteger(value) || value < 0) {
714 throw new TypeError('Invalid size, must contain positive integers ' +
715 '(size: ' + string.format(size) + ')')
716 }
717 })
718
719 // m * n must not change
720 if (this._size[0] * this._size[1] !== size[0] * size[1]) {
721 throw new Error('Reshaping sparse matrix will result in the wrong number of elements')
722 }
723
724 // matrix to reshape
725 const m = copy ? this.clone() : this
726
727 // return unchanged if the same shape
728 if (this._size[0] === size[0] && this._size[1] === size[1]) {
729 return m
730 }
731
732 // Convert to COO format (generate a column index)
733 const colIndex = []
734 for (let i = 0; i < m._ptr.length; i++) {
735 for (let j = 0; j < m._ptr[i + 1] - m._ptr[i]; j++) {
736 colIndex.push(i)
737 }
738 }
739
740 // Clone the values array
741 const values = m._values.slice()
742
743 // Clone the row index array
744 const rowIndex = m._index.slice()
745
746 // Transform the (row, column) indices
747 for (let i = 0; i < m._index.length; i++) {
748 const r1 = rowIndex[i]
749 const c1 = colIndex[i]
750 const flat = r1 * m._size[1] + c1
751 colIndex[i] = flat % size[1]
752 rowIndex[i] = Math.floor(flat / size[1])
753 }
754
755 // Now reshaping is supposed to preserve the row-major order, BUT these sparse matrices are stored
756 // in column-major order, so we have to reorder the value array now. One option is to use a multisort,
757 // sorting several arrays based on some other array.
758
759 // OR, we could easily just:
760
761 // 1. Remove all values from the matrix
762 m._values.length = 0
763 m._index.length = 0
764 m._ptr.length = size[1] + 1
765 m._size = size.slice()
766 for (let i = 0; i < m._ptr.length; i++) {
767 m._ptr[i] = 0
768 }
769
770 // 2. Re-insert all elements in the proper order (simplified code from SparseMatrix.prototype.set)
771 // This step is probably the most time-consuming
772 for (let h = 0; h < values.length; h++) {
773 const i = rowIndex[h]
774 const j = colIndex[h]
775 const v = values[h]
776 const k = _getValueIndex(i, m._ptr[j], m._ptr[j + 1], m._index)
777 _insert(k, i, j, v, m._values, m._index, m._ptr)
778 }
779
780 // The value indices are inserted out of order, but apparently that's... still OK?
781
782 return m
783 }
784
785 /**
786 * Create a clone of the matrix
787 * @memberof SparseMatrix
788 * @return {SparseMatrix} clone
789 */
790 SparseMatrix.prototype.clone = function () {
791 const m = new SparseMatrix({
792 values: this._values ? object.clone(this._values) : undefined,
793 index: object.clone(this._index),
794 ptr: object.clone(this._ptr),
795 size: object.clone(this._size),
796 datatype: this._datatype
797 })
798 return m
799 }
800
801 /**
802 * Retrieve the size of the matrix.
803 * @memberof SparseMatrix
804 * @returns {number[]} size
805 */
806 SparseMatrix.prototype.size = function () {
807 return this._size.slice(0) // copy the Array
808 }
809
810 /**
811 * Create a new matrix with the results of the callback function executed on
812 * each entry of the matrix.
813 * @memberof SparseMatrix
814 * @param {Function} callback The callback function is invoked with three
815 * parameters: the value of the element, the index
816 * of the element, and the Matrix being traversed.
817 * @param {boolean} [skipZeros] Invoke callback function for non-zero values only.
818 *
819 * @return {SparseMatrix} matrix
820 */
821 SparseMatrix.prototype.map = function (callback, skipZeros) {
822 // check it is a pattern matrix
823 if (!this._values) { throw new Error('Cannot invoke map on a Pattern only matrix') }
824 // matrix instance
825 const me = this
826 // rows and columns
827 const rows = this._size[0]
828 const columns = this._size[1]
829 // invoke callback
830 const invoke = function (v, i, j) {
831 // invoke callback
832 return callback(v, [i, j], me)
833 }
834 // invoke _map
835 return _map(this, 0, rows - 1, 0, columns - 1, invoke, skipZeros)
836 }
837
838 /**
839 * Create a new matrix with the results of the callback function executed on the interval
840 * [minRow..maxRow, minColumn..maxColumn].
841 */
842 function _map (matrix, minRow, maxRow, minColumn, maxColumn, callback, skipZeros) {
843 // result arrays
844 const values = []
845 const index = []
846 const ptr = []
847
848 // equal signature to use
849 let eq = equalScalar
850 // zero value
851 let zero = 0
852
853 if (isString(matrix._datatype)) {
854 // find signature that matches (datatype, datatype)
855 eq = typed.find(equalScalar, [matrix._datatype, matrix._datatype]) || equalScalar
856 // convert 0 to the same datatype
857 zero = typed.convert(0, matrix._datatype)
858 }
859
860 // invoke callback
861 const invoke = function (v, x, y) {
862 // invoke callback
863 v = callback(v, x, y)
864 // check value != 0
865 if (!eq(v, zero)) {
866 // store value
867 values.push(v)
868 // index
869 index.push(x)
870 }
871 }
872 // loop columns
873 for (let j = minColumn; j <= maxColumn; j++) {
874 // store pointer to values index
875 ptr.push(values.length)
876 // k0 <= k < k1 where k0 = _ptr[j] && k1 = _ptr[j+1]
877 const k0 = matrix._ptr[j]
878 const k1 = matrix._ptr[j + 1]
879 // row pointer
880 let p = minRow
881 // loop k within [k0, k1[
882 for (let k = k0; k < k1; k++) {
883 // row index
884 const i = matrix._index[k]
885 // check i is in range
886 if (i >= minRow && i <= maxRow) {
887 // zero values
888 if (!skipZeros) {
889 for (let x = p; x < i; x++) { invoke(0, x - minRow, j - minColumn) }
890 }
891 // value @ k
892 invoke(matrix._values[k], i - minRow, j - minColumn)
893 }
894 // update pointer
895 p = i + 1
896 }
897 // zero values
898 if (!skipZeros) {
899 for (let y = p; y <= maxRow; y++) { invoke(0, y - minRow, j - minColumn) }
900 }
901 }
902 // store number of values in ptr
903 ptr.push(values.length)
904 // return sparse matrix
905 return new SparseMatrix({
906 values: values,
907 index: index,
908 ptr: ptr,
909 size: [maxRow - minRow + 1, maxColumn - minColumn + 1]
910 })
911 }
912
913 /**
914 * Execute a callback function on each entry of the matrix.
915 * @memberof SparseMatrix
916 * @param {Function} callback The callback function is invoked with three
917 * parameters: the value of the element, the index
918 * of the element, and the Matrix being traversed.
919 * @param {boolean} [skipZeros] Invoke callback function for non-zero values only.
920 */
921 SparseMatrix.prototype.forEach = function (callback, skipZeros) {
922 // check it is a pattern matrix
923 if (!this._values) { throw new Error('Cannot invoke forEach on a Pattern only matrix') }
924 // matrix instance
925 const me = this
926 // rows and columns
927 const rows = this._size[0]
928 const columns = this._size[1]
929 // loop columns
930 for (let j = 0; j < columns; j++) {
931 // k0 <= k < k1 where k0 = _ptr[j] && k1 = _ptr[j+1]
932 const k0 = this._ptr[j]
933 const k1 = this._ptr[j + 1]
934 // column pointer
935 let p = 0
936 // loop k within [k0, k1[
937 for (let k = k0; k < k1; k++) {
938 // row index
939 const i = this._index[k]
940 // check we need to process zeros
941 if (!skipZeros) {
942 // zero values
943 for (let x = p; x < i; x++) { callback(0, [x, j], me) } // eslint-disable-line standard/no-callback-literal
944 }
945 // value @ k
946 callback(this._values[k], [i, j], me)
947 // update pointer
948 p = i + 1
949 }
950 // check we need to process zeros
951 if (!skipZeros) {
952 // zero values
953 for (let y = p; y < rows; y++) { callback(0, [y, j], me) } // eslint-disable-line standard/no-callback-literal
954 }
955 }
956 }
957
958 /**
959 * Create an Array with a copy of the data of the SparseMatrix
960 * @memberof SparseMatrix
961 * @returns {Array} array
962 */
963 SparseMatrix.prototype.toArray = function () {
964 return _toArray(this._values, this._index, this._ptr, this._size, true)
965 }
966
967 /**
968 * Get the primitive value of the SparseMatrix: a two dimensions array
969 * @memberof SparseMatrix
970 * @returns {Array} array
971 */
972 SparseMatrix.prototype.valueOf = function () {
973 return _toArray(this._values, this._index, this._ptr, this._size, false)
974 }
975
976 function _toArray (values, index, ptr, size, copy) {
977 // rows and columns
978 const rows = size[0]
979 const columns = size[1]
980 // result
981 const a = []
982 // vars
983 let i, j
984 // initialize array
985 for (i = 0; i < rows; i++) {
986 a[i] = []
987 for (j = 0; j < columns; j++) { a[i][j] = 0 }
988 }
989
990 // loop columns
991 for (j = 0; j < columns; j++) {
992 // k0 <= k < k1 where k0 = _ptr[j] && k1 = _ptr[j+1]
993 const k0 = ptr[j]
994 const k1 = ptr[j + 1]
995 // loop k within [k0, k1[
996 for (let k = k0; k < k1; k++) {
997 // row index
998 i = index[k]
999 // set value (use one for pattern matrix)
1000 a[i][j] = values ? (copy ? object.clone(values[k]) : values[k]) : 1
1001 }
1002 }
1003 return a
1004 }
1005
1006 /**
1007 * Get a string representation of the matrix, with optional formatting options.
1008 * @memberof SparseMatrix
1009 * @param {Object | number | Function} [options] Formatting options. See
1010 * lib/utils/number:format for a
1011 * description of the available
1012 * options.
1013 * @returns {string} str
1014 */
1015 SparseMatrix.prototype.format = function (options) {
1016 // rows and columns
1017 const rows = this._size[0]
1018 const columns = this._size[1]
1019 // density
1020 const density = this.density()
1021 // rows & columns
1022 let str = 'Sparse Matrix [' + string.format(rows, options) + ' x ' + string.format(columns, options) + '] density: ' + string.format(density, options) + '\n'
1023 // loop columns
1024 for (let j = 0; j < columns; j++) {
1025 // k0 <= k < k1 where k0 = _ptr[j] && k1 = _ptr[j+1]
1026 const k0 = this._ptr[j]
1027 const k1 = this._ptr[j + 1]
1028 // loop k within [k0, k1[
1029 for (let k = k0; k < k1; k++) {
1030 // row index
1031 const i = this._index[k]
1032 // append value
1033 str += '\n (' + string.format(i, options) + ', ' + string.format(j, options) + ') ==> ' + (this._values ? string.format(this._values[k], options) : 'X')
1034 }
1035 }
1036 return str
1037 }
1038
1039 /**
1040 * Get a string representation of the matrix
1041 * @memberof SparseMatrix
1042 * @returns {string} str
1043 */
1044 SparseMatrix.prototype.toString = function () {
1045 return string.format(this.toArray())
1046 }
1047
1048 /**
1049 * Get a JSON representation of the matrix
1050 * @memberof SparseMatrix
1051 * @returns {Object}
1052 */
1053 SparseMatrix.prototype.toJSON = function () {
1054 return {
1055 mathjs: 'SparseMatrix',
1056 values: this._values,
1057 index: this._index,
1058 ptr: this._ptr,
1059 size: this._size,
1060 datatype: this._datatype
1061 }
1062 }
1063
1064 /**
1065 * Get the kth Matrix diagonal.
1066 *
1067 * @memberof SparseMatrix
1068 * @param {number | BigNumber} [k=0] The kth diagonal where the vector will retrieved.
1069 *
1070 * @returns {Matrix} The matrix vector with the diagonal values.
1071 */
1072 SparseMatrix.prototype.diagonal = function (k) {
1073 // validate k if any
1074 if (k) {
1075 // convert BigNumber to a number
1076 if (type.isBigNumber(k)) { k = k.toNumber() }
1077 // is must be an integer
1078 if (!isNumber(k) || !isInteger(k)) {
1079 throw new TypeError('The parameter k must be an integer number')
1080 }
1081 } else {
1082 // default value
1083 k = 0
1084 }
1085
1086 const kSuper = k > 0 ? k : 0
1087 const kSub = k < 0 ? -k : 0
1088
1089 // rows & columns
1090 const rows = this._size[0]
1091 const columns = this._size[1]
1092
1093 // number diagonal values
1094 const n = Math.min(rows - kSub, columns - kSuper)
1095
1096 // diagonal arrays
1097 const values = []
1098 const index = []
1099 const ptr = []
1100 // initial ptr value
1101 ptr[0] = 0
1102 // loop columns
1103 for (let j = kSuper; j < columns && values.length < n; j++) {
1104 // k0 <= k < k1 where k0 = _ptr[j] && k1 = _ptr[j+1]
1105 const k0 = this._ptr[j]
1106 const k1 = this._ptr[j + 1]
1107 // loop x within [k0, k1[
1108 for (let x = k0; x < k1; x++) {
1109 // row index
1110 const i = this._index[x]
1111 // check row
1112 if (i === j - kSuper + kSub) {
1113 // value on this column
1114 values.push(this._values[x])
1115 // store row
1116 index[values.length - 1] = i - kSub
1117 // exit loop
1118 break
1119 }
1120 }
1121 }
1122 // close ptr
1123 ptr.push(values.length)
1124 // return matrix
1125 return new SparseMatrix({
1126 values: values,
1127 index: index,
1128 ptr: ptr,
1129 size: [n, 1]
1130 })
1131 }
1132
1133 /**
1134 * Generate a matrix from a JSON object
1135 * @memberof SparseMatrix
1136 * @param {Object} json An object structured like
1137 * `{"mathjs": "SparseMatrix", "values": [], "index": [], "ptr": [], "size": []}`,
1138 * where mathjs is optional
1139 * @returns {SparseMatrix}
1140 */
1141 SparseMatrix.fromJSON = function (json) {
1142 return new SparseMatrix(json)
1143 }
1144
1145 /**
1146 * Create a diagonal matrix.
1147 *
1148 * @memberof SparseMatrix
1149 * @param {Array} size The matrix size.
1150 * @param {number | Array | Matrix } value The values for the diagonal.
1151 * @param {number | BigNumber} [k=0] The kth diagonal where the vector will be filled in.
1152 * @param {string} [datatype] The Matrix datatype, values must be of this datatype.
1153 *
1154 * @returns {SparseMatrix}
1155 */
1156 SparseMatrix.diagonal = function (size, value, k, defaultValue, datatype) {
1157 if (!isArray(size)) { throw new TypeError('Array expected, size parameter') }
1158 if (size.length !== 2) { throw new Error('Only two dimensions matrix are supported') }
1159
1160 // map size & validate
1161 size = size.map(function (s) {
1162 // check it is a big number
1163 if (type.isBigNumber(s)) {
1164 // convert it
1165 s = s.toNumber()
1166 }
1167 // validate arguments
1168 if (!isNumber(s) || !isInteger(s) || s < 1) {
1169 throw new Error('Size values must be positive integers')
1170 }
1171 return s
1172 })
1173
1174 // validate k if any
1175 if (k) {
1176 // convert BigNumber to a number
1177 if (type.isBigNumber(k)) { k = k.toNumber() }
1178 // is must be an integer
1179 if (!isNumber(k) || !isInteger(k)) {
1180 throw new TypeError('The parameter k must be an integer number')
1181 }
1182 } else {
1183 // default value
1184 k = 0
1185 }
1186
1187 // equal signature to use
1188 let eq = equalScalar
1189 // zero value
1190 let zero = 0
1191
1192 if (isString(datatype)) {
1193 // find signature that matches (datatype, datatype)
1194 eq = typed.find(equalScalar, [datatype, datatype]) || equalScalar
1195 // convert 0 to the same datatype
1196 zero = typed.convert(0, datatype)
1197 }
1198
1199 const kSuper = k > 0 ? k : 0
1200 const kSub = k < 0 ? -k : 0
1201
1202 // rows and columns
1203 const rows = size[0]
1204 const columns = size[1]
1205
1206 // number of non-zero items
1207 const n = Math.min(rows - kSub, columns - kSuper)
1208
1209 // value extraction function
1210 let _value
1211
1212 // check value
1213 if (isArray(value)) {
1214 // validate array
1215 if (value.length !== n) {
1216 // number of values in array must be n
1217 throw new Error('Invalid value array length')
1218 }
1219 // define function
1220 _value = function (i) {
1221 // return value @ i
1222 return value[i]
1223 }
1224 } else if (type.isMatrix(value)) {
1225 // matrix size
1226 const ms = value.size()
1227 // validate matrix
1228 if (ms.length !== 1 || ms[0] !== n) {
1229 // number of values in array must be n
1230 throw new Error('Invalid matrix length')
1231 }
1232 // define function
1233 _value = function (i) {
1234 // return value @ i
1235 return value.get([i])
1236 }
1237 } else {
1238 // define function
1239 _value = function () {
1240 // return value
1241 return value
1242 }
1243 }
1244
1245 // create arrays
1246 const values = []
1247 const index = []
1248 const ptr = []
1249
1250 // loop items
1251 for (let j = 0; j < columns; j++) {
1252 // number of rows with value
1253 ptr.push(values.length)
1254 // diagonal index
1255 const i = j - kSuper
1256 // check we need to set diagonal value
1257 if (i >= 0 && i < n) {
1258 // get value @ i
1259 const v = _value(i)
1260 // check for zero
1261 if (!eq(v, zero)) {
1262 // column
1263 index.push(i + kSub)
1264 // add value
1265 values.push(v)
1266 }
1267 }
1268 }
1269 // last value should be number of values
1270 ptr.push(values.length)
1271 // create SparseMatrix
1272 return new SparseMatrix({
1273 values: values,
1274 index: index,
1275 ptr: ptr,
1276 size: [rows, columns]
1277 })
1278 }
1279
1280 /**
1281 * Swap rows i and j in Matrix.
1282 *
1283 * @memberof SparseMatrix
1284 * @param {number} i Matrix row index 1
1285 * @param {number} j Matrix row index 2
1286 *
1287 * @return {Matrix} The matrix reference
1288 */
1289 SparseMatrix.prototype.swapRows = function (i, j) {
1290 // check index
1291 if (!isNumber(i) || !isInteger(i) || !isNumber(j) || !isInteger(j)) {
1292 throw new Error('Row index must be positive integers')
1293 }
1294 // check dimensions
1295 if (this._size.length !== 2) {
1296 throw new Error('Only two dimensional matrix is supported')
1297 }
1298 // validate index
1299 validateIndex(i, this._size[0])
1300 validateIndex(j, this._size[0])
1301
1302 // swap rows
1303 SparseMatrix._swapRows(i, j, this._size[1], this._values, this._index, this._ptr)
1304 // return current instance
1305 return this
1306 }
1307
1308 /**
1309 * Loop rows with data in column j.
1310 *
1311 * @param {number} j Column
1312 * @param {Array} values Matrix values
1313 * @param {Array} index Matrix row indeces
1314 * @param {Array} ptr Matrix column pointers
1315 * @param {Function} callback Callback function invoked for every row in column j
1316 */
1317 SparseMatrix._forEachRow = function (j, values, index, ptr, callback) {
1318 // indeces for column j
1319 const k0 = ptr[j]
1320 const k1 = ptr[j + 1]
1321 // loop
1322 for (let k = k0; k < k1; k++) {
1323 // invoke callback
1324 callback(index[k], values[k])
1325 }
1326 }
1327
1328 /**
1329 * Swap rows x and y in Sparse Matrix data structures.
1330 *
1331 * @param {number} x Matrix row index 1
1332 * @param {number} y Matrix row index 2
1333 * @param {number} columns Number of columns in matrix
1334 * @param {Array} values Matrix values
1335 * @param {Array} index Matrix row indeces
1336 * @param {Array} ptr Matrix column pointers
1337 */
1338 SparseMatrix._swapRows = function (x, y, columns, values, index, ptr) {
1339 // loop columns
1340 for (let j = 0; j < columns; j++) {
1341 // k0 <= k < k1 where k0 = _ptr[j] && k1 = _ptr[j+1]
1342 const k0 = ptr[j]
1343 const k1 = ptr[j + 1]
1344 // find value index @ x
1345 const kx = _getValueIndex(x, k0, k1, index)
1346 // find value index @ x
1347 const ky = _getValueIndex(y, k0, k1, index)
1348 // check both rows exist in matrix
1349 if (kx < k1 && ky < k1 && index[kx] === x && index[ky] === y) {
1350 // swap values (check for pattern matrix)
1351 if (values) {
1352 const v = values[kx]
1353 values[kx] = values[ky]
1354 values[ky] = v
1355 }
1356 // next column
1357 continue
1358 }
1359 // check x row exist & no y row
1360 if (kx < k1 && index[kx] === x && (ky >= k1 || index[ky] !== y)) {
1361 // value @ x (check for pattern matrix)
1362 const vx = values ? values[kx] : undefined
1363 // insert value @ y
1364 index.splice(ky, 0, y)
1365 if (values) { values.splice(ky, 0, vx) }
1366 // remove value @ x (adjust array index if needed)
1367 index.splice(ky <= kx ? kx + 1 : kx, 1)
1368 if (values) { values.splice(ky <= kx ? kx + 1 : kx, 1) }
1369 // next column
1370 continue
1371 }
1372 // check y row exist & no x row
1373 if (ky < k1 && index[ky] === y && (kx >= k1 || index[kx] !== x)) {
1374 // value @ y (check for pattern matrix)
1375 const vy = values ? values[ky] : undefined
1376 // insert value @ x
1377 index.splice(kx, 0, x)
1378 if (values) { values.splice(kx, 0, vy) }
1379 // remove value @ y (adjust array index if needed)
1380 index.splice(kx <= ky ? ky + 1 : ky, 1)
1381 if (values) { values.splice(kx <= ky ? ky + 1 : ky, 1) }
1382 }
1383 }
1384 }
1385
1386 // register this type in the base class Matrix
1387 type.Matrix._storage.sparse = SparseMatrix
1388
1389 return SparseMatrix
1390}
1391
1392exports.name = 'SparseMatrix'
1393exports.path = 'type'
1394exports.factory = factory
1395exports.lazy = false // no lazy loading, as we alter type.Matrix._storage