1 | import { isArray, isBigNumber, isIndex, isMatrix, isNumber, isString, typeOf } from '../../utils/is'
|
2 | import { arraySize, getArrayDataType, reshape, resize, unsqueeze, validate, validateIndex } from '../../utils/array'
|
3 | import { format } from '../../utils/string'
|
4 | import { isInteger } from '../../utils/number'
|
5 | import { clone, deepStrictEqual } from '../../utils/object'
|
6 | import { DimensionError } from '../../error/DimensionError'
|
7 | import { factory } from '../../utils/factory'
|
8 |
|
9 | const name = 'DenseMatrix'
|
10 | const dependencies = [
|
11 | 'Matrix'
|
12 | ]
|
13 |
|
14 | export const createDenseMatrixClass = /* #__PURE__ */ factory(name, dependencies, ({ Matrix }) => {
|
15 | /**
|
16 | * Dense Matrix implementation. A regular, dense matrix, supporting multi-dimensional matrices. This is the default matrix type.
|
17 | * @class DenseMatrix
|
18 | */
|
19 | function DenseMatrix (data, datatype) {
|
20 | if (!(this instanceof DenseMatrix)) { throw new SyntaxError('Constructor must be called with the new operator') }
|
21 | if (datatype && !isString(datatype)) { throw new Error('Invalid datatype: ' + datatype) }
|
22 |
|
23 | if (isMatrix(data)) {
|
24 | // check data is a DenseMatrix
|
25 | if (data.type === 'DenseMatrix') {
|
26 | // clone data & size
|
27 | this._data = clone(data._data)
|
28 | this._size = clone(data._size)
|
29 | this._datatype = datatype || data._datatype
|
30 | } else {
|
31 | // build data from existing matrix
|
32 | this._data = data.toArray()
|
33 | this._size = data.size()
|
34 | this._datatype = datatype || data._datatype
|
35 | }
|
36 | } else if (data && isArray(data.data) && isArray(data.size)) {
|
37 | // initialize fields from JSON representation
|
38 | this._data = data.data
|
39 | this._size = data.size
|
40 | this._datatype = datatype || data.datatype
|
41 | } else if (isArray(data)) {
|
42 | // replace nested Matrices with Arrays
|
43 | this._data = preprocess(data)
|
44 | // get the dimensions of the array
|
45 | this._size = arraySize(this._data)
|
46 | // verify the dimensions of the array, TODO: compute size while processing array
|
47 | validate(this._data, this._size)
|
48 | // data type unknown
|
49 | this._datatype = datatype
|
50 | } else if (data) {
|
51 | // unsupported type
|
52 | throw new TypeError('Unsupported type of data (' + typeOf(data) + ')')
|
53 | } else {
|
54 | // nothing provided
|
55 | this._data = []
|
56 | this._size = [0]
|
57 | this._datatype = datatype
|
58 | }
|
59 | }
|
60 |
|
61 | DenseMatrix.prototype = new Matrix()
|
62 |
|
63 | /**
|
64 | * Create a new DenseMatrix
|
65 | */
|
66 | DenseMatrix.prototype.createDenseMatrix = function (data, datatype) {
|
67 | return new DenseMatrix(data, datatype)
|
68 | }
|
69 |
|
70 | /**
|
71 | * Attach type information
|
72 | */
|
73 | DenseMatrix.prototype.type = 'DenseMatrix'
|
74 | DenseMatrix.prototype.isDenseMatrix = true
|
75 |
|
76 | /**
|
77 | * Get the matrix type
|
78 | *
|
79 | * Usage:
|
80 | * const matrixType = matrix.getDataType() // retrieves the matrix type
|
81 | *
|
82 | * @memberOf DenseMatrix
|
83 | * @return {string} type information; if multiple types are found from the Matrix, it will return "mixed"
|
84 | */
|
85 | DenseMatrix.prototype.getDataType = function () {
|
86 | return getArrayDataType(this._data, typeOf)
|
87 | }
|
88 |
|
89 | /**
|
90 | * Get the storage format used by the matrix.
|
91 | *
|
92 | * Usage:
|
93 | * const format = matrix.storage() // retrieve storage format
|
94 | *
|
95 | * @memberof DenseMatrix
|
96 | * @return {string} The storage format.
|
97 | */
|
98 | DenseMatrix.prototype.storage = function () {
|
99 | return 'dense'
|
100 | }
|
101 |
|
102 | /**
|
103 | * Get the datatype of the data stored in the matrix.
|
104 | *
|
105 | * Usage:
|
106 | * const format = matrix.datatype() // retrieve matrix datatype
|
107 | *
|
108 | * @memberof DenseMatrix
|
109 | * @return {string} The datatype.
|
110 | */
|
111 | DenseMatrix.prototype.datatype = function () {
|
112 | return this._datatype
|
113 | }
|
114 |
|
115 | /**
|
116 | * Create a new DenseMatrix
|
117 | * @memberof DenseMatrix
|
118 | * @param {Array} data
|
119 | * @param {string} [datatype]
|
120 | */
|
121 | DenseMatrix.prototype.create = function (data, datatype) {
|
122 | return new DenseMatrix(data, datatype)
|
123 | }
|
124 |
|
125 | /**
|
126 | * Get a subset of the matrix, or replace a subset of the matrix.
|
127 | *
|
128 | * Usage:
|
129 | * const subset = matrix.subset(index) // retrieve subset
|
130 | * const value = matrix.subset(index, replacement) // replace subset
|
131 | *
|
132 | * @memberof DenseMatrix
|
133 | * @param {Index} index
|
134 | * @param {Array | Matrix | *} [replacement]
|
135 | * @param {*} [defaultValue=0] Default value, filled in on new entries when
|
136 | * the matrix is resized. If not provided,
|
137 | * new matrix elements will be filled with zeros.
|
138 | */
|
139 | DenseMatrix.prototype.subset = function (index, replacement, defaultValue) {
|
140 | switch (arguments.length) {
|
141 | case 1:
|
142 | return _get(this, index)
|
143 |
|
144 | // intentional fall through
|
145 | case 2:
|
146 | case 3:
|
147 | return _set(this, index, replacement, defaultValue)
|
148 |
|
149 | default:
|
150 | throw new SyntaxError('Wrong number of arguments')
|
151 | }
|
152 | }
|
153 |
|
154 | /**
|
155 | * Get a single element from the matrix.
|
156 | * @memberof DenseMatrix
|
157 | * @param {number[]} index Zero-based index
|
158 | * @return {*} value
|
159 | */
|
160 | DenseMatrix.prototype.get = function (index) {
|
161 | if (!isArray(index)) { throw new TypeError('Array expected') }
|
162 | if (index.length !== this._size.length) { throw new DimensionError(index.length, this._size.length) }
|
163 |
|
164 | // check index
|
165 | for (let x = 0; x < index.length; x++) { validateIndex(index[x], this._size[x]) }
|
166 |
|
167 | let data = this._data
|
168 | for (let i = 0, ii = index.length; i < ii; i++) {
|
169 | const indexI = index[i]
|
170 | validateIndex(indexI, data.length)
|
171 | data = data[indexI]
|
172 | }
|
173 |
|
174 | return data
|
175 | }
|
176 |
|
177 | /**
|
178 | * Replace a single element in the matrix.
|
179 | * @memberof DenseMatrix
|
180 | * @param {number[]} index Zero-based index
|
181 | * @param {*} value
|
182 | * @param {*} [defaultValue] Default value, filled in on new entries when
|
183 | * the matrix is resized. If not provided,
|
184 | * new matrix elements will be left undefined.
|
185 | * @return {DenseMatrix} self
|
186 | */
|
187 | DenseMatrix.prototype.set = function (index, value, defaultValue) {
|
188 | if (!isArray(index)) { throw new TypeError('Array expected') }
|
189 | if (index.length < this._size.length) { throw new DimensionError(index.length, this._size.length, '<') }
|
190 |
|
191 | let i, ii, indexI
|
192 |
|
193 | // enlarge matrix when needed
|
194 | const size = index.map(function (i) {
|
195 | return i + 1
|
196 | })
|
197 | _fit(this, size, defaultValue)
|
198 |
|
199 | // traverse over the dimensions
|
200 | let data = this._data
|
201 | for (i = 0, ii = index.length - 1; i < ii; i++) {
|
202 | indexI = index[i]
|
203 | validateIndex(indexI, data.length)
|
204 | data = data[indexI]
|
205 | }
|
206 |
|
207 | // set new value
|
208 | indexI = index[index.length - 1]
|
209 | validateIndex(indexI, data.length)
|
210 | data[indexI] = value
|
211 |
|
212 | return this
|
213 | }
|
214 |
|
215 | /**
|
216 | * Get a submatrix of this matrix
|
217 | * @memberof DenseMatrix
|
218 | * @param {DenseMatrix} matrix
|
219 | * @param {Index} index Zero-based index
|
220 | * @private
|
221 | */
|
222 | function _get (matrix, index) {
|
223 | if (!isIndex(index)) {
|
224 | throw new TypeError('Invalid index')
|
225 | }
|
226 |
|
227 | const isScalar = index.isScalar()
|
228 | if (isScalar) {
|
229 | // return a scalar
|
230 | return matrix.get(index.min())
|
231 | } else {
|
232 | // validate dimensions
|
233 | const size = index.size()
|
234 | if (size.length !== matrix._size.length) {
|
235 | throw new DimensionError(size.length, matrix._size.length)
|
236 | }
|
237 |
|
238 | // validate if any of the ranges in the index is out of range
|
239 | const min = index.min()
|
240 | const max = index.max()
|
241 | for (let i = 0, ii = matrix._size.length; i < ii; i++) {
|
242 | validateIndex(min[i], matrix._size[i])
|
243 | validateIndex(max[i], matrix._size[i])
|
244 | }
|
245 |
|
246 | // retrieve submatrix
|
247 | // TODO: more efficient when creating an empty matrix and setting _data and _size manually
|
248 | return new DenseMatrix(_getSubmatrix(matrix._data, index, size.length, 0), matrix._datatype)
|
249 | }
|
250 | }
|
251 |
|
252 | /**
|
253 | * Recursively get a submatrix of a multi dimensional matrix.
|
254 | * Index is not checked for correct number or length of dimensions.
|
255 | * @memberof DenseMatrix
|
256 | * @param {Array} data
|
257 | * @param {Index} index
|
258 | * @param {number} dims Total number of dimensions
|
259 | * @param {number} dim Current dimension
|
260 | * @return {Array} submatrix
|
261 | * @private
|
262 | */
|
263 | function _getSubmatrix (data, index, dims, dim) {
|
264 | const last = (dim === dims - 1)
|
265 | const range = index.dimension(dim)
|
266 |
|
267 | if (last) {
|
268 | return range.map(function (i) {
|
269 | validateIndex(i, data.length)
|
270 | return data[i]
|
271 | }).valueOf()
|
272 | } else {
|
273 | return range.map(function (i) {
|
274 | validateIndex(i, data.length)
|
275 | const child = data[i]
|
276 | return _getSubmatrix(child, index, dims, dim + 1)
|
277 | }).valueOf()
|
278 | }
|
279 | }
|
280 |
|
281 | /**
|
282 | * Replace a submatrix in this matrix
|
283 | * Indexes are zero-based.
|
284 | * @memberof DenseMatrix
|
285 | * @param {DenseMatrix} matrix
|
286 | * @param {Index} index
|
287 | * @param {DenseMatrix | Array | *} submatrix
|
288 | * @param {*} defaultValue Default value, filled in on new entries when
|
289 | * the matrix is resized.
|
290 | * @return {DenseMatrix} matrix
|
291 | * @private
|
292 | */
|
293 | function _set (matrix, index, submatrix, defaultValue) {
|
294 | if (!index || index.isIndex !== true) {
|
295 | throw new TypeError('Invalid index')
|
296 | }
|
297 |
|
298 | // get index size and check whether the index contains a single value
|
299 | const iSize = index.size()
|
300 | const isScalar = index.isScalar()
|
301 |
|
302 | // calculate the size of the submatrix, and convert it into an Array if needed
|
303 | let sSize
|
304 | if (isMatrix(submatrix)) {
|
305 | sSize = submatrix.size()
|
306 | submatrix = submatrix.valueOf()
|
307 | } else {
|
308 | sSize = arraySize(submatrix)
|
309 | }
|
310 |
|
311 | if (isScalar) {
|
312 | // set a scalar
|
313 |
|
314 | // check whether submatrix is a scalar
|
315 | if (sSize.length !== 0) {
|
316 | throw new TypeError('Scalar expected')
|
317 | }
|
318 |
|
319 | matrix.set(index.min(), submatrix, defaultValue)
|
320 | } else {
|
321 | // set a submatrix
|
322 |
|
323 | // validate dimensions
|
324 | if (iSize.length < matrix._size.length) {
|
325 | throw new DimensionError(iSize.length, matrix._size.length, '<')
|
326 | }
|
327 |
|
328 | if (sSize.length < iSize.length) {
|
329 | // calculate number of missing outer dimensions
|
330 | let i = 0
|
331 | let outer = 0
|
332 | while (iSize[i] === 1 && sSize[i] === 1) {
|
333 | i++
|
334 | }
|
335 | while (iSize[i] === 1) {
|
336 | outer++
|
337 | i++
|
338 | }
|
339 |
|
340 | // unsqueeze both outer and inner dimensions
|
341 | submatrix = unsqueeze(submatrix, iSize.length, outer, sSize)
|
342 | }
|
343 |
|
344 | // check whether the size of the submatrix matches the index size
|
345 | if (!deepStrictEqual(iSize, sSize)) {
|
346 | throw new DimensionError(iSize, sSize, '>')
|
347 | }
|
348 |
|
349 | // enlarge matrix when needed
|
350 | const size = index.max().map(function (i) {
|
351 | return i + 1
|
352 | })
|
353 | _fit(matrix, size, defaultValue)
|
354 |
|
355 | // insert the sub matrix
|
356 | const dims = iSize.length
|
357 | const dim = 0
|
358 | _setSubmatrix(matrix._data, index, submatrix, dims, dim)
|
359 | }
|
360 |
|
361 | return matrix
|
362 | }
|
363 |
|
364 | /**
|
365 | * Replace a submatrix of a multi dimensional matrix.
|
366 | * @memberof DenseMatrix
|
367 | * @param {Array} data
|
368 | * @param {Index} index
|
369 | * @param {Array} submatrix
|
370 | * @param {number} dims Total number of dimensions
|
371 | * @param {number} dim
|
372 | * @private
|
373 | */
|
374 | function _setSubmatrix (data, index, submatrix, dims, dim) {
|
375 | const last = (dim === dims - 1)
|
376 | const range = index.dimension(dim)
|
377 |
|
378 | if (last) {
|
379 | range.forEach(function (dataIndex, subIndex) {
|
380 | validateIndex(dataIndex)
|
381 | data[dataIndex] = submatrix[subIndex[0]]
|
382 | })
|
383 | } else {
|
384 | range.forEach(function (dataIndex, subIndex) {
|
385 | validateIndex(dataIndex)
|
386 | _setSubmatrix(data[dataIndex], index, submatrix[subIndex[0]], dims, dim + 1)
|
387 | })
|
388 | }
|
389 | }
|
390 |
|
391 | /**
|
392 | * Resize the matrix to the given size. Returns a copy of the matrix when
|
393 | * `copy=true`, otherwise return the matrix itself (resize in place).
|
394 | *
|
395 | * @memberof DenseMatrix
|
396 | * @param {number[]} size The new size the matrix should have.
|
397 | * @param {*} [defaultValue=0] Default value, filled in on new entries.
|
398 | * If not provided, the matrix elements will
|
399 | * be filled with zeros.
|
400 | * @param {boolean} [copy] Return a resized copy of the matrix
|
401 | *
|
402 | * @return {Matrix} The resized matrix
|
403 | */
|
404 | DenseMatrix.prototype.resize = function (size, defaultValue, copy) {
|
405 | // validate arguments
|
406 | if (!isArray(size)) { throw new TypeError('Array expected') }
|
407 |
|
408 | // matrix to resize
|
409 | const m = copy ? this.clone() : this
|
410 | // resize matrix
|
411 | return _resize(m, size, defaultValue)
|
412 | }
|
413 |
|
414 | function _resize (matrix, size, defaultValue) {
|
415 | // check size
|
416 | if (size.length === 0) {
|
417 | // first value in matrix
|
418 | let v = matrix._data
|
419 | // go deep
|
420 | while (isArray(v)) {
|
421 | v = v[0]
|
422 | }
|
423 | return v
|
424 | }
|
425 | // resize matrix
|
426 | matrix._size = size.slice(0) // copy the array
|
427 | matrix._data = resize(matrix._data, matrix._size, defaultValue)
|
428 | // return matrix
|
429 | return matrix
|
430 | }
|
431 |
|
432 | /**
|
433 | * Reshape the matrix to the given size. Returns a copy of the matrix when
|
434 | * `copy=true`, otherwise return the matrix itself (reshape in place).
|
435 | *
|
436 | * NOTE: This might be better suited to copy by default, instead of modifying
|
437 | * in place. For now, it operates in place to remain consistent with
|
438 | * resize().
|
439 | *
|
440 | * @memberof DenseMatrix
|
441 | * @param {number[]} size The new size the matrix should have.
|
442 | * @param {boolean} [copy] Return a reshaped copy of the matrix
|
443 | *
|
444 | * @return {Matrix} The reshaped matrix
|
445 | */
|
446 | DenseMatrix.prototype.reshape = function (size, copy) {
|
447 | const m = copy ? this.clone() : this
|
448 |
|
449 | m._data = reshape(m._data, size)
|
450 | m._size = size.slice(0)
|
451 | return m
|
452 | }
|
453 |
|
454 | /**
|
455 | * Enlarge the matrix when it is smaller than given size.
|
456 | * If the matrix is larger or equal sized, nothing is done.
|
457 | * @memberof DenseMatrix
|
458 | * @param {DenseMatrix} matrix The matrix to be resized
|
459 | * @param {number[]} size
|
460 | * @param {*} defaultValue Default value, filled in on new entries.
|
461 | * @private
|
462 | */
|
463 | function _fit (matrix, size, defaultValue) {
|
464 | const // copy the array
|
465 | newSize = matrix._size.slice(0)
|
466 |
|
467 | let changed = false
|
468 |
|
469 | // add dimensions when needed
|
470 | while (newSize.length < size.length) {
|
471 | newSize.push(0)
|
472 | changed = true
|
473 | }
|
474 |
|
475 | // enlarge size when needed
|
476 | for (let i = 0, ii = size.length; i < ii; i++) {
|
477 | if (size[i] > newSize[i]) {
|
478 | newSize[i] = size[i]
|
479 | changed = true
|
480 | }
|
481 | }
|
482 |
|
483 | if (changed) {
|
484 | // resize only when size is changed
|
485 | _resize(matrix, newSize, defaultValue)
|
486 | }
|
487 | }
|
488 |
|
489 | /**
|
490 | * Create a clone of the matrix
|
491 | * @memberof DenseMatrix
|
492 | * @return {DenseMatrix} clone
|
493 | */
|
494 | DenseMatrix.prototype.clone = function () {
|
495 | const m = new DenseMatrix({
|
496 | data: clone(this._data),
|
497 | size: clone(this._size),
|
498 | datatype: this._datatype
|
499 | })
|
500 | return m
|
501 | }
|
502 |
|
503 | /**
|
504 | * Retrieve the size of the matrix.
|
505 | * @memberof DenseMatrix
|
506 | * @returns {number[]} size
|
507 | */
|
508 | DenseMatrix.prototype.size = function () {
|
509 | return this._size.slice(0) // return a clone of _size
|
510 | }
|
511 |
|
512 | /**
|
513 | * Create a new matrix with the results of the callback function executed on
|
514 | * each entry of the matrix.
|
515 | * @memberof DenseMatrix
|
516 | * @param {Function} callback The callback function is invoked with three
|
517 | * parameters: the value of the element, the index
|
518 | * of the element, and the Matrix being traversed.
|
519 | *
|
520 | * @return {DenseMatrix} matrix
|
521 | */
|
522 | DenseMatrix.prototype.map = function (callback) {
|
523 | // matrix instance
|
524 | const me = this
|
525 | const recurse = function (value, index) {
|
526 | if (isArray(value)) {
|
527 | return value.map(function (child, i) {
|
528 | return recurse(child, index.concat(i))
|
529 | })
|
530 | } else {
|
531 | return callback(value, index, me)
|
532 | }
|
533 | }
|
534 | // return dense format
|
535 | return new DenseMatrix({
|
536 | data: recurse(this._data, []),
|
537 | size: clone(this._size),
|
538 | datatype: this._datatype
|
539 | })
|
540 | }
|
541 |
|
542 | /**
|
543 | * Execute a callback function on each entry of the matrix.
|
544 | * @memberof DenseMatrix
|
545 | * @param {Function} callback The callback function is invoked with three
|
546 | * parameters: the value of the element, the index
|
547 | * of the element, and the Matrix being traversed.
|
548 | */
|
549 | DenseMatrix.prototype.forEach = function (callback) {
|
550 | // matrix instance
|
551 | const me = this
|
552 | const recurse = function (value, index) {
|
553 | if (isArray(value)) {
|
554 | value.forEach(function (child, i) {
|
555 | recurse(child, index.concat(i))
|
556 | })
|
557 | } else {
|
558 | callback(value, index, me)
|
559 | }
|
560 | }
|
561 | recurse(this._data, [])
|
562 | }
|
563 |
|
564 | /**
|
565 | * Create an Array with a copy of the data of the DenseMatrix
|
566 | * @memberof DenseMatrix
|
567 | * @returns {Array} array
|
568 | */
|
569 | DenseMatrix.prototype.toArray = function () {
|
570 | return clone(this._data)
|
571 | }
|
572 |
|
573 | /**
|
574 | * Get the primitive value of the DenseMatrix: a multidimensional array
|
575 | * @memberof DenseMatrix
|
576 | * @returns {Array} array
|
577 | */
|
578 | DenseMatrix.prototype.valueOf = function () {
|
579 | return this._data
|
580 | }
|
581 |
|
582 | /**
|
583 | * Get a string representation of the matrix, with optional formatting options.
|
584 | * @memberof DenseMatrix
|
585 | * @param {Object | number | Function} [options] Formatting options. See
|
586 | * lib/utils/number:format for a
|
587 | * description of the available
|
588 | * options.
|
589 | * @returns {string} str
|
590 | */
|
591 | DenseMatrix.prototype.format = function (options) {
|
592 | return format(this._data, options)
|
593 | }
|
594 |
|
595 | /**
|
596 | * Get a string representation of the matrix
|
597 | * @memberof DenseMatrix
|
598 | * @returns {string} str
|
599 | */
|
600 | DenseMatrix.prototype.toString = function () {
|
601 | return format(this._data)
|
602 | }
|
603 |
|
604 | /**
|
605 | * Get a JSON representation of the matrix
|
606 | * @memberof DenseMatrix
|
607 | * @returns {Object}
|
608 | */
|
609 | DenseMatrix.prototype.toJSON = function () {
|
610 | return {
|
611 | mathjs: 'DenseMatrix',
|
612 | data: this._data,
|
613 | size: this._size,
|
614 | datatype: this._datatype
|
615 | }
|
616 | }
|
617 |
|
618 | /**
|
619 | * Get the kth Matrix diagonal.
|
620 | *
|
621 | * @memberof DenseMatrix
|
622 | * @param {number | BigNumber} [k=0] The kth diagonal where the vector will retrieved.
|
623 | *
|
624 | * @returns {Matrix} The matrix with the diagonal values.
|
625 | */
|
626 | DenseMatrix.prototype.diagonal = function (k) {
|
627 | // validate k if any
|
628 | if (k) {
|
629 | // convert BigNumber to a number
|
630 | if (isBigNumber(k)) { k = k.toNumber() }
|
631 | // is must be an integer
|
632 | if (!isNumber(k) || !isInteger(k)) {
|
633 | throw new TypeError('The parameter k must be an integer number')
|
634 | }
|
635 | } else {
|
636 | // default value
|
637 | k = 0
|
638 | }
|
639 |
|
640 | const kSuper = k > 0 ? k : 0
|
641 | const kSub = k < 0 ? -k : 0
|
642 |
|
643 | // rows & columns
|
644 | const rows = this._size[0]
|
645 | const columns = this._size[1]
|
646 |
|
647 | // number diagonal values
|
648 | const n = Math.min(rows - kSub, columns - kSuper)
|
649 |
|
650 | // x is a matrix get diagonal from matrix
|
651 | const data = []
|
652 |
|
653 | // loop rows
|
654 | for (let i = 0; i < n; i++) {
|
655 | data[i] = this._data[i + kSub][i + kSuper]
|
656 | }
|
657 |
|
658 | // create DenseMatrix
|
659 | return new DenseMatrix({
|
660 | data: data,
|
661 | size: [n],
|
662 | datatype: this._datatype
|
663 | })
|
664 | }
|
665 |
|
666 | /**
|
667 | * Create a diagonal matrix.
|
668 | *
|
669 | * @memberof DenseMatrix
|
670 | * @param {Array} size The matrix size.
|
671 | * @param {number | Matrix | Array } value The values for the diagonal.
|
672 | * @param {number | BigNumber} [k=0] The kth diagonal where the vector will be filled in.
|
673 | * @param {number} [defaultValue] The default value for non-diagonal
|
674 | * @param {string} [datatype] The datatype for the diagonal
|
675 | *
|
676 | * @returns {DenseMatrix}
|
677 | */
|
678 | DenseMatrix.diagonal = function (size, value, k, defaultValue) {
|
679 | if (!isArray(size)) { throw new TypeError('Array expected, size parameter') }
|
680 | if (size.length !== 2) { throw new Error('Only two dimensions matrix are supported') }
|
681 |
|
682 | // map size & validate
|
683 | size = size.map(function (s) {
|
684 | // check it is a big number
|
685 | if (isBigNumber(s)) {
|
686 | // convert it
|
687 | s = s.toNumber()
|
688 | }
|
689 | // validate arguments
|
690 | if (!isNumber(s) || !isInteger(s) || s < 1) {
|
691 | throw new Error('Size values must be positive integers')
|
692 | }
|
693 | return s
|
694 | })
|
695 |
|
696 | // validate k if any
|
697 | if (k) {
|
698 | // convert BigNumber to a number
|
699 | if (isBigNumber(k)) { k = k.toNumber() }
|
700 | // is must be an integer
|
701 | if (!isNumber(k) || !isInteger(k)) {
|
702 | throw new TypeError('The parameter k must be an integer number')
|
703 | }
|
704 | } else {
|
705 | // default value
|
706 | k = 0
|
707 | }
|
708 |
|
709 | const kSuper = k > 0 ? k : 0
|
710 | const kSub = k < 0 ? -k : 0
|
711 |
|
712 | // rows and columns
|
713 | const rows = size[0]
|
714 | const columns = size[1]
|
715 |
|
716 | // number of non-zero items
|
717 | const n = Math.min(rows - kSub, columns - kSuper)
|
718 |
|
719 | // value extraction function
|
720 | let _value
|
721 |
|
722 | // check value
|
723 | if (isArray(value)) {
|
724 | // validate array
|
725 | if (value.length !== n) {
|
726 | // number of values in array must be n
|
727 | throw new Error('Invalid value array length')
|
728 | }
|
729 | // define function
|
730 | _value = function (i) {
|
731 | // return value @ i
|
732 | return value[i]
|
733 | }
|
734 | } else if (isMatrix(value)) {
|
735 | // matrix size
|
736 | const ms = value.size()
|
737 | // validate matrix
|
738 | if (ms.length !== 1 || ms[0] !== n) {
|
739 | // number of values in array must be n
|
740 | throw new Error('Invalid matrix length')
|
741 | }
|
742 | // define function
|
743 | _value = function (i) {
|
744 | // return value @ i
|
745 | return value.get([i])
|
746 | }
|
747 | } else {
|
748 | // define function
|
749 | _value = function () {
|
750 | // return value
|
751 | return value
|
752 | }
|
753 | }
|
754 |
|
755 | // discover default value if needed
|
756 | if (!defaultValue) {
|
757 | // check first value in array
|
758 | defaultValue = isBigNumber(_value(0))
|
759 | ? _value(0).mul(0) // trick to create a BigNumber with value zero
|
760 | : 0
|
761 | }
|
762 |
|
763 | // empty array
|
764 | let data = []
|
765 |
|
766 | // check we need to resize array
|
767 | if (size.length > 0) {
|
768 | // resize array
|
769 | data = resize(data, size, defaultValue)
|
770 | // fill diagonal
|
771 | for (let d = 0; d < n; d++) {
|
772 | data[d + kSub][d + kSuper] = _value(d)
|
773 | }
|
774 | }
|
775 |
|
776 | // create DenseMatrix
|
777 | return new DenseMatrix({
|
778 | data: data,
|
779 | size: [rows, columns]
|
780 | })
|
781 | }
|
782 |
|
783 | /**
|
784 | * Generate a matrix from a JSON object
|
785 | * @memberof DenseMatrix
|
786 | * @param {Object} json An object structured like
|
787 | * `{"mathjs": "DenseMatrix", data: [], size: []}`,
|
788 | * where mathjs is optional
|
789 | * @returns {DenseMatrix}
|
790 | */
|
791 | DenseMatrix.fromJSON = function (json) {
|
792 | return new DenseMatrix(json)
|
793 | }
|
794 |
|
795 | /**
|
796 | * Swap rows i and j in Matrix.
|
797 | *
|
798 | * @memberof DenseMatrix
|
799 | * @param {number} i Matrix row index 1
|
800 | * @param {number} j Matrix row index 2
|
801 | *
|
802 | * @return {Matrix} The matrix reference
|
803 | */
|
804 | DenseMatrix.prototype.swapRows = function (i, j) {
|
805 | // check index
|
806 | if (!isNumber(i) || !isInteger(i) || !isNumber(j) || !isInteger(j)) {
|
807 | throw new Error('Row index must be positive integers')
|
808 | }
|
809 | // check dimensions
|
810 | if (this._size.length !== 2) {
|
811 | throw new Error('Only two dimensional matrix is supported')
|
812 | }
|
813 | // validate index
|
814 | validateIndex(i, this._size[0])
|
815 | validateIndex(j, this._size[0])
|
816 |
|
817 | // swap rows
|
818 | DenseMatrix._swapRows(i, j, this._data)
|
819 | // return current instance
|
820 | return this
|
821 | }
|
822 |
|
823 | /**
|
824 | * Swap rows i and j in Dense Matrix data structure.
|
825 | *
|
826 | * @param {number} i Matrix row index 1
|
827 | * @param {number} j Matrix row index 2
|
828 | * @param {Array} data Matrix data
|
829 | */
|
830 | DenseMatrix._swapRows = function (i, j, data) {
|
831 | // swap values i <-> j
|
832 | const vi = data[i]
|
833 | data[i] = data[j]
|
834 | data[j] = vi
|
835 | }
|
836 |
|
837 | /**
|
838 | * Preprocess data, which can be an Array or DenseMatrix with nested Arrays and
|
839 | * Matrices. Replaces all nested Matrices with Arrays
|
840 | * @memberof DenseMatrix
|
841 | * @param {Array} data
|
842 | * @return {Array} data
|
843 | */
|
844 | function preprocess (data) {
|
845 | for (let i = 0, ii = data.length; i < ii; i++) {
|
846 | const elem = data[i]
|
847 | if (isArray(elem)) {
|
848 | data[i] = preprocess(elem)
|
849 | } else if (elem && elem.isMatrix === true) {
|
850 | data[i] = preprocess(elem.valueOf())
|
851 | }
|
852 | }
|
853 |
|
854 | return data
|
855 | }
|
856 |
|
857 | return DenseMatrix
|
858 | }, { isClass: true })
|