In this part, we will cover the most of basic matrix operations. We are going add and subtract matrices, multiply them by a scalar, implement matrix-matrix multiplication, find transpose matrix and take a more deep look at determinant. We are not going to look at concepts such as inverse matrix and rank in this part, but leave them for the future.

The matrix **addition** and **subtraction** operations take pairs of matrices as inputs and produce matrices as outputs. Addition and subtraction are performed component-wise and matrices must have the same dimensions to be added or subtracted.

To not repeat the same code in addition and subtraction we first write a method that receives operation, function that will do something with two components and another matrix. Then we can call it in both addition and subtraction methods with different operations.

matrix-add-subtract.js

```
class Matrix {
// ...
componentWiseOperation(func, { rows }) {
const newRows = rows.map((row, i) =>
row.map((element, j) => func(this.rows[i][j], element))
)
return new Matrix(...newRows)
}
add(other) {
return this.componentWiseOperation((a, b) => a + b, other)
}
subtract(other) {
return this.componentWiseOperation((a, b) => a - b, other)
}
}
const one = new Matrix(
[1, 2],
[3, 4]
)
const other = new Matrix(
[5, 6],
[7, 8]
)
console.log(one.add(other))
// Matrix { rows: [ [ 6, 8 ], [ 10, 12 ] ] }
console.log(other.subtract(one))
// Matrix { rows: [ [ 4, 4 ], [ 4, 4 ] ] }
```

Scalar multiplication of matrices is defined in a similar way as for vectors and is done by multiplying every element of the matrix by the scalar.

matrix-scale.js

```
class Matrix {
// ...
scaleBy(number) {
const newRows = this.rows.map(row =>
row.map(element => element * number)
)
return new Matrix(...newRows)
}
}
const matrix = new Matrix(
[2, 3],
[4, 5]
)
console.log(matrix.scaleBy(2))
// Matrix { rows: [ [ 4, 6 ], [ 8, 10 ] ] }
```

We can multiply two matrices *A* and *B* provided their dimensions are **compatible**, which means the number of columns of *A* equals the number of rows of *B*. The matrix product *AB* of matrices consists of computing the dot product between each row of *A* and each column of *B*:

matrix-matrix-multiplication.js

```
class Matrix {
// ...
multiply(other) {
if (this.rows[0].length !== other.rows.length) {
throw new Error('The number of columns of this matrix is not equal to the number of rows of the given matrix.')
}
const columns = other.columns()
const newRows = this.rows.map(row =>
columns.map(column => sum(row.map((element, i) => element * column[i])))
)
return new Matrix(...newRows)
}
}
const one = new Matrix(
[3, -4],
[0, -3],
[6, -2],
[-1, 1]
)
const other = new Matrix(
[3, 2, -4],
[4, -3, 5]
)
console.log(one.multiply(other))
// Matrix {
// rows:
// [ [ -7, 18, -32 ],
// [ -12, 9, -15 ],
// [ 10, 18, -34 ],
// [ 1, -5, 9 ] ]}
```

We can think of a result of a matrix-matrix product *AB* as a linear transformation that first applies linear transformation *B* and then *A*. To better understand this concept let’s go to the linear-algebra-demo project.

Yellow shape in the picture below is the result of applying linear transformation *C* to the red square. Transformation *C* is a result of matrix-matrix multiplication. Where *A* is a reflection matrix, and *B* is a shear matrix.

If we change the order of matrices *A* and *B* in matrix-matrix multiplication, we will receive a different result, because first will be applied transformation *B* and then *A*.

The **transpose** matrix *Aᵀ* is defined by the formula *aᵀᵢⱼ = aⱼᵢ*. In other words, we obtain the transpose by “flipping” the matrix through its diagonal. Note that entries on the diagonal of the matrix are not affected by the transpose operation.

transpose.js

```
class Matrix {
// ...
transpose() {
return new Matrix(...this.columns())
}
}
const matrix = new Matrix(
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[9, 10, 11]
)
console.log(matrix.transpose())
// Matrix {
// rows: [
// [ 0, 3, 6, 9 ],
// [ 1, 4, 7, 10 ],
// [ 2, 5, 8, 11 ]
// ]
// }
```

The **determinant** of a matrix is a calculation that involves all the coefficients of the matrix, and whose output is a single number. The determinant describes the relative geometry of the vectors that make up the rows of the matrix. More specifically, the determinant of a matrix A tells you the volume of a box with sides given by rows of *A*. The determinant of a *2×2* matrix is:

The determinant of a *3×3* matrix is:

Our method will calculate determinant for a matrix of any size, as long as it has the same number of rows and columns.

determinant.js

```
class Matrix {
// ...
determinant() {
if (this.rows.length !== this.rows[0].length) {
throw new Error('Only matrices with the same number of rows and columns are supported.')
}
if (this.rows.length === 2) {
return this.rows[0][0] * this.rows[1][1] - this.rows[0][1] * this.rows[1][0]
}
const parts = this.rows[0].map((coef, index) => {
const matrixRows = this.rows.slice(1).map(row => [ ...row.slice(0, index), ...row.slice(index + 1)])
const matrix = new Matrix(...matrixRows)
const result = coef * matrix.determinant()
return index % 2 === 0 ? result : -result
})
return sum(parts)
}
}
const matrix2 = new Matrix(
[ 0, 3],
[-2, 1]
)
console.log(matrix2.determinant())
// 6
const matrix3 = new Matrix(
[2, -3, 1],
[2, 0, -1],
[1, 4, 5]
)
console.log(matrix3.determinant())
// 49
const matrix4 = new Matrix(
[3, 0, 2, -1],
[1, 2, 0, -2],
[4, 0, 6, -3],
[5, 0, 2, 0]
)
console.log(matrix4.determinant())
// 20
```

Determinant shows us exactly how much are things being stretched. We can think of it as the factor by which a linear transformation changes any area. To better understand this concept let’s go to the linear-algebra-demo project.

In the picture below we can see that after applying the linear transformation area to red *1×1* square we get a *3×2* rectangle with area equal to *6*, exactly the same number as a determinant of the matrix.

If we apply a shear transformation, we can see that the square become parallelogram, and the area remains the same. A determinant is equal to one.

The determinant is **negative** if after applying the linear transformation orientation of the space will be reverted. In the picture below we can see that before transformation *ĵ* is to the left of î, and after the transformation *ĵ* on the right of *î*.

The determinant of a transformation is **zero** if it squishes all of space onto a line or even on a single point. It means checking if the determinant of a given matrix is 0 will give away if computing weather or not the transformation associated with that matrix squishes everything into a smaller dimension.

In three-dimensional space, determinant tells you how much volumes get scaled.

The determinant of 0 would mean that all of space is squished onto something with 0 volume. If in two-dimensional space it would mean that result of transformation squished onto a line or point, in three-dimensional case it also can mean that objects squished onto a plane, as in the example below.