Skip to content

Commit 2354e68

Browse files
committed
Add pow, sqrt and root to quaternions
1 parent 9483c80 commit 2354e68

File tree

2 files changed

+76
-4
lines changed

2 files changed

+76
-4
lines changed

Sources/QuaternionModule/ElementaryFunctions.swift

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,45 @@ extension Quaternion/*: ElementaryFunctions */ {
163163
public static func tan(_ q: Quaternion) -> Quaternion {
164164
return sin(q) / cos(q)
165165
}
166+
167+
// MARK: - pow-like functions
168+
@inlinable
169+
public static func pow(_ q: Quaternion, _ p: Quaternion) -> Quaternion {
170+
// pow(q, p) = exp(log(q^p)) = exp(p * log(q))
171+
return exp(p * log(q))
172+
}
173+
174+
@inlinable
175+
public static func pow(_ q: Quaternion, _ n: Int) -> Quaternion {
176+
if q.isZero { return .zero }
177+
// TODO: this implementation is not quite correct, because n may be
178+
// rounded in conversion to RealType. This only effects very extreme
179+
// cases, so we'll leave it alone for now.
180+
//
181+
// Note that this does not have the same problems that a similar
182+
// implementation for a real type would have, because there's no
183+
// parity/sign interaction in the complex plane.
184+
return exp(log(q).multiplied(by: RealType(n)))
185+
}
186+
187+
@inlinable
188+
public static func sqrt(_ q: Quaternion) -> Quaternion<RealType> {
189+
if q.isZero { return .zero }
190+
// TODO: This is not the fastest implementation available
191+
return exp(log(q).divided(by: 2))
192+
}
193+
194+
@inlinable
195+
public static func root(_ q: Quaternion, _ n: Int) -> Quaternion {
196+
if q.isZero { return .zero }
197+
// TODO: this implementation is not quite correct, because n may be
198+
// rounded in conversion to RealType. This only effects very extreme
199+
// cases, so we'll leave it alone for now.
200+
//
201+
// Note that this does not have the same problems that a similar
202+
// implementation for a real type would have, because there's no
203+
// parity/sign interaction in the complex plane.
204+
return exp(log(q).divided(by: RealType(n)))
166205
}
167206
}
168207

Tests/QuaternionTests/ElementaryFunctionTests.swift

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,7 @@ final class ElementaryFunctionTests: XCTestCase {
210210
}
211211

212212

213-
func testCosSinIdentity<T: Real & FixedWidthFloatingPoint & SIMDScalar>(_ type: T.Type) {
214-
// For randomly-chosen well-scaled finite values, we expect to have cos² + sin² ≈ 1
213+
func testCos<T: Real & FixedWidthFloatingPoint & SIMDScalar>(_ type: T.Type) {
215214
var g = SystemRandomNumberGenerator()
216215
let values: [Quaternion<T>] = (0..<1000).map { _ in
217216
Quaternion(
@@ -224,23 +223,57 @@ final class ElementaryFunctionTests: XCTestCase {
224223
}
225224
for q in values {
226225
let c = Quaternion.cos(q)
226+
227+
// For randomly-chosen well-scaled finite values, we expect to have
228+
// cos ≈ (e^(q*||v||)+e^(-q*||v||)) / 2
229+
let p = Quaternion(imaginary: q.imaginary / q.imaginary.length)
230+
let e = (.exp(p * q) + .exp(-p * q)) / 2
231+
XCTAssert(c.isApproximatelyEqual(to: e))
232+
}
233+
}
234+
235+
func testSin<T: Real & FixedWidthFloatingPoint & SIMDScalar>(_ type: T.Type) {
236+
var g = SystemRandomNumberGenerator()
237+
let values: [Quaternion<T>] = (0..<1000).map { _ in
238+
Quaternion(
239+
real: T.random(in: -2 ... 2, using: &g),
240+
imaginary:
241+
T.random(in: -2 ... 2, using: &g) / 3,
242+
T.random(in: -2 ... 2, using: &g) / 3,
243+
T.random(in: -2 ... 2, using: &g) / 3
244+
)
245+
}
246+
for q in values {
227247
let s = Quaternion.sin(q)
248+
249+
// For randomly-chosen well-scaled finite values, we expect to have
250+
// cos ≈ (e^(q*||v||)+e^(-q*||v||)) / 2
251+
let p = Quaternion(imaginary: q.imaginary / q.imaginary.length)
252+
let e = (.exp(p * q) - .exp(-p * q)) / (p * 2)
253+
XCTAssert(s.isApproximatelyEqual(to: e))
254+
255+
// For randomly-chosen well-scaled finite values, we expect to have
256+
// cos² + sin² ≈ 1
257+
let c = Quaternion.cos(q)
228258
XCTAssert((c*c + s*s).isApproximatelyEqual(to: .one))
229259
}
230260
}
261+
231262
func testFloat() {
232263
testExp(Float32.self)
233264
testExpMinusOne(Float32.self)
234265
testCosh(Float32.self)
235266
testSinh(Float32.self)
236-
testCosSinIdentity(Float32.self)
267+
testCos(Float32.self)
268+
testSin(Float32.self)
237269
}
238270

239271
func testDouble() {
240272
testExp(Float64.self)
241273
testExpMinusOne(Float64.self)
242274
testCosh(Float64.self)
243275
testSinh(Float64.self)
244-
testCosSinIdentity(Float64.self)
276+
testCos(Float64.self)
277+
testSin(Float64.self)
245278
}
246279
}

0 commit comments

Comments
 (0)