Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implementing rotate(w, theta) #1992

Merged
merged 8 commits into from Oct 28, 2020
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/expression/embeddedDocs/embeddedDocs.js
Expand Up @@ -215,6 +215,7 @@ import { numericDocs } from './function/utils/numeric'
import { columnDocs } from './function/matrix/column'
import { rowDocs } from './function/matrix/row'
import { rotationMatrixDocs } from './function/matrix/rotationMatrix'
import { rotateDocs } from './function/matrix/rotate'

export const embeddedDocs = {

Expand Down Expand Up @@ -427,6 +428,7 @@ export const embeddedDocs = {
range: rangeDocs,
resize: resizeDocs,
reshape: reshapeDocs,
rotate: rotateDocs,
rotationMatrix: rotationMatrixDocs,
row: rowDocs,
size: sizeDocs,
Expand Down
19 changes: 19 additions & 0 deletions src/expression/embeddedDocs/function/matrix/rotate.js
@@ -0,0 +1,19 @@
export const rotateDocs = {
name: 'rotate',
category: 'Matrix',
syntax: [
'rotate(w, theta)',
'rotate(w, theta, v)'
],
description: 'Returns a 2-D rotation matrix (2x2) for a given angle (in radians). ' +
'Returns a 2-D rotation matrix (3x3) of a given angle (in radians) around given axis.',
examples: [
'rotate([1, 0], math.pi / 2)',
'rotate(matrix([1, 0]), unit("35deg"))',
'rotate([1, 0, 0], unit("90deg"), [0, 0, 1])',
'rotate(matrix([1, 0, 0]), unit("90deg"), matrix([0, 0, 1]))'
],
seealso: [
'matrix', 'rotationMatrix'
]
}
1 change: 1 addition & 0 deletions src/factoriesAny.js
Expand Up @@ -78,6 +78,7 @@ export { createOnes } from './function/matrix/ones'
export { createRange } from './function/matrix/range'
export { createReshape } from './function/matrix/reshape'
export { createResize } from './function/matrix/resize'
export { createRotate } from './function/matrix/rotate'
export { createRotationMatrix } from './function/matrix/rotationMatrix'
export { createRow } from './function/matrix/row'
export { createSize } from './function/matrix/size'
Expand Down
79 changes: 79 additions & 0 deletions src/function/matrix/rotate.js
@@ -0,0 +1,79 @@
import { factory } from '../../utils/factory'
import { arraySize } from '../../utils/array'

const name = 'rotate'
const dependencies = [
'typed',
'multiply',
'rotationMatrix'
]

export const createRotate = /* #__PURE__ */ factory(name, dependencies, (
{
typed, multiply, rotationMatrix
}) => {
/**
* Rotate a vector of size 1x2 counter-clockwise by a given angle
* Rotate a vector of size 1x3 counter-clockwise by a given angle around the given axis
*
* Syntax:
*
* math.rotate(w, theta)
* math.rotate(w, theta, v)
*
* Examples:
*
* math.rotate([11, 12], math.pi / 2) // returns matrix([-12, 11])
* math.rotate(matrix([11, 12]), math.pi / 2) // returns matrix([-12, 11])
*
* math.rotate([1, 0, 0], unit('90deg'), [0, 0, 1]) // returns matrix([0, 1, 0])
* math.rotate(matrix([1, 0, 0]), unit('90deg'), [0, 0, 1]) // returns matrix([0, 1, 0])
*
* math.rotate([1, 0], math.complex(1 + i)) // returns matrix([cos(1 + i) - sin(1 + i), sin(1 + i) + cos(1 + i)])
*
* See also:
*
* matrix, rotationMatrix
*
* @param {Array | Matrix} w Vector to rotate
* @param {number | BigNumber | Complex | Unit} theta Rotation angle
* @param {Array | Matrix} [v] Rotation axis
* @return {Array | Matrix} Multiplication of the rotation matrix and w
*/
return typed(name, {
'Array , number | BigNumber | Complex | Unit': function (w, theta) {
_validateSize(w, 2)
const matrixRes = multiply(rotationMatrix(theta), w)
return matrixRes.toArray()
},

'Matrix , number | BigNumber | Complex | Unit': function (w, theta) {
_validateSize(w, 2)
return multiply(rotationMatrix(theta), w)
},

'Array, number | BigNumber | Complex | Unit, Array | Matrix': function (w, theta, v) {
_validateSize(w, 3)
const matrixRes = multiply(rotationMatrix(theta, v), w)
return matrixRes.toArray()
},

'Matrix, number | BigNumber | Complex | Unit, Array | Matrix': function (w, theta, v) {
_validateSize(w, 3)
return multiply(rotationMatrix(theta, v), w)
}
})

function _validateSize (v, expectedSize) {
const actualSize = Array.isArray(v) ? arraySize(v) : v.size()
if (actualSize.length > 2) {
throw new RangeError(`Vector must be of dimensions 1x${expectedSize}`)
}
if (actualSize.length === 2 && actualSize[1] !== 1) {
throw new RangeError(`Vector must be of dimensions 1x${expectedSize}`)
}
if (actualSize[0] !== expectedSize) {
throw new RangeError(`Vector must be of dimensions 1x${expectedSize}`)
}
}
})
4 changes: 2 additions & 2 deletions src/function/matrix/rotationMatrix.js
Expand Up @@ -37,7 +37,7 @@ export const createRotationMatrix = /* #__PURE__ */ factory(name, dependencies,
* Examples:
*
* math.rotationMatrix(math.pi / 2) // returns [[0, -1], [1, 0]]
* math.rotationMatrix(math.bignumber(45)) // returns [[ bignumber(1 / sqrt(2)), - bignumber(1 / sqrt(2))], [ bignumber(1 / sqrt(2)), bignumber(1 / sqrt(2))]]
* math.rotationMatrix(math.bignumber(1)) // returns [[bignumber(cos(1)), bignumber(-sin(1))], [bignumber(sin(1)), bignumber(cos(1))]]
* math.rotationMatrix(math.complex(1 + i)) // returns [[cos(1 + i), -sin(1 + i)], [sin(1 + i), cos(1 + i)]]
* math.rotationMatrix(math.unit('1rad')) // returns [[cos(1), -sin(1)], [sin(1), cos(1)]]
*
Expand Down Expand Up @@ -152,7 +152,7 @@ export const createRotationMatrix = /* #__PURE__ */ factory(name, dependencies,
function _rotationMatrix3x3 (theta, v, format) {
const normV = norm(v)
if (normV === 0) {
return _convertToFormat([], format)
throw new RangeError('Rotation around zero vector')
}

const Big = isBigNumber(theta) ? BigNumber : null
Expand Down
187 changes: 187 additions & 0 deletions test/unit-tests/function/matrix/rotate.test.js
@@ -0,0 +1,187 @@
import assert from 'assert'
import approx from '../../../../tools/approx'

import math from '../../../../src/bundleAny'

const unit = math.unit
const complex = math.complex
const cos = math.cos
const sin = math.sin
const add = math.add
const multiply = math.multiply
const matrix = math.matrix
const rotate = math.rotate

describe('rotationMatrix', function () {
it('should return a rotated 1x2 vector when it is provided as array', function () {
assert.deepStrictEqual(rotate([1, 0], 0), [1, 0])

assert.deepStrictEqual(rotate([100, 0], 1), [100 * cos(1), 100 * sin(1)])
assert.deepStrictEqual(rotate([2, 3], 2), [2 * cos(2) - 3 * sin(2), 2 * sin(2) + 3 * cos(2)])

const cos45 = cos(unit('45deg'))
const sin45 = sin(unit('45deg'))
assert.deepStrictEqual(rotate([4, 5], unit('45deg')), [4 * cos45 - 5 * sin45,
4 * cos45 + 5 * sin45])

const reCos = 4.18962569096881
const imCos = 9.10922789375534
const reSin = 9.15449914691142
const imSin = 4.16890695996656
const cosComplex = complex(-reCos, -imCos)
const sinComplex = complex(reSin, -imSin)
approx.deepEqual(rotate([1, 1], complex('2+3i')),
[add(cosComplex, multiply(-1.0, sinComplex)), add(cosComplex, sinComplex)])
})

it('should return a rotated 1x2 vector when it is provided as matrix', function () {
assert.deepStrictEqual(rotate(matrix([100, 200]), 0), matrix([100, 200]))

assert.deepStrictEqual(rotate(matrix([100, 0]), 1), matrix([100 * cos(1), 100 * sin(1)]))
assert.deepStrictEqual(rotate(matrix([2, 3]), 2), matrix([2 * cos(2) - 3 * sin(2), 2 * sin(2) + 3 * cos(2)]))

const cos45 = cos(unit('45deg'))
const sin45 = sin(unit('45deg'))
assert.deepStrictEqual(rotate(matrix([4, 5]), unit('45deg')), matrix([4 * cos45 - 5 * sin45,
4 * cos45 + 5 * sin45]))

const reCos = 4.18962569096881
const imCos = 9.10922789375534
const reSin = 9.15449914691142
const imSin = 4.16890695996656
const cosComplex = complex(-reCos, -imCos)
const sinComplex = complex(reSin, -imSin)
approx.deepEqual(rotate(matrix([1, 1]), complex('2+3i')),
matrix([add(cosComplex, multiply(-1.0, sinComplex)), add(cosComplex, sinComplex)]))
})

it('should return a rotated 1x2 bignumber vector', function () {
const bigmath = math.create({ number: 'BigNumber' })
const minusOne = bigmath.bignumber(-1)
const cos1 = bigmath.cos(bigmath.bignumber(1))
const sin1 = bigmath.sin(bigmath.bignumber(1))
const minusSin1 = bigmath.multiply(sin1, minusOne)
const big2 = bigmath.bignumber(2)
const big3 = bigmath.bignumber(3)
assert.deepStrictEqual(bigmath.rotate([big2, big3], bigmath.bignumber(1)),
[add(cos1.times(big2), minusSin1.times(big3)), add(sin1.times(big2), cos1.times(big3))])

assert.deepStrictEqual(bigmath.rotate(bigmath.matrix([big2, big3]), bigmath.bignumber(1)),
bigmath.matrix([add(cos1.times(big2), minusSin1.times(big3)),
add(sin1.times(big2), cos1.times(big3))]))
})

it('should return a rotated 1x3 vector when it is provided as an array', function () {
assert.deepStrictEqual(rotate([11, 12, 13], 0.7, [0, 0, 1]),
[11 * cos(0.7) - 12 * sin(0.7), 11 * sin(0.7) + 12 * cos(0.7), 13])
assert.deepStrictEqual(rotate([11, 12, 13], 0.7, [0, 1, 0]),
[11 * cos(0.7) + 13 * sin(0.7), 12, -11 * sin(0.7) + 13 * cos(0.7)])
assert.deepStrictEqual(rotate([11, 12, 13], 0.7, [1, 0, 0]),
[11, 12 * cos(0.7) - 13 * sin(0.7), 12 * sin(0.7) + 13 * cos(0.7)])

const cos30 = cos(unit('30deg'))
const sin30 = sin(unit('30deg'))
assert.deepStrictEqual(rotate([11, 12, 13], unit('30deg'), [1, 0, 0]),
[11, 12 * cos30 - 13 * sin30, 12 * sin30 + 13 * cos30])

const reCos = 4.18962569096881
const imCos = 9.10922789375534
const reSin = 9.15449914691142
const imSin = 4.16890695996656
const cosComplex = complex(-reCos, -imCos)
const sinComplex = complex(reSin, -imSin)
approx.deepEqual(rotate([11, 12, 13], complex('2+3i'), [0, 0, 1]),
[add(multiply(11, cosComplex), multiply(-12.0, sinComplex)),
add(multiply(11, sinComplex), multiply(12, cosComplex)),
13])
})

it('should return a rotated 1x3 vector when it is provided as matrix', function () {
assert.deepStrictEqual(rotate(matrix([11, 12, 13]), 0.7, [0, 0, 1]),
matrix([11 * cos(0.7) - 12 * sin(0.7), 11 * sin(0.7) + 12 * cos(0.7), 13]))
assert.deepStrictEqual(rotate(matrix([11, 12, 13]), 0.7, [0, 1, 0]),
matrix([11 * cos(0.7) + 13 * sin(0.7), 12, -11 * sin(0.7) + 13 * cos(0.7)]))
assert.deepStrictEqual(rotate(matrix([11, 12, 13]), 0.7, [1, 0, 0]),
matrix([11, 12 * cos(0.7) - 13 * sin(0.7), 12 * sin(0.7) + 13 * cos(0.7)]))

const cos30 = cos(unit('30deg'))
const sin30 = sin(unit('30deg'))
assert.deepStrictEqual(rotate(matrix([11, 12, 13]), unit('30deg'), [1, 0, 0]),
matrix([11, 12 * cos30 - 13 * sin30, 12 * sin30 + 13 * cos30]))

const reCos = 4.18962569096881
const imCos = 9.10922789375534
const reSin = 9.15449914691142
const imSin = 4.16890695996656
const cosComplex = complex(-reCos, -imCos)
const sinComplex = complex(reSin, -imSin)
approx.deepEqual(rotate(matrix([11, 12, 13]), complex('2+3i'), [0, 0, 1]),
matrix([add(multiply(11, cosComplex), multiply(-12.0, sinComplex)),
add(multiply(11, sinComplex), multiply(12, cosComplex)),
13]))
})

it('should return a rotated 1x3 bignumber vector', function () {
const bigmath = math.create({ number: 'BigNumber' })
const minusOne = bigmath.bignumber(-1)
const cos1 = bigmath.cos(bigmath.bignumber(1))
const sin1 = bigmath.sin(bigmath.bignumber(1))
const minusSin1 = bigmath.multiply(sin1, minusOne)
const big2 = bigmath.bignumber(2)
const big3 = bigmath.bignumber(3)
const big4 = bigmath.bignumber(4)
assert.deepStrictEqual(bigmath.rotate([big2, big3, big4], bigmath.bignumber(1), [0, 0, 1]),
[add(cos1.times(big2), minusSin1.times(big3)), add(sin1.times(big2), cos1.times(big3)), big4])

assert.deepStrictEqual(bigmath.rotate(bigmath.matrix([big2, big3, big4]), bigmath.bignumber(1), [0, 0, 1]),
bigmath.matrix([add(cos1.times(big2), minusSin1.times(big3)),
add(sin1.times(big2), cos1.times(big3)), big4]))
})

it('should return an object of predictable type', function () {
const predictableMath = math.create({ predictable: true })
assert.deepStrictEqual(predictableMath.rotate([1, 0], 1), [cos(1), sin(1)])
assert.deepStrictEqual(predictableMath.rotate([1, 0, 0], -1, [0, 0, 1]), [cos(-1), sin(-1), 0])

assert.deepStrictEqual(predictableMath.rotate(matrix([1, 0]), 1),
predictableMath.matrix([cos(1), sin(1)]))
assert.deepStrictEqual(predictableMath.rotate(matrix([1, 0, 0]), -1, [0, 0, 1]),
predictableMath.matrix([cos(-1), sin(-1), 0]))
})

it('should return a rotated 1x3 vector as sparse matrix', function () {
const expectedX = 4 * cos(unit('-90deg')) - 5 * sin(unit('-90deg'))
const expectedY = 4 * sin(unit('-90deg')) + 5 * cos(unit('-90deg'))
assert.deepStrictEqual(rotate(matrix([4, 5], 'sparse'), unit('-90deg')), matrix([expectedX, expectedY], 'sparse'))
})

it('should throw an error with invalid number of arguments', function () {
assert.throws(function () { rotate() }, /TypeError: Too few arguments/)
assert.throws(function () { rotate(1) }, /TypeError: Unexpected type of argument/)
assert.throws(function () { rotate([], null) }, /TypeError: Unexpected type of argument/)
assert.throws(function () { rotate([], 1, [], 2) }, /TypeError: Too many arguments/)
})

it('should throw an error with invalid type of arguments', function () {
assert.throws(function () { rotate(1) }, /TypeError: Unexpected type of argument/)
assert.throws(function () { rotate([], 1, [], 2) }, /TypeError: Too many arguments/)

assert.throws(function () { rotate([1, 0], math.pi / 2, [0, 0, 1]) }, /RangeError: Vector must be of dimensions 1x3/)
assert.throws(function () { rotate(matrix([1, 0]), math.pi / 2, [0, 0, 1]) }, /RangeError: Vector must be of dimensions 1x3/)
assert.throws(function () { rotate(matrix([[[1]], [[0]]]), math.pi / 2, [0, 0, 1]) }, /RangeError: Vector must be of dimensions 1x3/)
assert.throws(function () { rotate(matrix([[1, 0], [1, 0]]), math.pi / 2, [0, 0, 1]) }, /RangeError: Vector must be of dimensions 1x3/)
assert.throws(function () { rotate([1, 0, 0, 0], math.pi / 2, [0, 0, 1]) }, /RangeError: Vector must be of dimensions 1x3/)
assert.throws(function () { rotate(matrix([1, 0, 0, 0]), math.pi / 2, [0, 0, 1]) }, /RangeError: Vector must be of dimensions 1x3/)

assert.throws(function () { rotate([1, 0, 0], 1, [0.0, 0.0, 0.0]) }, /Rotation around zero vector/)
})

it('should LaTeX rotationMatrix', function () {
const expression1 = math.parse('rotate([1, 2, 3], 1)')
assert.strictEqual(expression1.toTex(), '\\mathrm{rotate}\\left(\\begin{bmatrix}1\\\\2\\\\3\\\\\\end{bmatrix},1\\right)')

const expression2 = math.parse('rotate([1, 2, 3], 1, [4, 5, 6])')
assert.strictEqual(expression2.toTex(), '\\mathrm{rotate}\\left(\\begin{bmatrix}1\\\\2\\\\3\\\\\\end{bmatrix},1,' +
'\\begin{bmatrix}4\\\\5\\\\6\\\\\\end{bmatrix}\\right)')
})
})
2 changes: 1 addition & 1 deletion test/unit-tests/function/matrix/rotationMatrix.test.js
Expand Up @@ -17,7 +17,6 @@ describe('rotationMatrix', function () {

it('should create an empty matrix', function () {
assert.deepStrictEqual(rotationMatrix(), matrix())
assert.deepStrictEqual(rotationMatrix(1, [0.0, 0.0, 0.0]), matrix())
assert.deepStrictEqual(rotationMatrix('sparse'), matrix('sparse'))
assert.deepStrictEqual(rotationMatrix('dense'), matrix('dense'))

Expand Down Expand Up @@ -243,6 +242,7 @@ describe('rotationMatrix', function () {
assert.throws(function () { rotationMatrix(0, [0, 1]) }, /RangeError: Vector must be of dimensions 1x3/)
assert.throws(function () { rotationMatrix(0, [0, 1, 0], 'something') }, /TypeError: Unknown matrix type/)
assert.throws(function () { rotationMatrix(0, [0, 1, 0], 'sparse', 4) }, /TypeError: Too many arguments/)
assert.throws(function () { rotationMatrix(1, [0.0, 0.0, 0.0]) }, /Rotation around zero vector/)
})

it('should LaTeX rotationMatrix', function () {
Expand Down