Documentation

Overview

Package dualquat provides the dual quaternion numeric type and functions.

Dual quaternions provide a system for rigid transformation with interpolation and blending in ℝ³. See https://www.cs.utah.edu/~ladislav/kavan06dual/kavan06dual.pdf and https://en.wikipedia.org/wiki/Dual_quaternion for more details.

Example

Code:

package main

import (
	"fmt"
	"math"

	"gonum.org/v1/gonum/floats/scalar"
	"gonum.org/v1/gonum/num/dualquat"
	"gonum.org/v1/gonum/num/quat"
)

// point is a 3-dimensional point/vector.
type point struct {
	x, y, z float64
}

// raise raises the dimensionality of a point to a quaternion.
func raise(p point) quat.Number {
	return quat.Number{Imag: p.x, Jmag: p.y, Kmag: p.z}
}

// raiseDual raises the dimensionality of a point to a dual quaternion.
func raiseDual(p point) dualquat.Number {
	return dualquat.Number{
		Real: quat.Number{Real: 1},
		Dual: raise(p),
	}
}

// transform performs the transformation of p by the given dual quaternions.
// The transformations are normalized to unit vectors.
func transform(p point, by ...dualquat.Number) point {
	if len(by) == 0 {
		return p
	}

	// Ensure the modulus of by is correctly scaled.
	for i := range by {
		if len := quat.Abs(by[i].Real); len != 1 {
			by[i].Real = quat.Scale(1/len, by[i].Real)
		}
	}

	// Perform the transformations.
	q := by[0]
	for _, o := range by[1:] {
		q = dualquat.Mul(o, q)
	}
	pp := dualquat.Mul(dualquat.Mul(q, raiseDual(p)), dualquat.Conj(q))

	// Extract the point.
	return point{x: pp.Dual.Imag, y: pp.Dual.Jmag, z: pp.Dual.Kmag}
}

func main() {
	// Translate a 1×1×1 cube by [3, 4, 5] and rotate it 120° around the
	// diagonal vector [1, 1, 1].
	fmt.Println("cube:")

	// Construct a displacement.
	displace := dualquat.Number{
		Real: quat.Number{Real: 1},
		Dual: quat.Scale(0.5, raise(point{3, 4, 5})),
	}

	// Construct a rotations.
	alpha := 2 * math.Pi / 3
	axis := raise(point{1, 1, 1})
	rotate := dualquat.Number{Real: axis}
	rotate.Real = quat.Scale(math.Sin(alpha/2)/quat.Abs(rotate.Real), rotate.Real)
	rotate.Real.Real += math.Cos(alpha / 2)

	for i, p := range []point{
		{x: 0, y: 0, z: 0},
		{x: 0, y: 0, z: 1},
		{x: 0, y: 1, z: 0},
		{x: 0, y: 1, z: 1},
		{x: 1, y: 0, z: 0},
		{x: 1, y: 0, z: 1},
		{x: 1, y: 1, z: 0},
		{x: 1, y: 1, z: 1},
	} {
		pp := transform(p,
			displace, rotate,
		)

		// Clean up floating point error for clarity.
		pp.x = scalar.Round(pp.x, 2)
		pp.y = scalar.Round(pp.y, 2)
		pp.z = scalar.Round(pp.z, 2)

		fmt.Printf(" %d %+v -> %+v\n", i, p, pp)
	}

	// Rotate a line segment from {[2, 1, 1], [2, 1, 2]} 120° around
	// the diagonal vector [1, 1, 1] at its lower end.
	fmt.Println("\nline segment:")

	// Construct an displacement to the origin from the lower end...
	origin := dualquat.Number{
		Real: quat.Number{Real: 1},
		Dual: quat.Scale(0.5, raise(point{-2, -1, -1})),
	}
	// ... and back from the origin to the lower end.
	replace := dualquat.Number{
		Real: quat.Number{Real: 1},
		Dual: quat.Scale(-1, origin.Dual),
	}

	for i, p := range []point{
		{x: 2, y: 1, z: 1},
		{x: 2, y: 1, z: 2},
	} {
		pp := transform(p,
			origin,  // Displace to origin.
			rotate,  // Rotate around axis.
			replace, // Displace back to original location.
		)

		// Clean up floating point error for clarity.
		pp.x = scalar.Round(pp.x, 2)
		pp.y = scalar.Round(pp.y, 2)
		pp.z = scalar.Round(pp.z, 2)

		fmt.Printf(" %d %+v -> %+v\n", i, p, pp)
	}

}

cube:
 0 {x:0 y:0 z:0} -> {x:5 y:3 z:4}
 1 {x:0 y:0 z:1} -> {x:6 y:3 z:4}
 2 {x:0 y:1 z:0} -> {x:5 y:3 z:5}
 3 {x:0 y:1 z:1} -> {x:6 y:3 z:5}
 4 {x:1 y:0 z:0} -> {x:5 y:4 z:4}
 5 {x:1 y:0 z:1} -> {x:6 y:4 z:4}
 6 {x:1 y:1 z:0} -> {x:5 y:4 z:5}
 7 {x:1 y:1 z:1} -> {x:6 y:4 z:5}

line segment:
 0 {x:2 y:1 z:1} -> {x:2 y:1 z:1}
 1 {x:2 y:1 z:2} -> {x:3 y:1 z:1}
Example (Displace)

Code:

package main

import (
	"fmt"
	"gonum.org/v1/gonum/num/dualquat"
	"gonum.org/v1/gonum/num/quat"
)

func main() {
	// Displace a point [3, 4, 5] by [4, 2, 6].
	// See http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/other/dualQuaternion/index.htm

	// Point to be transformed in the dual imaginary vector.
	p := dualquat.Number{Real: quat.Number{Real: 1}, Dual: quat.Number{Imag: 3, Jmag: 4, Kmag: 5}}

	// Displacement vector, half [4, 2, 6], in the dual imaginary vector.
	d := dualquat.Number{Real: quat.Number{Real: 1}, Dual: quat.Number{Imag: 2, Jmag: 1, Kmag: 3}}

	fmt.Println(dualquat.Mul(dualquat.Mul(d, p), dualquat.Conj(d)).Dual)

}

(0+7i+6j+11k)
Example (DisplaceAndRotate)

Code:

package main

import (
	"fmt"
	"gonum.org/v1/gonum/num/dualquat"
	"gonum.org/v1/gonum/num/quat"
)

func main() {
	// Displace a point [3, 4, 5] by [4, 2, 6] and then rotate
	// by 180° around the x axis.
	// See http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/other/dualQuaternion/index.htm

	// Point to be transformed in the dual imaginary vector.
	p := dualquat.Number{Real: quat.Number{Real: 1}, Dual: quat.Number{Imag: 3, Jmag: 4, Kmag: 5}}

	// Displacement vector, half [4, 2, 6], in the dual imaginary vector.
	d := dualquat.Number{Real: quat.Number{Real: 1}, Dual: quat.Number{Imag: 2, Jmag: 1, Kmag: 3}}

	// Rotation in the real quaternion.
	r := dualquat.Number{Real: quat.Number{Real: 0, Imag: 1}}

	// Combine the rotation and displacement so
	// the displacement is performed first.
	q := dualquat.Mul(r, d)

	fmt.Println(dualquat.Mul(dualquat.Mul(q, p), dualquat.Conj(q)).Dual)

}

(0+7i-6j-11k)
Example (Rotate)

Code:

package main

import (
	"fmt"
	"gonum.org/v1/gonum/num/dualquat"
	"gonum.org/v1/gonum/num/quat"
)

func main() {
	// Rotate a point [3, 4, 5] by 180° around the x axis.
	// See http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/other/dualQuaternion/index.htm

	// Point to be transformed in the dual imaginary vector.
	p := dualquat.Number{Real: quat.Number{Real: 1}, Dual: quat.Number{Imag: 3, Jmag: 4, Kmag: 5}}

	// Rotation in the real quaternion.
	r := dualquat.Number{Real: quat.Number{Real: 0, Imag: 1}}

	fmt.Println(dualquat.Mul(dualquat.Mul(r, p), dualquat.Conj(r)).Dual)

}

(0+3i-4j-5k)
Example (RotateAndDisplace)

Code:

package main

import (
	"fmt"
	"gonum.org/v1/gonum/num/dualquat"
	"gonum.org/v1/gonum/num/quat"
)

func main() {
	// Rotate a point [3, 4, 5] by 180° around the x axis and then
	// displace by [4, 2, 6]

	// Point to be transformed in the dual imaginary vector.
	p := dualquat.Number{Real: quat.Number{Real: 1}, Dual: quat.Number{Imag: 3, Jmag: 4, Kmag: 5}}

	// Displacement vector, half [4, 2, 6], in the dual imaginary vector.
	d := dualquat.Number{Real: quat.Number{Real: 1}, Dual: quat.Number{Imag: 2, Jmag: 1, Kmag: 3}}

	// Rotation in the real quaternion.
	r := dualquat.Number{Real: quat.Number{Real: 0, Imag: 1}}

	// Combine the rotation and displacement so
	// the rotations is performed first.
	q := dualquat.Mul(d, r)

	fmt.Println(dualquat.Mul(dualquat.Mul(q, p), dualquat.Conj(q)).Dual)

}

(0+7i-2j+1k)

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Abs

func Abs(d Number) dual.Number

Abs returns the absolute value of d.

Types

type Number

type Number struct {
	Real, Dual quat.Number
}

Number is a float64 precision dual quaternion. A dual quaternion is a hypercomplex number composed of two quaternions, q₀+q₂ϵ, where ϵ²=0, but ϵ≠0. Here, q₀ is termed the real and q₂ the dual.

func Add

func Add(x, y Number) Number

Add returns the sum of x and y.

func Conj

func Conj(d Number) Number

Conj returns the dual quaternion conjugate of d₁+d₂ϵ, d̅₁-d̅₂ϵ.

func ConjDual

func ConjDual(d Number) Number

ConjDual returns the dual conjugate of d₁+d₂ϵ, d₁-d₂ϵ.

func ConjQuat

func ConjQuat(d Number) Number

ConjQuat returns the quaternion conjugate of d₁+d₂ϵ, d̅₁+d̅₂ϵ.

func Exp

func Exp(d Number) Number

Exp returns e**d, the base-e exponential of d.

Special cases are:

Exp(+Inf) = +Inf
Exp(NaN) = NaN

Very large values overflow to 0 or +Inf. Very small values underflow to 1.

func Inv

func Inv(d Number) Number

Inv returns the dual inverse of d.

func Log

func Log(d Number) Number

Log returns the natural logarithm of d.

Special cases are:

Log(+Inf) = (+Inf+0ϵ)
Log(0) = (-Inf±Infϵ)
Log(x < 0) = NaN
Log(NaN) = NaN

func Mul

func Mul(x, y Number) Number

Mul returns the dual product of x and y.

func Pow

func Pow(d, p Number) Number

Pow return d**p, the base-d exponential of p.

func PowReal

func PowReal(d Number, p float64) Number

PowReal returns d**p, the base-d exponential of p.

Special cases are (in order):

PowReal(NaN+xϵ, ±0) = 1+NaNϵ for any x
PowReal(x, ±0) = 1 for any x
PowReal(1+xϵ, y) = 1+xyϵ for any y
PowReal(x, 1) = x for any x
PowReal(NaN+xϵ, y) = NaN+NaNϵ
PowReal(x, NaN) = NaN+NaNϵ
PowReal(±0, y) = ±Inf for y an odd integer < 0
PowReal(±0, -Inf) = +Inf
PowReal(±0, +Inf) = +0
PowReal(±0, y) = +Inf for finite y < 0 and not an odd integer
PowReal(±0, y) = ±0 for y an odd integer > 0
PowReal(±0, y) = +0 for finite y > 0 and not an odd integer
PowReal(-1, ±Inf) = 1
PowReal(x+0ϵ, +Inf) = +Inf+NaNϵ for |x| > 1
PowReal(x+yϵ, +Inf) = +Inf for |x| > 1
PowReal(x, -Inf) = +0+NaNϵ for |x| > 1
PowReal(x, +Inf) = +0+NaNϵ for |x| < 1
PowReal(x+0ϵ, -Inf) = +Inf+NaNϵ for |x| < 1
PowReal(x, -Inf) = +Inf-Infϵ for |x| < 1
PowReal(+Inf, y) = +Inf for y > 0
PowReal(+Inf, y) = +0 for y < 0
PowReal(-Inf, y) = Pow(-0, -y)

func Scale

func Scale(f float64, d Number) Number

Scale returns d scaled by f.

func Sqrt

func Sqrt(d Number) Number

Sqrt returns the square root of d

Special cases are:

Sqrt(+Inf) = +Inf
Sqrt(±0) = (±0+Infϵ)
Sqrt(x < 0) = NaN
Sqrt(NaN) = NaN

func Sub

func Sub(x, y Number) Number

Sub returns the difference of x and y, x-y.

func (Number) Format

func (d Number) Format(fs fmt.State, c rune)

Format implements fmt.Formatter.