Skip to content

Commit eeff10e

Browse files
markuswntrstephentyrone
authored andcommitted
Sync quaternion module structure with complex module
1 parent aa37683 commit eeff10e

14 files changed

+536
-463
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ let package = Package(
4747
.target(
4848
name: "QuaternionModule",
4949
dependencies: ["RealModule"],
50-
exclude: excludedFilenames
50+
exclude: ["README.md", "Transformation.md"]
5151
),
5252

5353
.target(

Sources/QuaternionModule/Norms.swift

Lines changed: 0 additions & 112 deletions
This file was deleted.

Sources/QuaternionModule/Polar.swift

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
//===--- Polar.swift ------------------------------------------*- swift -*-===//
2+
//
3+
// This source file is part of the Swift Numerics open source project
4+
//
5+
// Copyright (c) 2019 - 2022 Apple Inc. and the Swift Numerics project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
// Norms and related quantities defined for Quaternion.
13+
//
14+
// The following API are provided by this extension:
15+
//
16+
// var magnitude: RealType // infinity norm
17+
// var length: RealType // Euclidean norm
18+
// var lengthSquared: RealType // Euclidean norm squared
19+
//
20+
// For detailed documentation, consult Norms.md or the inline documentation
21+
// for each operation.
22+
//
23+
// Implementation notes:
24+
//
25+
// `.magnitude` does not bind the Euclidean norm; it binds the infinity norm
26+
// instead. There are two reasons for this choice:
27+
//
28+
// - It's simply faster to compute in general, because it does not require
29+
// a square root.
30+
//
31+
// - There exist finite values `q` for which the Euclidean norm is not
32+
// representable (consider the quaternion with `r`, `x`, `y` and `z` all
33+
// equal to `RealType.greatestFiniteMagnitude`; the Euclidean norm is
34+
// `.sqrt(4) * .greatestFiniteMagnitude`, which overflows).
35+
//
36+
// The infinity norm is unique among the common vector norms in having
37+
// the property that every finite vector has a representable finite norm,
38+
// which makes it the obvious choice to bind `.magnitude`.
39+
extension Quaternion {
40+
/// The Euclidean norm (a.k.a. 2-norm, `sqrt(r*r + x*x + y*y + z*z)`).
41+
///
42+
/// This value is highly prone to overflow or underflow.
43+
///
44+
/// For most use cases, you can use the cheaper `.magnitude`
45+
/// property (which computes the ∞-norm) instead, which always produces
46+
/// a representable result.
47+
///
48+
/// Edge cases:
49+
/// -
50+
/// If a quaternion is not finite, its `.length` is `infinity`.
51+
///
52+
/// See also:
53+
/// -
54+
/// - `.magnitude`
55+
/// - `.lengthSquared`
56+
@_transparent
57+
public var length: RealType {
58+
let naive = lengthSquared
59+
guard naive.isNormal else { return carefulLength }
60+
return .sqrt(naive)
61+
}
62+
63+
// Internal implementation detail of `length`, moving slow path off
64+
// of the inline function.
65+
@usableFromInline
66+
internal var carefulLength: RealType {
67+
guard isFinite else { return .infinity }
68+
guard !magnitude.isZero else { return .zero }
69+
// Unscale the quaternion, calculate its length and rescale the result
70+
return divided(by: magnitude).length * magnitude
71+
}
72+
73+
/// The squared length `(r*r + x*x + y*y + z*z)`.
74+
///
75+
/// This value is highly prone to overflow or underflow.
76+
///
77+
/// For many cases, `.magnitude` can be used instead, which is similarly
78+
/// cheap to compute and always returns a representable value.
79+
///
80+
/// This property is more efficient to compute than `length`.
81+
///
82+
/// See also:
83+
/// -
84+
/// - `.length`
85+
/// - `.magnitude`
86+
@_transparent
87+
public var lengthSquared: RealType {
88+
(components * components).sum()
89+
}
90+
91+
/// The [polar decomposition][wiki].
92+
///
93+
/// Returns the length of this quaternion, phase in radians of range *[0, π]*
94+
/// and the rotation axis as SIMD3 vector of unit length.
95+
///
96+
/// Edge cases:
97+
/// -
98+
/// - If the quaternion is zero, length is `.zero` and angle and axis
99+
/// are `nan`.
100+
/// - If the quaternion is non-finite, length is `.infinity` and angle and
101+
/// axis are `nan`.
102+
/// - For any length other than `.zero` or `.infinity`, if angle is zero, axis
103+
/// is `nan`.
104+
///
105+
/// See also:
106+
/// -
107+
/// - `.angle`
108+
/// - `.axis`
109+
/// - `.angleAxis`
110+
/// - `.rotationVector`
111+
/// - `init(length:angle:axis:)`
112+
/// - `init(length:phase:axis)`
113+
/// - `init(rotation:)`
114+
///
115+
/// [wiki]: https://en.wikipedia.org/wiki/Polar_decomposition#Quaternion_polar_decomposition
116+
public var polar: (length: RealType, phase: RealType, axis: SIMD3<RealType>) {
117+
(length, halfAngle, axis)
118+
}
119+
120+
/// Creates a quaternion specified with [polar coordinates][wiki].
121+
///
122+
/// This initializer reads given `length`, `phase` and `axis` values and
123+
/// creates a quaternion of equal rotation properties and specified *length*
124+
/// using the following equation:
125+
///
126+
/// Q = (cos(phase), axis * sin(phase)) * length
127+
///
128+
/// - Note: `axis` must be of unit length, or an assertion failure occurs.
129+
///
130+
/// Edge cases:
131+
/// -
132+
/// - Negative lengths are interpreted as reflecting the point through the origin, i.e.:
133+
/// ```
134+
/// Quaternion(length: -r, phase: θ, axis: axis) == -Quaternion(length: r, phase: θ, axis: axis)
135+
/// ```
136+
/// - For any `θ` and any `axis`, even `.infinity` or `.nan`:
137+
/// ```
138+
/// Quaternion(length: .zero, phase: θ, axis: axis) == .zero
139+
/// ```
140+
/// - For any `θ` and any `axis`, even `.infinity` or `.nan`:
141+
/// ```
142+
/// Quaternion(length: .infinity, phase: θ, axis: axis) == .infinity
143+
/// ```
144+
/// - Otherwise, `θ` must be finite, or a precondition failure occurs.
145+
///
146+
/// See also:
147+
/// -
148+
/// - `.angle`
149+
/// - `.axis`
150+
/// - `.angleAxis`
151+
/// - `.rotationVector`
152+
/// - `.polar`
153+
/// - `init(length:angle:axis:)`
154+
/// - `init(rotation:)`
155+
///
156+
/// [wiki]: https://en.wikipedia.org/wiki/Polar_decomposition#Quaternion_polar_decomposition
157+
@inlinable
158+
public init(length: RealType, phase: RealType, axis: SIMD3<RealType>) {
159+
guard !length.isZero, length.isFinite else {
160+
self = Quaternion(length)
161+
return
162+
}
163+
164+
// Length is finite and non-zero, therefore
165+
// 1. `phase` must be finite or a precondition failure needs to occur; as
166+
// this is not representable.
167+
// 2. `axis` must be of unit length or an assertion failure occurs; while
168+
// "wrong" by definition, it is representable.
169+
precondition(
170+
phase.isFinite,
171+
"Either phase must be finite, or length must be zero or infinite."
172+
)
173+
assert(
174+
// TODO: Replace with `approximateEquality()`
175+
abs(.sqrt(axis.lengthSquared)-1) < max(.sqrt(axis.lengthSquared), 1)*RealType.ulpOfOne.squareRoot(),
176+
"Given axis must be of unit length."
177+
)
178+
179+
self = Quaternion(halfAngle: phase, unitAxis: axis).multiplied(by: length)
180+
}
181+
}
182+
183+
// MARK: - Operations for working with polar form
184+
185+
extension Quaternion {
186+
/// The half rotation angle in radians within *[0, π]* range.
187+
///
188+
/// Edge cases:
189+
/// -
190+
/// If the quaternion is zero or non-finite, halfAngle is `nan`.
191+
@usableFromInline @inline(__always)
192+
internal var halfAngle: RealType {
193+
guard isFinite else { return .nan }
194+
guard imaginary != .zero else {
195+
// A zero quaternion does not encode transformation properties.
196+
// If imaginary is zero, real must be non-zero or nan is returned.
197+
return real.isZero ? .nan : .zero
198+
}
199+
200+
// If lengthSquared computes without over/underflow, everything is fine
201+
// and the result is correct. If not, we have to do the computation
202+
// carefully and unscale the quaternion first.
203+
let lenSq = imaginary.lengthSquared
204+
guard lenSq.isNormal else { return divided(by: magnitude).halfAngle }
205+
return .atan2(y: .sqrt(lenSq), x: real)
206+
}
207+
208+
/// Creates a new quaternion from given half rotation angle about given
209+
/// rotation axis.
210+
///
211+
/// The angle-axis values are transformed using the following equation:
212+
///
213+
/// Q = (cos(halfAngle), unitAxis * sin(halfAngle))
214+
///
215+
/// - Parameters:
216+
/// - halfAngle: The half rotation angle
217+
/// - unitAxis: The rotation axis of unit length
218+
@usableFromInline @inline(__always)
219+
internal init(halfAngle: RealType, unitAxis: SIMD3<RealType>) {
220+
self.init(real: .cos(halfAngle), imaginary: unitAxis * .sin(halfAngle))
221+
}
222+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//===--- Quaternion+AdditiveArithmetic.swift ------------------*- swift -*-===//
2+
//
3+
// This source file is part of the Swift Numerics open source project
4+
//
5+
// Copyright (c) 2019 - 2022 Apple Inc. and the Swift Numerics project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
extension Quaternion: AdditiveArithmetic {
13+
/// The additive identity, with real and *all* imaginary parts zero, i.e.:
14+
/// `0 + 0i + 0j + 0k`
15+
///
16+
/// See also: `one`, `i`, `j`, `k`, `infinity`
17+
@_transparent
18+
public static var zero: Quaternion {
19+
Quaternion(from: SIMD4(repeating: 0))
20+
}
21+
22+
@_transparent
23+
public static func + (lhs: Quaternion, rhs: Quaternion) -> Quaternion {
24+
Quaternion(from: lhs.components + rhs.components)
25+
}
26+
27+
@_transparent
28+
public static func - (lhs: Quaternion, rhs: Quaternion) -> Quaternion {
29+
Quaternion(from: lhs.components - rhs.components)
30+
}
31+
32+
@_transparent
33+
public static func += (lhs: inout Quaternion, rhs: Quaternion) {
34+
lhs = lhs + rhs
35+
}
36+
37+
@_transparent
38+
public static func -= (lhs: inout Quaternion, rhs: Quaternion) {
39+
lhs = lhs - rhs
40+
}
41+
}

0 commit comments

Comments
 (0)