Rotations
§3.4 established that orthogonal matrices preserve every length and every angle. That is a strong property, and it leaves exactly two kinds of map: those that preserve orientation and those that reverse it. The first kind are rotations.
They are the transformations that move data without distorting any of the geometry this chapter built — which makes them the right tool whenever you want to change coordinates without changing what the data means. Every and in a singular value decomposition is one, which is why Chapter 4’s factorisation reads as “rotate, scale, rotate”.
What you’ll learn
Section titled “What you’ll learn”- What a rotation is, and why is the extra condition beyond orthogonality.
- The rotation matrix, derived from where it sends the standard basis.
- The three rotations about the coordinate axes (Equations 3.77 to 3.79) and the convention that fixes their signs.
- Givens rotations (Definition 3.11): rotation in one plane of , leaving everything else alone.
- The four properties of §3.9.4 — including the measured failure of commutativity in three dimensions.
- Why composing a hundred thousand rotations drifts, and by how much.
Intuition: turning the page, not stretching it
Section titled “Intuition: turning the page, not stretching it”Put a photograph on a table and turn it. Nothing about the photograph changes: no distance between two printed points changes, no angle changes, nothing is stretched or mirrored. Only the orientation relative to the table changed, and the table’s origin stayed put.
That is a rotation. Contrast it with three neighbours:
| operation | preserves lengths? | preserves angles? | preserves orientation? |
|---|---|---|---|
| rotation | yes | yes | yes |
| reflection | yes | yes | no |
| uniform scaling | no | yes | yes |
| shear | no | no | yes |
Rotations are the intersection of the first two columns and the third. Orthogonality buys the first two; buys the third.
flowchart TD ORTH["orthogonal matrix
RᵀR = I, so R⁻¹ = Rᵀ
Def 3.8"] ORTH --> LEN["preserves every length
Eq 3.31"] ORTH --> ANG["preserves every angle
Eq 3.32"] ORTH --> DET["det R = ±1
forced, since det(RᵀR) = (det R)² = 1"] DET --> PLUS["det = +1
ROTATION
orientation preserved"] DET --> MINUS["det = −1
reflection
orientation reversed"] PLUS --> R2["ℝ²: one angle θ
Eq 3.76"] PLUS --> R3["ℝ³: three axes
Eq 3.77–3.79"] PLUS --> RN["ℝⁿ: Givens R_ij(θ)
Def 3.11 — rotate ONE plane"] RN --> DOF["n(n−1)/2 free parameters
1 in the plane, 3 in space, 45 in ℝ¹⁰"] R3 --> NC["and in 3D or more they
DO NOT COMMUTE — §3.9.4"]
The math
Section titled “The math”Note “automorphism”: a rotation maps the space onto itself bijectively. It cannot flatten anything, which the determinant being nonzero already guarantees.
Rotations in the plane
Section titled “Rotations in the plane”Fix the standard basis and ask where a rotation by sends each basis vector. Trigonometry on the unit circle gives
and since the columns of a matrix are the images of the basis vectors (Chapter 2, §2.7),
That is the entire derivation. There is nothing to memorise beyond “where does go” — the minus sign sits in the top right because swings backwards into the second quadrant.
Two facts fall out. because , and exactly. And the composition rule is addition of angles: , which is the angle-sum identity written as a matrix product.
Rotations in three dimensions
Section titled “Rotations in three dimensions”In the plane there is one plane to rotate. In space you must say which plane, and the standard choice is to name the axis left fixed. The convention: “counterclockwise” about an axis means looking at that axis head on, from its tip toward the origin.
fixes the coordinate and rotates the plane; fixes and is the case padded out. has its signs the other way round — in the top right — and that is not a typo: it follows from the head-on-from-the-tip convention applied to the plane, which the book spells out. If you write from pattern-matching rather than from the convention you will get it backwards, and the resulting bug rotates the right amount in the wrong direction.
Rotations in n dimensions
Section titled “Rotations in n dimensions”Equation 3.80 looks forbidding and says something simple: take the identity, and change four entries. A Givens rotation fixes dimensions and rotates the remaining two-dimensional plane. It is how you rotate in high dimensions at all, and it is how numerical routines zero out matrix entries one at a time — Givens QR does exactly that.
The parameter count follows: choosing the plane means choosing , so a general rotation in needs angles. That is in the plane, in space, in and in — so “one angle” is a coincidence of two dimensions, not the general rule.
Properties
Section titled “Properties”The first two are §3.4’s Equations 3.31 and 3.32, since a rotation is an orthogonal matrix. The third is the one that costs people real time, and it is worth measuring rather than accepting.
Worked example by hand
Section titled “Worked example by hand”The book’s Exercise 3.10 — rotate by 30 degrees
Section titled “The book’s Exercise 3.10 — rotate by 30 degrees”and , so
:
:
Note that is rotated by , which is rotated by — the second column of , negated. Rotating a basis vector just reads off a column.
The checks:
| quantity | before | after |
|---|---|---|
| angle between them | ||
| — |
Nine matching digits on the angle, and the determinant is exactly in floating point.
The book’s Figure 3.14, and a lesson about rounding
Section titled “The book’s Figure 3.14, and a lesson about rounding”The book’s opening example gives
and captions the figure “Rotated by ”. Check it: , which is close to but not equal, and
The matrix as printed is not orthogonal: . The reason is benign — the book rounded and to two decimals for readability — but the lesson generalises. A rounded rotation matrix is not a rotation matrix. It shrinks every vector it touches by about here, and if you compose a few hundred of them the drift compounds. This is why graphics and robotics code re-orthonormalises stored rotations periodically, and why quaternions (which need only be renormalised, one division) are popular for storing orientations.
import numpy as np
def R2(theta):
c, s = np.cos(theta), np.sin(theta)
return np.array([[c, -s], [s, c]])
R = R2(np.radians(30.0))
print("R(30) =\n", np.round(R, 6))
cos = lambda a, b: float(a @ b) / (np.linalg.norm(a) * np.linalg.norm(b))
x1, x2 = np.array([2.0, 3.0]), np.array([0.0, -1.0])
y1, y2 = R @ x1, R @ x2
print("x1 ->", np.round(y1, 6), " exact:", round(np.sqrt(3) - 1.5, 6), round(1 + 1.5 * np.sqrt(3), 6))
print("x2 ->", np.round(y2, 6), " exact:", 0.5, round(-np.sqrt(3) / 2, 6))
print("lengths:", round(float(np.linalg.norm(x1)), 6), "->", round(float(np.linalg.norm(y1)), 6))
print("angle before:", round(float(np.degrees(np.arccos(np.clip(cos(x1, x2), -1, 1)))), 6))
print("angle after: ", round(float(np.degrees(np.arccos(np.clip(cos(y1, y2), -1, 1)))), 6))
print("det R =", f"{np.linalg.det(R):.15f}")
# The book's Equation 3.74, as printed.
Rb = np.array([[-0.38, -0.92], [0.92, -0.38]])
print()
print("Eq 3.74 det =", round(float(np.linalg.det(Rb)), 6))
print("||Rb^T Rb - I|| =", f"{np.linalg.norm(Rb.T @ Rb - np.eye(2)):.4f}")
print("its angle:", round(float(np.degrees(np.arctan2(0.92, -0.38))), 4), "deg")
print("exact cos(112.5) =", round(float(np.cos(np.radians(112.5))), 6),
" sin(112.5) =", round(float(np.sin(np.radians(112.5))), 6))R(30) =
[[ 0.866025 -0.5 ]
[ 0.5 0.866025]]
x1 -> [0.232051 3.598076] exact: 0.232051 3.598076
x2 -> [ 0.5 -0.866025] exact: 0.5 -0.866025
lengths: 3.605551 -> 3.605551
angle before: 146.309932
angle after: 146.309932
det R = 1.000000000000000
Eq 3.74 det = 0.9908
||Rb^T Rb - I|| = 0.0130
its angle: 112.4428 deg
exact cos(112.5) = -0.382683 sin(112.5) = 0.92388See it move
Section titled “See it move”The first sketch is Equation 3.76 with the matrix on screen and both preservation properties measured live.
The second sketch is the non-commutativity, in an oblique three-dimensional view.
The third sketch shows what a Givens rotation actually is: the identity with four entries changed.
And the matrix stepper on a rotation, which reports the eigenvalues — a rotation in the plane has no real eigenvectors unless is a multiple of , and that is worth seeing:
The lattice turns rigidly. Note that no real direction is preserved: a genuine rotation in the plane has a complex pair of eigenvalues, which is the algebraic statement that it fixes no line.
From scratch
Section titled “From scratch”import numpy as np
def rot2(theta):
c, s = np.cos(theta), np.sin(theta)
return np.array([[c, -s], [s, c]])
def givens(n, i, j, theta):
"""Equation 3.80: the identity with four entries replaced. 1-based i, j."""
R = np.eye(n)
c, s = np.cos(theta), np.sin(theta)
R[i - 1, i - 1] = c
R[i - 1, j - 1] = -s
R[j - 1, i - 1] = s
R[j - 1, j - 1] = c
return R
def axis_rotations(theta):
"""Equations 3.77 to 3.79. Note R2's signs are the other way round."""
c, s = np.cos(theta), np.sin(theta)
R1 = np.array([[1, 0, 0], [0, c, -s], [0, s, c]], dtype=float)
R2 = np.array([[c, 0, s], [0, 1, 0], [-s, 0, c]], dtype=float)
R3 = np.array([[c, -s, 0], [s, c, 0], [0, 0, 1]], dtype=float)
return R1, R2, R3
# Are they all orthogonal with determinant one?
for name, M in zip(("R1", "R2", "R3"), axis_rotations(0.7)):
print(f"{name}: det {np.linalg.det(M):.15f} ||M^T M - I|| {np.linalg.norm(M.T @ M - np.eye(3)):.2e}")
G = givens(6, 2, 5, 0.7)
print("Givens R_25 in R^6: det", f"{np.linalg.det(G):.15f}",
" entries differing from I:", int(np.sum(np.abs(G - np.eye(6)) > 1e-12)))
# Commutativity: yes in the plane, no in space.
a, b = np.radians(35.0), np.radians(70.0)
print()
print("2-D: ||R(a)R(b) - R(b)R(a)|| =", f"{np.linalg.norm(rot2(a) @ rot2(b) - rot2(b) @ rot2(a)):.3e}")
print("2-D: ||R(a)R(b) - R(a+b)|| =", f"{np.linalg.norm(rot2(a) @ rot2(b) - rot2(a + b)):.3e}")
R1a, _, _ = axis_rotations(a)
_, _, R3b = axis_rotations(b)
print("3-D: ||R1R3 - R3R1||_F =", round(float(np.linalg.norm(R1a @ R3b - R3b @ R1a)), 6))
# How far does orthogonality drift when you compose many rotations?
print()
rng = np.random.default_rng(11)
M = np.eye(3)
for k in range(1, 100001):
which = k % 3
R1k, R2k, R3k = axis_rotations(rng.uniform(0, 2 * np.pi))
M = M @ (R1k if which == 0 else R2k if which == 1 else R3k)
if k in (10, 100, 1000, 10000, 100000):
print(f"after {k:6} products: ||M^T M - I|| {np.linalg.norm(M.T @ M - np.eye(3)):.3e} "
f"det {np.linalg.det(M):.15f}")R1: det 1.000000000000000 ||M^T M - I|| 2.95e-17
R2: det 1.000000000000000 ||M^T M - I|| 2.95e-17
R3: det 1.000000000000000 ||M^T M - I|| 2.95e-17
Givens R_25 in R^6: det 1.000000000000000 entries differing from I: 4
2-D: ||R(a)R(b) - R(b)R(a)|| = 1.570e-16
2-D: ||R(a)R(b) - R(a+b)|| = 1.110e-16
3-D: ||R1R3 - R3R1||_F = 0.961059
after 10 products: ||M^T M - I|| 3.362e-16 det 1.000000000000000
after 100 products: ||M^T M - I|| 1.940e-15 det 1.000000000000000
after 1000 products: ||M^T M - I|| 4.724e-15 det 0.999999999999998
after 10000 products: ||M^T M - I|| 2.482e-14 det 0.999999999999980
after 100000 products: ||M^T M - I|| 2.907e-13 det 0.999999999999751Three results. The Givens rotation differs from the identity in exactly four entries, as Equation 3.81 says. In the plane the two orders agree to and the composition really is the angle sum; in space , which is not a small number.
The drift table is the practical one. Composing rotations leaves orthogonality off by and the determinant off by . That is remarkably good — the error grows roughly like rather than like , because the individual rounding errors are uncorrelated — and it is still a drift. Long-running simulations that accumulate rotations re-orthonormalise periodically for exactly this reason.
On real data
Section titled “On real data”Reading the plot
Section titled “Reading the plot”From the preservation plot. Three curves sit flat at the bottom across a full turn. The measured worst cases over the whole sweep are for distances and for angles, against a machine epsilon of . So distances hold to about eight units in the last place and angles to about eight hundred.
Why the difference? The angle goes through arccos, whose derivative blows up near — an
argument known to gives an angle known to in the worst case. The
angles here are not near-degenerate so the amplification is only a factor of a few hundred, but the
asymmetry between the two curves is a property of arccos rather than of rotations. This is the same
sensitivity that makes the clip on the
Angles and Orthogonality page mandatory.
From the commutativity figure. The left panel is the control: the two orders in the plane are drawn one on top of the other and you cannot see two outlines, because the largest gap is . The right panel uses the same two angles and the gap is — over a plate whose longest side is , so the two results are nearly a plate-width apart.
The commutator norm is the measurement to keep. Two rotation matrices, each of Frobenius norm , whose products in the two orders differ by nearly . This is not a small effect to be managed with care; it is the reason Euler angles need a stated convention, the reason “roll, pitch, yaw” is ambiguous without an order, and the reason rotation composition is written down as a group rather than as a sum of angles.
And the group structure is the clean way to say it. In two dimensions , measured to : the rotations are just the reals modulo in disguise, an Abelian group. In three dimensions there is no such parametrisation, and the failure of commutativity is precisely the obstruction.
Pitfalls
Section titled “Pitfalls”Compare
Section titled “Compare”| map | condition | preserves | determinant | example |
|---|---|---|---|---|
| rotation | , | lengths, angles, orientation | , Givens, SVD’s and | |
| reflection | , | lengths, angles | ; Householder | |
| uniform scaling | angles | |||
| general orthogonal | lengths, angles | whatever qr gives you | ||
| shear | none | area (if ) | ||
| projection | nothing | unless | §3.8 |
Reading the determinant column: is a rotation, a reflection, a projection, anything else a scaling of some kind. It is the fastest single diagnostic on an unfamiliar matrix.
-
Orthogonality gives length and angle preservation. What does det R = +1 add?
Orthogonality forces det(R)^2 = 1, so the determinant is plus or minus one and both occur. diag(1, -1) is orthogonal with determinant -1, and np.linalg.qr returns a determinant of exactly (-1)^(n-1) — so in every even dimension its Q is a reflection, not a rotation.
pch.quizShowAnswer
B — Orientation preservation — the other case, det = -1, is a reflection, which preserves every length and every unsigned angle while flipping handedness — Orthogonality forces det(R)^2 = 1, so the determinant is plus or minus one and both occur. diag(1, -1) is orthogonal with determinant -1, and np.linalg.qr returns a determinant of exactly (-1)^(n-1) — so in every even dimension its Q is a reflection, not a rotation.
-
Why does Equation 3.78 have +sin(theta) in the top right where 3.77 and 3.79 have -sin(theta)?
Writing R2 by pattern-matching the other two gives a rotation by minus theta about e2. The symptom is a system turning the right amount in the wrong direction.
pch.quizShowAnswer
B — It follows from the convention that counterclockwise about an axis means looking at that axis head on from its tip toward the origin — applied to the e1-e3 plane, that convention flips the sign pattern — Writing R2 by pattern-matching the other two gives a rotation by minus theta about e2. The symptom is a system turning the right amount in the wrong direction.
-
A Givens rotation R_ij(theta) in R^6 differs from the identity in how many entries?
Equation 3.81 names them: r_ii = cos, r_ij = -sin, r_ji = sin, r_jj = cos. It fixes the other four dimensions entirely, which is why a general rotation in R^6 needs 15 angles rather than one.
pch.quizShowAnswer
B — 4 — Equation 3.81 names them: r_ii = cos, r_ij = -sin, r_ji = sin, r_jj = cos. It fixes the other four dimensions entirely, which is why a general rotation in R^6 needs 15 angles rather than one.
-
The measured commutator norm for two 3-D axis rotations at 35 and 70 degrees is 0.961. What is the practical consequence?
Each of those matrices has Frobenius norm sqrt(3), so a commutator of 0.961 is not a small perturbation. In the plane the same measurement is 1.6e-16, because two-dimensional rotations are an Abelian group.
pch.quizShowAnswer
B — Three Euler angles do not determine a rotation without an ordering convention — there are twelve conventional orderings and they give twelve different results from the same three numbers — Each of those matrices has Frobenius norm sqrt(3), so a commutator of 0.961 is not a small perturbation. In the plane the same measurement is 1.6e-16, because two-dimensional rotations are an Abelian group.
-
Composing 100000 random 3-D rotations leaves ||M-transpose M minus I|| at 2.9e-13. How should that be read?
The determinant also drifts, to 0.999999999999751. Both are small enough to ignore for a hundred products and worth handling for a hundred thousand.
pch.quizShowAnswer
B — Orthogonality is robust but does drift — the error grows roughly like the square root of the number of products, since individual rounding errors are uncorrelated — so long-running accumulations should re-orthonormalise — The determinant also drifts, to 0.999999999999751. Both are small enough to ignore for a hundred products and worth handling for a hundred thousand.
🧪 Try It Yourself
Section titled “🧪 Try It Yourself”Exercise 1 – The book’s Exercise 3.10
Section titled “Exercise 1 – The book’s Exercise 3.10”Exercise 2 – Test the book’s Equation 3.74
Section titled “Exercise 2 – Test the book’s Equation 3.74”Exercise 3 – Build a Givens rotation
Section titled “Exercise 3 – Build a Givens rotation”Exercise 4 – Commutativity, measured in two dimensions and three
Section titled “Exercise 4 – Commutativity, measured in two dimensions and three”Exercise 5 – Watch orthogonality drift
Section titled “Exercise 5 – Watch orthogonality drift”Recall card
Section titled “Recall card”- A rotation is an orthogonal matrix with determinant plus one — orthogonality buys length and angle preservation, and the determinant condition adds orientation preservation. Determinant minus one is a reflection.
- The two-by-two rotation matrix is read off the images of the basis vectors: cosine and sine down the first column, minus sine and cosine down the second.
- In two dimensions rotations compose by adding angles and they commute, forming an Abelian group.
- In three dimensions there are three axis rotations, and the middle one has its sine signs reversed because of the look-at-the-axis-from-its-tip convention.
- A Givens rotation is the identity with four entries changed, rotating one plane and fixing the other n minus two dimensions. A general rotation in n dimensions needs n(n−1)/2 angles.
- Rotations in three or more dimensions do not commute. Measured on two axis rotations at 35 and 70 degrees, the commutator has Frobenius norm 0.961 and the two orders move a plate’s corners 0.743 apart.
- Rotations form a group only about a shared fixed point; rotating about any other point is an affine map, the shift-transform-shift-back pattern of section 3.8.4.
- A rounded rotation matrix is not a rotation. The book’s own Equation 3.74 has determinant 0.9908 and shrinks vectors by about half a percent.
- Composing a hundred thousand rotations drifts orthogonality to three times ten to the minus thirteen, growing like the square root of the count — negligible for a hundred products, worth correcting for a hundred thousand.
Next: Chapter 3 Exercises and Solutions — all ten of the book’s exercises, worked.
pch.coffeeTagline
pch.coffeeCtapch.feedbackHeading
pch.feedbackSubheading