From 8d41a25871975c1968bbcb849e564a63d297755c Mon Sep 17 00:00:00 2001 From: Sarthak Tayal Date: Tue, 4 Aug 2026 01:56:05 -0400 Subject: [PATCH 1/9] --- src/matrix.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/matrix.js b/src/matrix.js index bd8e513..552a9b6 100644 --- a/src/matrix.js +++ b/src/matrix.js @@ -1420,6 +1420,36 @@ export class AbstractMatrix { return this; } + concat(other, by = 'row') { + other = Matrix.checkMatrix(other); + switch (by) { + case 'row': { + if (this.columns !== other.columns) { + throw new RangeError( + 'both matrices must have the same number of columns', + ); + } + const result = new Matrix(this.rows + other.rows, this.columns); + result.setSubMatrix(this, 0, 0); + result.setSubMatrix(other, this.rows, 0); + return result; + } + case 'column': { + if (this.rows !== other.rows) { + throw new RangeError( + 'both matrices must have the same number of rows', + ); + } + const result = new Matrix(this.rows, this.columns + other.columns); + result.setSubMatrix(this, 0, 0); + result.setSubMatrix(other, 0, this.columns); + return result; + } + default: + throw new Error(`invalid option: ${by}`); + } + } + selection(rowIndices, columnIndices) { checkRowIndices(this, rowIndices); checkColumnIndices(this, columnIndices); From ab091ad71076ee21f6132c4b2d29f71b7a70b676 Mon Sep 17 00:00:00 2001 From: Sarthak Tayal Date: Tue, 4 Aug 2026 01:56:25 -0400 Subject: [PATCH 2/9] feat: add concat to stack two matrices vertically or side by side --- matrix.d.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/matrix.d.ts b/matrix.d.ts index a418fe8..5563a4d 100644 --- a/matrix.d.ts +++ b/matrix.d.ts @@ -763,6 +763,15 @@ export abstract class AbstractMatrix { startColumn: number, ): this; + /** + * Returns a new matrix made of this matrix followed by the other one. + * Concatenating by 'row' stacks the two matrices vertically and requires the same number of columns. + * Concatenating by 'column' stacks them side by side and requires the same number of rows. + * @param other - The matrix to append. + * @param by - Concatenate by 'row' or 'column'. Default: `'row'`. + */ + concat(other: MaybeMatrix, by?: MatrixDimension): Matrix; + /** * Return a new matrix based on a selection of rows and columns. * Order of the indices matters and the same index can be used more than once. From a52f5ef8a8ca194d9767a436a6b7e00875271eba Mon Sep 17 00:00:00 2001 From: Sarthak Tayal Date: Tue, 4 Aug 2026 01:56:58 -0400 Subject: [PATCH 3/9] --- src/__tests__/matrix/concat.test.js | 144 ++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 src/__tests__/matrix/concat.test.js diff --git a/src/__tests__/matrix/concat.test.js b/src/__tests__/matrix/concat.test.js new file mode 100644 index 0000000..42218d8 --- /dev/null +++ b/src/__tests__/matrix/concat.test.js @@ -0,0 +1,144 @@ +import { describe, it, expect } from 'vitest'; + +import { Matrix, MatrixTransposeView, SymmetricMatrix } from '../..'; + +describe('concat', () => { + const matrix = new Matrix([ + [1, 2], + [3, 4], + ]); + const other = new Matrix([ + [5, 6], + [7, 8], + [9, 10], + ]); + + it('by row stacks the matrices vertically', () => { + const result = matrix.concat(other, 'row'); + expect(result.rows).toBe(5); + expect(result.columns).toBe(2); + expect(result.to2DArray()).toStrictEqual([ + [1, 2], + [3, 4], + [5, 6], + [7, 8], + [9, 10], + ]); + }); + + it('by column stacks the matrices side by side', () => { + const result = matrix.concat(other.transpose(), 'column'); + expect(result.rows).toBe(2); + expect(result.columns).toBe(5); + expect(result.to2DArray()).toStrictEqual([ + [1, 2, 5, 7, 9], + [3, 4, 6, 8, 10], + ]); + }); + + it('concatenates by row when the dimension is omitted', () => { + expect(matrix.concat(other).to2DArray()).toStrictEqual( + matrix.concat(other, 'row').to2DArray(), + ); + }); + + it('accepts a 2D array', () => { + expect(matrix.concat([[5, 6]]).to2DArray()).toStrictEqual([ + [1, 2], + [3, 4], + [5, 6], + ]); + }); + + it('leaves both operands untouched', () => { + matrix.concat(other, 'row'); + expect(matrix.to2DArray()).toStrictEqual([ + [1, 2], + [3, 4], + ]); + expect(other.rows).toBe(3); + }); + + it('always returns a plain matrix', () => { + const symmetric = new SymmetricMatrix([ + [1, 2], + [2, 3], + ]); + const result = symmetric.concat(matrix, 'row'); + expect(result).toBeInstanceOf(Matrix); + expect(result.to2DArray()).toStrictEqual([ + [1, 2], + [2, 3], + [1, 2], + [3, 4], + ]); + }); + + it('reads a view along the view dimensions', () => { + const view = new MatrixTransposeView( + new Matrix([ + [1, 2, 3], + [4, 5, 6], + ]), + ); + expect(view.concat([[7, 8]], 'row').to2DArray()).toStrictEqual([ + [1, 4], + [2, 5], + [3, 6], + [7, 8], + ]); + }); +}); + +describe('concat with degenerate matrices', () => { + it('by row with a matrix without rows', () => { + const result = new Matrix(0, 2).concat(new Matrix([[1, 2]]), 'row'); + expect(result.to2DArray()).toStrictEqual([[1, 2]]); + }); + + it('by row onto a matrix without rows', () => { + const result = new Matrix([[1, 2]]).concat(new Matrix(0, 2), 'row'); + expect(result.to2DArray()).toStrictEqual([[1, 2]]); + }); + + it('by column with a matrix without columns', () => { + const result = new Matrix(2, 0).concat(new Matrix([[1], [2]]), 'column'); + expect(result.to2DArray()).toStrictEqual([[1], [2]]); + }); + + it('by column onto a matrix without columns', () => { + const result = new Matrix([[1], [2]]).concat(new Matrix(2, 0), 'column'); + expect(result.to2DArray()).toStrictEqual([[1], [2]]); + }); + + it('by row of two 0x0 matrices', () => { + const result = new Matrix(0, 0).concat(new Matrix(0, 0), 'row'); + expect(result.rows).toBe(0); + expect(result.columns).toBe(0); + }); +}); + +describe('concat error handling', () => { + const matrix = new Matrix([ + [1, 2], + [3, 4], + ]); + + it('throws when the number of columns differs', () => { + expect(() => matrix.concat(new Matrix([[1, 2, 3]]), 'row')).toThrow( + /^both matrices must have the same number of columns$/, + ); + }); + + it('throws when the number of rows differs', () => { + expect(() => matrix.concat(new Matrix([[1, 2, 3]]), 'column')).toThrow( + /^both matrices must have the same number of rows$/, + ); + }); + + it('throws when the dimension is unknown', () => { + expect(() => matrix.concat(matrix, 'diagonal')).toThrow( + /^invalid option: diagonal$/, + ); + }); +}); From a41a2518787e2af9219c903d14f4f4072c75517d Mon Sep 17 00:00:00 2001 From: Sarthak Tayal Date: Tue, 4 Aug 2026 01:57:51 -0400 Subject: [PATCH 4/9] --- src/__tests__/matrix/concat.test.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/__tests__/matrix/concat.test.js b/src/__tests__/matrix/concat.test.js index 42218d8..51d7a46 100644 --- a/src/__tests__/matrix/concat.test.js +++ b/src/__tests__/matrix/concat.test.js @@ -88,6 +88,34 @@ describe('concat', () => { [7, 8], ]); }); + + it('appends a column vector', () => { + const result = matrix.concat(Matrix.columnVector([5, 6]), 'column'); + expect(result.to2DArray()).toStrictEqual([ + [1, 2, 5], + [3, 4, 6], + ]); + }); + + it('appends a row vector', () => { + const result = matrix.concat(Matrix.rowVector([5, 6]), 'row'); + expect(result.to2DArray()).toStrictEqual([ + [1, 2], + [3, 4], + [5, 6], + ]); + }); + + it('chains to gather several matrices', () => { + const result = matrix + .concat([[5, 6]], 'row') + .concat(Matrix.columnVector([7, 8, 9]), 'column'); + expect(result.to2DArray()).toStrictEqual([ + [1, 2, 7], + [3, 4, 8], + [5, 6, 9], + ]); + }); }); describe('concat with degenerate matrices', () => { From e772825dbdb29b8a875c52857336b9853676911d Mon Sep 17 00:00:00 2001 From: Sarthak Tayal Date: Tue, 4 Aug 2026 01:58:01 -0400 Subject: [PATCH 5/9] --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index f4ea79a..4897188 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,16 @@ var norm = A.norm(); // norm = 10.14889156509222 (Frobenius var transpose = A.transpose(); // transpose = Matrix [[1, 10], [1, -1], rows: 2, columns: 2] ``` +#### Concatenation of matrices +```js +var M = new Matrix([ + [1, 2], + [3, 4], +]); + +var stacked = M.concat([[5, 6]]); // stacked = Matrix [[1, 2], [3, 4], [5, 6], rows: 3, columns: 2] +``` + #### Instantiation of matrix ```js var z = Matrix.zeros(3, 2); // z = Matrix [[0, 0], [0, 0], [0, 0], rows: 3, columns: 2] From fbb4498e6fd0d5adef90c644de01498187ee24d0 Mon Sep 17 00:00:00 2001 From: Sarthak Tayal Date: Tue, 4 Aug 2026 01:58:12 -0400 Subject: [PATCH 6/9] docs: show concat in the readme examples --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4897188..9f08614 100644 --- a/README.md +++ b/README.md @@ -131,8 +131,10 @@ var M = new Matrix([ [3, 4], ]); -var stacked = M.concat([[5, 6]]); // stacked = Matrix [[1, 2], [3, 4], [5, 6], rows: 3, columns: 2] +var stacked = M.concat([[5, 6]]); // stacked = Matrix [[1, 2], [3, 4], [5, 6], rows: 3, columns: 2] +var widened = M.concat(Matrix.columnVector([5, 6]), 'column'); // widened = Matrix [[1, 2, 5], [3, 4, 6], rows: 2, columns: 3] ``` +Concatenating by row needs the same number of columns on both sides, concatenating by column needs the same number of rows. The two operands are left untouched. #### Instantiation of matrix ```js From 1d52c6c9c87e6558e541920de16fd8afd3414f00 Mon Sep 17 00:00:00 2001 From: Sarthak Tayal Date: Tue, 4 Aug 2026 01:59:39 -0400 Subject: [PATCH 7/9] --- src/__tests__/matrix/concat.test.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/__tests__/matrix/concat.test.js b/src/__tests__/matrix/concat.test.js index 51d7a46..5d5abde 100644 --- a/src/__tests__/matrix/concat.test.js +++ b/src/__tests__/matrix/concat.test.js @@ -139,6 +139,18 @@ describe('concat with degenerate matrices', () => { expect(result.to2DArray()).toStrictEqual([[1], [2]]); }); + it('by column of two matrices without rows', () => { + const result = new Matrix(0, 1).concat(new Matrix(0, 2), 'column'); + expect(result.rows).toBe(0); + expect(result.columns).toBe(3); + }); + + it('by row of two matrices without columns', () => { + const result = new Matrix(1, 0).concat(new Matrix(2, 0), 'row'); + expect(result.rows).toBe(3); + expect(result.columns).toBe(0); + }); + it('by row of two 0x0 matrices', () => { const result = new Matrix(0, 0).concat(new Matrix(0, 0), 'row'); expect(result.rows).toBe(0); From 2fd4ce0212c45fa60ee0fb8b1f781e6e6d6690d3 Mon Sep 17 00:00:00 2001 From: Sarthak Tayal Date: Tue, 4 Aug 2026 01:59:54 -0400 Subject: [PATCH 8/9] --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9f08614..c47ce64 100644 --- a/README.md +++ b/README.md @@ -131,8 +131,8 @@ var M = new Matrix([ [3, 4], ]); -var stacked = M.concat([[5, 6]]); // stacked = Matrix [[1, 2], [3, 4], [5, 6], rows: 3, columns: 2] -var widened = M.concat(Matrix.columnVector([5, 6]), 'column'); // widened = Matrix [[1, 2, 5], [3, 4, 6], rows: 2, columns: 3] +var stacked = M.concat([[5, 6]]); // stacked = Matrix [[1, 2], [3, 4], [5, 6], rows: 3, columns: 2] +var widened = M.concat(Matrix.columnVector([5, 6]), 'column'); // widened = Matrix [[1, 2, 5], [3, 4, 6], rows: 2, columns: 3] ``` Concatenating by row needs the same number of columns on both sides, concatenating by column needs the same number of rows. The two operands are left untouched. From e42066508afd2fe37d91c0c6e4a9bcd8482a39c1 Mon Sep 17 00:00:00 2001 From: Sarthak Tayal Date: Tue, 4 Aug 2026 02:00:43 -0400 Subject: [PATCH 9/9] --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index c47ce64..e93ad91 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,13 @@ var norm = A.norm(); // norm = 10.14889156509222 (Frobenius var transpose = A.transpose(); // transpose = Matrix [[1, 10], [1, -1], rows: 2, columns: 2] ``` +#### Instantiation of matrix +```js +var z = Matrix.zeros(3, 2); // z = Matrix [[0, 0], [0, 0], [0, 0], rows: 3, columns: 2] +var z = Matrix.ones(2, 3); // z = Matrix [[1, 1, 1], [1, 1, 1], rows: 2, columns: 3] +var z = Matrix.eye(3, 4); // z = Matrix [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], rows: 3, columns: 4]. there are 1 only in the diagonal +``` + #### Concatenation of matrices ```js var M = new Matrix([ @@ -136,13 +143,6 @@ var widened = M.concat(Matrix.columnVector([5, 6]), 'column'); // widened = Matr ``` Concatenating by row needs the same number of columns on both sides, concatenating by column needs the same number of rows. The two operands are left untouched. -#### Instantiation of matrix -```js -var z = Matrix.zeros(3, 2); // z = Matrix [[0, 0], [0, 0], [0, 0], rows: 3, columns: 2] -var z = Matrix.ones(2, 3); // z = Matrix [[1, 1, 1], [1, 1, 1], rows: 2, columns: 3] -var z = Matrix.eye(3, 4); // z = Matrix [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], rows: 3, columns: 4]. there are 1 only in the diagonal -``` - ### Maths ```js const {