Skip to content

Commit c3361c7

Browse files
committed
First stab at log1p
1 parent 5e7dd3a commit c3361c7

File tree

1 file changed

+111
-42
lines changed

1 file changed

+111
-42
lines changed

Sources/QuaternionModule/ElementaryFunctions.swift

Lines changed: 111 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import RealModule
2929
extension Quaternion/*: ElementaryFunctions */ {
3030

3131
// MARK: - exp-like functions
32-
3332
@inlinable
3433
public static func exp(_ q: Quaternion) -> Quaternion {
3534
// Mathematically, this operation can be expanded in terms of the `Real`
@@ -45,21 +44,17 @@ extension Quaternion/*: ElementaryFunctions */ {
4544
// less than 1 for most inputs (i.e. `exp(r)` may be infinity when
4645
// `exp(r) cos(||v||)` would not be.
4746
guard q.isFinite else { return q }
48-
let θ = q.imaginary.length
49-
let axis = .isZero ? (q.imaginary / θ) : .zero
47+
let (â, θ) = q.imaginary.unitAxisAndLength
48+
let rotation = Quaternion(halfAngle: θ, unitAxis: â)
5049
// If real < log(greatestFiniteMagnitude), then exp(real) does not overflow.
5150
// To protect ourselves against sketchy log or exp implementations in
5251
// an unknown host library, or slight rounding disagreements between
5352
// the two, subtract one from the bound for a little safety margin.
5453
guard q.real < RealType.log(.greatestFiniteMagnitude) - 1 else {
5554
let halfScale = RealType.exp(q.real/2)
56-
let rotation = Quaternion(halfAngle: θ, unitAxis: axis)
5755
return rotation.multiplied(by: halfScale).multiplied(by: halfScale)
5856
}
59-
return Quaternion(
60-
halfAngle: θ,
61-
unitAxis: axis
62-
).multiplied(by: .exp(q.real))
57+
return rotation.multiplied(by: .exp(q.real))
6358
}
6459

6560
@inlinable
@@ -87,21 +82,20 @@ extension Quaternion/*: ElementaryFunctions */ {
8782
//
8883
// See `expMinusOne` on complex numbers for error bounds.
8984
guard q.isFinite else { return q }
90-
let θ = q.imaginary.length
91-
let axis =.isZero ? (q.imaginary / θ) : .zero
85+
let (â, θ) = q.imaginary.unitAxisAndLength
9286
// If exp(q) is close to the overflow boundary, we don't need to
9387
// worry about the "MinusOne" part of this function; we're just
9488
// computing exp(q). (Even when θ is near a multiple of π/2,
9589
// it can't be close enough to overcome the scaling from exp(r),
9690
// so the -1 term is _always_ negligable).
9791
guard q.real < RealType.log(.greatestFiniteMagnitude) - 1 else {
9892
let halfScale = RealType.exp(q.real/2)
99-
let rotation = Quaternion(halfAngle: θ, unitAxis: axis)
93+
let rotation = Quaternion(halfAngle: θ, unitAxis: â)
10094
return rotation.multiplied(by: halfScale).multiplied(by: halfScale)
10195
}
10296
return Quaternion(
10397
real: RealType._mulAdd(.cos(θ), .expMinusOne(q.real), .cosMinusOne(θ)),
104-
imaginary: axis * .exp(q.real) * .sin(θ)
98+
imaginary: â * .exp(q.real) * .sin(θ)
10599
)
106100
}
107101

@@ -120,23 +114,22 @@ extension Quaternion/*: ElementaryFunctions */ {
120114
// evaluation of the naive expression, so all we need to be careful
121115
// about is the behavior near the overflow boundary.
122116
//
123-
// Fortunately, if |x| >= -log(ulpOfOne), cosh(x) and sinh(x) are
124-
// both just exp(|x|)/2, and we already know how to compute that.
117+
// Fortunately, if |r| >= -log(ulpOfOne), cosh(r) and sinh(r) are
118+
// both just exp(|r|)/2, and we already know how to compute that.
125119
//
126120
// This function and sinh should stay in sync; if you make a
127121
// modification here, you should almost surely make a parallel
128122
// modification to sinh below.
129123
guard q.isFinite else { return q }
130-
let θ = q.imaginary.length
131-
let axis =.isZero ? (q.imaginary / θ) : .zero
124+
let (â, θ) = q.imaginary.unitAxisAndLength
132125
guard q.real.magnitude < -RealType.log(.ulpOfOne) else {
133-
let rotation = Quaternion(halfAngle: θ, unitAxis: axis)
126+
let rotation = Quaternion(halfAngle: θ, unitAxis: â)
134127
let firstScale = RealType.exp(q.real.magnitude/2)
135128
return rotation.multiplied(by: firstScale).multiplied(by: firstScale/2)
136129
}
137130
return Quaternion(
138131
real: .cosh(q.real) * .cos(θ),
139-
imaginary: axis * .sinh(q.real) * .sin(θ)
132+
imaginary: â * .sinh(q.real) * .sin(θ)
140133
)
141134
}
142135

@@ -150,17 +143,16 @@ extension Quaternion/*: ElementaryFunctions */ {
150143
// = sinh(r) cos(θ) + (v/θ) cosh(r) sin(θ)
151144
// ```
152145
guard q.isFinite else { return q }
153-
let θ = q.imaginary.length
154-
let axis =.isZero ? (q.imaginary / θ) : .zero
146+
let (â, θ) = q.imaginary.unitAxisAndLength
155147
guard q.real.magnitude < -RealType.log(.ulpOfOne) else {
156-
let rotation = Quaternion(halfAngle: θ, unitAxis: axis)
148+
let rotation = Quaternion(halfAngle: θ, unitAxis: â)
157149
let firstScale = RealType.exp(q.real.magnitude/2)
158150
let secondScale = RealType(signOf: q.real, magnitudeOf: firstScale/2)
159151
return rotation.multiplied(by: firstScale).multiplied(by: secondScale)
160152
}
161153
return Quaternion(
162154
real: .sinh(q.real) * .cos(θ),
163-
imaginary: axis * .cosh(q.real) * .sin(θ)
155+
imaginary: â * .cosh(q.real) * .sin(θ)
164156
)
165157
}
166158

@@ -179,9 +171,9 @@ extension Quaternion/*: ElementaryFunctions */ {
179171
return Quaternion(
180172
real: RealType(signOf: q.real, magnitudeOf: 1),
181173
imaginary:
182-
RealType(signOf: q.imaginary.x, magnitudeOf: 0),
183-
RealType(signOf: q.imaginary.y, magnitudeOf: 0),
184-
RealType(signOf: q.imaginary.z, magnitudeOf: 0)
174+
RealType(signOf: q.components.x, magnitudeOf: 0),
175+
RealType(signOf: q.components.y, magnitudeOf: 0),
176+
RealType(signOf: q.components.z, magnitudeOf: 0)
185177
)
186178
}
187179
return sinh(q) / cosh(q)
@@ -197,17 +189,16 @@ extension Quaternion/*: ElementaryFunctions */ {
197189
// = cos(r) cosh(θ) - (v/θ) sin(r) sinh(θ)
198190
// ```
199191
guard q.isFinite else { return q }
200-
let θ = q.imaginary.length
201-
let axis =.isZero ? (q.imaginary / θ) : .zero
192+
let (â, θ) = q.imaginary.unitAxisAndLength
202193
guard θ.magnitude < -RealType.log(.ulpOfOne) else {
203-
let rotation = Quaternion(halfAngle: q.real, unitAxis: axis)
194+
let rotation = Quaternion(halfAngle: q.real, unitAxis: â)
204195
let firstScale = RealType.exp(θ.magnitude/2)
205196
let secondScale = firstScale/2
206197
return rotation.multiplied(by: firstScale).multiplied(by: secondScale)
207198
}
208199
return Quaternion(
209200
real: .cosh(θ) * .cos(q.real),
210-
imaginary: -axis * .sinh(θ) * .sin(q.real)
201+
imaginary: -â * .sinh(θ) * .sin(q.real)
211202
)
212203
}
213204

@@ -221,17 +212,16 @@ extension Quaternion/*: ElementaryFunctions */ {
221212
// = sin(r) cosh(θ) + (v/θ) cos(r) sinh(θ)
222213
// ```
223214
guard q.isFinite else { return q }
224-
let θ = q.imaginary.length
225-
let axis =.isZero ? (q.imaginary / θ) : .zero
215+
let (â, θ) = q.imaginary.unitAxisAndLength
226216
guard θ.magnitude < -RealType.log(.ulpOfOne) else {
227-
let rotation = Quaternion(halfAngle: q.real, unitAxis: axis)
217+
let rotation = Quaternion(halfAngle: q.real, unitAxis: â)
228218
let firstScale = RealType.exp(θ.magnitude/2)
229219
let secondScale = RealType(signOf: θ, magnitudeOf: firstScale/2)
230220
return rotation.multiplied(by: firstScale).multiplied(by: secondScale)
231221
}
232222
return Quaternion(
233223
real: .cosh(θ) * .sin(q.real),
234-
imaginary: axis * .sinh(θ) * .cos(q.real)
224+
imaginary: â * .sinh(θ) * .cos(q.real)
235225
)
236226
}
237227

@@ -244,17 +234,17 @@ extension Quaternion/*: ElementaryFunctions */ {
244234
// tan(q) = sin(q) / cos(q)
245235
// ```
246236
guard q.isFinite else { return q }
247-
let θ = q.imaginary.length
248237
// Note that when |θ| is larger than -log(.ulpOfOne),
249238
// sin(r + v) == ±cos(r + v), so tan(r + v) is just ±1.
250-
guard θ.magnitude < -RealType.log(.ulpOfOne) else {
239+
guard q.imaginary.length.magnitude < -RealType.log(.ulpOfOne) else {
240+
let r = RealType(signOf: q.components.w, magnitudeOf: 1)
251241
return Quaternion(
252-
real: RealType(signOf: q.real, magnitudeOf: 1),
242+
real: r,
253243
imaginary:
254-
RealType(signOf: q.imaginary.x, magnitudeOf: 0),
255-
RealType(signOf: q.imaginary.y, magnitudeOf: 0),
256-
RealType(signOf: q.imaginary.z, magnitudeOf: 0)
257-
) * Quaternion(RealType(signOf: q.real, magnitudeOf: 1))
244+
RealType(signOf: q.components.x, magnitudeOf: 0),
245+
RealType(signOf: q.components.y, magnitudeOf: 0),
246+
RealType(signOf: q.components.z, magnitudeOf: 0)
247+
).multiplied(by: r)
258248
}
259249
return sin(q) / cos(q)
260250
}
@@ -276,9 +266,57 @@ extension Quaternion/*: ElementaryFunctions */ {
276266
return Quaternion(real: .log(q.length), imaginary: axis * q.halfAngle)
277267
}
278268

279-
280-
//
281269
@inlinable
270+
public static func log(onePlus q: Quaternion) -> Quaternion {
271+
// If either |r| or ||v||₁ is bounded away from the origin, we don't need
272+
// any extra precision, and can just literally compute log(1+z). Note
273+
// that this includes part of the sphere |1+q| = 1 where log(onePlus:)
274+
// vanishes (where r <= -0.5), but on this portion of the sphere 1+r
275+
// is always exact by Sterbenz' lemma, so as long as log( ) produces
276+
// a good result, log(1+q) will too.
277+
guard 2*q.real.magnitude < 1 && q.imaginary.oneNorm < 1 else {
278+
return log(.one + q)
279+
}
280+
// q is in (±0.5, ±1), so we need to evaluate more carefully.
281+
// The imaginary part is straightforward:
282+
let argument = (.one + q).halfAngle
283+
let (â,_) = q.imaginary.unitAxisAndLength
284+
let imaginary = â * argument
285+
// For the real part, we _could_ use the same approach that we do for
286+
// log( ), but we'd need an extra-precise (1+r)², which can potentially
287+
// be quite painful to calculate. Instead, we can use an approach that
288+
// NevinBR suggested on the Swift forums for complex numbers:
289+
//
290+
// Re(log 1+q) = (log 1+q + log 1+q̅)/2
291+
// = log((1+q)(1+q̅)/2
292+
// = log(1 + q + q̅ + qq̅)/2
293+
// = log1p((2+r)r + x² + y² + z²)/2
294+
//
295+
// So now we need to evaluate (2+r)r + x² + y² + z² accurately. To do this,
296+
// we employ augmented arithmetic;
297+
// (2+r)r + x² + y² + z²
298+
// --↓--
299+
let rp2 = Augmented.fastTwoSum(2, q.real) // Known that 2 > |r|
300+
var (head, δ) = Augmented.twoProdFMA(q.real, rp2.head)
301+
var tail = δ
302+
// head + x² + y² + z²
303+
// ----↓----
304+
let x² = Augmented.twoProdFMA(q.imaginary.x, q.imaginary.x)
305+
(head, δ) = Augmented.twoSum(head, x².head)
306+
tail += (δ + x².tail)
307+
// head + y² + z²
308+
// ----↓----
309+
let y² = Augmented.twoProdFMA(q.imaginary.y, q.imaginary.y)
310+
(head, δ) = Augmented.twoSum(head, y².head)
311+
tail += (δ + y².tail)
312+
// head + z²
313+
// ----↓----
314+
let z² = Augmented.twoProdFMA(q.imaginary.z, q.imaginary.z)
315+
(head, δ) = Augmented.twoSum(head, z².head)
316+
tail += (δ + z².tail)
317+
318+
let s = (head + tail).addingProduct(q.real, rp2.tail)
319+
return Quaternion(real: .log(onePlus: s)/2, imaginary: imaginary)
282320
}
283321

284322
//
@@ -341,3 +379,34 @@ extension Quaternion/*: ElementaryFunctions */ {
341379
return exp(log(q).divided(by: RealType(n)))
342380
}
343381
}
382+
383+
extension SIMD3 where Scalar: Real {
384+
385+
/// Returns the normalized axis and the length of this vector.
386+
@usableFromInline @inline(__always)
387+
internal var unitAxisAndLength: (Self, Scalar) {
388+
if self == .zero {
389+
return (SIMD3(
390+
Scalar(signOf: x, magnitudeOf: 0),
391+
Scalar(signOf: y, magnitudeOf: 0),
392+
Scalar(signOf: z, magnitudeOf: 0)
393+
), .zero)
394+
}
395+
return (self/length, length)
396+
}
397+
}
398+
399+
extension Augmented {
400+
401+
// TODO: Move to Augmented.swift
402+
@usableFromInline @_transparent
403+
internal static func twoSum<T:Real>(_ a: T, _ b: T) -> (head: T, tail: T) {
404+
let head = a + b
405+
let x = head - b
406+
let y = head - x
407+
let ax = a - x
408+
let by = b - y
409+
let tail = ax + by
410+
return (head, tail)
411+
}
412+
}

0 commit comments

Comments
 (0)