Transformations#

class sage.plot.plot3d.transform.Transformation#

Bases: object

avg_scale()#
get_matrix()#
is_skew(eps=1e-05)#
is_uniform(eps=1e-05)#
is_uniform_on(basis, eps=1e-05)#
max_scale()#
transform_bounding_box(box)#
transform_point(x)#
transform_vector(v)#
sage.plot.plot3d.transform.rotate_arbitrary(v, theta)#

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

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]

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]

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]

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

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])

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

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

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

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

Do some simplifying here to avoid blow-up:

sage: 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

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)]

Re-expressing some entries in terms of y and resolving the absolute values introduced by eliminating y, we get the desired result.