{
  "id": "P038",
  "slug": "matrix-calculator",
  "key": "P038-matrix-calculator",
  "title": "Matrix calculator",
  "summary": "Up to three matrices of at most 5 x 5, typed with whole numbers, fractions or decimals and kept as exact fractions: sum, difference, product, a number times a matrix, transpose, the determinant by Gaussian elimination and the inverse by Gauss-Jordan elimination - checked by multiplying back. Each result is kept as R for the next operation.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P038 - Matrix calculator\n\nUp to three matrices, A, B and C, of at most 5 x 5. Entries are typed as whole\nnumbers, fractions or decimals and kept as exact fractions, so no answer is\nrounded. One operation at a time - sum, difference, product, a number times a\nmatrix, transpose, determinant, inverse - and each result is kept as R, which\nthe next operation can use.\n\n- `main.eml` - the menu, entering a matrix row by row, reading an operation,\n  and the matrices on screen\n- `matrix.eml` - the operations\n- `frac.eml` - exact fractions, from P007 (calculator), with reading and\n  writing a number\n\nHow each part works:\n\n- A number is a fraction in lowest terms with a positive denominator. EML\n  has no `//` and `/` goes through a float, so the whole-number division that\n  keeps fractions in lowest terms is written out by doubling, as in P007.\n- The product takes row i of the first matrix times column j of the second\n  for each entry, as in the corpus case `matrix-multiplication`; the\n  transpose turns rows into columns as in `matrix-transpose-manual`.\n- The determinant is found by Gaussian elimination: multiples of one row are\n  taken from the rows below it until the matrix is upper triangular, and the\n  determinant is the product of the diagonal, its sign flipped for each swap\n  of two rows. The corpus case `matrix-determinant` expands by cofactors,\n  which takes n! terms - 120 for a 5 x 5 matrix - where elimination takes\n  about n^3 / 3 steps; with exact fractions both give the same answer.\n- The inverse is found by Gauss-Jordan elimination on A beside the identity\n  matrix: when the left half has become the identity, the right half is the\n  inverse. A matrix whose determinant is 0 has none. Every inverse is\n  multiplied back, and the program says so when A times R is the identity.\n- An operation is read as one of `A+B`, `A-B`, `A*B`, a number times a\n  matrix (`2*A`, `-3/2*B`), `trans A`, `det A` and `inv A`; spaces do not\n  matter, and R can stand in for a matrix.\n\nWhat is checked: A, B or C as the matrix to enter; 1 to 5 rows and columns;\neach row as exactly that many numbers - whole numbers, fractions like `3/4`\nor decimals like `0.25`, each part at most 9 digits, a denominator never 0;\nan operation the matrices fit: the same size for a sum or difference, as\nmany columns in the first as rows in the second for a product, a square\nmatrix for a determinant or an inverse. An empty answer cancels.\n\nSessions: `sessions/basic.in` enters a 3 x 3 matrix A, a 3 x 2 matrix B\nwith fractions and decimals, and a 2 x 2 matrix C whose second row is twice\nits first; finds the determinant of A (-1), its inverse (checked), R times A\n(the identity), A times B, the transpose of B and 1/2 times C; then the\ndeterminant of C is 0 and C has no inverse. `sessions/bad-input.in` gives menu\nchoices 0 and x, shows and calculates with no matrices, matrix D, sizes 0, 6\nand x, rows with too few and too many numbers, x and 1/0, a matrix cancelled\nhalfway; then a 2 x 3 matrix A and the identity B: a sum and a product of the\nwrong sizes, the determinant and inverse of a matrix that is not square, R\nbefore there is a result, C and Q, and three operations that are not one;\nand finally B times A, its transpose, 0.5 times R and -3/2 times B.\n\nBuilt on the verified corpus cases `matrix-multiplication` (a product by\nrow times column), `matrix-determinant` (a determinant by cofactor\nexpansion, checked against known values) and `matrix-transpose-manual` (a\ntranspose by nested loops).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P038 matrix calculator: up to three matrices A, B and C of at most 5 x 5,\n# with entries typed as whole numbers, fractions or decimals and kept as\n# exact fractions. One operation at a time - sum, difference, product, a\n# number times a matrix, transpose, determinant, inverse - and its result is\n# kept as R, which the next operation can use.\nimport frac\nimport matrix\n\n5 => largest\n\ndef trim(s):\n    0 => i\n    len(s) => j\n    while i < j and s[i] == \" \":\n        i + 1 => i\n    while j > i and s[j - 1] == \" \":\n        j - 1 => j\n    return s[i:j]\n\ndef upper(s):\n    \"abcdefghijklmnopqrstuvwxyz\" => small\n    \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\" => big\n    \"\" => out\n    for c in s:\n        0 => k\n        while k < 26 and small[k] != c:\n            k + 1 => k\n        if k < 26:\n            out + big[k] => out\n        else:\n            out + c => out\n    return out\n\ndef words(s):\n    # s cut at spaces and commas.\n    [] => out\n    \"\" => word\n    for c in s + \" \":\n        if c == \" \" or c == \",\":\n            if word != \"\":\n                out + [word] => out\n            \"\" => word\n        else:\n            word + c => word\n    return out\n\ndef without_spaces(s):\n    \"\" => out\n    for c in s:\n        if c != \" \":\n            out + c => out\n    return out\n\ndef dims(m):\n    return str(len(m)) + \" x \" + str(len(m[0]))\n\ndef show(name, m):\n    # Columns right-aligned to their widest entry.\n    (name + \" (\" + dims(m) + \"):\") ^0\n    [] => widths\n    for j in [0:len(m[0]) - 1]:\n        0 => w\n        for row in m:\n            if len(frac.text(row[j])) > w:\n                len(frac.text(row[j])) => w\n        widths + [w] => widths\n    for row in m:\n        \"  [\" => line\n        for j in [0:len(row) - 1]:\n            frac.text(row[j]) => t\n            while len(t) < widths[j]:\n                \" \" + t => t\n            line + \" \" + t => line\n        (line + \" ]\") ^0\n\ndef ask_size(prompt):\n    while True:\n        trim(input(prompt)) => answer\n        if answer == \"\":\n            return -1\n        frac.digits_value(answer) => n\n        if n >= 1 and n <= largest:\n            return n\n        (\"Type a number from 1 to \" + str(largest) + \".\") ^0\n\ndef ask_row(i, cols):\n    # One row of cols numbers, or [] when the answer is empty.\n    while True:\n        trim(input(\"row \" + str(i) + \"> \")) => answer\n        if answer == \"\":\n            return []\n        words(answer) => ws\n        [] => row\n        True => good\n        for w in ws:\n            frac.parse(w) => x\n            if len(x) == 0:\n                False => good\n            else:\n                row + [x] => row\n        if not good:\n            \"Type whole numbers, fractions like 3/4 or decimals like 0.25, separated by spaces.\" ^0\n        elif len(row) != cols:\n            (\"That is \" + str(len(row)) + \" numbers; this row needs \" + str(cols) + \".\") ^0\n        else:\n            return row\n\ndef enter(store):\n    while True:\n        upper(trim(input(\"which matrix (A, B or C)> \"))) => name\n        if name == \"\":\n            \"Cancelled.\" ^0\n            return store\n        if name == \"A\" or name == \"B\" or name == \"C\":\n            ask_size(\"rows (1 to \" + str(largest) + \")> \") => rows\n            if rows == -1:\n                \"Cancelled.\" ^0\n                return store\n            ask_size(\"columns (1 to \" + str(largest) + \")> \") => cols\n            if cols == -1:\n                \"Cancelled.\" ^0\n                return store\n            [] => m\n            for i in [1:rows]:\n                ask_row(i, cols) => row\n                if len(row) == 0:\n                    \"Cancelled.\" ^0\n                    return store\n                m + [row] => m\n            m => store[name]\n            show(name, m)\n            return store\n        \"Type A, B or C.\" ^0\n\ndef operand(store, s):\n    # The matrix called s, [False, why] if there is none.\n    upper(s) => name\n    if not (name in store):\n        if name == \"A\" or name == \"B\" or name == \"C\":\n            return [False, name + \" has not been entered yet.\"]\n        if name == \"R\":\n            return [False, \"There is no result yet.\"]\n        return [False, \"There is no matrix called \" + s + \".\"]\n    return [True, store[name]]\n\ndef calculate(store, s):\n    # One operation. Returns [True, \"matrix\" or \"number\", the result, what\n    # was done, a check to show or \"\"], or [False, why not].\n    without_spaces(s) => s\n    upper(s) => u\n    if u[0:3] == \"DET\" or u[0:3] == \"INV\" or u[0:5] == \"TRANS\":\n        3 => k\n        if u[0:5] == \"TRANS\":\n            5 => k\n        operand(store, s[k:len(s)]) => x\n        if not x[0]:\n            return x\n        x[1] => a\n        upper(s[k:len(s)]) => name\n        if u[0:5] == \"TRANS\":\n            return [True, \"matrix\", matrix.transposed(a), \"the transpose of \" + name, \"\"]\n        if len(a) != len(a[0]):\n            return [False, \"Only a square matrix has a determinant or an inverse; this one is \" + dims(a) + \".\"]\n        if u[0:3] == \"DET\":\n            return [True, \"number\", matrix.determinant(a), \"the determinant of \" + name, \"\"]\n        matrix.inverse(a) => inv\n        if len(inv) == 0:\n            return [False, name + \" has no inverse: its determinant is 0.\"]\n        \"\" => check\n        if matrix.product(a, inv) == matrix.identity(len(a)):\n            \"Checked: \" + name + \" * R is the identity matrix.\" => check\n        return [True, \"matrix\", inv, \"the inverse of \" + name, check]\n    # a binary operation: * first, so that \"-2*A\" keeps its minus sign\n    -1 => at\n    for i in [0:len(s) - 1]:\n        if at == -1 and s[i] == \"*\":\n            i => at\n    if at == -1:\n        for i in [1:len(s) - 1]:\n            if at == -1 and (s[i] == \"+\" or s[i] == \"-\"):\n                i => at\n    if at <= 0 or at == len(s) - 1:\n        return [False, \"Type one operation, like A+B, A-B, A*B, 2*A, trans A, det A or inv A.\"]\n    s[at] => op\n    s[0:at] => left\n    s[at + 1:len(s)] => right\n    operand(store, right) => y\n    if not y[0]:\n        return y\n    y[1] => b\n    if op == \"*\":\n        frac.parse(left) => k\n        if len(k) > 0:\n            return [True, \"matrix\", matrix.scaled(k, b), frac.text(k) + \" times \" + upper(right), \"\"]\n    operand(store, left) => x\n    if not x[0]:\n        return x\n    x[1] => a\n    upper(left) + \" \" + op + \" \" + upper(right) => what\n    if op == \"*\":\n        if len(a[0]) != len(b):\n            return [False, upper(left) + \" is \" + dims(a) + \" and \" + upper(right) + \" is \" + dims(b) + \": a product needs the columns of the first (\" + str(len(a[0])) + \") to equal the rows of the second (\" + str(len(b)) + \").\"]\n        return [True, \"matrix\", matrix.product(a, b), what, \"\"]\n    if len(a) != len(b) or len(a[0]) != len(b[0]):\n        return [False, upper(left) + \" is \" + dims(a) + \" and \" + upper(right) + \" is \" + dims(b) + \": adding or subtracting needs the same size.\"]\n    return [True, \"matrix\", matrix.combined(a, b, op == \"-\"), what, \"\"]\n\n\"== Matrix calculator ==\" ^0\n\"Matrices A, B and C of up to 5 x 5; entries are kept as exact fractions.\" ^0\n{} => store\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) enter a matrix  2) show the matrices  3) calculate  4) quit\" ^0\n    trim(input(\"choice> \")) => choice\n    if choice == \"1\":\n        enter(store) => store\n    elif choice == \"2\":\n        if len(store) == 0:\n            \"No matrices yet.\" ^0\n        for name in [\"A\", \"B\", \"C\", \"R\"]:\n            if name in store:\n                show(name, store[name])\n    elif choice == \"3\":\n        trim(input(\"operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> \")) => op\n        if op == \"\":\n            \"Cancelled.\" ^0\n        else:\n            calculate(store, op) => r\n            if not r[0]:\n                r[1] ^0\n            elif r[1] == \"number\":\n                (\"The answer is \" + r[3] + \": \" + frac.text(r[2]) + \".\") ^0\n            else:\n                r[2] => store[\"R\"]\n                (\"R is \" + r[3] + \".\") ^0\n                show(\"R\", r[2])\n                if r[4] != \"\":\n                    r[4] ^0\n    elif choice == \"4\":\n        False => running\n    else:\n        \"Pick a number from 1 to 4.\" ^0\n\"Bye.\" ^0\n",
      "python": "import frac\nimport matrix\nlargest = 5\n\ndef trim(s):\n    i = 0\n    j = len(s)\n    while i < j and s[i] == \" \":\n        i = i + 1\n    while j > i and s[j - 1] == \" \":\n        j = j - 1\n    return s[i:j]\n\ndef upper(s):\n    small = \"abcdefghijklmnopqrstuvwxyz\"\n    big = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n    out = \"\"\n    for c in s:\n        k = 0\n        while k < 26 and small[k] != c:\n            k = k + 1\n        if k < 26:\n            out = out + big[k]\n        else:\n            out = out + c\n    return out\n\ndef words(s):\n    out = []\n    word = \"\"\n    for c in s + \" \":\n        if c == \" \" or c == \",\":\n            if word != \"\":\n                out = out + [word]\n            word = \"\"\n        else:\n            word = word + c\n    return out\n\ndef without_spaces(s):\n    out = \"\"\n    for c in s:\n        if c != \" \":\n            out = out + c\n    return out\n\ndef dims(m):\n    return str(len(m)) + \" x \" + str(len(m[0]))\n\ndef show(name, m):\n    print(name + \" (\" + dims(m) + \"):\")\n    widths = []\n    for j in range(0, len(m[0])):\n        w = 0\n        for row in m:\n            if len(frac.text(row[j])) > w:\n                w = len(frac.text(row[j]))\n        widths = widths + [w]\n    for row in m:\n        line = \"  [\"\n        for j in range(0, len(row)):\n            t = frac.text(row[j])\n            while len(t) < widths[j]:\n                t = \" \" + t\n            line = line + \" \" + t\n        print(line + \" ]\")\n\ndef ask_size(prompt):\n    while True:\n        answer = trim(input(prompt))\n        if answer == \"\":\n            return -1\n        n = frac.digits_value(answer)\n        if n >= 1 and n <= largest:\n            return n\n        print(\"Type a number from 1 to \" + str(largest) + \".\")\n\ndef ask_row(i, cols):\n    while True:\n        answer = trim(input(\"row \" + str(i) + \"> \"))\n        if answer == \"\":\n            return []\n        ws = words(answer)\n        row = []\n        good = True\n        for w in ws:\n            x = frac.parse(w)\n            if len(x) == 0:\n                good = False\n            else:\n                row = row + [x]\n        if not good:\n            print(\"Type whole numbers, fractions like 3/4 or decimals like 0.25, separated by spaces.\")\n        elif len(row) != cols:\n            print(\"That is \" + str(len(row)) + \" numbers; this row needs \" + str(cols) + \".\")\n        else:\n            return row\n\ndef enter(store):\n    while True:\n        name = upper(trim(input(\"which matrix (A, B or C)> \")))\n        if name == \"\":\n            print(\"Cancelled.\")\n            return store\n        if name == \"A\" or name == \"B\" or name == \"C\":\n            rows = ask_size(\"rows (1 to \" + str(largest) + \")> \")\n            if rows == -1:\n                print(\"Cancelled.\")\n                return store\n            cols = ask_size(\"columns (1 to \" + str(largest) + \")> \")\n            if cols == -1:\n                print(\"Cancelled.\")\n                return store\n            m = []\n            for i in range(1, rows+1):\n                row = ask_row(i, cols)\n                if len(row) == 0:\n                    print(\"Cancelled.\")\n                    return store\n                m = m + [row]\n            store[name] = m\n            show(name, m)\n            return store\n        print(\"Type A, B or C.\")\n\ndef operand(store, s):\n    name = upper(s)\n    if not name in store:\n        if name == \"A\" or name == \"B\" or name == \"C\":\n            return [False, name + \" has not been entered yet.\"]\n        if name == \"R\":\n            return [False, \"There is no result yet.\"]\n        return [False, \"There is no matrix called \" + s + \".\"]\n    return [True, store[name]]\n\ndef calculate(store, s):\n    s = without_spaces(s)\n    u = upper(s)\n    if u[0:3] == \"DET\" or u[0:3] == \"INV\" or u[0:5] == \"TRANS\":\n        k = 3\n        if u[0:5] == \"TRANS\":\n            k = 5\n        x = operand(store, s[k:len(s)])\n        if not x[0]:\n            return x\n        a = x[1]\n        name = upper(s[k:len(s)])\n        if u[0:5] == \"TRANS\":\n            return [True, \"matrix\", matrix.transposed(a), \"the transpose of \" + name, \"\"]\n        if len(a) != len(a[0]):\n            return [False, \"Only a square matrix has a determinant or an inverse; this one is \" + dims(a) + \".\"]\n        if u[0:3] == \"DET\":\n            return [True, \"number\", matrix.determinant(a), \"the determinant of \" + name, \"\"]\n        inv = matrix.inverse(a)\n        if len(inv) == 0:\n            return [False, name + \" has no inverse: its determinant is 0.\"]\n        check = \"\"\n        if matrix.product(a, inv) == matrix.identity(len(a)):\n            check = \"Checked: \" + name + \" * R is the identity matrix.\"\n        return [True, \"matrix\", inv, \"the inverse of \" + name, check]\n    at = -1\n    for i in range(0, len(s)):\n        if at == -1 and s[i] == \"*\":\n            at = i\n    if at == -1:\n        for i in range(1, len(s)):\n            if at == -1 and (s[i] == \"+\" or s[i] == \"-\"):\n                at = i\n    if at <= 0 or at == len(s) - 1:\n        return [False, \"Type one operation, like A+B, A-B, A*B, 2*A, trans A, det A or inv A.\"]\n    op = s[at]\n    left = s[0:at]\n    right = s[at + 1:len(s)]\n    y = operand(store, right)\n    if not y[0]:\n        return y\n    b = y[1]\n    if op == \"*\":\n        k = frac.parse(left)\n        if len(k) > 0:\n            return [True, \"matrix\", matrix.scaled(k, b), frac.text(k) + \" times \" + upper(right), \"\"]\n    x = operand(store, left)\n    if not x[0]:\n        return x\n    a = x[1]\n    what = upper(left) + \" \" + op + \" \" + upper(right)\n    if op == \"*\":\n        if len(a[0]) != len(b):\n            return [False, upper(left) + \" is \" + dims(a) + \" and \" + upper(right) + \" is \" + dims(b) + \": a product needs the columns of the first (\" + str(len(a[0])) + \") to equal the rows of the second (\" + str(len(b)) + \").\"]\n        return [True, \"matrix\", matrix.product(a, b), what, \"\"]\n    if len(a) != len(b) or len(a[0]) != len(b[0]):\n        return [False, upper(left) + \" is \" + dims(a) + \" and \" + upper(right) + \" is \" + dims(b) + \": adding or subtracting needs the same size.\"]\n    return [True, \"matrix\", matrix.combined(a, b, op == \"-\"), what, \"\"]\n\nprint(\"== Matrix calculator ==\")\nprint(\"Matrices A, B and C of up to 5 x 5; entries are kept as exact fractions.\")\nstore = {}\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) enter a matrix  2) show the matrices  3) calculate  4) quit\")\n    choice = trim(input(\"choice> \"))\n    if choice == \"1\":\n        store = enter(store)\n    elif choice == \"2\":\n        if len(store) == 0:\n            print(\"No matrices yet.\")\n        for name in [\"A\", \"B\", \"C\", \"R\"]:\n            if name in store:\n                show(name, store[name])\n    elif choice == \"3\":\n        op = trim(input(\"operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> \"))\n        if op == \"\":\n            print(\"Cancelled.\")\n        else:\n            r = calculate(store, op)\n            if not r[0]:\n                print(r[1])\n            elif r[1] == \"number\":\n                print(\"The answer is \" + r[3] + \": \" + frac.text(r[2]) + \".\")\n            else:\n                store[\"R\"] = r[2]\n                print(\"R is \" + r[3] + \".\")\n                show(\"R\", r[2])\n                if r[4] != \"\":\n                    print(r[4])\n    elif choice == \"4\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 4.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "matrix.eml",
      "eml": "# P038 matrix calculator - the operations. A matrix is a list of rows of\n# fractions (see frac.eml), all rows the same length.\nimport frac\n\ndef size(m):\n    return [len(m), len(m[0])]\n\ndef zero(rows, cols):\n    [] => m\n    for i in [1:rows]:\n        [] => row\n        for j in [1:cols]:\n            row + [[0, 1]] => row\n        m + [row] => m\n    return m\n\ndef identity(n):\n    zero(n, n) => m\n    for i in [0:n - 1]:\n        [1, 1] => m[i][i]\n    return m\n\ndef combined(a, b, subtract):\n    # a + b, or a - b when subtract is True; the sizes are equal.\n    [] => out\n    for i in [0:len(a) - 1]:\n        [] => row\n        for j in [0:len(a[0]) - 1]:\n            if subtract:\n                row + [frac.minus(a[i][j], b[i][j])] => row\n            else:\n                row + [frac.plus(a[i][j], b[i][j])] => row\n        out + [row] => out\n    return out\n\ndef product(a, b):\n    # a * b: entry (i, j) is row i of a times column j of b, as in the corpus\n    # case matrix-multiplication; columns of a = rows of b.\n    [] => out\n    for i in [0:len(a) - 1]:\n        [] => row\n        for j in [0:len(b[0]) - 1]:\n            [0, 1] => total\n            for k in [0:len(b) - 1]:\n                frac.plus(total, frac.times(a[i][k], b[k][j])) => total\n            row + [total] => row\n        out + [row] => out\n    return out\n\ndef scaled(k, a):\n    [] => out\n    for row in a:\n        [] => r\n        for x in row:\n            r + [frac.times(k, x)] => r\n        out + [r] => out\n    return out\n\ndef transposed(a):\n    [] => out\n    for j in [0:len(a[0]) - 1]:\n        [] => row\n        for i in [0:len(a) - 1]:\n            row + [a[i][j]] => row\n        out + [row] => out\n    return out\n\ndef copied(a):\n    [] => out\n    for row in a:\n        out + [row[0:len(row)]] => out\n    return out\n\ndef determinant(a):\n    # Gaussian elimination with exact fractions: bring the matrix to upper\n    # triangular form by taking multiples of one row from the rows below it;\n    # the determinant is the product of the diagonal, with its sign flipped\n    # for each swap of two rows. About n^3 / 3 steps, where expanding by\n    # cofactors (the corpus case matrix-determinant) takes n! terms.\n    copied(a) => m\n    len(m) => n\n    [1, 1] => det\n    for col in [0:n - 1]:\n        col => p\n        while p < n and frac.is_zero(m[p][col]):\n            p + 1 => p\n        if p == n:\n            return [0, 1]\n        if p != col:\n            m[p] => keep\n            m[col] => m[p]\n            keep => m[col]\n            frac.minus([0, 1], det) => det\n        frac.times(det, m[col][col]) => det\n        for r in [col + 1:n - 1]:\n            if not frac.is_zero(m[r][col]):\n                frac.over(m[r][col], m[col][col]) => f\n                for j in [col:n - 1]:\n                    frac.minus(m[r][j], frac.times(f, m[col][j])) => m[r][j]\n    return det\n\ndef inverse(a):\n    # Gauss-Jordan elimination on [a | I]: when the left half has become I,\n    # the right half is the inverse. [] if a is singular.\n    len(a) => n\n    [] => m\n    identity(n) => e\n    for i in [0:n - 1]:\n        m + [a[i] + e[i]] => m\n    for col in [0:n - 1]:\n        col => p\n        while p < n and frac.is_zero(m[p][col]):\n            p + 1 => p\n        if p == n:\n            return []\n        if p != col:\n            m[p] => keep\n            m[col] => m[p]\n            keep => m[col]\n        m[col][col] => pivot\n        [] => row\n        for x in m[col]:\n            row + [frac.over(x, pivot)] => row\n        row => m[col]\n        for r in [0:n - 1]:\n            if r != col and not frac.is_zero(m[r][col]):\n                m[r][col] => f\n                [] => changed\n                for j in [0:2 * n - 1]:\n                    changed + [frac.minus(m[r][j], frac.times(f, m[col][j]))] => changed\n                changed => m[r]\n    [] => out\n    for i in [0:n - 1]:\n        out + [m[i][n:2 * n]] => out\n    return out\n",
      "python": "import frac\n\ndef size(m):\n    return [len(m), len(m[0])]\n\ndef zero(rows, cols):\n    m = []\n    for i in range(1, rows+1):\n        row = []\n        for j in range(1, cols+1):\n            row = row + [[0, 1]]\n        m = m + [row]\n    return m\n\ndef identity(n):\n    m = zero(n, n)\n    for i in range(0, n):\n        m[i][i] = [1, 1]\n    return m\n\ndef combined(a, b, subtract):\n    out = []\n    for i in range(0, len(a)):\n        row = []\n        for j in range(0, len(a[0])):\n            if subtract:\n                row = row + [frac.minus(a[i][j], b[i][j])]\n            else:\n                row = row + [frac.plus(a[i][j], b[i][j])]\n        out = out + [row]\n    return out\n\ndef product(a, b):\n    out = []\n    for i in range(0, len(a)):\n        row = []\n        for j in range(0, len(b[0])):\n            total = [0, 1]\n            for k in range(0, len(b)):\n                total = frac.plus(total, frac.times(a[i][k], b[k][j]))\n            row = row + [total]\n        out = out + [row]\n    return out\n\ndef scaled(k, a):\n    out = []\n    for row in a:\n        r = []\n        for x in row:\n            r = r + [frac.times(k, x)]\n        out = out + [r]\n    return out\n\ndef transposed(a):\n    out = []\n    for j in range(0, len(a[0])):\n        row = []\n        for i in range(0, len(a)):\n            row = row + [a[i][j]]\n        out = out + [row]\n    return out\n\ndef copied(a):\n    out = []\n    for row in a:\n        out = out + [row[0:len(row)]]\n    return out\n\ndef determinant(a):\n    m = copied(a)\n    n = len(m)\n    det = [1, 1]\n    for col in range(0, n):\n        p = col\n        while p < n and frac.is_zero(m[p][col]):\n            p = p + 1\n        if p == n:\n            return [0, 1]\n        if p != col:\n            keep = m[p]\n            m[p] = m[col]\n            m[col] = keep\n            det = frac.minus([0, 1], det)\n        det = frac.times(det, m[col][col])\n        for r in range(col + 1, n):\n            if not frac.is_zero(m[r][col]):\n                f = frac.over(m[r][col], m[col][col])\n                for j in range(col, n):\n                    m[r][j] = frac.minus(m[r][j], frac.times(f, m[col][j]))\n    return det\n\ndef inverse(a):\n    n = len(a)\n    m = []\n    e = identity(n)\n    for i in range(0, n):\n        m = m + [a[i] + e[i]]\n    for col in range(0, n):\n        p = col\n        while p < n and frac.is_zero(m[p][col]):\n            p = p + 1\n        if p == n:\n            return []\n        if p != col:\n            keep = m[p]\n            m[p] = m[col]\n            m[col] = keep\n        pivot = m[col][col]\n        row = []\n        for x in m[col]:\n            row = row + [frac.over(x, pivot)]\n        m[col] = row\n        for r in range(0, n):\n            if r != col and not frac.is_zero(m[r][col]):\n                f = m[r][col]\n                changed = []\n                for j in range(0, 2 * n):\n                    changed = changed + [frac.minus(m[r][j], frac.times(f, m[col][j]))]\n                m[r] = changed\n    out = []\n    for i in range(0, n):\n        out = out + [m[i][n:2 * n]]\n    return out\n"
    },
    {
      "name": "frac.eml",
      "eml": "# P038 matrix calculator - exact fractions, from P007 (calculator). A number\n# is a list [n, d]: n / d in lowest terms with d > 0. Integers have no size\n# limit, but EML has no //, and a / b goes through a float that cannot hold a\n# large quotient exactly, so whole-number division is written out below.\n\ndef quotient(a, b):\n    # a // b for whole numbers a >= 0 and b > 0, exact at any size. Long\n    # division by doubling: take away the largest b * 2^k that still fits.\n    0 => q\n    while a >= b:\n        b => m\n        1 => k\n        while m + m <= a:\n            m + m => m\n            k + k => k\n        a - m => a\n        q + k => q\n    return q\n\ndef gcd(a, b):\n    while b != 0:\n        a % b => r\n        b => a\n        r => b\n    return a\n\ndef make(n, d):\n    # n / d in lowest terms with a positive denominator (d != 0).\n    if d < 0:\n        0 - n => n\n        0 - d => d\n    abs(n) => a\n    gcd(a, d) => g\n    if n < 0:\n        return [0 - quotient(a, g), quotient(d, g)]\n    return [quotient(a, g), quotient(d, g)]\n\ndef plus(x, y):\n    return make(x[0] * y[1] + y[0] * x[1], x[1] * y[1])\n\ndef minus(x, y):\n    return make(x[0] * y[1] - y[0] * x[1], x[1] * y[1])\n\ndef times(x, y):\n    return make(x[0] * y[0], x[1] * y[1])\n\ndef over(x, y):\n    # x / y; the caller has checked that y is not zero.\n    return make(x[0] * y[1], x[1] * y[0])\n\ndef is_zero(x):\n    return x[0] == 0\n\ndef digits_value(s):\n    # The value of a string of 1 to 9 digits, otherwise -1.\n    if s == \"\" or len(s) > 9:\n        return -1\n    0 => n\n    for c in s:\n        if not (c in \"0123456789\"):\n            return -1\n        n * 10 + int(c) => n\n    return n\n\ndef parse(s):\n    # \"3\", \"-2\", \"3/4\", \"-0.25\" as a fraction; [] if it is not a number.\n    1 => sign\n    if len(s) > 0 and s[0] == \"-\":\n        -1 => sign\n        s[1:len(s)] => s\n    0 => k\n    while k < len(s) and s[k] != \"/\" and s[k] != \".\":\n        k + 1 => k\n    if k == len(s):\n        digits_value(s) => n\n        if n < 0:\n            return []\n        return make(sign * n, 1)\n    digits_value(s[0:k]) => whole\n    s[k + 1:len(s)] => rest\n    digits_value(rest) => part\n    if whole < 0 or part < 0:\n        return []\n    if s[k] == \"/\":\n        if part == 0:\n            return []\n        return make(sign * whole, part)\n    # a decimal point: 0.25 is 25 / 100\n    1 => scale\n    for c in rest:\n        scale * 10 => scale\n    return make(sign * (whole * scale + part), scale)\n\ndef text(x):\n    # \"3\", \"-3\", \"3/4\", \"-3/4\".\n    if x[1] == 1:\n        return str(x[0])\n    return str(x[0]) + \"/\" + str(x[1])\n",
      "python": "def quotient(a, b):\n    q = 0\n    while a >= b:\n        m = b\n        k = 1\n        while m + m <= a:\n            m = m + m\n            k = k + k\n        a = a - m\n        q = q + k\n    return q\n\ndef gcd(a, b):\n    while b != 0:\n        r = a % b\n        a = b\n        b = r\n    return a\n\ndef make(n, d):\n    if d < 0:\n        n = 0 - n\n        d = 0 - d\n    a = abs(n)\n    g = gcd(a, d)\n    if n < 0:\n        return [0 - quotient(a, g), quotient(d, g)]\n    return [quotient(a, g), quotient(d, g)]\n\ndef plus(x, y):\n    return make(x[0] * y[1] + y[0] * x[1], x[1] * y[1])\n\ndef minus(x, y):\n    return make(x[0] * y[1] - y[0] * x[1], x[1] * y[1])\n\ndef times(x, y):\n    return make(x[0] * y[0], x[1] * y[1])\n\ndef over(x, y):\n    return make(x[0] * y[1], x[1] * y[0])\n\ndef is_zero(x):\n    return x[0] == 0\n\ndef digits_value(s):\n    if s == \"\" or len(s) > 9:\n        return -1\n    n = 0\n    for c in s:\n        if not c in \"0123456789\":\n            return -1\n        n = n * 10 + int(c)\n    return n\n\ndef parse(s):\n    sign = 1\n    if len(s) > 0 and s[0] == \"-\":\n        sign = -1\n        s = s[1:len(s)]\n    k = 0\n    while k < len(s) and s[k] != \"/\" and s[k] != \".\":\n        k = k + 1\n    if k == len(s):\n        n = digits_value(s)\n        if n < 0:\n            return []\n        return make(sign * n, 1)\n    whole = digits_value(s[0:k])\n    rest = s[k + 1:len(s)]\n    part = digits_value(rest)\n    if whole < 0 or part < 0:\n        return []\n    if s[k] == \"/\":\n        if part == 0:\n            return []\n        return make(sign * whole, part)\n    scale = 1\n    for c in rest:\n        scale = scale * 10\n    return make(sign * (whole * scale + part), scale)\n\ndef text(x):\n    if x[1] == 1:\n        return str(x[0])\n    return str(x[0]) + \"/\" + str(x[1])\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n2\n3\ndet A\n3\n\n1\nD\n\n1\nA\n0\n6\nx\n\n1\nA\n2\n3\n1 2\n1 2 3 4\n1 x 3\n1/0 2 3\n1 2 3\n\n1\nA\n2\n3\n1 2 3\n4 5 6\n1\nb\n2\n2\n1 0\n0 1\n3\nA+B\n3\nA*A\n3\ndet A\n3\ninv A\n3\nR*A\n3\nC+A\n3\nQ*A\n3\nA\n3\nA+\n3\n+A\n3\nB*A\n3\ntrans R\n3\n0.5*R\n3\n-3/2*B\n2\n4\n",
      "screen": "== Matrix calculator ==\nMatrices A, B and C of up to 5 x 5; entries are kept as exact fractions.\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 0\nPick a number from 1 to 4.\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> x\nPick a number from 1 to 4.\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 2\nNo matrices yet.\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 3\noperation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> det A\nA has not been entered yet.\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 3\noperation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> \nCancelled.\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 1\nwhich matrix (A, B or C)> D\nType A, B or C.\nwhich matrix (A, B or C)> \nCancelled.\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 1\nwhich matrix (A, B or C)> A\nrows (1 to 5)> 0\nType a number from 1 to 5.\nrows (1 to 5)> 6\nType a number from 1 to 5.\nrows (1 to 5)> x\nType a number from 1 to 5.\nrows (1 to 5)> \nCancelled.\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 1\nwhich matrix (A, B or C)> A\nrows (1 to 5)> 2\ncolumns (1 to 5)> 3\nrow 1> 1 2\nThat is 2 numbers; this row needs 3.\nrow 1> 1 2 3 4\nThat is 4 numbers; this row needs 3.\nrow 1> 1 x 3\nType whole numbers, fractions like 3/4 or decimals like 0.25, separated by spaces.\nrow 1> 1/0 2 3\nType whole numbers, fractions like 3/4 or decimals like 0.25, separated by spaces.\nrow 1> 1 2 3\nrow 2> \nCancelled.\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 1\nwhich matrix (A, B or C)> A\nrows (1 to 5)> 2\ncolumns (1 to 5)> 3\nrow 1> 1 2 3\nrow 2> 4 5 6\nA (2 x 3):\n  [ 1 2 3 ]\n  [ 4 5 6 ]\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 1\nwhich matrix (A, B or C)> b\nrows (1 to 5)> 2\ncolumns (1 to 5)> 2\nrow 1> 1 0\nrow 2> 0 1\nB (2 x 2):\n  [ 1 0 ]\n  [ 0 1 ]\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 3\noperation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> A+B\nA is 2 x 3 and B is 2 x 2: adding or subtracting needs the same size.\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 3\noperation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> A*A\nA is 2 x 3 and A is 2 x 3: a product needs the columns of the first (3) to equal the rows of the second (2).\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 3\noperation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> det A\nOnly a square matrix has a determinant or an inverse; this one is 2 x 3.\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 3\noperation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> inv A\nOnly a square matrix has a determinant or an inverse; this one is 2 x 3.\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 3\noperation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> R*A\nThere is no result yet.\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 3\noperation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> C+A\nC has not been entered yet.\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 3\noperation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> Q*A\nThere is no matrix called Q.\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 3\noperation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> A\nType one operation, like A+B, A-B, A*B, 2*A, trans A, det A or inv A.\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 3\noperation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> A+\nType one operation, like A+B, A-B, A*B, 2*A, trans A, det A or inv A.\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 3\noperation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> +A\nType one operation, like A+B, A-B, A*B, 2*A, trans A, det A or inv A.\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 3\noperation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> B*A\nR is B * A.\nR (2 x 3):\n  [ 1 2 3 ]\n  [ 4 5 6 ]\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 3\noperation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> trans R\nR is the transpose of R.\nR (3 x 2):\n  [ 1 4 ]\n  [ 2 5 ]\n  [ 3 6 ]\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 3\noperation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> 0.5*R\nR is 1/2 times R.\nR (3 x 2):\n  [ 1/2   2 ]\n  [   1 5/2 ]\n  [ 3/2   3 ]\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 3\noperation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> -3/2*B\nR is -3/2 times B.\nR (2 x 2):\n  [ -3/2    0 ]\n  [    0 -3/2 ]\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 2\nA (2 x 3):\n  [ 1 2 3 ]\n  [ 4 5 6 ]\nB (2 x 2):\n  [ 1 0 ]\n  [ 0 1 ]\nR (2 x 2):\n  [ -3/2    0 ]\n  [    0 -3/2 ]\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 4\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\nA\n3\n3\n2 1 -1\n-3 -1 2\n-2 1 2\n1\nB\n3\n2\n1/2 0.25\n-1 2\n3 -3/4\n1\nc\n2\n2\n1 2\n2, 4\n2\n3\ndet A\n3\ninv A\n3\nR * A\n3\nA*B\n3\ntrans B\n3\n1/2 * C\n3\ndet C\n3\ninv C\n4\n",
      "screen": "== Matrix calculator ==\nMatrices A, B and C of up to 5 x 5; entries are kept as exact fractions.\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 1\nwhich matrix (A, B or C)> A\nrows (1 to 5)> 3\ncolumns (1 to 5)> 3\nrow 1> 2 1 -1\nrow 2> -3 -1 2\nrow 3> -2 1 2\nA (3 x 3):\n  [  2  1 -1 ]\n  [ -3 -1  2 ]\n  [ -2  1  2 ]\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 1\nwhich matrix (A, B or C)> B\nrows (1 to 5)> 3\ncolumns (1 to 5)> 2\nrow 1> 1/2 0.25\nrow 2> -1 2\nrow 3> 3 -3/4\nB (3 x 2):\n  [ 1/2  1/4 ]\n  [  -1    2 ]\n  [   3 -3/4 ]\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 1\nwhich matrix (A, B or C)> c\nrows (1 to 5)> 2\ncolumns (1 to 5)> 2\nrow 1> 1 2\nrow 2> 2, 4\nC (2 x 2):\n  [ 1 2 ]\n  [ 2 4 ]\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 2\nA (3 x 3):\n  [  2  1 -1 ]\n  [ -3 -1  2 ]\n  [ -2  1  2 ]\nB (3 x 2):\n  [ 1/2  1/4 ]\n  [  -1    2 ]\n  [   3 -3/4 ]\nC (2 x 2):\n  [ 1 2 ]\n  [ 2 4 ]\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 3\noperation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> det A\nThe answer is the determinant of A: -1.\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 3\noperation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> inv A\nR is the inverse of A.\nR (3 x 3):\n  [  4  3 -1 ]\n  [ -2 -2  1 ]\n  [  5  4 -1 ]\nChecked: A * R is the identity matrix.\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 3\noperation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> R * A\nR is R * A.\nR (3 x 3):\n  [ 1 0 0 ]\n  [ 0 1 0 ]\n  [ 0 0 1 ]\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 3\noperation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> A*B\nR is A * B.\nR (3 x 2):\n  [   -3  13/4 ]\n  [ 11/2 -17/4 ]\n  [    4     0 ]\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 3\noperation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> trans B\nR is the transpose of B.\nR (2 x 3):\n  [ 1/2 -1    3 ]\n  [ 1/4  2 -3/4 ]\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 3\noperation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> 1/2 * C\nR is 1/2 times C.\nR (2 x 2):\n  [ 1/2 1 ]\n  [   1 2 ]\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 3\noperation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> det C\nThe answer is the determinant of C: 0.\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 3\noperation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> inv C\nC has no inverse: its determinant is 0.\n\n1) enter a matrix  2) show the matrices  3) calculate  4) quit\nchoice> 4\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "matrix-multiplication",
      "caseId": "090-matrix-multiplication",
      "title": "Matrix multiplication"
    },
    {
      "slug": "matrix-determinant",
      "caseId": "115-matrix-determinant",
      "title": "Matrix determinant (cofactor expansion)"
    },
    {
      "slug": "matrix-transpose-manual",
      "caseId": "029-matrix-transpose-manual",
      "title": "Matrix transpose (manual)"
    }
  ],
  "updated": "2026-10-10"
}
