Three-dimensional vectors
This example shows how we might define a module type for programming with vectors in normal 3D space.
module type vspace_3d = {
We keep the type of vector components abstract, because it will be
useful to have the same interface for vectors with components that
are f32
, f64
, or something even more exotic.
type scalar
However, the vector type itself is opaque. It must be a record
with x
/y
/z
fields.
type vector = {x: scalar, y: scalar, z: scalar}
Vectors must support the usual binary operations.
val +: vector -> vector -> vector
val -: vector -> vector -> vector
val *: vector -> vector -> vector
val /: vector -> vector -> vector
As well as dot products and cross products.
val dot: vector -> vector -> scalar
val cross: vector -> vector -> vector
Vectors can be scaled by a scalar.
val scale: scalar -> vector -> vector
We can also take the length of a vector, as well as normalise a vector to have unit length.
val length: vector -> scalar
val normalise: vector -> vector
Finally, vectors can be rotated around the three axes.
val rot_x : (radians: scalar) -> vector -> vector
val rot_y : (radians: scalar) -> vector -> vector
val rot_z : (radians: scalar) -> vector -> vector
}
So for which scalars can this module type be implemented? We describe this with yet another module type.
module type scalar = {
type t
val +: t -> t -> t
val -: t -> t -> t
val *: t -> t -> t
val /: t -> t -> t
val f64: f64 -> t
val sqrt : t -> t
val cos : t -> t
val sin : t -> t
}
Because of Futhark’s structural type system, the built-in modules
f32
and f64
already satisfy this interface.
We can now define a parametric module for constructing vector spaces:
module mk_vspace_3d(P: scalar): vspace_3d with scalar = P.t = {
type scalar = P.t
type vector = {x: scalar, y: scalar, z: scalar}
def zero = {x = P.f64 0, y = P.f64 0, z = P.f64 0}
def one = P.f64 1
Most of the definitions are straight out of a textbook, and so we won’t be providing much commentary.
We start out by defining two helper functions for doing operations on the components of a vector.
def map (f: scalar -> scalar) (v : vector) =
{x = f v.x, y = f v.y, z = f v.z}
def map2 (f: scalar -> scalar -> scalar) (a : vector) (b : vector) =
{x = f a.x b.x, y = f a.y b.y, z = f a.z b.z}
This allows us to conveniently define vector arithmetic and scaling.
def (+) = map2 (P.+)
def (-) = map2 (P.-)
def (*) = map2 (P.*)
def (/) = map2 (P./)
def scale (s: scalar) = map (s P.*)
The remaining operations are defined explicitly.
def dot (a: vector) (b: vector) =
P.(a.x*b.x + a.y*b.y + a.z*b.z)
def cross ({x=ax,y=ay,z=az}: vector)
({x=bx,y=by,z=bz}: vector): vector =
P.({x=ay*bz-az*by, y=az*bx-ax*bz, z=ax*by-ay*bx})
def length v = P.sqrt (dot v v)
def normalise (v: vector): vector =
let l = length v
in scale (one P./ l) v
def rot_x (theta: scalar) ({x,y,z} : vector) =
let cos_theta = P.cos theta
let sin_theta = P.sin theta
in { x
, y = P.(cos_theta * y - sin_theta * z)
, z = P.(sin_theta * y + cos_theta * z)}
def rot_y (theta: scalar) ({x,y,z} : vector) =
let cos_theta = P.cos theta
let sin_theta = P.sin theta
in { x = P.(cos_theta * x - sin_theta * z)
, y
, z = P.(sin_theta * x + cos_theta * z)}
def rot_z (theta: scalar) ({x,y,z} : vector) =
let cos_theta = P.cos theta
let sin_theta = P.sin theta
in { x = P.(cos_theta * x - sin_theta * y)
, y = P.(sin_theta * x + cos_theta * y)
, z} }
Instantiating the module:
module f64_3d = mk_vspace_3d f64
And trying it out:
> f64_3d.cross {x=1,y=2,z=3} {x=4,y=5,z=6}
{x = -3.0f64, y = 6.0f64, z = -3.0f64}
See also
The vector package.