From 7480c11bf70bd3b267656dcbbfaa554f6d0f4e24 Mon Sep 17 00:00:00 2001 From: HanchaiN <85230240+HanchaiN@users.noreply.github.com> Date: Sat, 9 Apr 2022 02:15:54 +0700 Subject: [PATCH] feat: implement negative integer exponents for square matrices (#2517) Resolves #2463 Co-authored-by: Glen Whitney --- AUTHORS | 1 + HISTORY.md | 4 +++ .../embeddedDocs/function/arithmetic/pow.js | 4 ++- src/function/arithmetic/pow.js | 29 +++++++++++++++---- .../function/arithmetic/pow.test.js | 20 +++++++++++++ 5 files changed, 51 insertions(+), 7 deletions(-) diff --git a/AUTHORS b/AUTHORS index e3c11c5a7..a1fc63289 100644 --- a/AUTHORS +++ b/AUTHORS @@ -187,5 +187,6 @@ Hjortur Jonasson Abraham Sam Estep Sinan <43215895+SinanAkkoyun@users.noreply.github.com> +HanchaiN <85230240+HanchaiN@users.noreply.github.com> # Generated by tools/update-authors.js diff --git a/HISTORY.md b/HISTORY.md index 9374900c3..7ab48bf86 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,9 @@ # History +# unpublished changes since 10.4.3: + +- Implement #2463: allow negative integer powers of invertible square matrices + (#2517). Thanks @HanchaiN. # 2022-04-08, version 10.4.3 diff --git a/src/expression/embeddedDocs/function/arithmetic/pow.js b/src/expression/embeddedDocs/function/arithmetic/pow.js index fa63bf373..9b8805136 100644 --- a/src/expression/embeddedDocs/function/arithmetic/pow.js +++ b/src/expression/embeddedDocs/function/arithmetic/pow.js @@ -10,7 +10,9 @@ export const powDocs = { examples: [ '2^3', '2*2*2', - '1 + e ^ (pi * i)' + '1 + e ^ (pi * i)', + 'math.pow([[1, 2], [4, 3]], 2)', + 'math.pow([[1, 2], [4, 3]], -1)' ], seealso: [ 'multiply', diff --git a/src/function/arithmetic/pow.js b/src/function/arithmetic/pow.js index 44f0d9d5a..891aa3b5f 100644 --- a/src/function/arithmetic/pow.js +++ b/src/function/arithmetic/pow.js @@ -10,16 +10,20 @@ const dependencies = [ 'identity', 'multiply', 'matrix', + 'inv', 'fraction', 'number', 'Complex' ] -export const createPow = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, identity, multiply, matrix, number, fraction, Complex }) => { +export const createPow = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, identity, multiply, matrix, inv, number, fraction, Complex }) => { /** * Calculates the power of x to y, `x ^ y`. - * Matrix exponentiation is supported for square matrices `x`, and positive - * integer exponents `y`. + * + * Matrix exponentiation is supported for square matrices `x` and integers `y`: + * when `y` is nonnegative, `x` may be any square matrix; and when `y` is + * negative, `x` must be invertible, and then this function returns + * inv(x)^(-y). * * For cubic roots of negative numbers, the function returns the principal * root by default. In order to let the function return the real root, @@ -40,6 +44,9 @@ export const createPow = /* #__PURE__ */ factory(name, dependencies, ({ typed, c * const b = [[1, 2], [4, 3]] * math.pow(b, 2) // returns Array [[9, 8], [16, 17]] * + * const c = [[1, 2], [4, 3]] + * math.pow(c, -1) // returns Array [[-0.6, 0.4], [0.8, -0.2]] + * * See also: * * multiply, sqrt, cbrt, nthRoot @@ -150,13 +157,13 @@ export const createPow = /* #__PURE__ */ factory(name, dependencies, ({ typed, c /** * Calculate the power of a 2d array * @param {Array} x must be a 2 dimensional, square matrix - * @param {number} y a positive, integer value + * @param {number} y a integer value (positive if `x` is not invertible) * @returns {Array} * @private */ function _powArray (x, y) { - if (!isInteger(y) || y < 0) { - throw new TypeError('For A^b, b must be a positive integer (value is ' + y + ')') + if (!isInteger(y)) { + throw new TypeError('For A^b, b must be an integer (value is ' + y + ')') } // verify that A is a 2 dimensional square matrix const s = size(x) @@ -166,6 +173,16 @@ export const createPow = /* #__PURE__ */ factory(name, dependencies, ({ typed, c if (s[0] !== s[1]) { throw new Error('For A^b, A must be square (size is ' + s[0] + 'x' + s[1] + ')') } + if (y < 0) { + try { + return _powArray(inv(x), -y) + } catch (error) { + if (error.message === 'Cannot calculate inverse, determinant is zero') { + throw new TypeError('For A^b, when A is not invertible, b must be a positive integer (value is ' + y + ')') + } + throw error + } + } let res = identity(s[0]).valueOf() let px = x diff --git a/test/unit-tests/function/arithmetic/pow.test.js b/test/unit-tests/function/arithmetic/pow.test.js index 6cd9aba2e..f9d513316 100644 --- a/test/unit-tests/function/arithmetic/pow.test.js +++ b/test/unit-tests/function/arithmetic/pow.test.js @@ -241,6 +241,21 @@ describe('pow', function () { approx.deepEqual(pow(matrix(a), 2), matrix(res)) }) + it('should raise an inverted matrix for power -1', function () { + const a = [ + [2, -1, 0], + [-1, 2, -1], + [0, -1, 2] + ] + const res = [ + [3 / 4, 1 / 2, 1 / 4], + [1 / 2, 1, 1 / 2], + [1 / 4, 1 / 2, 3 / 4] + ] + approx.deepEqual(pow(a, -1), res) + approx.deepEqual(pow(matrix(a), -1), matrix(res)) + }) + it('should return identity matrix for power 0', function () { const a = [[1, 2], [3, 4]] const res = [[1, 0], [0, 1]] @@ -266,6 +281,11 @@ describe('pow', function () { assert.throws(function () { pow(a, [2, 3]) }) }) + it('should throw an error when raising a non-invertible matrix to a negative integer power', function () { + const a = [[1, 1, 1], [1, 0, 0], [0, 0, 0]] + assert.throws(function () { pow(a, -1) }) + }) + it('should LaTeX pow', function () { const expression = math.parse('pow(2,10)') assert.strictEqual(expression.toTex(), '\\left(2\\right)^{10}')