Transformations#
- sage.plot.plot3d.transform.rotate_arbitrary(v, theta)[source]#
Return a matrix that rotates the coordinate space about the axis v by the angle
theta
.INPUT:
theta
– real number, the angle
EXAMPLES:
sage: from sage.plot.plot3d.transform import rotate_arbitrary
>>> from sage.all import * >>> from sage.plot.plot3d.transform import rotate_arbitrary
Try rotating about the axes:
sage: rotate_arbitrary((1,0,0), 1) [ 1.0 0.0 0.0] [ 0.0 0.5403023058681398 0.8414709848078965] [ 0.0 -0.8414709848078965 0.5403023058681398] sage: rotate_arbitrary((0,1,0), 1) [ 0.5403023058681398 0.0 -0.8414709848078965] [ 0.0 1.0 0.0] [ 0.8414709848078965 0.0 0.5403023058681398] sage: rotate_arbitrary((0,0,1), 1) [ 0.5403023058681398 0.8414709848078965 0.0] [-0.8414709848078965 0.5403023058681398 0.0] [ 0.0 0.0 1.0]
>>> from sage.all import * >>> rotate_arbitrary((Integer(1),Integer(0),Integer(0)), Integer(1)) [ 1.0 0.0 0.0] [ 0.0 0.5403023058681398 0.8414709848078965] [ 0.0 -0.8414709848078965 0.5403023058681398] >>> rotate_arbitrary((Integer(0),Integer(1),Integer(0)), Integer(1)) [ 0.5403023058681398 0.0 -0.8414709848078965] [ 0.0 1.0 0.0] [ 0.8414709848078965 0.0 0.5403023058681398] >>> rotate_arbitrary((Integer(0),Integer(0),Integer(1)), Integer(1)) [ 0.5403023058681398 0.8414709848078965 0.0] [-0.8414709848078965 0.5403023058681398 0.0] [ 0.0 0.0 1.0]
These next two should be the same (up to floating-point errors):
sage: rotate_arbitrary((1,1,1), 1) # rel tol 1e-15 [ 0.6935348705787598 0.6390560643047186 -0.33259093488347846] [-0.33259093488347846 0.6935348705787598 0.6390560643047186] [ 0.6390560643047186 -0.3325909348834784 0.6935348705787598] sage: rotate_arbitrary((1,1,1), -1)^(-1) # rel tol 1e-15 [ 0.6935348705787598 0.6390560643047186 -0.33259093488347846] [-0.33259093488347846 0.6935348705787598 0.6390560643047186] [ 0.6390560643047185 -0.33259093488347835 0.6935348705787598]
>>> from sage.all import * >>> rotate_arbitrary((Integer(1),Integer(1),Integer(1)), Integer(1)) # rel tol 1e-15 [ 0.6935348705787598 0.6390560643047186 -0.33259093488347846] [-0.33259093488347846 0.6935348705787598 0.6390560643047186] [ 0.6390560643047186 -0.3325909348834784 0.6935348705787598] >>> rotate_arbitrary((Integer(1),Integer(1),Integer(1)), -Integer(1))**(-Integer(1)) # rel tol 1e-15 [ 0.6935348705787598 0.6390560643047186 -0.33259093488347846] [-0.33259093488347846 0.6935348705787598 0.6390560643047186] [ 0.6390560643047185 -0.33259093488347835 0.6935348705787598]
Make sure it does the right thing…:
sage: rotate_arbitrary((1,2,3), -1).det() 1.0000000000000002 sage: rotate_arbitrary((1,1,1), 2*pi/3) * vector(RDF, (1,2,3)) # rel tol 2e-15 # needs sage.symbolic (1.9999999999999996, 2.9999999999999996, 0.9999999999999999) sage: rotate_arbitrary((1,2,3), 5) * vector(RDF, (1,2,3)) # rel tol 2e-15 (1.0000000000000002, 2.0, 3.000000000000001) sage: rotate_arbitrary((1,1,1), pi/7)^7 # rel tol 2e-15 # needs sage.symbolic [-0.33333333333333337 0.6666666666666671 0.6666666666666665] [ 0.6666666666666665 -0.33333333333333337 0.6666666666666671] [ 0.6666666666666671 0.6666666666666667 -0.33333333333333326]
>>> from sage.all import * >>> rotate_arbitrary((Integer(1),Integer(2),Integer(3)), -Integer(1)).det() 1.0000000000000002 >>> rotate_arbitrary((Integer(1),Integer(1),Integer(1)), Integer(2)*pi/Integer(3)) * vector(RDF, (Integer(1),Integer(2),Integer(3))) # rel tol 2e-15 # needs sage.symbolic (1.9999999999999996, 2.9999999999999996, 0.9999999999999999) >>> rotate_arbitrary((Integer(1),Integer(2),Integer(3)), Integer(5)) * vector(RDF, (Integer(1),Integer(2),Integer(3))) # rel tol 2e-15 (1.0000000000000002, 2.0, 3.000000000000001) >>> rotate_arbitrary((Integer(1),Integer(1),Integer(1)), pi/Integer(7))**Integer(7) # rel tol 2e-15 # needs sage.symbolic [-0.33333333333333337 0.6666666666666671 0.6666666666666665] [ 0.6666666666666665 -0.33333333333333337 0.6666666666666671] [ 0.6666666666666671 0.6666666666666667 -0.33333333333333326]
AUTHORS:
Robert Bradshaw
ALGORITHM:
There is a formula. Where did it come from? Lets take a quick jaunt into Sage’s calculus package…
Setup some variables:
sage: vx,vy,vz,theta = var('x y z theta') # needs sage.symbolic
>>> from sage.all import * >>> vx,vy,vz,theta = var('x y z theta') # needs sage.symbolic
Symbolic rotation matrices about X and Y axis:
sage: def rotX(theta): return matrix(SR, 3, 3, [1, 0, 0, 0, cos(theta), -sin(theta), 0, sin(theta), cos(theta)]) sage: def rotZ(theta): return matrix(SR, 3, 3, [cos(theta), -sin(theta), 0, sin(theta), cos(theta), 0, 0, 0, 1])
>>> from sage.all import * >>> def rotX(theta): return matrix(SR, Integer(3), Integer(3), [Integer(1), Integer(0), Integer(0), Integer(0), cos(theta), -sin(theta), Integer(0), sin(theta), cos(theta)]) >>> def rotZ(theta): return matrix(SR, Integer(3), Integer(3), [cos(theta), -sin(theta), Integer(0), sin(theta), cos(theta), Integer(0), Integer(0), Integer(0), Integer(1)])
Normalizing \(y\) so that \(|v|=1\). Perhaps there is a better way to tell Maxima that \(x^2+y^2+z^2=1\) which would make for a much cleaner calculation:
sage: vy = sqrt(1-vx^2-vz^2) # needs sage.symbolic
>>> from sage.all import * >>> vy = sqrt(Integer(1)-vx**Integer(2)-vz**Integer(2)) # needs sage.symbolic
Now we rotate about the \(x\)-axis so \(v\) is in the \(xy\)-plane:
sage: t = arctan(vy/vz)+pi/2 # needs sage.symbolic sage: m = rotX(t) # needs sage.symbolic sage: new_y = vy*cos(t) - vz*sin(t) # needs sage.symbolic
>>> from sage.all import * >>> t = arctan(vy/vz)+pi/Integer(2) # needs sage.symbolic >>> m = rotX(t) # needs sage.symbolic >>> new_y = vy*cos(t) - vz*sin(t) # needs sage.symbolic
And rotate about the \(z\) axis so \(v\) lies on the \(x\) axis:
sage: s = arctan(vx/new_y) + pi/2 # needs sage.symbolic sage: m = rotZ(s) * m # needs sage.symbolic
>>> from sage.all import * >>> s = arctan(vx/new_y) + pi/Integer(2) # needs sage.symbolic >>> m = rotZ(s) * m # needs sage.symbolic
Rotating about \(v\) in our old system is the same as rotating about the \(x\)-axis in the new:
sage: m = rotX(theta) * m # needs sage.symbolic
>>> from sage.all import * >>> m = rotX(theta) * m # needs sage.symbolic
Do some simplifying here to avoid blow-up:
sage: m = m.simplify_rational() # needs sage.symbolic
>>> from sage.all import * >>> m = m.simplify_rational() # needs sage.symbolic
Now go back to the original coordinate system:
sage: m = rotZ(-s) * m # needs sage.symbolic sage: m = rotX(-t) * m # needs sage.symbolic
>>> from sage.all import * >>> m = rotZ(-s) * m # needs sage.symbolic >>> m = rotX(-t) * m # needs sage.symbolic
And simplify every single entry (which is more effective that simplify the whole matrix like above):
sage: m.parent()([x.simplify_full() for x in m._list()]) # random # long time, needs sage.symbolic [ -(cos(theta) - 1)*x^2 + cos(theta) -(cos(theta) - 1)*sqrt(-x^2 - z^2 + 1)*x + sin(theta)*abs(z) -((cos(theta) - 1)*x*z^2 + sqrt(-x^2 - z^2 + 1)*sin(theta)*abs(z))/z] [ -(cos(theta) - 1)*sqrt(-x^2 - z^2 + 1)*x - sin(theta)*abs(z) (cos(theta) - 1)*x^2 + (cos(theta) - 1)*z^2 + 1 -((cos(theta) - 1)*sqrt(-x^2 - z^2 + 1)*z*abs(z) - x*z*sin(theta))/abs(z)] [ -((cos(theta) - 1)*x*z^2 - sqrt(-x^2 - z^2 + 1)*sin(theta)*abs(z))/z -((cos(theta) - 1)*sqrt(-x^2 - z^2 + 1)*z*abs(z) + x*z*sin(theta))/abs(z) -(cos(theta) - 1)*z^2 + cos(theta)]
>>> from sage.all import * >>> m.parent()([x.simplify_full() for x in m._list()]) # random # long time, needs sage.symbolic [ -(cos(theta) - 1)*x^2 + cos(theta) -(cos(theta) - 1)*sqrt(-x^2 - z^2 + 1)*x + sin(theta)*abs(z) -((cos(theta) - 1)*x*z^2 + sqrt(-x^2 - z^2 + 1)*sin(theta)*abs(z))/z] [ -(cos(theta) - 1)*sqrt(-x^2 - z^2 + 1)*x - sin(theta)*abs(z) (cos(theta) - 1)*x^2 + (cos(theta) - 1)*z^2 + 1 -((cos(theta) - 1)*sqrt(-x^2 - z^2 + 1)*z*abs(z) - x*z*sin(theta))/abs(z)] [ -((cos(theta) - 1)*x*z^2 - sqrt(-x^2 - z^2 + 1)*sin(theta)*abs(z))/z -((cos(theta) - 1)*sqrt(-x^2 - z^2 + 1)*z*abs(z) + x*z*sin(theta))/abs(z) -(cos(theta) - 1)*z^2 + cos(theta)]
Re-expressing some entries in terms of y and resolving the absolute values introduced by eliminating y, we get the desired result.