Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/math/matrix.js
2868 views
1
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
// http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS-IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
/**
16
* @fileoverview Class for representing matrices and static helper functions.
17
*/
18
19
goog.provide('goog.math.Matrix');
20
21
goog.require('goog.array');
22
goog.require('goog.asserts');
23
goog.require('goog.math');
24
goog.require('goog.math.Size');
25
goog.require('goog.string');
26
27
28
29
/**
30
* Class for representing and manipulating matrices.
31
*
32
* The entry that lies in the i-th row and the j-th column of a matrix is
33
* typically referred to as the i,j entry of the matrix.
34
*
35
* The m-by-n matrix A would have its entries referred to as:
36
* [ a0,0 a0,1 a0,2 ... a0,j ... a0,n ]
37
* [ a1,0 a1,1 a1,2 ... a1,j ... a1,n ]
38
* [ a2,0 a2,1 a2,2 ... a2,j ... a2,n ]
39
* [ . . . . . ]
40
* [ . . . . . ]
41
* [ . . . . . ]
42
* [ ai,0 ai,1 ai,2 ... ai,j ... ai,n ]
43
* [ . . . . . ]
44
* [ . . . . . ]
45
* [ . . . . . ]
46
* [ am,0 am,1 am,2 ... am,j ... am,n ]
47
*
48
* @param {!goog.math.Matrix|!Array<!Array<number>>|!goog.math.Size|number} m
49
* A matrix to copy, a 2D-array to take as a template, a size object for
50
* dimensions, or the number of rows.
51
* @param {number=} opt_n Number of columns of the matrix (only applicable if
52
* the first argument is also numeric).
53
* @struct
54
* @constructor
55
* @final
56
*/
57
goog.math.Matrix = function(m, opt_n) {
58
if (m instanceof goog.math.Matrix) {
59
this.array_ = m.toArray();
60
} else if (
61
goog.isArrayLike(m) &&
62
goog.math.Matrix.isValidArray(
63
/** @type {!Array<!Array<number>>} */ (m))) {
64
this.array_ = goog.array.clone(/** @type {!Array<!Array<number>>} */ (m));
65
} else if (m instanceof goog.math.Size) {
66
this.array_ = goog.math.Matrix.createZeroPaddedArray_(m.height, m.width);
67
} else if (goog.isNumber(m) && goog.isNumber(opt_n) && m > 0 && opt_n > 0) {
68
this.array_ = goog.math.Matrix.createZeroPaddedArray_(
69
/** @type {number} */ (m), opt_n);
70
} else {
71
throw Error('Invalid argument(s) for Matrix contructor');
72
}
73
74
this.size_ = new goog.math.Size(this.array_[0].length, this.array_.length);
75
};
76
77
78
/**
79
* Creates a square identity matrix. i.e. for n = 3:
80
* <pre>
81
* [ 1 0 0 ]
82
* [ 0 1 0 ]
83
* [ 0 0 1 ]
84
* </pre>
85
* @param {number} n The size of the square identity matrix.
86
* @return {!goog.math.Matrix} Identity matrix of width and height {@code n}.
87
*/
88
goog.math.Matrix.createIdentityMatrix = function(n) {
89
var rv = [];
90
for (var i = 0; i < n; i++) {
91
rv[i] = [];
92
for (var j = 0; j < n; j++) {
93
rv[i][j] = i == j ? 1 : 0;
94
}
95
}
96
return new goog.math.Matrix(rv);
97
};
98
99
100
/**
101
* Calls a function for each cell in a matrix.
102
* @param {goog.math.Matrix} matrix The matrix to iterate over.
103
* @param {function(this:T, number, number, number, !goog.math.Matrix)} fn
104
* The function to call for every element. This function
105
* takes 4 arguments (value, i, j, and the matrix)
106
* and the return value is irrelevant.
107
* @param {T=} opt_obj The object to be used as the value of 'this'
108
* within {@code fn}.
109
* @template T
110
*/
111
goog.math.Matrix.forEach = function(matrix, fn, opt_obj) {
112
for (var i = 0; i < matrix.getSize().height; i++) {
113
for (var j = 0; j < matrix.getSize().width; j++) {
114
fn.call(opt_obj, matrix.array_[i][j], i, j, matrix);
115
}
116
}
117
};
118
119
120
/**
121
* Tests whether an array is a valid matrix. A valid array is an array of
122
* arrays where all arrays are of the same length and all elements are numbers.
123
* @param {!Array<!Array<number>>} arr An array to test.
124
* @return {boolean} Whether the array is a valid matrix.
125
*/
126
goog.math.Matrix.isValidArray = function(arr) {
127
var len = 0;
128
for (var i = 0; i < arr.length; i++) {
129
if (!goog.isArrayLike(arr[i]) || len > 0 && arr[i].length != len) {
130
return false;
131
}
132
for (var j = 0; j < arr[i].length; j++) {
133
if (!goog.isNumber(arr[i][j])) {
134
return false;
135
}
136
}
137
if (len == 0) {
138
len = arr[i].length;
139
}
140
}
141
return len != 0;
142
};
143
144
145
/**
146
* Calls a function for every cell in a matrix and inserts the result into a
147
* new matrix of equal dimensions.
148
* @param {!goog.math.Matrix} matrix The matrix to iterate over.
149
* @param {function(this:T, number, number, number, !goog.math.Matrix): number}
150
* fn The function to call for every element. This function
151
* takes 4 arguments (value, i, j and the matrix)
152
* and should return a number, which will be inserted into a new matrix.
153
* @param {T=} opt_obj The object to be used as the value of 'this'
154
* within {@code fn}.
155
* @return {!goog.math.Matrix} A new matrix with the results from {@code fn}.
156
* @template T
157
*/
158
goog.math.Matrix.map = function(matrix, fn, opt_obj) {
159
var m = new goog.math.Matrix(matrix.getSize());
160
goog.math.Matrix.forEach(matrix, function(value, i, j) {
161
m.array_[i][j] = fn.call(opt_obj, value, i, j, matrix);
162
});
163
return m;
164
};
165
166
167
/**
168
* Creates a new zero padded matix.
169
* @param {number} m Height of matrix.
170
* @param {number} n Width of matrix.
171
* @return {!Array<!Array<number>>} The new zero padded matrix.
172
* @private
173
*/
174
goog.math.Matrix.createZeroPaddedArray_ = function(m, n) {
175
var rv = [];
176
for (var i = 0; i < m; i++) {
177
rv[i] = [];
178
for (var j = 0; j < n; j++) {
179
rv[i][j] = 0;
180
}
181
}
182
return rv;
183
};
184
185
186
/**
187
* Internal array representing the matrix.
188
* @type {!Array<!Array<number>>}
189
* @private
190
*/
191
goog.math.Matrix.prototype.array_;
192
193
194
/**
195
* After construction the Matrix's size is constant and stored in this object.
196
* @type {!goog.math.Size}
197
* @private
198
*/
199
goog.math.Matrix.prototype.size_;
200
201
202
/**
203
* Returns a new matrix that is the sum of this and the provided matrix.
204
* @param {goog.math.Matrix} m The matrix to add to this one.
205
* @return {!goog.math.Matrix} Resultant sum.
206
*/
207
goog.math.Matrix.prototype.add = function(m) {
208
if (!goog.math.Size.equals(this.size_, m.getSize())) {
209
throw Error('Matrix summation is only supported on arrays of equal size');
210
}
211
return goog.math.Matrix.map(
212
this, function(val, i, j) { return val + m.array_[i][j]; });
213
};
214
215
216
/**
217
* Appends the given matrix to the right side of this matrix.
218
* @param {goog.math.Matrix} m The matrix to augment this matrix with.
219
* @return {!goog.math.Matrix} A new matrix with additional columns on the
220
* right.
221
*/
222
goog.math.Matrix.prototype.appendColumns = function(m) {
223
if (this.size_.height != m.getSize().height) {
224
throw Error(
225
'The given matrix has height ' + m.size_.height + ', but ' +
226
' needs to have height ' + this.size_.height + '.');
227
}
228
var result =
229
new goog.math.Matrix(this.size_.height, this.size_.width + m.size_.width);
230
goog.math.Matrix.forEach(
231
this, function(value, i, j) { result.array_[i][j] = value; });
232
goog.math.Matrix.forEach(m, function(value, i, j) {
233
result.array_[i][this.size_.width + j] = value;
234
}, this);
235
return result;
236
};
237
238
239
/**
240
* Appends the given matrix to the bottom of this matrix.
241
* @param {goog.math.Matrix} m The matrix to augment this matrix with.
242
* @return {!goog.math.Matrix} A new matrix with added columns on the bottom.
243
*/
244
goog.math.Matrix.prototype.appendRows = function(m) {
245
if (this.size_.width != m.getSize().width) {
246
throw Error(
247
'The given matrix has width ' + m.size_.width + ', but ' +
248
' needs to have width ' + this.size_.width + '.');
249
}
250
var result = new goog.math.Matrix(
251
this.size_.height + m.size_.height, this.size_.width);
252
goog.math.Matrix.forEach(
253
this, function(value, i, j) { result.array_[i][j] = value; });
254
goog.math.Matrix.forEach(m, function(value, i, j) {
255
result.array_[this.size_.height + i][j] = value;
256
}, this);
257
return result;
258
};
259
260
261
/**
262
* Returns whether the given matrix equals this matrix.
263
* @param {goog.math.Matrix} m The matrix to compare to this one.
264
* @param {number=} opt_tolerance The tolerance when comparing array entries.
265
* @return {boolean} Whether the given matrix equals this matrix.
266
*/
267
goog.math.Matrix.prototype.equals = function(m, opt_tolerance) {
268
if (this.size_.width != m.size_.width) {
269
return false;
270
}
271
if (this.size_.height != m.size_.height) {
272
return false;
273
}
274
275
var tolerance = opt_tolerance || 0;
276
for (var i = 0; i < this.size_.height; i++) {
277
for (var j = 0; j < this.size_.width; j++) {
278
if (!goog.math.nearlyEquals(
279
this.array_[i][j], m.array_[i][j], tolerance)) {
280
return false;
281
}
282
}
283
}
284
285
return true;
286
};
287
288
289
/**
290
* Returns the determinant of this matrix. The determinant of a matrix A is
291
* often denoted as |A| and can only be applied to a square matrix.
292
* @return {number} The determinant of this matrix.
293
*/
294
goog.math.Matrix.prototype.getDeterminant = function() {
295
if (!this.isSquare()) {
296
throw Error('A determinant can only be take on a square matrix');
297
}
298
299
return this.getDeterminant_();
300
};
301
302
303
/**
304
* Returns the inverse of this matrix if it exists or null if the matrix is
305
* not invertible.
306
* @return {goog.math.Matrix} A new matrix which is the inverse of this matrix.
307
*/
308
goog.math.Matrix.prototype.getInverse = function() {
309
if (!this.isSquare()) {
310
throw Error('An inverse can only be taken on a square matrix.');
311
}
312
if (this.getSize().width == 1) {
313
var a = this.getValueAt(0, 0);
314
return a == 0 ? null : new goog.math.Matrix([[1 / Number(a)]]);
315
}
316
var identity = goog.math.Matrix.createIdentityMatrix(this.size_.height);
317
var mi = this.appendColumns(identity).getReducedRowEchelonForm();
318
var i = mi.getSubmatrixByCoordinates_(
319
0, 0, identity.size_.width - 1, identity.size_.height - 1);
320
if (!i.equals(identity)) {
321
return null; // This matrix was not invertible
322
}
323
return mi.getSubmatrixByCoordinates_(0, identity.size_.width);
324
};
325
326
327
/**
328
* Transforms this matrix into reduced row echelon form.
329
* @return {!goog.math.Matrix} A new matrix reduced row echelon form.
330
*/
331
goog.math.Matrix.prototype.getReducedRowEchelonForm = function() {
332
var result = new goog.math.Matrix(this);
333
var col = 0;
334
// Each iteration puts one row in reduced row echelon form
335
for (var row = 0; row < result.size_.height; row++) {
336
if (col >= result.size_.width) {
337
return result;
338
}
339
340
// Scan each column starting from this row on down for a non-zero value
341
var i = row;
342
while (result.array_[i][col] == 0) {
343
i++;
344
if (i == result.size_.height) {
345
i = row;
346
col++;
347
if (col == result.size_.width) {
348
return result;
349
}
350
}
351
}
352
353
// Make the row we found the current row with a leading 1
354
this.swapRows_(i, row);
355
var divisor = result.array_[row][col];
356
for (var j = col; j < result.size_.width; j++) {
357
result.array_[row][j] = result.array_[row][j] / divisor;
358
}
359
360
// Subtract a multiple of this row from each other row
361
// so that all the other entries in this column are 0
362
for (i = 0; i < result.size_.height; i++) {
363
if (i != row) {
364
var multiple = result.array_[i][col];
365
for (var j = col; j < result.size_.width; j++) {
366
result.array_[i][j] -= multiple * result.array_[row][j];
367
}
368
}
369
}
370
371
// Move on to the next column
372
col++;
373
}
374
return result;
375
};
376
377
378
/**
379
* @return {!goog.math.Size} The dimensions of the matrix.
380
*/
381
goog.math.Matrix.prototype.getSize = function() {
382
return this.size_;
383
};
384
385
386
/**
387
* Return the transpose of this matrix. For an m-by-n matrix, the transpose
388
* is the n-by-m matrix which results from turning rows into columns and columns
389
* into rows
390
* @return {!goog.math.Matrix} A new matrix A^T.
391
*/
392
goog.math.Matrix.prototype.getTranspose = function() {
393
var m = new goog.math.Matrix(this.size_.width, this.size_.height);
394
goog.math.Matrix.forEach(
395
this, function(value, i, j) { m.array_[j][i] = value; });
396
return m;
397
};
398
399
400
/**
401
* Retrieves the value of a particular coordinate in the matrix or null if the
402
* requested coordinates are out of range.
403
* @param {number} i The i index of the coordinate.
404
* @param {number} j The j index of the coordinate.
405
* @return {?number} The value at the specified coordinate.
406
*/
407
goog.math.Matrix.prototype.getValueAt = function(i, j) {
408
if (!this.isInBounds_(i, j)) {
409
return null;
410
}
411
return this.array_[i][j];
412
};
413
414
415
/**
416
* @return {boolean} Whether the horizontal and vertical dimensions of this
417
* matrix are the same.
418
*/
419
goog.math.Matrix.prototype.isSquare = function() {
420
return this.size_.width == this.size_.height;
421
};
422
423
424
/**
425
* Sets the value at a particular coordinate (if the coordinate is within the
426
* bounds of the matrix).
427
* @param {number} i The i index of the coordinate.
428
* @param {number} j The j index of the coordinate.
429
* @param {number} value The new value for the coordinate.
430
*/
431
goog.math.Matrix.prototype.setValueAt = function(i, j, value) {
432
if (!this.isInBounds_(i, j)) {
433
throw Error(
434
'Index out of bounds when setting matrix value, (' + i + ',' + j +
435
') in size (' + this.size_.height + ',' + this.size_.width + ')');
436
}
437
this.array_[i][j] = value;
438
};
439
440
441
/**
442
* Performs matrix or scalar multiplication on a matrix and returns the
443
* resultant matrix.
444
*
445
* Matrix multiplication is defined between two matrices only if the number of
446
* columns of the first matrix is the same as the number of rows of the second
447
* matrix. If A is an m-by-n matrix and B is an n-by-p matrix, then their
448
* product AB is an m-by-p matrix
449
*
450
* Scalar multiplication returns a matrix of the same size as the original,
451
* each value multiplied by the given value.
452
*
453
* @param {goog.math.Matrix|number} m Matrix/number to multiply the matrix by.
454
* @return {!goog.math.Matrix} Resultant product.
455
*/
456
goog.math.Matrix.prototype.multiply = function(m) {
457
if (m instanceof goog.math.Matrix) {
458
if (this.size_.width != m.getSize().height) {
459
throw Error(
460
'Invalid matrices for multiplication. Second matrix ' +
461
'should have the same number of rows as the first has columns.');
462
}
463
return this.matrixMultiply_(/** @type {!goog.math.Matrix} */ (m));
464
} else if (goog.isNumber(m)) {
465
return this.scalarMultiply_(/** @type {number} */ (m));
466
} else {
467
throw Error(
468
'A matrix can only be multiplied by' +
469
' a number or another matrix.');
470
}
471
};
472
473
474
/**
475
* Returns a new matrix that is the difference of this and the provided matrix.
476
* @param {goog.math.Matrix} m The matrix to subtract from this one.
477
* @return {!goog.math.Matrix} Resultant difference.
478
*/
479
goog.math.Matrix.prototype.subtract = function(m) {
480
if (!goog.math.Size.equals(this.size_, m.getSize())) {
481
throw Error(
482
'Matrix subtraction is only supported on arrays of equal size.');
483
}
484
return goog.math.Matrix.map(
485
this, function(val, i, j) { return val - m.array_[i][j]; });
486
};
487
488
489
/**
490
* @return {!Array<!Array<number>>} A 2D internal array representing this
491
* matrix. Not a clone.
492
*/
493
goog.math.Matrix.prototype.toArray = function() {
494
return this.array_;
495
};
496
497
498
if (goog.DEBUG) {
499
/**
500
* Returns a string representation of the matrix. e.g.
501
* <pre>
502
* [ 12 5 9 1 ]
503
* [ 4 16 0 17 ]
504
* [ 12 5 1 23 ]
505
* </pre>
506
*
507
* @return {string} A string representation of this matrix.
508
* @override
509
*/
510
goog.math.Matrix.prototype.toString = function() {
511
// Calculate correct padding for optimum display of matrix
512
var maxLen = 0;
513
goog.math.Matrix.forEach(this, function(val) {
514
var len = String(val).length;
515
if (len > maxLen) {
516
maxLen = len;
517
}
518
});
519
520
// Build the string
521
var sb = [];
522
goog.array.forEach(this.array_, function(row, x) {
523
sb.push('[ ');
524
goog.array.forEach(row, function(val, y) {
525
var strval = String(val);
526
sb.push(goog.string.repeat(' ', maxLen - strval.length) + strval + ' ');
527
});
528
sb.push(']\n');
529
});
530
531
return sb.join('');
532
};
533
}
534
535
536
/**
537
* Returns the signed minor.
538
* @param {number} i The row index.
539
* @param {number} j The column index.
540
* @return {number} The cofactor C[i,j] of this matrix.
541
* @private
542
*/
543
goog.math.Matrix.prototype.getCofactor_ = function(i, j) {
544
return (i + j % 2 == 0 ? 1 : -1) * this.getMinor_(i, j);
545
};
546
547
548
/**
549
* Returns the determinant of this matrix. The determinant of a matrix A is
550
* often denoted as |A| and can only be applied to a square matrix. Same as
551
* public method but without validation. Implemented using Laplace's formula.
552
* @return {number} The determinant of this matrix.
553
* @private
554
*/
555
goog.math.Matrix.prototype.getDeterminant_ = function() {
556
if (this.getSize().area() == 1) {
557
return this.array_[0][0];
558
}
559
560
// We might want to use matrix decomposition to improve running time
561
// For now we'll do a Laplace expansion along the first row
562
var determinant = 0;
563
for (var j = 0; j < this.size_.width; j++) {
564
determinant += (this.array_[0][j] * this.getCofactor_(0, j));
565
}
566
return determinant;
567
};
568
569
570
/**
571
* Returns the determinant of the submatrix resulting from the deletion of row i
572
* and column j.
573
* @param {number} i The row to delete.
574
* @param {number} j The column to delete.
575
* @return {number} The first minor M[i,j] of this matrix.
576
* @private
577
*/
578
goog.math.Matrix.prototype.getMinor_ = function(i, j) {
579
return this.getSubmatrixByDeletion_(i, j).getDeterminant_();
580
};
581
582
583
/**
584
* Returns a submatrix contained within this matrix.
585
* @param {number} i1 The upper row index.
586
* @param {number} j1 The left column index.
587
* @param {number=} opt_i2 The lower row index.
588
* @param {number=} opt_j2 The right column index.
589
* @return {!goog.math.Matrix} The submatrix contained within the given bounds.
590
* @private
591
*/
592
goog.math.Matrix.prototype.getSubmatrixByCoordinates_ = function(
593
i1, j1, opt_i2, opt_j2) {
594
var i2 = opt_i2 ? opt_i2 : this.size_.height - 1;
595
var j2 = opt_j2 ? opt_j2 : this.size_.width - 1;
596
var result = new goog.math.Matrix(i2 - i1 + 1, j2 - j1 + 1);
597
goog.math.Matrix.forEach(result, function(value, i, j) {
598
result.array_[i][j] = this.array_[i1 + i][j1 + j];
599
}, this);
600
return result;
601
};
602
603
604
/**
605
* Returns a new matrix equal to this one, but with row i and column j deleted.
606
* @param {number} i The row index of the coordinate.
607
* @param {number} j The column index of the coordinate.
608
* @return {!goog.math.Matrix} The value at the specified coordinate.
609
* @private
610
*/
611
goog.math.Matrix.prototype.getSubmatrixByDeletion_ = function(i, j) {
612
var m = new goog.math.Matrix(this.size_.width - 1, this.size_.height - 1);
613
goog.math.Matrix.forEach(m, function(value, x, y) {
614
m.setValueAt(x, y, this.array_[x >= i ? x + 1 : x][y >= j ? y + 1 : y]);
615
}, this);
616
return m;
617
};
618
619
620
/**
621
* Returns whether the given coordinates are contained within the bounds of the
622
* matrix.
623
* @param {number} i The i index of the coordinate.
624
* @param {number} j The j index of the coordinate.
625
* @return {boolean} The value at the specified coordinate.
626
* @private
627
*/
628
goog.math.Matrix.prototype.isInBounds_ = function(i, j) {
629
return i >= 0 && i < this.size_.height && j >= 0 && j < this.size_.width;
630
};
631
632
633
/**
634
* Matrix multiplication is defined between two matrices only if the number of
635
* columns of the first matrix is the same as the number of rows of the second
636
* matrix. If A is an m-by-n matrix and B is an n-by-p matrix, then their
637
* product AB is an m-by-p matrix
638
*
639
* @param {goog.math.Matrix} m Matrix to multiply the matrix by.
640
* @return {!goog.math.Matrix} Resultant product.
641
* @private
642
*/
643
goog.math.Matrix.prototype.matrixMultiply_ = function(m) {
644
var resultMatrix = new goog.math.Matrix(this.size_.height, m.getSize().width);
645
goog.math.Matrix.forEach(resultMatrix, function(val, x, y) {
646
var newVal = 0;
647
for (var i = 0; i < this.size_.width; i++) {
648
newVal += goog.asserts.assertNumber(this.getValueAt(x, i)) *
649
goog.asserts.assertNumber(m.getValueAt(i, y));
650
}
651
resultMatrix.setValueAt(x, y, newVal);
652
}, this);
653
return resultMatrix;
654
};
655
656
657
/**
658
* Scalar multiplication returns a matrix of the same size as the original,
659
* each value multiplied by the given value.
660
*
661
* @param {number} m number to multiply the matrix by.
662
* @return {!goog.math.Matrix} Resultant product.
663
* @private
664
*/
665
goog.math.Matrix.prototype.scalarMultiply_ = function(m) {
666
return goog.math.Matrix.map(this, function(val, x, y) { return val * m; });
667
};
668
669
670
/**
671
* Swaps two rows.
672
* @param {number} i1 The index of the first row to swap.
673
* @param {number} i2 The index of the second row to swap.
674
* @private
675
*/
676
goog.math.Matrix.prototype.swapRows_ = function(i1, i2) {
677
var tmp = this.array_[i1];
678
this.array_[i1] = this.array_[i2];
679
this.array_[i2] = tmp;
680
};
681
682