diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml
index 10c08d1..f9c9698 100644
--- a/.github/workflows/github-actions.yml
+++ b/.github/workflows/github-actions.yml
@@ -1,10 +1,9 @@
name: Github Actions
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 +22,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
diff --git a/app/classes/Matrix.ts b/app/classes/Matrix.ts
index cad7f6e..0af9b58 100644
--- a/app/classes/Matrix.ts
+++ b/app/classes/Matrix.ts
@@ -1,3 +1,10 @@
+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
@@ -23,6 +30,213 @@ 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 increasing 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
+ }
+
+ /**
+ * 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)
+ }
+
+ /**
+ * 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
+ }
+
+ /**
+ * 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
+ })
+ }
+
+ /**
+ * 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
+
+ actions.push({
+ action: "none",
+ params: [],
+ matrix: _.cloneDeep(this.entries),
+ })
+
+ 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
+ if (rowsDone !== firstEntryRowIdx) {
+ this.swapRows(rowsDone, firstEntryRowIdx)
+ actions.push({
+ action: "swap",
+ params: [rowsDone, firstEntryRowIdx],
+ matrix: _.cloneDeep(this.entries),
+ })
+ }
+
+ // 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],
+ matrix: _.cloneDeep(this.entries),
+ })
+ }
+
+ // mark as done
+ rowsDone++
+ colIdx++
+ } else {
+ break
+ }
+ }
+
+ 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
+ if (factor !== 1) {
+ this.multiplyRow(rowIdx, factor)
+ actions.push({
+ action: "multiplyRow",
+ params: [rowIdx, factor],
+ matrix: _.cloneDeep(this.entries),
+ })
+ }
+ }
+ })
+
+ // 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],
+ matrix: _.cloneDeep(this.entries),
+ })
+ }
+ }
+ }
+
+ return actions
+ }
}
class Matrix extends BaseMatrix {
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,
}
diff --git a/app/tests/unit/index.ts b/app/tests/unit/index.ts
new file mode 100644
index 0000000..f27ce0e
--- /dev/null
+++ b/app/tests/unit/index.ts
@@ -0,0 +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..f883a59
--- /dev/null
+++ b/app/tests/unit/matrix.ts
@@ -0,0 +1,243 @@
+// 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],
+ ],
+ ]
+
+ matrices.forEach((arr) => {
+ const matrix = new Matrix({
+ rows: arr.length,
+ columns: arr[0].length,
+ entries: arr,
+ })
+ const status = matrix.echelonStatus()
+ chai.expect(status).to.equal(EchelonType.RREF)
+ })
+ })
+ 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],
+ ],
+ ]
+
+ matrices.forEach((arr) => {
+ const matrix = new Matrix({
+ rows: arr.length,
+ columns: arr[0].length,
+ entries: arr,
+ })
+ const status = matrix.echelonStatus()
+ chai.expect(status).to.equal(EchelonType.REF)
+ })
+ })
+ 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],
+ ],
+ ]
+
+ matrices.forEach((arr) => {
+ const matrix = new Matrix({
+ rows: arr.length,
+ columns: arr[0].length,
+ entries: arr,
+ })
+ const status = matrix.echelonStatus()
+ chai.expect(status).to.equal(EchelonType.NONE)
+ })
+ })
+ })
+
+ 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])
+ })
+ })
+ })
+ 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)
+ })
+ })
+ })
+ 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])
+ })
+ })
+ })
+ })
+
+ 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)
+ })
+ })
+ })
+
+ 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)
+ })
+ })
+ })
+}
diff --git a/app/tests/unit/utils.ts b/app/tests/unit/utils.ts
new file mode 100644
index 0000000..6793577
--- /dev/null
+++ b/app/tests/unit/utils.ts
@@ -0,0 +1,57 @@
+// standard testing modules
+import chai from "chai"
+
+// import utils to be tested
+import { range } from "../../utils/misc"
+import { isZeroRow, leadingEntryIndex } from "../../utils/Matrix"
+
+export default () => {
+ 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) => {
+ // 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)
+ })
+ })
+ })
+
+ 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)
+ })
+ })
+}
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 }
diff --git a/app/utils/Matrix.ts b/app/utils/Matrix.ts
new file mode 100644
index 0000000..82f7248
--- /dev/null
+++ b/app/utils/Matrix.ts
@@ -0,0 +1,22 @@
+/**
+ * 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)
+}
+
+/**
+ * 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 }
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 }
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 cd5f4e6..a6389d6 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/unit/index.ts"
},
"repository": {
"type": "git",
@@ -28,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",
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
diff --git a/pages/matrix.js b/pages/matrix.js
index 7321aa1..f4d44ea 100644
--- a/pages/matrix.js
+++ b/pages/matrix.js
@@ -1,15 +1,33 @@
-import { Button, Flex, HStack, Input, Text, VStack } from "@chakra-ui/react"
-import { useState } from "react"
+import {
+ Button,
+ Container,
+ HStack,
+ Input,
+ Spinner,
+ Text,
+ VStack,
+} from "@chakra-ui/react"
+import { useEffect, useState } from "react"
+import Router, { useRouter } from "next/router"
import axios from "axios"
import { convert2DArrayToMatrix } from "../utils"
const Page = () => {
- const [query, setQuery] = useState("")
- const [inputArray, setInputArray] = useState("")
- const [answer, setAnswer] = useState("")
- const [error, setError] = useState("")
+ const [query, setQuery] = useState("") // text in the input
+ const [error, setError] = useState("") // error regarding query
+
+ const [loading, setLoading] = useState(true) // if page is loading or handleSubmit is running
+
+ const [command, setCommand] = useState("") // action from processed query
+ const [inputArray, setInputArray] = useState([[]]) // matrix from processed query
+ const [answer, setAnswer] = useState("") // Latex string
+
+ // only for rref
+ const [rrefActions, setRrefActions] = useState([])
+
+ const router = useRouter()
const handleChange = (e) => {
setError("")
@@ -20,62 +38,200 @@ 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 {
- const res = await axios.get("/api/matrix/determinant", {
- params: {
- matrix: query,
- },
- })
+ setLoading(true)
+
+ // split query into action and matrix
+ let arr = query === "" ? passedQuery.split(" ") : query.split(" ")
+ const action = arr.shift()
+ const matrix = arr.join(" ")
- setInputArray(JSON.parse(query))
- setAnswer(res.data)
+ let res
+ switch (action) {
+ case "determinant":
+ case "det":
+ res = await axios.get("/api/matrix/determinant", {
+ params: {
+ matrix,
+ },
+ })
+ setCommand("\\mathrm{det}")
+ setAnswer(res.data)
+ setRrefActions([])
+ Router.push({
+ query: { action: "det", matrix },
+ })
+ break
+ case "rref":
+ res = await axios.get("/api/matrix/rref", {
+ params: {
+ matrix,
+ },
+ })
+ setCommand("\\mathrm{rref}")
+ setAnswer(convert2DArrayToMatrix(res.data.matrix))
+ setRrefActions(res.data.actions)
+ Router.push({
+ query: { action: "rref", matrix },
+ })
+ break
+ default:
+ setLoading(false)
+ setAnswer("")
+ setRrefActions([])
+ throw new Error("Unrecognized command")
+ }
+ setInputArray(JSON.parse(matrix))
// force math typesetting
MathJax.typeset()
+
+ setLoading(false)
} catch (err) {
setError(err.message)
}
}
+ useEffect(() => {
+ 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(() => {
+ setLoading(false)
+ }, [])
+
return (
-
-
+
+
{error !== "" && Error: {error}}
+ {loading && (
+
+
+
+ )}
{answer !== "" && (
-
+
Your input is interpreted as:
${convert2DArrayToMatrix(inputArray)}$
- $\mathrm{"{"}det{"}"}
+ ${command}
{convert2DArrayToMatrix(inputArray)} = $
${answer}$
+ {rrefActions.length > 0 && rrefSteps(rrefActions)}
)}
-
+
+ )
+}
+
+const rrefSteps = (rrefActions) => {
+ const descriptionText = (action, params) => {
+ if (action === "none") return ["Begin with"]
+ 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]}$`,
+ "from row",
+ `$${params[2]}$`,
+ `$(\\text{or }R_${params[2]} - ${-params[1]}R_${params[0]})$`,
+ ]
+ } else if (action === "swap")
+ return [
+ "Swap row",
+ `$${params[0]}$`,
+ "with row",
+ `$${params[1]}$`,
+ `$(\\text{or }R_${params[0]} \\leftrightarrow R_${params[1]})$`,
+ ]
+ else if (action === "multiplyRow")
+ return [
+ "Multiply row",
+ `$${params[0]}$`,
+ "by factor",
+ `$${params[1]}$`,
+ `$(\\text{or }${params[1]}R_${params[0]})$`,
+ ]
+ }
+
+ return (
+
+ {rrefActions.map((one) => (
+ <>
+
+ {descriptionText(one.action, one.params).map((two) => (
+ {two}
+ ))}
+
+ ${convert2DArrayToMatrix(one.matrix)}$
+ >
+ ))}
+
)
}