From 2895aaac889ba54592782248c4a1b26cfa831597 Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sat, 7 Aug 2021 14:37:13 +0800 Subject: [PATCH 01/54] feat: utils/Matrix.ts to contain helper functions that do not belong in the Matrix class --- app/utils/Matrix.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 app/utils/Matrix.ts diff --git a/app/utils/Matrix.ts b/app/utils/Matrix.ts new file mode 100644 index 0000000..919f720 --- /dev/null +++ b/app/utils/Matrix.ts @@ -0,0 +1,9 @@ +/** + * Returns true if every entry in the array is 0, false otherwise + * @param arr 1D array + */ +const isZeroRow = (arr: number[]) => { + return !arr.some((num) => num !== 0) +} + +export { isZeroRow } From e9c889835aa023ccb7a1b6d0b1e194a35323a7ac Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sat, 7 Aug 2021 15:15:02 +0800 Subject: [PATCH 02/54] feat: create util range --- app/utils/misc.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 app/utils/misc.ts diff --git a/app/utils/misc.ts b/app/utils/misc.ts new file mode 100644 index 0000000..d6c25d1 --- /dev/null +++ b/app/utils/misc.ts @@ -0,0 +1,9 @@ +/** + * @param max highest value + * @returns Array of [0, 1, ..., max-1] + */ +const range = (max: number) => { + return [...Array(max).keys()] +} + +export { range } From e82a8b50c04cce44e1477841f6a306cf162d36da Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sat, 7 Aug 2021 15:15:23 +0800 Subject: [PATCH 03/54] test: create test for isZeroRow --- app/tests/utils.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 app/tests/utils.ts diff --git a/app/tests/utils.ts b/app/tests/utils.ts new file mode 100644 index 0000000..9b509e8 --- /dev/null +++ b/app/tests/utils.ts @@ -0,0 +1,28 @@ +// standard testing modules +import chai from "chai" + +import { range } from "../utils/misc" + +// import utils to be tested +import { isZeroRow } from "../utils/Matrix" + +describe("Util tests", () => { + describe("Matrix: isZeroRow", () => { + it("should return true for zero array", () => { + const res = range(5).every((val) => { + // create zero array with length val+1 + const tmp = [] + for (let i = 0; i < val + 1; i++) tmp.push(0) + return isZeroRow(tmp) + }) + chai.expect(res).to.equal(true) + }) + it("should return false for non-zero array", () => { + const nonZeroArrays = [[1, 2, 3], [0, 0, 1], [1]] + nonZeroArrays.forEach((arr) => { + const res = isZeroRow(arr) + chai.expect(res).to.equal(false) + }) + }) + }) +}) From 98d3196b51d48b889e2b02b60cbd3a53b89d3457 Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sat, 7 Aug 2021 15:15:41 +0800 Subject: [PATCH 04/54] chore: add script for unit tests --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index cd5f4e6..4a09212 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "build": "next build", "start": "next start", "version": "auto-changelog -p -l 0 && git add CHANGELOG.md", - "test": "mocha -r ts-node/register app/tests/index.ts" + "test": "mocha -r ts-node/register app/tests/index.ts", + "test-unit": "mocha -r ts-node/register app/tests/utils.ts" }, "repository": { "type": "git", From 0dc9bbaa33dc730de9c0825f27870faaa173af27 Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sat, 7 Aug 2021 15:20:02 +0800 Subject: [PATCH 05/54] test: also test range() --- app/tests/utils.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/tests/utils.ts b/app/tests/utils.ts index 9b509e8..8fb931c 100644 --- a/app/tests/utils.ts +++ b/app/tests/utils.ts @@ -1,12 +1,20 @@ // standard testing modules import chai from "chai" -import { range } from "../utils/misc" - // import utils to be tested +import { range } from "../utils/misc" import { isZeroRow } from "../utils/Matrix" describe("Util tests", () => { + describe("Misc: range", () => { + it("should return an array of length N", () => { + const res = range(5).every((val) => { + return range(val + 1).length === val + 1 + }) + chai.expect(res).to.equal(true) + }) + }) + describe("Matrix: isZeroRow", () => { it("should return true for zero array", () => { const res = range(5).every((val) => { From a064ca2cf13c663a675b073c43319fe9409eed23 Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sat, 7 Aug 2021 15:23:36 +0800 Subject: [PATCH 06/54] feat: create util leadingEntryIndex --- app/utils/Matrix.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/app/utils/Matrix.ts b/app/utils/Matrix.ts index 919f720..82f7248 100644 --- a/app/utils/Matrix.ts +++ b/app/utils/Matrix.ts @@ -6,4 +6,17 @@ const isZeroRow = (arr: number[]) => { return !arr.some((num) => num !== 0) } -export { isZeroRow } +/** + * Returns the index of the first non-zero entry in the array. Returns -1 if not found + * @param arr 1D array + */ +const leadingEntryIndex = (arr: number[]) => { + let idx = 0 + while (idx < arr.length) { + if (arr[idx] !== 0) return idx + idx++ + } + return -1 +} + +export { isZeroRow, leadingEntryIndex } From 88ef2a1249bf4df8fbbaa9f9a4ce51fe58b597a5 Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sat, 7 Aug 2021 15:23:54 +0800 Subject: [PATCH 07/54] test: create test for leadingEntryIndex --- app/tests/utils.ts | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/app/tests/utils.ts b/app/tests/utils.ts index 8fb931c..8587ca4 100644 --- a/app/tests/utils.ts +++ b/app/tests/utils.ts @@ -3,7 +3,7 @@ import chai from "chai" // import utils to be tested import { range } from "../utils/misc" -import { isZeroRow } from "../utils/Matrix" +import { isZeroRow, leadingEntryIndex } from "../utils/Matrix" describe("Util tests", () => { describe("Misc: range", () => { @@ -33,4 +33,25 @@ describe("Util tests", () => { }) }) }) + + describe("Matrix: leadingEntryIndex", () => { + it("should return index of the first nonzero entry", () => { + const arrays = [[1, 2, 3], [0, 0, 1], [1]] + const ans = [0, 2, 0] + + arrays.forEach((arr, idx) => { + const res = leadingEntryIndex(arr) + chai.expect(res).to.equal(ans[idx]) + }) + }) + it("should return -1 for zero arrays", () => { + const res = range(5).every((val) => { + // create zero array with length val+1 + const tmp = [] + for (let i = 0; i < val + 1; i++) tmp.push(0) + return leadingEntryIndex(tmp) === -1 + }) + chai.expect(res).to.equal(true) + }) + }) }) From e05e02bc63186371222f1174fb8a9bd0b05431df Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sat, 7 Aug 2021 15:35:20 +0800 Subject: [PATCH 08/54] chore: move utils test into unit folder --- app/tests/{ => unit}/utils.ts | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename app/tests/{ => unit}/utils.ts (93%) diff --git a/app/tests/utils.ts b/app/tests/unit/utils.ts similarity index 93% rename from app/tests/utils.ts rename to app/tests/unit/utils.ts index 8587ca4..33d5041 100644 --- a/app/tests/utils.ts +++ b/app/tests/unit/utils.ts @@ -2,8 +2,8 @@ import chai from "chai" // import utils to be tested -import { range } from "../utils/misc" -import { isZeroRow, leadingEntryIndex } from "../utils/Matrix" +import { range } from "../../utils/misc" +import { isZeroRow, leadingEntryIndex } from "../../utils/Matrix" describe("Util tests", () => { describe("Misc: range", () => { diff --git a/package.json b/package.json index 4a09212..395b180 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "start": "next start", "version": "auto-changelog -p -l 0 && git add CHANGELOG.md", "test": "mocha -r ts-node/register app/tests/index.ts", - "test-unit": "mocha -r ts-node/register app/tests/utils.ts" + "test-unit": "mocha -r ts-node/register app/tests/unit/utils.ts" }, "repository": { "type": "git", From 8ef288f768bdcb6bf7525389bdf0585fd53f4cfb Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sat, 7 Aug 2021 15:46:52 +0800 Subject: [PATCH 09/54] chore: reorganize unit test to run from index file --- app/tests/unit/index.ts | 5 +++++ app/tests/unit/utils.ts | 4 ++-- package.json | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 app/tests/unit/index.ts diff --git a/app/tests/unit/index.ts b/app/tests/unit/index.ts new file mode 100644 index 0000000..0012fb5 --- /dev/null +++ b/app/tests/unit/index.ts @@ -0,0 +1,5 @@ +import utils from "./utils" + +describe("Unit tests", () => { + describe("Utils tests", utils) +}) diff --git a/app/tests/unit/utils.ts b/app/tests/unit/utils.ts index 33d5041..6793577 100644 --- a/app/tests/unit/utils.ts +++ b/app/tests/unit/utils.ts @@ -5,7 +5,7 @@ import chai from "chai" import { range } from "../../utils/misc" import { isZeroRow, leadingEntryIndex } from "../../utils/Matrix" -describe("Util tests", () => { +export default () => { describe("Misc: range", () => { it("should return an array of length N", () => { const res = range(5).every((val) => { @@ -54,4 +54,4 @@ describe("Util tests", () => { chai.expect(res).to.equal(true) }) }) -}) +} diff --git a/package.json b/package.json index 395b180..740d84c 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "start": "next start", "version": "auto-changelog -p -l 0 && git add CHANGELOG.md", "test": "mocha -r ts-node/register app/tests/index.ts", - "test-unit": "mocha -r ts-node/register app/tests/unit/utils.ts" + "test-unit": "mocha -r ts-node/register app/tests/unit/index.ts" }, "repository": { "type": "git", From be730b0a8f02b5faae870c79c00b083fe68c09d4 Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sat, 7 Aug 2021 16:02:27 +0800 Subject: [PATCH 10/54] feat: create enum EchelonType --- app/types/Matrix.ts | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 app/types/Matrix.ts diff --git a/app/types/Matrix.ts b/app/types/Matrix.ts new file mode 100644 index 0000000..4b68bbc --- /dev/null +++ b/app/types/Matrix.ts @@ -0,0 +1,7 @@ +enum EchelonType { + NONE = "None", + REF = "Row echelon form", + RREF = "Reduced row echelon form", +} + +export { EchelonType } From 40eae32512331054ae713ee5a8a352228297d935 Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sat, 7 Aug 2021 16:03:46 +0800 Subject: [PATCH 11/54] feat: create method echelonStatus --- app/classes/Matrix.ts | 60 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/app/classes/Matrix.ts b/app/classes/Matrix.ts index cad7f6e..6d4c2e0 100644 --- a/app/classes/Matrix.ts +++ b/app/classes/Matrix.ts @@ -1,3 +1,8 @@ +import { range } from "../utils/misc" + +import { EchelonType } from "../types/Matrix" +import { leadingEntryIndex } from "../utils/Matrix" + interface IMatrix { rows: number columns: number @@ -23,6 +28,61 @@ abstract class BaseMatrix { this.entries = tmp } } + + /** + * Returns whether a matrix is in RREF, REF, or NONE. + */ + echelonStatus(): EchelonType { + // obtain array of leading entry indices + let leadingIndices: number[] = [] + this.entries.forEach((row) => { + const leadingIndex = leadingEntryIndex(row) + leadingIndices.push(leadingIndex) + }) + + // remove all end -1s as they are extra zero rows at the bottom of the matrix + while ( + leadingIndices.length > 0 && + leadingIndices[leadingIndices.length - 1] === -1 + ) + leadingIndices.pop() + + // Check 1a: if there are still non-zero rows, this means they are not at the bottom, so return NONE + if (leadingIndices.some((one) => one === -1)) return EchelonType.NONE + + // Check 1b: if leadingEntryIndices not in non-decreasing order, return NONE + let idx = 1 + while (idx < this.rows) { + if (leadingIndices[idx - 1] > leadingIndices[idx]) return EchelonType.NONE + idx++ + } + + // reaching here means is either REF or RREF + + // Check 2: if leadingIndices has no more entries (i.e. was all -1s), this is a zero matrix, and is RREF + if (leadingIndices.length === 0) return EchelonType.RREF + + // Check 3: For RREF, all leading entries of nonzero rows should be 1 + const allLeadingOne = leadingIndices.every( + (colIdx, rowIdx) => this.entries[rowIdx][colIdx] === 1 + ) + if (!allLeadingOne) return EchelonType.REF + + // Check 4: For RREF, the only nonzero value in a pivot column is the pivot point + const pivotColumnsCheck = leadingIndices.every((colIdx, idx) => { + const res = range(this.rows).every((rowIdx) => { + // if the leading index found previously belongs to this row + if (idx == rowIdx) return true + + return this.entries[rowIdx][colIdx] === 0 + }) + return res + }) + if (!pivotColumnsCheck) return EchelonType.REF + + // reaching here means RREF + return EchelonType.RREF + } } class Matrix extends BaseMatrix { From 2883a97fb591c40871671bb2f792ce39c6ccf28f Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sat, 7 Aug 2021 16:04:03 +0800 Subject: [PATCH 12/54] test: create matrix unit test file --- app/tests/unit/index.ts | 2 + app/tests/unit/matrix.ts | 91 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 app/tests/unit/matrix.ts diff --git a/app/tests/unit/index.ts b/app/tests/unit/index.ts index 0012fb5..f27ce0e 100644 --- a/app/tests/unit/index.ts +++ b/app/tests/unit/index.ts @@ -1,5 +1,7 @@ import utils from "./utils" +import matrix from "./matrix" describe("Unit tests", () => { describe("Utils tests", utils) + describe("Matrix tests", matrix) }) diff --git a/app/tests/unit/matrix.ts b/app/tests/unit/matrix.ts new file mode 100644 index 0000000..795502e --- /dev/null +++ b/app/tests/unit/matrix.ts @@ -0,0 +1,91 @@ +// standard testing modules +import chai from "chai" + +import { EchelonType } from "../../types/Matrix" + +// import stuff to be tested +import { Matrix } from "../../classes/Matrix" + +export default () => { + describe("echelonStatus", () => { + it("should return RREF correctly", () => { + const matrices = [ + [ + [1, 0, 0], + [0, 1, 0], + ], + [ + [0, 0], + [0, 0], + [0, 0], + ], + ] + + const res = matrices.every((arr) => { + const matrix = new Matrix({ + rows: arr.length, + columns: arr[0].length, + entries: arr, + }) + const status = matrix.echelonStatus() + return status == EchelonType.RREF + }) + + chai.expect(res).to.equal(true) + }) + it("should return REF correctly", () => { + const matrices = [ + [ + [1, 0, 0], + [0, 2, 0], + ], + [ + [1, 1, 3], + [0, 2, 0], + ], + [ + [1, 0, 3], + [0, 0, 1], + ], + ] + + const res = matrices.every((arr) => { + const matrix = new Matrix({ + rows: arr.length, + columns: arr[0].length, + entries: arr, + }) + const status = matrix.echelonStatus() + return status == EchelonType.REF + }) + + chai.expect(res).to.equal(true) + }) + it("should return NONE correctly", () => { + const matrices = [ + [ + [0, 0, 0], + [1, 0, 3], + [0, 0, 0], + ], + [ + [0, 1, 0], + [1, 0, 3], + [0, 0, 0], + ], + ] + + const res = matrices.every((arr) => { + const matrix = new Matrix({ + rows: arr.length, + columns: arr[0].length, + entries: arr, + }) + const status = matrix.echelonStatus() + return status == EchelonType.NONE + }) + + chai.expect(res).to.equal(true) + }) + }) +} From 0292d76606f2eee22547b3c2156879d3bb13fbb4 Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sat, 7 Aug 2021 16:08:50 +0800 Subject: [PATCH 13/54] chore: add unit test to github actions --- .github/workflows/github-actions.yml | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml index 10c08d1..aa1b254 100644 --- a/.github/workflows/github-actions.yml +++ b/.github/workflows/github-actions.yml @@ -3,8 +3,8 @@ on: push: pull_request: jobs: - test: - name: Test + test-api: + name: test-api runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 @@ -23,3 +23,23 @@ jobs: ${{ runner.OS }}- - run: npm ci - run: npm run test + test-unit: + name: test-unit + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - name: Use Node.js + uses: actions/setup-node@v1 + with: + node-version: "14.x" + - name: Cache Node.js modules + uses: actions/cache@v2 + with: + # npm cache files are stored in `~/.npm` on Linux/macOS + path: ~/.npm + key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.OS }}-node- + ${{ runner.OS }}- + - run: npm ci + - run: npm run test-unit From bb313e18d5a743fa464a9fcfbbbe3116d9059a16 Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sat, 7 Aug 2021 16:10:37 +0800 Subject: [PATCH 14/54] chore: remove push github actions trigger --- .github/workflows/github-actions.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml index aa1b254..f9c9698 100644 --- a/.github/workflows/github-actions.yml +++ b/.github/workflows/github-actions.yml @@ -1,6 +1,5 @@ name: Github Actions on: - push: pull_request: jobs: test-api: From cc7c4ded5902bb2f2bdc662d1140500c4ec6e7df Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sat, 7 Aug 2021 17:33:50 +0800 Subject: [PATCH 15/54] test: run chai.expect for each subtest rather than on a .every --- app/tests/unit/matrix.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/app/tests/unit/matrix.ts b/app/tests/unit/matrix.ts index 795502e..0b2cdbe 100644 --- a/app/tests/unit/matrix.ts +++ b/app/tests/unit/matrix.ts @@ -21,17 +21,15 @@ export default () => { ], ] - const res = matrices.every((arr) => { + matrices.forEach((arr) => { const matrix = new Matrix({ rows: arr.length, columns: arr[0].length, entries: arr, }) const status = matrix.echelonStatus() - return status == EchelonType.RREF + chai.expect(status).to.equal(EchelonType.RREF) }) - - chai.expect(res).to.equal(true) }) it("should return REF correctly", () => { const matrices = [ @@ -49,17 +47,15 @@ export default () => { ], ] - const res = matrices.every((arr) => { + matrices.forEach((arr) => { const matrix = new Matrix({ rows: arr.length, columns: arr[0].length, entries: arr, }) const status = matrix.echelonStatus() - return status == EchelonType.REF + chai.expect(status).to.equal(EchelonType.REF) }) - - chai.expect(res).to.equal(true) }) it("should return NONE correctly", () => { const matrices = [ @@ -75,17 +71,15 @@ export default () => { ], ] - const res = matrices.every((arr) => { + matrices.forEach((arr) => { const matrix = new Matrix({ rows: arr.length, columns: arr[0].length, entries: arr, }) const status = matrix.echelonStatus() - return status == EchelonType.NONE + chai.expect(status).to.equal(EchelonType.NONE) }) - - chai.expect(res).to.equal(true) }) }) } From c584e182c1f0cc45bb936802b218450565a40b2c Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sat, 7 Aug 2021 17:41:38 +0800 Subject: [PATCH 16/54] feat: create multiplyRow and test --- app/classes/Matrix.ts | 9 +++++++++ app/tests/unit/matrix.ts | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/app/classes/Matrix.ts b/app/classes/Matrix.ts index 6d4c2e0..790b857 100644 --- a/app/classes/Matrix.ts +++ b/app/classes/Matrix.ts @@ -83,6 +83,15 @@ abstract class BaseMatrix { // reaching here means RREF return EchelonType.RREF } + + /** + * Multiplies a row by a given factor + * @param rowIdx row index number + * @param factor factor to multiply by + */ + multiplyRow(rowIdx: number, factor: number) { + this.entries[rowIdx] = this.entries[rowIdx].map((one) => factor * one) + } } class Matrix extends BaseMatrix { diff --git a/app/tests/unit/matrix.ts b/app/tests/unit/matrix.ts index 0b2cdbe..299428c 100644 --- a/app/tests/unit/matrix.ts +++ b/app/tests/unit/matrix.ts @@ -82,4 +82,37 @@ export default () => { }) }) }) + + describe("Row operations", () => { + describe("multiplyRow", () => { + it("should multiply a row by a factor correctly", () => { + const matrix = new Matrix({ + rows: 3, + columns: 3, + entries: [ + [1, 2, 3], + [1, 0, 0], + [0, 1, 0], + ], + }) + + // tests + const inputs = [ + { rowIdx: 0, factor: 2 }, + { rowIdx: 1, factor: 1 }, + { rowIdx: 2, factor: -1 }, + ] + const outputs = [ + [2, 4, 6], + [1, 0, 0], + [-0, -1, -0], + ] + + inputs.forEach((options, idx) => { + matrix.multiplyRow(options.rowIdx, options.factor) + chai.expect(matrix.entries[options.rowIdx]).to.eql(outputs[idx]) + }) + }) + }) + }) } From e83a6cf18830e6c815b857c57d9e6be5f0f8219d Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sat, 7 Aug 2021 17:48:06 +0800 Subject: [PATCH 17/54] feat: create swapRows and test --- app/classes/Matrix.ts | 11 +++++++++++ app/tests/unit/matrix.ts | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/app/classes/Matrix.ts b/app/classes/Matrix.ts index 790b857..7453a21 100644 --- a/app/classes/Matrix.ts +++ b/app/classes/Matrix.ts @@ -92,6 +92,17 @@ abstract class BaseMatrix { multiplyRow(rowIdx: number, factor: number) { this.entries[rowIdx] = this.entries[rowIdx].map((one) => factor * one) } + + /** + * Swaps rows i and j + * @param i row index number + * @param j row index number + */ + swapRows(i: number, j: number) { + const tmp = this.entries[i] + this.entries[i] = this.entries[j] + this.entries[j] = tmp + } } class Matrix extends BaseMatrix { diff --git a/app/tests/unit/matrix.ts b/app/tests/unit/matrix.ts index 299428c..fc17bb0 100644 --- a/app/tests/unit/matrix.ts +++ b/app/tests/unit/matrix.ts @@ -114,5 +114,44 @@ export default () => { }) }) }) + describe("swapRows", () => { + it("should swap rows correctly", () => { + const matrix = new Matrix({ + rows: 3, + columns: 3, + entries: [ + [1, 2, 3], + [1, 0, 0], + [0, 1, 0], + ], + }) + + // tests + const inputs = [ + { rowOne: 0, rowTwo: 2 }, + { rowOne: 1, rowTwo: 0 }, + ] + const outputs = [ + { + newRowOne: [0, 1, 0], + newRowTwo: [1, 2, 3], + }, + { + newRowOne: [0, 1, 0], + newRowTwo: [1, 0, 0], + }, + ] + + inputs.forEach((options, idx) => { + matrix.swapRows(options.rowOne, options.rowTwo) + chai + .expect(matrix.entries[options.rowOne]) + .to.eql(outputs[idx].newRowOne) + chai + .expect(matrix.entries[options.rowTwo]) + .to.eql(outputs[idx].newRowTwo) + }) + }) + }) }) } From c1c45f72455d629813e86ab7b156e2a1f58fbaff Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sat, 7 Aug 2021 17:53:49 +0800 Subject: [PATCH 18/54] feat: create addMultiple and test --- app/classes/Matrix.ts | 12 ++++++++++++ app/tests/unit/matrix.ts | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/app/classes/Matrix.ts b/app/classes/Matrix.ts index 7453a21..2acde4a 100644 --- a/app/classes/Matrix.ts +++ b/app/classes/Matrix.ts @@ -103,6 +103,18 @@ abstract class BaseMatrix { this.entries[i] = this.entries[j] this.entries[j] = tmp } + + /** + * Adds a multiple of one row to another row + * @param rowIdx index of the row to multiply factor + * @param factor factor to multiply by + * @param addToIdx index of the row to add to + */ + addMultiple(rowIdx: number, factor: number, addToIdx: number) { + range(this.columns).forEach((colIdx) => { + this.entries[addToIdx][colIdx] += this.entries[rowIdx][colIdx] * factor + }) + } } class Matrix extends BaseMatrix { diff --git a/app/tests/unit/matrix.ts b/app/tests/unit/matrix.ts index fc17bb0..ebcf90e 100644 --- a/app/tests/unit/matrix.ts +++ b/app/tests/unit/matrix.ts @@ -153,5 +153,37 @@ export default () => { }) }) }) + describe("addMultiple", () => { + it("should add a multiple of one row to another row correctly", () => { + const matrix = new Matrix({ + rows: 3, + columns: 3, + entries: [ + [1, 2, 3], + [1, 0, 0], + [0, 1, 0], + ], + }) + + // tests + const inputs = [ + { baseRowIdx: 0, factor: 1, addToIdx: 2 }, + { baseRowIdx: 1, factor: 2, addToIdx: 0 }, + ] + const outputs = [ + [1, 3, 3], + [3, 2, 3], + ] + + inputs.forEach((options, idx) => { + matrix.addMultiple( + options.baseRowIdx, + options.factor, + options.addToIdx + ) + chai.expect(matrix.entries[options.addToIdx]).to.eql(outputs[idx]) + }) + }) + }) }) } From 7b7e2d58ca32b28e8d843bafb2293d0cb444daa0 Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sat, 7 Aug 2021 22:02:23 +0800 Subject: [PATCH 19/54] fix: leading entry logic error --- app/classes/Matrix.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/classes/Matrix.ts b/app/classes/Matrix.ts index 2acde4a..25b5589 100644 --- a/app/classes/Matrix.ts +++ b/app/classes/Matrix.ts @@ -50,10 +50,11 @@ abstract class BaseMatrix { // Check 1a: if there are still non-zero rows, this means they are not at the bottom, so return NONE if (leadingIndices.some((one) => one === -1)) return EchelonType.NONE - // Check 1b: if leadingEntryIndices not in non-decreasing order, return NONE + // Check 1b: if leadingEntryIndices not in increasing order, return NONE let idx = 1 while (idx < this.rows) { - if (leadingIndices[idx - 1] > leadingIndices[idx]) return EchelonType.NONE + if (leadingIndices[idx - 1] >= leadingIndices[idx]) + return EchelonType.NONE idx++ } From 1a21e7eb7ec5210bd30fad52b6a04cab57d12c4a Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sat, 7 Aug 2021 22:16:19 +0800 Subject: [PATCH 20/54] feat: create toREF and test --- app/classes/Matrix.ts | 65 ++++++++++++++++++++++++++++++++++++++++ app/tests/unit/matrix.ts | 27 +++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/app/classes/Matrix.ts b/app/classes/Matrix.ts index 25b5589..ad73af5 100644 --- a/app/classes/Matrix.ts +++ b/app/classes/Matrix.ts @@ -116,6 +116,71 @@ abstract class BaseMatrix { this.entries[addToIdx][colIdx] += this.entries[rowIdx][colIdx] * factor }) } + + /** + * Returns true if every entry in column colIdx is 0, false otherwise + * @param colIdx index of the column + */ + isZeroColumn(colIdx: number) { + return range(this.rows).every( + (rowIdx) => this.entries[rowIdx][colIdx] === 0 + ) + } + + /** + * Reduces the matrix to REF + */ + toREF() { + let actions = [] // array of actions done during REF + + let colIdx = 0 // leftmost nonzero column idx + let rowsDone = 0 // number of rows done + + while (this.echelonStatus() === EchelonType.NONE) { + // find next nonzero column + let foundColumn = false + while (!foundColumn && colIdx < this.columns) { + if (this.isZeroColumn(colIdx)) colIdx++ + else foundColumn = true + } + + if (foundColumn) { + let firstEntryRowIdx = -1 + // find first nonzero entry in column, that is excluding the done rows + for (let i = rowsDone; i < this.rows; i++) + if (this.entries[i][colIdx] !== 0) { + firstEntryRowIdx = i + break + } + + // swap top row with this row + this.swapRows(rowsDone, firstEntryRowIdx) + actions.push({ + action: "swap", + params: [rowsDone, firstEntryRowIdx], + }) + + // add multiple of top row to every other row below + const pivotValue = this.entries[rowsDone][colIdx] + for (let i = rowsDone + 1; i < this.rows; i++) { + const factor = (this.entries[i][colIdx] / pivotValue) * -1 + this.addMultiple(rowsDone, factor, i) + actions.push({ + action: "addMultiple", + params: [rowsDone, factor, i], + }) + } + + // mark as done + rowsDone++ + colIdx++ + } else { + break + } + } + + return actions + } } class Matrix extends BaseMatrix { diff --git a/app/tests/unit/matrix.ts b/app/tests/unit/matrix.ts index ebcf90e..aeb9b09 100644 --- a/app/tests/unit/matrix.ts +++ b/app/tests/unit/matrix.ts @@ -186,4 +186,31 @@ export default () => { }) }) }) + + describe("toREF", () => { + it("should correctly reduce a matrix to REF", () => { + const matrices = [ + [ + [1, 2, 3], + [2, 3, 4], + [3, 4, 5], + ], + [ + [1, 0, 0], + [0, 3, 0], + [0, 0, 2], + ], + ] + matrices.forEach((array) => { + const matrix = new Matrix({ + rows: array.length, + columns: array[0].length, + entries: array, + }) + const actions = matrix.toREF() + const res = matrix.echelonStatus() + chai.expect(res).to.not.equal(EchelonType.NONE) + }) + }) + }) } From 2c76a5df548c094133ff145f6225321cbb436249 Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sat, 7 Aug 2021 22:27:54 +0800 Subject: [PATCH 21/54] feat: create toRREF and test --- app/classes/Matrix.ts | 40 ++++++++++++++++++++++++++++++++++++++++ app/tests/unit/matrix.ts | 27 +++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/app/classes/Matrix.ts b/app/classes/Matrix.ts index ad73af5..376d109 100644 --- a/app/classes/Matrix.ts +++ b/app/classes/Matrix.ts @@ -181,6 +181,46 @@ abstract class BaseMatrix { return actions } + + /** + * Reduces the matrix to RREF + */ + toRREF() { + let actions = this.toREF() + + // make all leading entries 1 + this.entries.forEach((row, rowIdx) => { + const colIdx = leadingEntryIndex(row) + if (colIdx !== -1) { + const leadingVal = row[colIdx] + const factor = 1 / leadingVal + this.multiplyRow(rowIdx, factor) + actions.push({ + action: "multiplyRow", + params: [rowIdx, factor], + }) + } + }) + + // add multiples to rows above + for (let i = this.rows - 1; i >= 0; i--) { + const colIdx = leadingEntryIndex(this.entries[i]) + if (colIdx === -1) continue + + for (let j = i - 1; j >= 0; j--) { + const factor = -this.entries[j][colIdx] + if (factor !== 0) { + this.addMultiple(i, factor, j) + actions.push({ + action: "addMultiple", + params: [i, factor, j], + }) + } + } + } + + return actions + } } class Matrix extends BaseMatrix { diff --git a/app/tests/unit/matrix.ts b/app/tests/unit/matrix.ts index aeb9b09..f883a59 100644 --- a/app/tests/unit/matrix.ts +++ b/app/tests/unit/matrix.ts @@ -213,4 +213,31 @@ export default () => { }) }) }) + + describe("toREF", () => { + it("should correctly reduce a matrix to RREF", () => { + const matrices = [ + [ + [1, 2, 3], + [2, 3, 4], + [3, 4, 5], + ], + [ + [1, 0, 0], + [0, 3, 0], + [0, 0, 2], + ], + ] + matrices.forEach((array) => { + const matrix = new Matrix({ + rows: array.length, + columns: array[0].length, + entries: array, + }) + const actions = matrix.toRREF() + const res = matrix.echelonStatus() + chai.expect(res).to.equal(EchelonType.RREF) + }) + }) + }) } From 5274517c783c7c3db7d02ff96d9ab7d424dec682 Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sat, 7 Aug 2021 22:39:07 +0800 Subject: [PATCH 22/54] feat: user needs to specify action in frontend query --- pages/matrix.js | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/pages/matrix.js b/pages/matrix.js index 7321aa1..4e22009 100644 --- a/pages/matrix.js +++ b/pages/matrix.js @@ -25,13 +25,23 @@ const Page = () => { if (query === "") return try { - const res = await axios.get("/api/matrix/determinant", { - params: { - matrix: query, - }, - }) + // split query into action and matrix + const arr = query.split(" ") + const action = arr.shift() + const matrix = arr.join(" ") - setInputArray(JSON.parse(query)) + let res + switch (action) { + case "determinant": + case "det": + res = await axios.get("/api/matrix/determinant", { + params: { + matrix, + }, + }) + break + } + setInputArray(JSON.parse(matrix)) setAnswer(res.data) // force math typesetting From f74fc0d312d7882b2f17c819c6536bee461a176f Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sat, 7 Aug 2021 22:44:42 +0800 Subject: [PATCH 23/54] refactor: create frontend state command and add comments to states --- pages/matrix.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pages/matrix.js b/pages/matrix.js index 4e22009..9e51905 100644 --- a/pages/matrix.js +++ b/pages/matrix.js @@ -6,10 +6,12 @@ import axios from "axios" import { convert2DArrayToMatrix } from "../utils" const Page = () => { - const [query, setQuery] = useState("") - const [inputArray, setInputArray] = useState("") + const [query, setQuery] = useState("") // text in the input + const [error, setError] = useState("") // error regarding query + + const [command, setCommand] = useState("") // action from processed query + const [inputArray, setInputArray] = useState("") // matrix from processed query const [answer, setAnswer] = useState("") - const [error, setError] = useState("") const handleChange = (e) => { setError("") @@ -39,6 +41,7 @@ const Page = () => { matrix, }, }) + setCommand("\\mathrm{det}") break } setInputArray(JSON.parse(matrix)) @@ -77,7 +80,7 @@ const Page = () => { - $\mathrm{"{"}det{"}"} + ${command} {convert2DArrayToMatrix(inputArray)} = $ ${answer}$ From 66b40de0dedccad4c74a41868bbbabe6b6e76cba Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sat, 7 Aug 2021 22:55:59 +0800 Subject: [PATCH 24/54] fix: set initial state of inputArray to be empty 2D array --- pages/matrix.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/matrix.js b/pages/matrix.js index 9e51905..268ca84 100644 --- a/pages/matrix.js +++ b/pages/matrix.js @@ -10,7 +10,7 @@ const Page = () => { const [error, setError] = useState("") // error regarding query const [command, setCommand] = useState("") // action from processed query - const [inputArray, setInputArray] = useState("") // matrix from processed query + const [inputArray, setInputArray] = useState([[]]) // matrix from processed query const [answer, setAnswer] = useState("") const handleChange = (e) => { From 54c92499c045f6c50bb4fa4330b93249d94ed407 Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sat, 7 Aug 2021 23:00:41 +0800 Subject: [PATCH 25/54] feat: create controller function reduceRREF --- app/controllers/matrix.ts | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/app/controllers/matrix.ts b/app/controllers/matrix.ts index 14ec5f5..50d2813 100644 --- a/app/controllers/matrix.ts +++ b/app/controllers/matrix.ts @@ -1,6 +1,6 @@ import { VercelRequest, VercelResponse } from "@vercel/node" -import { SquareMatrix } from "../classes/Matrix" +import { Matrix, SquareMatrix } from "../classes/Matrix" const calcDeterminant = (req: VercelRequest, res: VercelResponse) => { try { @@ -27,6 +27,34 @@ const calcDeterminant = (req: VercelRequest, res: VercelResponse) => { } } +const reduceRREF = (req: VercelRequest, res: VercelResponse) => { + try { + let arr + // string array + if (Array.isArray(req.query.matrix)) + arr = JSON.parse(req.query.matrix.join()) + else arr = JSON.parse(req.query.matrix) + + const rows = arr.length + const cols = arr[0].length + + const matrix = new Matrix({ + rows, + columns: cols, + entries: arr, + }) + + const actions = matrix.toRREF() + res.json({ + actions, + matrix: matrix.entries, + }) + } catch (err) { + res.status(500).json({ message: err.message }) + } +} + export default { calcDeterminant, + reduceRREF, } From 7fdc564ff17efa9df544487168dcb85dc9c364cb Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sat, 7 Aug 2021 23:01:03 +0800 Subject: [PATCH 26/54] feat: create endpoint for rref --- pages/api/[...route].js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pages/api/[...route].js b/pages/api/[...route].js index 633cd93..987b193 100644 --- a/pages/api/[...route].js +++ b/pages/api/[...route].js @@ -5,11 +5,7 @@ const app = require("express")() app.get("/api/test", Test.test) -/** - * Calculates the determinant of a square matrix - * - * @param {*} matrix 2D Javascript array - */ app.get("/api/matrix/determinant", Matrix.calcDeterminant) +app.get("/api/matrix/rref", Matrix.reduceRREF) module.exports = app From 28d803c92466ae18a3776f06306cd92758f9d971 Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sat, 7 Aug 2021 23:01:23 +0800 Subject: [PATCH 27/54] feat: handle rref in frontend --- pages/matrix.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/pages/matrix.js b/pages/matrix.js index 268ca84..90f0a18 100644 --- a/pages/matrix.js +++ b/pages/matrix.js @@ -11,7 +11,7 @@ const Page = () => { const [command, setCommand] = useState("") // action from processed query const [inputArray, setInputArray] = useState([[]]) // matrix from processed query - const [answer, setAnswer] = useState("") + const [answer, setAnswer] = useState("") // Latex string const handleChange = (e) => { setError("") @@ -42,10 +42,19 @@ const Page = () => { }, }) setCommand("\\mathrm{det}") + setAnswer(res.data) + break + case "rref": + res = await axios.get("/api/matrix/rref", { + params: { + matrix, + }, + }) + setCommand("\\mathrm{rref}") + setAnswer(convert2DArrayToMatrix(res.data.matrix)) break } setInputArray(JSON.parse(matrix)) - setAnswer(res.data) // force math typesetting MathJax.typeset() From 2e115ac745db42624adb3fc144774a2fedd71486 Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sat, 7 Aug 2021 23:08:10 +0800 Subject: [PATCH 28/54] feat: only perform swap if necessary --- app/classes/Matrix.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/classes/Matrix.ts b/app/classes/Matrix.ts index 376d109..ee71759 100644 --- a/app/classes/Matrix.ts +++ b/app/classes/Matrix.ts @@ -154,11 +154,13 @@ abstract class BaseMatrix { } // swap top row with this row - this.swapRows(rowsDone, firstEntryRowIdx) - actions.push({ - action: "swap", - params: [rowsDone, firstEntryRowIdx], - }) + if (rowsDone !== firstEntryRowIdx) { + this.swapRows(rowsDone, firstEntryRowIdx) + actions.push({ + action: "swap", + params: [rowsDone, firstEntryRowIdx], + }) + } // add multiple of top row to every other row below const pivotValue = this.entries[rowsDone][colIdx] From 15abd173ea3dfa4da339b7803780981dd0667a72 Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sat, 7 Aug 2021 23:09:28 +0800 Subject: [PATCH 29/54] feat: add initial state to actions, and also record matrix state --- app/classes/Matrix.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/classes/Matrix.ts b/app/classes/Matrix.ts index ee71759..9fa5410 100644 --- a/app/classes/Matrix.ts +++ b/app/classes/Matrix.ts @@ -133,6 +133,12 @@ abstract class BaseMatrix { toREF() { let actions = [] // array of actions done during REF + actions.push({ + action: "none", + params: [], + matrix: this.entries, + }) + let colIdx = 0 // leftmost nonzero column idx let rowsDone = 0 // number of rows done @@ -159,6 +165,7 @@ abstract class BaseMatrix { actions.push({ action: "swap", params: [rowsDone, firstEntryRowIdx], + matrix: this.entries, }) } @@ -170,6 +177,7 @@ abstract class BaseMatrix { actions.push({ action: "addMultiple", params: [rowsDone, factor, i], + matrix: this.entries, }) } @@ -200,6 +208,7 @@ abstract class BaseMatrix { actions.push({ action: "multiplyRow", params: [rowIdx, factor], + matrix: this.entries, }) } }) @@ -216,6 +225,7 @@ abstract class BaseMatrix { actions.push({ action: "addMultiple", params: [i, factor, j], + matrix: this.entries, }) } } From 8ab5e1981caa7fafc52b0c45e0785ead876d3660 Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sat, 7 Aug 2021 23:32:47 +0800 Subject: [PATCH 30/54] chore: install lodash --- package-lock.json | 1 + package.json | 1 + 2 files changed, 2 insertions(+) diff --git a/package-lock.json b/package-lock.json index 579bbc5..d13a63f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "axios": "^0.21.1", "express": "^4.17.1", "framer-motion": "^4.1.17", + "lodash": "^4.17.21", "next": "^11.0.1", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/package.json b/package.json index 740d84c..a6389d6 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "axios": "^0.21.1", "express": "^4.17.1", "framer-motion": "^4.1.17", + "lodash": "^4.17.21", "next": "^11.0.1", "react": "^17.0.2", "react-dom": "^17.0.2", From d4bc87ad5a5968ae4c063f3f1d08b53ae97c2aea Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sat, 7 Aug 2021 23:45:40 +0800 Subject: [PATCH 31/54] fix: apply deep clone on entries --- app/classes/Matrix.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/classes/Matrix.ts b/app/classes/Matrix.ts index 9fa5410..a046b86 100644 --- a/app/classes/Matrix.ts +++ b/app/classes/Matrix.ts @@ -3,6 +3,8 @@ import { range } from "../utils/misc" import { EchelonType } from "../types/Matrix" import { leadingEntryIndex } from "../utils/Matrix" +import * as _ from "lodash" + interface IMatrix { rows: number columns: number @@ -136,7 +138,7 @@ abstract class BaseMatrix { actions.push({ action: "none", params: [], - matrix: this.entries, + matrix: _.cloneDeep(this.entries), }) let colIdx = 0 // leftmost nonzero column idx @@ -165,7 +167,7 @@ abstract class BaseMatrix { actions.push({ action: "swap", params: [rowsDone, firstEntryRowIdx], - matrix: this.entries, + matrix: _.cloneDeep(this.entries), }) } @@ -177,7 +179,7 @@ abstract class BaseMatrix { actions.push({ action: "addMultiple", params: [rowsDone, factor, i], - matrix: this.entries, + matrix: _.cloneDeep(this.entries), }) } @@ -208,7 +210,7 @@ abstract class BaseMatrix { actions.push({ action: "multiplyRow", params: [rowIdx, factor], - matrix: this.entries, + matrix: _.cloneDeep(this.entries), }) } }) @@ -225,7 +227,7 @@ abstract class BaseMatrix { actions.push({ action: "addMultiple", params: [i, factor, j], - matrix: this.entries, + matrix: _.cloneDeep(this.entries), }) } } From 09fcef4ab1c3e6df58877b719dbafbfc4238f389 Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sat, 7 Aug 2021 23:48:33 +0800 Subject: [PATCH 32/54] feat: add y-margin --- pages/matrix.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pages/matrix.js b/pages/matrix.js index 90f0a18..5f428bd 100644 --- a/pages/matrix.js +++ b/pages/matrix.js @@ -64,7 +64,13 @@ const Page = () => { } return ( - + Date: Sat, 7 Aug 2021 23:49:14 +0800 Subject: [PATCH 33/54] feat: show rref steps --- pages/matrix.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/pages/matrix.js b/pages/matrix.js index 5f428bd..e642e9e 100644 --- a/pages/matrix.js +++ b/pages/matrix.js @@ -13,6 +13,9 @@ const Page = () => { const [inputArray, setInputArray] = useState([[]]) // matrix from processed query const [answer, setAnswer] = useState("") // Latex string + // only for rref + const [rrefActions, setRrefActions] = useState([]) + const handleChange = (e) => { setError("") setQuery(e.target.value) @@ -52,6 +55,7 @@ const Page = () => { }) setCommand("\\mathrm{rref}") setAnswer(convert2DArrayToMatrix(res.data.matrix)) + setRrefActions(res.data.actions) break } setInputArray(JSON.parse(matrix)) @@ -100,6 +104,7 @@ const Page = () => { ${answer}$ + {rrefActions.length > 0 && rrefSteps(rrefActions)} )} @@ -107,4 +112,27 @@ const Page = () => { ) } +const rrefSteps = (rrefActions) => { + const descriptionText = (action, params) => { + if (action === "none") return "Begin with" + else if (action === "addMultiple") + return `Add ${params[1]} times of row ${params[0]} to row ${params[2]}` + else if (action === "swap") + return `Swap row ${params[0]} with row ${params[1]}` + else if (action === "multiplyRow") + return `Multiply row ${params[0]} by factor ${params[1]}` + } + + return ( + + {rrefActions.map((one) => ( + <> + {descriptionText(one.action, one.params)} + ${convert2DArrayToMatrix(one.matrix)}$ + + ))} + + ) +} + export default Page From e3913f6a7cc06bf8e86bbd73bcd18b870deca283 Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sat, 7 Aug 2021 23:51:35 +0800 Subject: [PATCH 34/54] feat: do not multiply if factor is 1 --- app/classes/Matrix.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/app/classes/Matrix.ts b/app/classes/Matrix.ts index a046b86..0af9b58 100644 --- a/app/classes/Matrix.ts +++ b/app/classes/Matrix.ts @@ -206,12 +206,14 @@ abstract class BaseMatrix { if (colIdx !== -1) { const leadingVal = row[colIdx] const factor = 1 / leadingVal - this.multiplyRow(rowIdx, factor) - actions.push({ - action: "multiplyRow", - params: [rowIdx, factor], - matrix: _.cloneDeep(this.entries), - }) + if (factor !== 1) { + this.multiplyRow(rowIdx, factor) + actions.push({ + action: "multiplyRow", + params: [rowIdx, factor], + matrix: _.cloneDeep(this.entries), + }) + } } }) From 4469de5f20e51baf975644781eeec4d5c4e1d394 Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sun, 8 Aug 2021 09:10:44 +0800 Subject: [PATCH 35/54] fix: issues with justifyContent and overflow --- pages/matrix.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pages/matrix.js b/pages/matrix.js index e642e9e..b40ee8f 100644 --- a/pages/matrix.js +++ b/pages/matrix.js @@ -73,10 +73,10 @@ const Page = () => { w="100vw" justifyContent="center" alignItems="center" - my="100px" + mx="auto" > - - + + Date: Sun, 8 Aug 2021 09:13:16 +0800 Subject: [PATCH 36/54] feat: put $$ around params for inline math --- pages/matrix.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pages/matrix.js b/pages/matrix.js index b40ee8f..4acae8d 100644 --- a/pages/matrix.js +++ b/pages/matrix.js @@ -116,11 +116,11 @@ const rrefSteps = (rrefActions) => { const descriptionText = (action, params) => { if (action === "none") return "Begin with" else if (action === "addMultiple") - return `Add ${params[1]} times of row ${params[0]} to row ${params[2]}` + return `Add $${params[1]}$ times of row $${params[0]}$ to row $${params[2]}$` else if (action === "swap") - return `Swap row ${params[0]} with row ${params[1]}` + return `Swap row $${params[0]}$ with row $${params[1]}$` else if (action === "multiplyRow") - return `Multiply row ${params[0]} by factor ${params[1]}` + return `Multiply row $${params[0]}$ by factor $${params[1]}$` } return ( From 2e249cddda72da35293ac5f06018345c684db407 Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sun, 8 Aug 2021 09:20:15 +0800 Subject: [PATCH 37/54] fix: make inline math actually render inline with Text --- pages/matrix.js | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/pages/matrix.js b/pages/matrix.js index 4acae8d..f528876 100644 --- a/pages/matrix.js +++ b/pages/matrix.js @@ -114,20 +114,31 @@ const Page = () => { const rrefSteps = (rrefActions) => { const descriptionText = (action, params) => { - if (action === "none") return "Begin with" + if (action === "none") return ["Begin with"] else if (action === "addMultiple") - return `Add $${params[1]}$ times of row $${params[0]}$ to row $${params[2]}$` + return [ + "Add", + `$${params[1]}$`, + "times of row", + `$${params[0]}$`, + "to row", + `$${params[2]}$`, + ] else if (action === "swap") - return `Swap row $${params[0]}$ with row $${params[1]}$` + return ["Swap row", `$${params[0]}$`, "with row", `$${params[1]}$`] else if (action === "multiplyRow") - return `Multiply row $${params[0]}$ by factor $${params[1]}$` + return ["Multiply row", `$${params[0]}$`, "by factor", `$${params[1]}$`] } return ( {rrefActions.map((one) => ( <> - {descriptionText(one.action, one.params)} + + {descriptionText(one.action, one.params).map((two) => ( + {two} + ))} + ${convert2DArrayToMatrix(one.matrix)}$ ))} From 09797bfbbf5738b56d25cc0901f2b49917b2e820 Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sun, 8 Aug 2021 09:20:29 +0800 Subject: [PATCH 38/54] feat: layout improvements --- pages/matrix.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pages/matrix.js b/pages/matrix.js index f528876..05a0ea6 100644 --- a/pages/matrix.js +++ b/pages/matrix.js @@ -75,7 +75,14 @@ const Page = () => { alignItems="center" mx="auto" > - + { {error !== "" && Error: {error}} {answer !== "" && ( - + Your input is interpreted as: ${convert2DArrayToMatrix(inputArray)}$ From 9e66151aa753116f37eaa3aff6ba1b1252e29328 Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sun, 8 Aug 2021 09:28:56 +0800 Subject: [PATCH 39/54] feat: add matrix row operation notation --- pages/matrix.js | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/pages/matrix.js b/pages/matrix.js index 05a0ea6..4ebef5a 100644 --- a/pages/matrix.js +++ b/pages/matrix.js @@ -130,11 +130,29 @@ const rrefSteps = (rrefActions) => { `$${params[0]}$`, "to row", `$${params[2]}$`, + "or", + `$(R_${params[2]} ${params[1] < 0 ? "-" : "+"} ${Math.abs( + params[1] + )}R_${params[0]})$`, ] else if (action === "swap") - return ["Swap row", `$${params[0]}$`, "with row", `$${params[1]}$`] + return [ + "Swap row", + `$${params[0]}$`, + "with row", + `$${params[1]}$`, + "or", + `$(R_${params[0]} \\leftrightarrow R_${params[1]})$`, + ] else if (action === "multiplyRow") - return ["Multiply row", `$${params[0]}$`, "by factor", `$${params[1]}$`] + return [ + "Multiply row", + `$${params[0]}$`, + "by factor", + `$${params[1]}$`, + "or", + `$(${params[1]}R_${params[0]})$`, + ] } return ( From 6a07b4521a35c4e6f9047f345e8b1ecc4274920a Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sun, 8 Aug 2021 09:31:18 +0800 Subject: [PATCH 40/54] feat: put or inside the brackets --- pages/matrix.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pages/matrix.js b/pages/matrix.js index 4ebef5a..cb270db 100644 --- a/pages/matrix.js +++ b/pages/matrix.js @@ -130,8 +130,7 @@ const rrefSteps = (rrefActions) => { `$${params[0]}$`, "to row", `$${params[2]}$`, - "or", - `$(R_${params[2]} ${params[1] < 0 ? "-" : "+"} ${Math.abs( + `$(\\text{or }R_${params[2]} ${params[1] < 0 ? "-" : "+"} ${Math.abs( params[1] )}R_${params[0]})$`, ] @@ -141,8 +140,7 @@ const rrefSteps = (rrefActions) => { `$${params[0]}$`, "with row", `$${params[1]}$`, - "or", - `$(R_${params[0]} \\leftrightarrow R_${params[1]})$`, + `$(\\text{or }R_${params[0]} \\leftrightarrow R_${params[1]})$`, ] else if (action === "multiplyRow") return [ @@ -150,8 +148,7 @@ const rrefSteps = (rrefActions) => { `$${params[0]}$`, "by factor", `$${params[1]}$`, - "or", - `$(${params[1]}R_${params[0]})$`, + `$(\\text{or }${params[1]}R_${params[0]})$`, ] } From e2d823357c462e15bb8bfd289c5f674f8085792e Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sun, 8 Aug 2021 09:31:33 +0800 Subject: [PATCH 41/54] feat: add vertical spacing between intermediate steps --- pages/matrix.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/matrix.js b/pages/matrix.js index cb270db..bac2dc4 100644 --- a/pages/matrix.js +++ b/pages/matrix.js @@ -153,7 +153,7 @@ const rrefSteps = (rrefActions) => { } return ( - + {rrefActions.map((one) => ( <> From 7f0846a3234c4afc3df40205e29eed8538bf19f8 Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sun, 8 Aug 2021 09:33:33 +0800 Subject: [PATCH 42/54] refactor: slitp addMultiple action into positive and negative cases --- pages/matrix.js | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/pages/matrix.js b/pages/matrix.js index bac2dc4..8f44815 100644 --- a/pages/matrix.js +++ b/pages/matrix.js @@ -122,19 +122,28 @@ const Page = () => { const rrefSteps = (rrefActions) => { const descriptionText = (action, params) => { if (action === "none") return ["Begin with"] - else if (action === "addMultiple") - return [ - "Add", - `$${params[1]}$`, - "times of row", - `$${params[0]}$`, - "to row", - `$${params[2]}$`, - `$(\\text{or }R_${params[2]} ${params[1] < 0 ? "-" : "+"} ${Math.abs( - params[1] - )}R_${params[0]})$`, - ] - else if (action === "swap") + else if (action === "addMultiple") { + if (params[1] > 0) + return [ + "Add", + `$${params[1]}$`, + "times of row", + `$${params[0]}$`, + "to row", + `$${params[2]}$`, + `$(\\text{or }R_${params[2]} + ${params[1]}R_${params[0]})$`, + ] + else + return [ + "Subtract", + `$${-params[1]}$`, + "times of row", + `$${params[0]}$`, + "to row", + `$${params[2]}$`, + `$(\\text{or }R_${params[2]} - ${-params[1]}R_${params[0]})$`, + ] + } else if (action === "swap") return [ "Swap row", `$${params[0]}$`, From 8945bf1e2546251d1b51995b9ee60fc93fe3810e Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sun, 8 Aug 2021 09:51:31 +0800 Subject: [PATCH 43/54] feat: trigger frontend query params upon submit --- pages/matrix.js | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/pages/matrix.js b/pages/matrix.js index 8f44815..285963a 100644 --- a/pages/matrix.js +++ b/pages/matrix.js @@ -1,6 +1,7 @@ import { Button, Flex, HStack, Input, Text, VStack } from "@chakra-ui/react" -import { useState } from "react" +import { useEffect, useState } from "react" +import Router, { useRouter } from "next/router" import axios from "axios" import { convert2DArrayToMatrix } from "../utils" @@ -16,6 +17,11 @@ const Page = () => { // only for rref const [rrefActions, setRrefActions] = useState([]) + // only when router.query is defined, and want to trigger submit after loading router.query into query + const [triggerSubmit, setTriggerSubmit] = useState(false) + + const router = useRouter() + const handleChange = (e) => { setError("") setQuery(e.target.value) @@ -46,6 +52,9 @@ const Page = () => { }) setCommand("\\mathrm{det}") setAnswer(res.data) + Router.push({ + query: { action: "det", matrix }, + }) break case "rref": res = await axios.get("/api/matrix/rref", { @@ -56,6 +65,9 @@ const Page = () => { setCommand("\\mathrm{rref}") setAnswer(convert2DArrayToMatrix(res.data.matrix)) setRrefActions(res.data.actions) + Router.push({ + query: { action: "rref", matrix }, + }) break } setInputArray(JSON.parse(matrix)) @@ -67,6 +79,26 @@ const Page = () => { } } + useEffect(() => { + if (router.query.action && router.query.matrix) { + const command = router.query.action + const matrix = router.query.matrix + setQuery(command + " " + matrix) + setTriggerSubmit(true) + } else { + setQuery("") + setInputArray([[]]) + setAnswer("") + } + }, [router.query]) + + useEffect(() => { + if (triggerSubmit && query) { + handleSubmit() + setTriggerSubmit(false) + } + }, [query]) + return ( { isRequired onChange={handleChange} onKeyDown={handleKeydown} + value={query} placeholder="Put in your query" /> {error !== "" && Error: {error}} + {loading && ( + + + + )} {answer !== "" && ( From 9a60b8e98979d9398b7a40f3a24bb5ba42518727 Mon Sep 17 00:00:00 2001 From: weiseng18 Date: Sun, 8 Aug 2021 10:21:29 +0800 Subject: [PATCH 48/54] feat: layout improvements --- pages/matrix.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pages/matrix.js b/pages/matrix.js index 6b3ea7c..f78dc75 100644 --- a/pages/matrix.js +++ b/pages/matrix.js @@ -133,7 +133,7 @@ const Page = () => { h="100%" overflowY="auto" > - + { {error !== "" && Error: {error}} {loading && ( - + Date: Sun, 8 Aug 2021 10:39:10 +0800 Subject: [PATCH 49/54] fix: prevent query from resubmitting upon each keypress --- pages/matrix.js | 38 +++++++++++++++----------------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/pages/matrix.js b/pages/matrix.js index f78dc75..f3da735 100644 --- a/pages/matrix.js +++ b/pages/matrix.js @@ -27,9 +27,6 @@ const Page = () => { // only for rref const [rrefActions, setRrefActions] = useState([]) - // only when router.query is defined, and want to trigger submit after loading router.query into query - const [triggerSubmit, setTriggerSubmit] = useState(false) - const router = useRouter() const handleChange = (e) => { @@ -41,15 +38,15 @@ const Page = () => { if (e.keyCode == 13 || e.key == "Enter") handleSubmit() } - const handleSubmit = async () => { + const handleSubmit = async (passedQuery = "") => { // empty query - if (query === "") return + if (query === "" && passedQuery === "") return try { setLoading(true) // split query into action and matrix - const arr = query.split(" ") + let arr = query === "" ? passedQuery.split(" ") : query.split(" ") const action = arr.shift() const matrix = arr.join(" ") @@ -94,25 +91,20 @@ const Page = () => { } useEffect(() => { - if (router.query.action && router.query.matrix) { - const command = router.query.action - const matrix = router.query.matrix - setQuery(command + " " + matrix) - setTriggerSubmit(true) - } else { - setQuery("") - setInputArray([[]]) - setAnswer("") + if (query === "") { + if (router.query.action && router.query.matrix) { + const command = router.query.action + const matrix = router.query.matrix + setQuery(command + " " + matrix) + handleSubmit(command + " " + matrix) + } else { + setQuery("") + setInputArray([[]]) + setAnswer("") + } } }, [router.query]) - useEffect(() => { - if (triggerSubmit && query) { - handleSubmit() - setTriggerSubmit(false) - } - }, [query]) - useEffect(() => { setLoading(false) }, []) @@ -142,7 +134,7 @@ const Page = () => { placeholder="Put in your query" />