http://mathling.com/core/vector  library module

http://mathling.com/core/vector


Vectors of numbers

Copyright© Mary Holstege 2020-2023
CC-BY (https://creativecommons.org/licenses/by/4.0/)

May 2021
Status: Incomplete

Imports

http://mathling.com/core/utilities
import module namespace util="http://mathling.com/core/utilities"
       at "../core/utilities.xqy"
http://mathling.com/core/config
import module namespace config="http://mathling.com/core/config"
       at "../core/config.xqy"
http://mathling.com/core/errors
import module namespace errors="http://mathling.com/core/errors"
       at "../core/errors.xqy"

Functions

Function: as-dimension
declare function as-dimension($vector as xs:double*, $dim as xs:integer) as xs:double*


as-dimension()
Cast the vector to a vector with the given number of dimensions.
If we are reducing dimensions, just remove excess coordinates.
If we are expanding dimensions, fill in with 0.

Params
  • vector as xs:double*: source vector
  • dim as xs:integer: new dimension of vector
Returns
  • xs:double*: new vector
declare function this:as-dimension($vector as xs:double*, $dim as xs:integer) as xs:double*
{
  ($vector, for $i in 1 to $dim return 0E0)[position() <= $dim]
}

Function: unquote
declare function unquote($string as xs:string) as xs:double*


unquote()
Parse a string representation of a vector and produce a vector.
The string consists of the coordinates in with commas and
optional spaces in between.

Params
  • string as xs:string: the input string to parse
Returns
  • xs:double*: vector
declare function this:unquote($string as xs:string) as xs:double*
{
  tokenize($string, "[ ]*,[ ]*")!xs:double(.)
}

Function: dimension
declare function dimension($vector as xs:double*) as xs:integer


dimension()
Return the dimension of the vector.

Params
  • vector as xs:double*: the vector
Returns
  • xs:integer: length of vector
declare function this:dimension($vector as xs:double*) as xs:integer
{
  count($vector)
}

Function: map
declare function map($f as function(xs:double) as xs:double, $vector as xs:double*) as xs:double*


map()
Map a function over the coordinates of a vector, returning the new vector.

Params
  • f as function(xs:double)asxs:double: function from coordinate to coordinate
  • vector as xs:double*: input vector
Returns
  • xs:double*: application of function to each coordinate
declare function this:map(
  $f as function(xs:double) as xs:double,
  $vector as xs:double*
) as xs:double*
{
  for-each($vector, $f)
}

Function: map
declare function map($f as function(xs:double) as xs:double, $vector as xs:double*, $d as xs:integer) as xs:double*


map()
Map a function over the coordinates of a vector, returning the new vector.
Treat the vector as having a particular dimension.

Params
  • f as function(xs:double)asxs:double: function from coordinate to coordinate
  • vector as xs:double*: input vector
  • d as xs:integer: the effective dimension of the vector
Returns
  • xs:double*: application of function to coordinates
declare function this:map(
  $f as function(xs:double) as xs:double,
  $vector as xs:double*,
  $d as xs:integer
) as xs:double*
{
  for-each(this:as-dimension($vector, $d), $f)
}

Function: map2
declare function map2($f as function(xs:double, xs:double) as xs:double, $p1 as xs:double*, $p2 as xs:double*) as xs:double*


map2()
Map a function over the coordinates of two vectors, pair by pair, returning
the new vector. That is, apply function to the x coordinates, then the
y coordinates, and so on.

Params
  • f as function(xs:double,xs:double)asxs:double: function from coordinate pairs to coordinate
  • p1 as xs:double*: one input vector
  • p2 as xs:double*: another input vector
Returns
  • xs:double*: application of function to each pair
declare function this:map2(
  $f as function(xs:double, xs:double) as xs:double,
  $p1 as xs:double*,
  $p2 as xs:double*
) as xs:double*
{
  util:zip($f, $p1, $p2)
}

Function: map2
declare function map2($f as function(xs:double, xs:double) as xs:double, $p1 as xs:double*, $p2 as xs:double*, $d as xs:integer) as xs:double*


map2()
Map a function over the coordinates of two vectors, pair by pair, returning
the new vector. That is, apply function to the x coordinates, then the
y coordinates, and so on. Treat the vector as having a particular dimension.

Params
  • f as function(xs:double,xs:double)asxs:double: function from coordinate pairs to coordinate
  • p1 as xs:double*: one input vector
  • p2 as xs:double*: another input vector
  • d as xs:integer: the effective dimension of the vectors
Returns
  • xs:double*: application of function to each pair
declare function this:map2(
  $f as function(xs:double, xs:double) as xs:double,
  $p1 as xs:double*,
  $p2 as xs:double*,
  $d as xs:integer
) as xs:double*
{
  util:zip($f, this:as-dimension($p1, $d), this:as-dimension($p2, $d))
}

Function: map3
declare function map3($f as function(xs:double, xs:double, xs:double) as xs:double, $p1 as xs:double*, $p2 as xs:double*, $p3 as xs:double*) as xs:double*


map3()
Map a function over the coordinates of three vectors, triple by triple,
returning the new vector. That is, apply function to the x coordinates,
then the y coordinates, and so on.

Params
  • f as function(xs:double,xs:double,xs:double)asxs:double: function from coordinate pairs to coordinate
  • p1 as xs:double*: one input vector
  • p2 as xs:double*: another input vector
  • p3 as xs:double*: third input vector
Returns
  • xs:double*: application of function to each triple
declare function this:map3(
  $f as function(xs:double, xs:double, xs:double) as xs:double,
  $p1 as xs:double*,
  $p2 as xs:double*,
  $p3 as xs:double*
) as xs:double*
{
  util:zip(
    function ($a as xs:double, $fc as function(xs:double) as xs:double) as xs:double {$fc($a)},
    $p3,
    util:zip(
      function ($a as xs:double, $b as xs:double) as function(*) { $f($a, $b, ?) },
      $p1, $p2
    )
  )
}

Function: map3
declare function map3($f as function(xs:double, xs:double, xs:double) as xs:double, $p1 as xs:double*, $p2 as xs:double*, $p3 as xs:double*, $d as xs:integer) as xs:double*


map3()
Map a function over the coordinates of three vectors, triple by triple,
returning the new vector. That is, apply function to the x coordinates,
then the y coordinates, and so on. Treat the vector as having a
particular dimension.

Params
  • f as function(xs:double,xs:double,xs:double)asxs:double: function from coordinate pairs to coordinate
  • p1 as xs:double*: one input vector
  • p2 as xs:double*: another input vector
  • p3 as xs:double*: third input vector
  • d as xs:integer: the effective dimension of the vectors
Returns
  • xs:double*: application of function to each triple
declare function this:map3(
  $f as function(xs:double, xs:double, xs:double) as xs:double,
  $p1 as xs:double*,
  $p2 as xs:double*,
  $p3 as xs:double*,
  $d as xs:integer
) as xs:double*
{
  util:zip(
    function ($a as xs:double, $fc as function(xs:double) as xs:double) as xs:double {$fc($a)},
    this:as-dimension($p3, $d),
    util:zip(
      function ($a as xs:double, $b as xs:double) as function(*) { $f($a, $b, ?) },
      this:as-dimension($p1, $d), this:as-dimension($p2, $d)
    )
  )
}

Function: map4
declare function map4($f as function(xs:double, xs:double, xs:double, xs:double) as xs:double, $p1 as xs:double*, $p2 as xs:double*, $p3 as xs:double*, $p4 as xs:double*) as xs:double*


map4()
Map a function over the coordinates of four vectors, coordinate by
coordinate, returning the new vector. That is, apply function to the x
coordinates, then the y coordinates, and so on.

Params
  • f as function(xs:double,xs:double,xs:double,xs:double)asxs:double: function from coordinate pairs to coordinate
  • p1 as xs:double*: one input vector
  • p2 as xs:double*: another input vector
  • p3 as xs:double*: third input vector
  • p4 as xs:double*: fourth input vector
Returns
  • xs:double*: application of function to each quad
declare function this:map4(
  $f as function(xs:double, xs:double, xs:double, xs:double) as xs:double,
  $p1 as xs:double*,
  $p2 as xs:double*,
  $p3 as xs:double*,
  $p4 as xs:double*
) as xs:double*
{
  util:zip(
    function ($d as xs:double, $fd as function(xs:double) as xs:double) as xs:double {$fd($d)},
    $p4,
    util:zip(
      function ($c as xs:double, $fc as function(xs:double, xs:double) as xs:double) as function(*) {$fc($c, ?)},
      $p3,
      util:zip(
        function ($a as xs:double, $b as xs:double) as function(*) { $f($a, $b, ?, ?) },
        $p1, $p2
      )
    )
  )
}

Function: map4
declare function map4($f as function(xs:double, xs:double, xs:double, xs:double) as xs:double, $p1 as xs:double*, $p2 as xs:double*, $p3 as xs:double*, $p4 as xs:double*, $dim as xs:integer) as xs:double*


map4()
Map a function over the coordinates of four vectors, coordinate by
coordinate, returning the new vector. That is, apply function to the x
coordinates, then the y coordinates, and so on. Treat the vector as having a
particular dimension.

Params
  • f as function(xs:double,xs:double,xs:double,xs:double)asxs:double: function from coordinate pairs to coordinate
  • p1 as xs:double*: one input vector
  • p2 as xs:double*: another input vector
  • p3 as xs:double*: third input vector
  • p4 as xs:double*: fourth input vector
  • dim as xs:integer
Returns
  • xs:double*: application of function to each quad
declare function this:map4(
  $f as function(xs:double, xs:double, xs:double, xs:double) as xs:double,
  $p1 as xs:double*,
  $p2 as xs:double*,
  $p3 as xs:double*,
  $p4 as xs:double*,
  $dim as xs:integer
) as xs:double*
{
  util:zip(
    function ($d as xs:double, $fd as function(xs:double) as xs:double) as xs:double {$fd($d)},
    this:as-dimension($p4, $dim),
    util:zip(
      function ($c as xs:double, $fc as function(xs:double, xs:double) as xs:double) as function(*) {$fc($c, ?)},
      this:as-dimension($p3, $dim),
      util:zip(
        function ($a as xs:double, $b as xs:double) as function(*) { $f($a, $b, ?, ?) },
        this:as-dimension($p1, $dim), this:as-dimension($p2, $dim)
      )
    )
  )
}

Function: px
declare function px($vector as xs:double*) as xs:double


px()
Accessor for the raw precision x coordinate

Params
  • vector as xs:double*: the vector
Returns
  • xs:double: raw x coordinate
declare function this:px($vector as xs:double*) as xs:double
{
  head(($vector[1],0E0))
}

Function: py
declare function py($vector as xs:double*) as xs:double


py()
Accessor for the raw precision y coordinate

Params
  • vector as xs:double*: the vector
Returns
  • xs:double: raw y coordinate
declare function this:py($vector as xs:double*) as xs:double
{
  head(($vector[2],0E0))
}

Function: pz
declare function pz($vector as xs:double*) as xs:double


pz()
Accessor for the raw precision z coordinate; returns 0 for 2D vector

Params
  • vector as xs:double*: the vector
Returns
  • xs:double: raw z coordinate
declare function this:pz($vector as xs:double*) as xs:double
{
  head(($vector[3],0E0))
}

Function: pw
declare function pw($vector as xs:double*) as xs:double


pw()
Accessor for the raw precision w coordinate; returns 0 for 2D or 3D vector

Params
  • vector as xs:double*: the vector
Returns
  • xs:double: raw w coordinate
declare function this:pw($vector as xs:double*) as xs:double
{
  head(($vector[4],0E0))
}

Function: pxy
declare function pxy($vector as xs:double*) as xs:double*


pxy()
Accessor for the raw precision xy vector.

Params
  • vector as xs:double*: the vector
Returns
  • xs:double*: new vector of just the raw x and y coordinates
declare function this:pxy($vector as xs:double*) as xs:double*
{
  head(($vector[1],0E0)), head(($vector[1],0E0))
}

Function: pxz
declare function pxz($vector as xs:double*) as xs:double*


pxz()
Accessor for the raw precision xz vector.

Params
  • vector as xs:double*: the vector
Returns
  • xs:double*: new vector of just the raw x and z coordinates
declare function this:pxz($vector as xs:double*) as xs:double*
{
  head(($vector[1],0E0)), head(($vector[3],0E0))
}

Function: x
declare function x($vector as xs:double*) as xs:integer


x()
Accessor for the snapped x coordinate

Params
  • vector as xs:double*: the vector
Returns
  • xs:integer: x coordinate, rounded
declare function this:x($vector as xs:double*) as xs:integer
{
  let $x := head(($vector[1],0))
  return (
    if ($x = xs:double("INF")) then $util:UINT64_MAX 
    else if ($x = xs:double("-INF")) then -$util:UINT64_MAX 
    else round-half-to-even($x) cast as xs:integer
  )
}

Function: y
declare function y($vector as xs:double*) as xs:integer


y()
Accessor for the snapped y coordinate

Params
  • vector as xs:double*: the vector
Returns
  • xs:integer: y coordinate, rounded
declare function this:y($vector as xs:double*) as xs:integer
{
  let $y := head(($vector[2],0))
  return (
    if ($y = xs:double("INF")) then $util:UINT64_MAX 
    else if ($y = xs:double("-INF")) then -$util:UINT64_MAX 
    else round-half-to-even($y) cast as xs:integer
  )
}

Function: z
declare function z($vector as xs:double*) as xs:integer


z()
Accessor for the snapped z coordinate

Params
  • vector as xs:double*: the vector
Returns
  • xs:integer: z coordinate, rounded
declare function this:z($vector as xs:double*) as xs:integer
{
  let $z := head(($vector[3],0))
  return (
    if ($z = xs:double("INF")) then $util:UINT64_MAX 
    else if ($z = xs:double("-INF")) then -$util:UINT64_MAX 
    else round-half-to-even($z) cast as xs:integer
  )
}

Function: w
declare function w($vector as xs:double*) as xs:integer


w()
Accessor for the snapped w coordinate

Params
  • vector as xs:double*: the vector
Returns
  • xs:integer: w coordinate, rounded
declare function this:w($vector as xs:double*) as xs:integer
{
  let $w := head(($vector[4],0))
  return (
    if ($w = xs:double("INF")) then $util:UINT64_MAX 
    else if ($w = xs:double("-INF")) then -$util:UINT64_MAX 
    else round-half-to-even($w) cast as xs:integer
  )
}

Function: magnitude
declare function magnitude($v as xs:double*) as xs:double


magnitude()
Magnitude of the vector from origin.

Params
  • v as xs:double*: the vector
Returns
  • xs:double: Euclidean length of vector
declare function this:magnitude($v as xs:double*) as xs:double
{
  math:sqrt(
    fn:sum(for $c in $v return $c*$c)
  )
}

Function: dot
declare function dot($p1 as xs:double*, $p2 as xs:double*) as xs:double


dot()
Compute the dot product of two vectors

Params
  • p1 as xs:double*: one vector
  • p2 as xs:double*: another vector
Returns
  • xs:double: p1·p2
declare function this:dot($p1 as xs:double*, $p2 as xs:double*) as xs:double
{
  fn:sum(
    this:map2(function ($a as xs:double, $b as xs:double) as xs:double {$a * $b}, $p1, $p2)
  )
}

Function: dot2
declare function dot2($p1 as xs:double*) as xs:double


dot2()
Compute the dot product of a vector with itself

Params
  • p1 as xs:double*
Returns
  • xs:double: p·p
declare function this:dot2($p1 as xs:double*) as xs:double
{
  fn:sum(
    this:map(function ($a as xs:double) as xs:double {$a * $a}, $p1)
  )
}

Function: ndot
declare function ndot($p1 as xs:double*, $p2 as xs:double*) as xs:double


Negated dot: useful for some of the SDF functions.

Params
  • p1 as xs:double*: one vector
  • p2 as xs:double*: another vector
Returns
  • xs:double: p1·-p2
declare function this:ndot($p1 as xs:double*, $p2 as xs:double*) as xs:double
{
  fn:sum(
    this:map2(function ($a as xs:double, $b as xs:double) as xs:double {$a * -$b}, $p1, $p2)
  )
}

Function: mix
declare function mix($u as xs:double*, $v as xs:double*, $f as xs:double) as xs:double*


Linear combination of two vectors, coordinate by coordinate.
Generally $f in in [0,1], but you can
get extrapolations with numbers outside that range.

Params
  • u as xs:double*: one vector
  • v as xs:double*: other vector
  • f as xs:double: combination fraction
Returns
  • xs:double*: vector of linear combination of each coordinate pair
declare function this:mix(
  $u as xs:double*,
  $v as xs:double*,
  $f as xs:double
) as xs:double*
{
  (: Logically $a + ($b - $a) * $f but better endvector behaviour :)
  this:map2(function ($a as xs:double, $b as xs:double) as xs:double {$a * (1 - $f) + $b * $f}, $u, $v)
}

Function: determinant
declare function determinant($u as xs:double*, $v as xs:double*) as xs:double


determinant()
Compute the determinant of two 2D vectors.

Params
  • u as xs:double*: one vector
  • v as xs:double*: another vector
Returns
  • xs:double
declare function this:determinant($u as xs:double*, $v as xs:double*) as xs:double
{
  this:px($u)*this:py($v) - this:py($u)*this:px($v)
}

Function: cross
declare function cross($u as xs:double*, $v as xs:double*) as xs:double*


cross()
Compute the cross product of two 3D vectors.

Params
  • u as xs:double*: one vector
  • v as xs:double*: another vector
Returns
  • xs:double*: uXv
declare function this:cross($u as xs:double*, $v as xs:double*) as xs:double*
{
  this:py($u)*this:pz($v) - this:pz($u)*this:py($v),
  this:pz($u)*this:px($v) - this:px($u)*this:pz($v),
  this:px($u)*this:py($v) - this:py($u)*this:px($v)
}

Function: round
declare function round($v as xs:double*) as xs:double*


round()
Round the coordinates of the vector.

Params
  • v as xs:double*: vector
Returns
  • xs:double*: rounded vector
declare function this:round($v as xs:double*) as xs:double*
{
  this:map(function ($c as xs:double) as xs:double {fn:round($c)}, $v)
}

Function: times
declare function times($v as xs:double*, $k as xs:double) as xs:double*


times()
Scale every coordinate by a constant, returning the new vector.

Params
  • v as xs:double*
  • k as xs:double: constant multiplier
Returns
  • xs:double*: scaled vector
declare function this:times($v as xs:double*, $k as xs:double) as xs:double*
{
  this:map(function ($c as xs:double) as xs:double {$k * $c}, $v)
}

Function: multiply
declare function multiply($u as xs:double*, $v as xs:double*) as xs:double*


multiply()
Coordinate-by-coordinate multiplication.

Params
  • u as xs:double*: one vector
  • v as xs:double*: another vector
Returns
  • xs:double*: new vector
declare function this:multiply($u as xs:double*, $v as xs:double*) as xs:double*
{
  this:map2(function ($a as xs:double, $b as xs:double) as xs:double {$a * $b}, $u, $v)
}

Function: div
declare function div($v as xs:double*, $k as xs:double) as xs:double*


div()
Divide every coordinate by a constant, returning the new vector.

Params
  • v as xs:double*
  • k as xs:double: constant multiplier
Returns
  • xs:double*: scaled vector
declare function this:div($v as xs:double*, $k as xs:double) as xs:double*
{
  this:map(function ($c as xs:double) as xs:double {($c div $k) cast as xs:double}, $v)
}

Function: divide
declare function divide($u as xs:double*, $v as xs:double*) as xs:double*


divide()
Coordinate-by-coordinate division

Params
  • u as xs:double*: one vector
  • v as xs:double*: another vector
Returns
  • xs:double*: new vector
declare function this:divide($u as xs:double*, $v as xs:double*) as xs:double*
{
  this:map2(function ($a as xs:double, $b as xs:double) as xs:double {$a div $b}, $u, $v)
}

Function: mod
declare function mod($v as xs:double*, $k as xs:double) as xs:double*


mod()
Compute the mod of every coordinate by a constant, returning the new vector.

Params
  • v as xs:double*
  • k as xs:double: constant modulus
Returns
  • xs:double*: new vector
declare function this:mod($v as xs:double*, $k as xs:double) as xs:double*
{
  this:map(function ($c as xs:double) as xs:double {$c mod $k}, $v)
}

Function: modulo
declare function modulo($u as xs:double*, $v as xs:double*) as xs:double*


divide()
Coordinate-by-coordinate modulo.

Params
  • u as xs:double*: one vector
  • v as xs:double*: another vector
Returns
  • xs:double*: new vector
declare function this:modulo($u as xs:double*, $v as xs:double*) as xs:double*
{
  this:map2(function ($a as xs:double, $b as xs:double) as xs:double {$a mod $b}, $u, $v)
}

Function: add
declare function add($u as xs:double*, $v as xs:double*) as xs:double*


add()
Add two vectors, returning the new vector.

Params
  • u as xs:double*: one vector
  • v as xs:double*: the other vector
Returns
  • xs:double*: u+v
declare function this:add($u as xs:double*, $v as xs:double*) as xs:double*
{
  this:map2(function ($a as xs:double, $b as xs:double) as xs:double {$a + $b}, $u, $v)
}

Function: plus
declare function plus($v as xs:double*, $k as xs:double) as xs:double*


plus()
Add a constant to every coordinate in a vector, returning the new vector.

Params
  • v as xs:double*: the vector
  • k as xs:double: constant to add
Returns
  • xs:double*: new vector
declare function this:plus($v as xs:double*, $k as xs:double) as xs:double*
{
  this:map(function ($a as xs:double) as xs:double {$a + $k}, $v)
}

Function: minus
declare function minus($v as xs:double*) as xs:double*


minus()
Negate the coordinates of the vector, returning the new vector.

Params
  • v as xs:double*: the vector
Returns
  • xs:double*: -v
declare function this:minus($v as xs:double*) as xs:double*
{
  this:map(function ($a as xs:double) as xs:double {-$a}, $v)
}

Function: minus
declare function minus($v as xs:double*, $k as xs:double) as xs:double*


minus()
Subtract a constant to every coordinate in a vector, returning the new vector.

Params
  • v as xs:double*: the vector
  • k as xs:double: constant to subtract
Returns
  • xs:double*: new vector
declare function this:minus($v as xs:double*, $k as xs:double) as xs:double*
{
  this:map(function ($a as xs:double) as xs:double {$a - $k}, $v)
}

Function: sub
declare function sub($u as xs:double*, $v as xs:double*) as xs:double*


sub()
Subtract one vector from another, returning the new vector.

Params
  • u as xs:double*: one vector
  • v as xs:double*: the other vector
Returns
  • xs:double*: u-v
declare function this:sub($u as xs:double*, $v as xs:double*) as xs:double*
{
  this:map2(function ($a as xs:double, $b as xs:double) as xs:double {$a - $b}, $u, $v)
}

Function: abs
declare function abs($v as xs:double*) as xs:double*


abs()
Coordinate-by-coordinate absolute value.

Params
  • v as xs:double*: vector
Returns
  • xs:double*: new vector
declare function this:abs($v as xs:double*) as xs:double*
{
  this:map(function ($a as xs:double) as xs:double {fn:abs($a)}, $v)
}

Function: sign
declare function sign($v as xs:double*) as xs:double*


sign()
Coordinate-by-coordinate sign.

Params
  • v as xs:double*: vector
Returns
  • xs:double*: new vector
declare function this:sign($v as xs:double*) as xs:double*
{
  this:map(
    function ($a as xs:double) as xs:double {
      if ($a = 0) then 0E0
      else if ($a < 0) then -1E0
      else 1E0
    },
    $v
  )
}

Function: min
declare function min($u as xs:double*, $v as xs:double*) as xs:double*


min()
Coordinate-by-coordinate minimum: minimum of each coordinate pair.

Params
  • u as xs:double*: one vector
  • v as xs:double*: another vector
Returns
  • xs:double*: new vector
declare function this:min($u as xs:double*, $v as xs:double*) as xs:double*
{
  this:map2(function ($a as xs:double, $b as xs:double) as xs:double {fn:min(($a,$b))}, $u, $v)
}

Function: max
declare function max($u as xs:double*, $v as xs:double*) as xs:double*


max()
Coordinate-by-coordinate maximum: maximum of each coordinate pair.

Params
  • u as xs:double*: one vector
  • v as xs:double*: another vector
Returns
  • xs:double*: new vector
declare function this:max($u as xs:double*, $v as xs:double*) as xs:double*
{
  this:map2(function ($a as xs:double, $b as xs:double) as xs:double {fn:max(($a,$b))}, $u, $v)
}

Function: at-min
declare function at-min($v as xs:double*, $val as xs:double) as xs:double*


at-min()
Coordinate-by-coordinate minimum: minimum of each coordinate with value.

Params
  • v as xs:double*: vectoral: comparison value
  • val as xs:double: comparison value
Returns
  • xs:double*: new vector
declare function this:at-min($v as xs:double*, $val as xs:double) as xs:double*
{
  this:map(function ($a as xs:double) as xs:double {fn:min(($a, $val))}, $v)
}

Function: at-max
declare function at-max($v as xs:double*, $val as xs:double) as xs:double*


at-max()
Coordinate-by-coordinate maximum: maximum of each coordinate with value.

Params
  • v as xs:double*: vectoral: comparison value
  • val as xs:double: comparison value
Returns
  • xs:double*: new vector
declare function this:at-max($v as xs:double*, $val as xs:double) as xs:double*
{
  this:map(function ($a as xs:double) as xs:double {fn:max(($a, $val))}, $v)
}

Function: normalize
declare function normalize($v as xs:double*) as xs:double*


normalize()
Normalize a vector represented as a vector (scale to unit vector), returning
the new vector.

Params
  • v as xs:double*: the vector
Returns
  • xs:double*: normalized vector
declare function this:normalize($v as xs:double*) as xs:double*
{
  let $l := this:magnitude($v)
  return
    if ($l = 0) then $v
    else this:map(function ($c as xs:double) as xs:double {$c div $l}, $v)
}

Function: clamp
declare function clamp($v as xs:double*, $min as xs:double, $max as xs:double) as xs:double*


clamp()
Coordinate-by-coordinate clamping.

Params
  • v as xs:double*: vector
  • min as xs:double: minimum value
  • max as xs:double: maximum value
Returns
  • xs:double*: new vector
declare function this:clamp($v as xs:double*, $min as xs:double, $max as xs:double) as xs:double*
{
  this:map(function ($a as xs:double) as xs:double {util:clamp($a, $min, $max)}, $v)
}

Function: clampv
declare function clampv($v as xs:double*, $minv as xs:double*, $maxv as xs:double*) as xs:double*


clampv()
Coordinate-by-coordinate clamping: clamp with corresponding coordinates of
min/max vectors.

Params
  • v as xs:double*: vector
  • minv as xs:double*: minimum values
  • maxv as xs:double*: maximum values
Returns
  • xs:double*: new vector
declare function this:clampv($v as xs:double*, $minv as xs:double*, $maxv as xs:double*) as xs:double*
{
  this:map3(function ($a as xs:double, $min as xs:double?, $max as xs:double?) as xs:double {util:clamp($a, $min, $max)}, $v, $minv, $maxv)
}

Function: perpendicular
declare function perpendicular($v as xs:double*) as xs:double*


perpendicular()
Compute the perpendicular vector for a 2D vector represented as a vector.
(Rotated by 90 degrees.)

Params
  • v as xs:double*: the vector
Returns
  • xs:double*: perpendicular vector
declare function this:perpendicular($v as xs:double*) as xs:double*
{
  (this:py($v), -this:px($v))
}

Function: snap
declare function snap($vector as xs:double*) as xs:integer*


snap()
Snap the coordinates of the vector, returning the vector with snapped
(i.e. integer) coordinates. NaN snaps to UINT64_MAX.

Params
  • vector as xs:double*: the vector
Returns
  • xs:integer*
declare function this:snap($vector as xs:double*) as xs:integer*
{
  for-each($vector,
    function ($c as xs:double) as xs:integer {
      if ($c = xs:double("INF") or not($c = $c)) then $util:UINT64_MAX 
      else if ($c = xs:double("-INF")) then -$util:UINT64_MAX
      else round-half-to-even($c) cast as xs:integer
    }
  )
}

Function: floor
declare function floor($vector as xs:double*) as xs:integer*


floor()
Return vector where all the coordinates have been set to the floor of
the original vector's values. Differs from snap() for negative coordinates.

Params
  • vector as xs:double*: the vector
Returns
  • xs:integer*
declare function this:floor($vector as xs:double*) as xs:integer*
{
  for-each($vector, function ($c as xs:double) as xs:integer {fn:floor($c) cast as xs:integer})
}

Function: decimal
declare function decimal($vector as xs:double*, $digits as xs:integer) as xs:double*


decimal()
Perform decimal rounding on all the coordinates (see util:decimal).

Params
  • vector as xs:double*: the vector to round
  • digits as xs:integer: how many digits after the decimal vector to keep
Returns
  • xs:double*
declare function this:decimal($vector as xs:double*, $digits as xs:integer) as xs:double*
{
  for-each($vector, function ($c as xs:double) as xs:double {util:decimal($c, $digits)})
}

Function: quote
declare function quote($vector as xs:double*) as xs:string


quote()
Return a string value for the vector, suitable for debugging.

Params
  • vector as xs:double*: the vector to quote
Returns
  • xs:string
declare function this:quote($vector as xs:double*) as xs:string
{
  string-join($vector!string(.),",")
}

Function: valid
declare function valid($point as xs:double*) as xs:boolean


valid()
False if a coordinate is INF or NaN

Params
  • point as xs:double*
Returns
  • xs:boolean
declare function this:valid($point as xs:double*) as xs:boolean
{
  every $c in $point
  satisfies $c=$c and not($c=(xs:double("INF"),xs:double("-INF")))
}

Function: same
declare function same($this as xs:double*, $other as xs:double*) as xs:boolean


same()
Equality comparison for vectors, ignoring annotation properties.
Return true() if they have equal coordinates.

Params
  • this as xs:double*: one vector
  • other as xs:double*: the vector to compare it to
Returns
  • xs:boolean: this=other
declare function this:same($this as xs:double*, $other as xs:double*) as xs:boolean
{
  let $d := max((count($this), count($other)))
  return (
    deep-equal(
      this:as-dimension($this, $d),
      this:as-dimension($other, $d)
    )
  )
}

Function: distance
declare function distance($v1 as xs:double*, $v2 as xs:double*) as xs:double


distance()
Euclidean distance between vectors

Params
  • v1 as xs:double*: one vector
  • v2 as xs:double*: another vector
Returns
  • xs:double: distance(v1,v2)
declare function this:distance($v1 as xs:double*, $v2 as xs:double*) as xs:double
{
  let $d := fn:max((this:dimension($v1), this:dimension($v2)))
  return (
    math:sqrt(
      fn:sum(
        util:zip(
          function ($c1 as xs:double, $c2 as xs:double) as xs:double {
            ($c1 - $c2)*($c1 - $c2)
          },
          this:as-dimension($v1, $d),
          this:as-dimension($v2, $d)
        )
      )
    )
  )
}

Function: polar-distance
declare function polar-distance($p as xs:double*, $q as xs:double*) as xs:double


polar-distance()
Angular distance between points taken as vectors.

Params
  • p as xs:double*
  • q as xs:double*
Returns
  • xs:double
declare function this:polar-distance(
  $p as xs:double*,
  $q as xs:double*
) as xs:double
{
  abs(this:angle((0,0), $p) - this:angle((0,0), $q))
}

Function: taxi-distance
declare function taxi-distance($p as xs:double*, $q as xs:double*) as xs:double


taxi-distance()
Taxi-cab distance between two points. That is, right angle distance, such as
a taxi going 2 blocks North plus 3 blocks East = distance 5 blocks.

Params
  • p as xs:double*
  • q as xs:double*
Returns
  • xs:double
declare function this:taxi-distance(
  $p as xs:double*,
  $q as xs:double*
) as xs:double
{
  let $d := max((count($p), count($q)))
  return (
    sum(
      util:zip(
        function ($cp as xs:double, $cq as xs:double) as xs:double {
          abs($cp - $cq)
        },
        this:as-dimension($p, $d),
        this:as-dimension($q, $d)
      )
    )
  )
}

Function: avg-distance
declare function avg-distance($p as xs:double*, $q as xs:double*) as xs:double


avg-distance()
Average of distances along each dimension.

Params
  • p as xs:double*
  • q as xs:double*
Returns
  • xs:double
declare function this:avg-distance(
  $p as xs:double*,
  $q as xs:double*
) as xs:double
{
  let $d := max((count($p), count($q)))
  return (
    avg(
      util:zip(
        function ($cp as xs:double, $cq as xs:double) as xs:double {
          abs($cp - $cq)
        },
        this:as-dimension($p, $d),
        this:as-dimension($q, $d)
      )
    )
  )
}

Function: rail-distance
declare function rail-distance($p as xs:double*, $q as xs:double*) as xs:double


rail-distance()
Railway distance, e.g distance to the central hub and back out.
The hub is the origin.

Params
  • p as xs:double*
  • q as xs:double*
Returns
  • xs:double
declare function this:rail-distance(
  $p as xs:double*,
  $q as xs:double*
) as xs:double
{
  this:distance($p, (0,0)) + this:distance($q, (0,0))
}

Function: rail-distance
declare function rail-distance($p as xs:double*, $q as xs:double*, $origin as xs:double*) as xs:double


rail-distance()
Railway distance, e.g distance to the central hub and back out where the hub
is specified.

Params
  • p as xs:double*
  • q as xs:double*
  • origin as xs:double*
Returns
  • xs:double
declare function this:rail-distance(
  $p as xs:double*,
  $q as xs:double*,
  $origin as xs:double*
) as xs:double
{
  this:distance($p, $origin) + this:distance($q, $origin)
}

Function: chebyshev-distance
declare function chebyshev-distance($p as xs:double*, $q as xs:double*) as xs:double


chebyshev-distance()
Chebyshev distance metric. The maximum of the dimensional distances.

Params
  • p as xs:double*
  • q as xs:double*
Returns
  • xs:double
declare function this:chebyshev-distance(
  $p as xs:double*,
  $q as xs:double*
) as xs:double
{
  let $d := max((count($p), count($q)))
  return (
    max(
      util:zip(
        function ($cp as xs:double, $cq as xs:double) as xs:double {
          abs($cp - $cq)
        },
        this:as-dimension($p, $d),
        this:as-dimension($q, $d)
      )
    )
  )
}

Function: minkowski-distance
declare function minkowski-distance($p as xs:double*, $q as xs:double*, $order as xs:double) as xs:double


minwokski-distance()
Minkowski distance metric of the given order. Certain orders give rise to
other metrics:
order=1=>taxi; order=2=>Euclidean; as order=>∞=>Chebyshev;
order<1 is not a metric unless undo the final pow. i.e. X is a metric but
X^(1/order) is not.

Params
  • p as xs:double*
  • q as xs:double*
  • order as xs:double
Returns
  • xs:double
declare function this:minkowski-distance(
  $p as xs:double*,
  $q as xs:double*,
  $order as xs:double
) as xs:double
{
  let $d := max((count($p), count($q)))
  return (
    math:pow(
      sum(
        util:zip(
          function ($cp as xs:double, $cq as xs:double) as xs:double {
            math:pow(abs($cp - $cq), $order)
          },
          this:as-dimension($p, $d),
          this:as-dimension($q, $d)
        )
      ),
      1 div $order
    )
  )
}

Function: cosine-similarity
declare function cosine-similarity($p as xs:double*, $q as xs:double*) as xs:double


cosine-similarity()
Cosine similarity: (A·B)/(∥A∥∥B∥) = ΣAiBi/(√Ai²√Bi²)

Params
  • p as xs:double*
  • q as xs:double*
Returns
  • xs:double
declare function this:cosine-similarity(
  $p as xs:double*,
  $q as xs:double*
) as xs:double
{
  this:dot($p, $q) div (this:magnitude($p)*this:magnitude($q))
}

Function: cosine-distance
declare function cosine-distance($p as xs:double*, $q as xs:double*) as xs:double


cosine-distance()
Cosine similarity as a metric: acos(similarity)/π => [0,1]

Params
  • p as xs:double*
  • q as xs:double*
Returns
  • xs:double
declare function this:cosine-distance(
  $p as xs:double*,
  $q as xs:double*
) as xs:double
{
  math:acos(this:cosine-similarity($p, $q)) div math:pi()
}

Function: angle
declare function angle($last as xs:double*, $curr as xs:double*) as xs:double


angle()
Compute the angle (azimuth) from one point to the next, in degrees
Return 0 if points are the same

Params
  • last as xs:double*: previous point; use (0,0) if no previous
  • curr as xs:double*: current point
Returns
  • xs:double
declare function this:angle($last as xs:double*, $curr as xs:double*) as xs:double
{
  let $this := $curr=>this:as-dimension(2)
  let $prev := if (empty($last)) then (0,0) else this:as-dimension($last,2)
  return
    if (this:same($prev, $this) or (this:distance($prev,$this) < $config:ε)) then 0
    else if (this:px($this)=this:px($prev)) then (
      if (this:py($prev) > this:py($this))
      then 270 (: remapped -90 :)
      else 90
    ) else (
      util:remap-degrees(util:degrees(
        (math:pi() div 2) - math:atan2(this:px($this) - this:px($prev), this:py($this) - this:py($prev))
      ))
    )
}

Function: inclination
declare function inclination($last as xs:double*, $curr as xs:double*) as xs:double


inclination()
Compute the inclination angle from one point in the next, in degrees
Return 90 if the points are the same

Params
  • last as xs:double*: previous point; use (0,0,0) if no previous
  • curr as xs:double*: current point
Returns
  • xs:double
declare function this:inclination($last as xs:double*, $curr as xs:double*) as xs:double
{
  let $curr := $curr=>this:as-dimension(3)
  let $prev := if (empty($last)) then (0,0,0) else $last=>this:as-dimension(3)
  let $d := this:distance($prev,$curr)
  return
    if (this:same($prev,$curr) or ($d < $config:ε)) then 90
    else (
      util:remap-degrees(util:degrees(
        math:acos( (this:pz($curr) - this:pz($prev)) div $d )
      ))
    )
}

Function: destination
declare function destination($point as xs:double*, $degrees as xs:double, $length as xs:double) as xs:double*


destination()
Point at a particular distance and direction.

Params
  • point as xs:double*: starting point
  • degrees as xs:double: bearing from point
  • length as xs:double: how far along bearing to travel
Returns
  • xs:double*
declare function this:destination(
  $point as xs:double*,
  $degrees as xs:double,
  $length as xs:double
) as xs:double*
{
  let $angle := util:radians($degrees)
  return
    (
      this:px($point) + math:cos($angle)*$length,
      this:py($point) + math:sin($angle)*$length
    )
}

Function: mutate
declare function mutate($vectors as xs:double*, $mutate as function(xs:double*) as xs:double*) as xs:double*


mutate()
Mutate a sequence of 2D vectors using a mutation function.

Params
  • vectors as xs:double*: the vectors
  • mutate as function(xs:double*)asxs:double*: function that takes a vector as an argument and returns a new vector
Returns
  • xs:double*
declare function this:mutate(
  $vectors as xs:double*,
  $mutate as function(xs:double*) as xs:double*
) as xs:double*
{
  for $i in 1 to count($vectors) idiv 2 return (
    $mutate(($vectors[2*$i - 1], $vectors[2*$i]))
  )
}

Original Source Code

xquery version "3.1";
(:~
 : Vectors of numbers
 :
 : Copyright© Mary Holstege 2020-2023
 : CC-BY (https://creativecommons.org/licenses/by/4.0/)
 : @since May 2021
 : @custom:Status Incomplete
 :)
module namespace this="http://mathling.com/core/vector"; 

import module namespace config="http://mathling.com/core/config"
       at "../core/config.xqy";
import module namespace errors="http://mathling.com/core/errors"
       at "../core/errors.xqy";
import module namespace util="http://mathling.com/core/utilities"
       at "../core/utilities.xqy";

declare namespace math="http://www.w3.org/2005/xpath-functions/math";

(:~
 : as-dimension()
 : Cast the vector to a vector with the given number of dimensions.
 : If we are reducing dimensions, just remove excess coordinates. 
 : If we are expanding dimensions, fill in with 0.
 :
 : @param $vector: source vector
 : @param $dim: new dimension of vector
 : @return new vector
 :)
declare function this:as-dimension($vector as xs:double*, $dim as xs:integer) as xs:double*
{
  ($vector, for $i in 1 to $dim return 0E0)[position() <= $dim]
};

(:~
 : unquote()
 : Parse a string representation of a vector and produce a vector.
 : The string consists of the coordinates in with commas and 
 : optional spaces in between.
 :
 : @param $string: the input string to parse
 : @return vector
 :)
declare function this:unquote($string as xs:string) as xs:double*
{
  tokenize($string, "[ ]*,[ ]*")!xs:double(.)
};

(:~
 : dimension()
 : Return the dimension of the vector.
 :
 : @param $vector: the vector
 : @return length of vector
 :)
declare function this:dimension($vector as xs:double*) as xs:integer
{
  count($vector)
};

(:~
 : map()
 : Map a function over the coordinates of a vector, returning the new vector.
 : 
 : @param $f: function from coordinate to coordinate
 : @param $vector: input vector
 : @return application of function to each coordinate
 :)
declare function this:map(
  $f as function(xs:double) as xs:double,
  $vector as xs:double*
) as xs:double*
{
  for-each($vector, $f)
};

(:~
 : map()
 : Map a function over the coordinates of a vector, returning the new vector.
 : Treat the vector as having a particular dimension.
 : 
 : @param $f: function from coordinate to coordinate
 : @param $vector: input vector
 : @param $d: the effective dimension of the vector
 : @return application of function to coordinates
 :)
declare function this:map(
  $f as function(xs:double) as xs:double,
  $vector as xs:double*,
  $d as xs:integer
) as xs:double*
{
  for-each(this:as-dimension($vector, $d), $f)
};

(:~
 : map2()
 : Map a function over the coordinates of two vectors, pair by pair, returning
 : the new vector. That is, apply function to the x coordinates, then the
 : y coordinates, and so on.
 : 
 : @param $f: function from coordinate pairs to coordinate
 : @param $p1: one input vector
 : @param $p2: another input vector
 : @return application of function to each pair
 :)
declare function this:map2(
  $f as function(xs:double, xs:double) as xs:double,
  $p1 as xs:double*,
  $p2 as xs:double*
) as xs:double*
{
  util:zip($f, $p1, $p2)
};

(:~
 : map2()
 : Map a function over the coordinates of two vectors, pair by pair, returning
 : the new vector. That is, apply function to the x coordinates, then the
 : y coordinates, and so on. Treat the vector as having a particular dimension.
 : 
 : @param $f: function from coordinate pairs to coordinate
 : @param $p1: one input vector
 : @param $p2: another input vector
 : @param $d: the effective dimension of the vectors
 : @return application of function to each pair
 :)
declare function this:map2(
  $f as function(xs:double, xs:double) as xs:double,
  $p1 as xs:double*,
  $p2 as xs:double*,
  $d as xs:integer
) as xs:double*
{
  util:zip($f, this:as-dimension($p1, $d), this:as-dimension($p2, $d))
};


(:~
 : map3()
 : Map a function over the coordinates of three vectors, triple by triple, 
 : returning the new vector. That is, apply function to the x coordinates, 
 : then the y coordinates, and so on.
 : 
 : @param $f: function from coordinate pairs to coordinate
 : @param $p1: one input vector
 : @param $p2: another input vector
 : @param $p3: third input vector
 : @return application of function to each triple
 :)
declare function this:map3(
  $f as function(xs:double, xs:double, xs:double) as xs:double,
  $p1 as xs:double*,
  $p2 as xs:double*,
  $p3 as xs:double*
) as xs:double*
{
  util:zip(
    function ($a as xs:double, $fc as function(xs:double) as xs:double) as xs:double {$fc($a)},
    $p3,
    util:zip(
      function ($a as xs:double, $b as xs:double) as function(*) { $f($a, $b, ?) },
      $p1, $p2
    )
  )
};

(:~
 : map3()
 : Map a function over the coordinates of three vectors, triple by triple, 
 : returning the new vector. That is, apply function to the x coordinates, 
 : then the y coordinates, and so on. Treat the vector as having a 
 : particular dimension.
 : 
 : @param $f: function from coordinate pairs to coordinate
 : @param $p1: one input vector
 : @param $p2: another input vector
 : @param $p3: third input vector
 : @param $d: the effective dimension of the vectors
 : @return application of function to each triple
 :)
declare function this:map3(
  $f as function(xs:double, xs:double, xs:double) as xs:double,
  $p1 as xs:double*,
  $p2 as xs:double*,
  $p3 as xs:double*,
  $d as xs:integer
) as xs:double*
{
  util:zip(
    function ($a as xs:double, $fc as function(xs:double) as xs:double) as xs:double {$fc($a)},
    this:as-dimension($p3, $d),
    util:zip(
      function ($a as xs:double, $b as xs:double) as function(*) { $f($a, $b, ?) },
      this:as-dimension($p1, $d), this:as-dimension($p2, $d)
    )
  )
};

(:~
 : map4()
 : Map a function over the coordinates of four vectors, coordinate by
 : coordinate, returning the new vector. That is, apply function to the x
 : coordinates, then the y coordinates, and so on.
 : 
 : @param $f: function from coordinate pairs to coordinate
 : @param $p1: one input vector
 : @param $p2: another input vector
 : @param $p3: third input vector
 : @param $p4: fourth input vector
 : @return application of function to each quad
 :)
declare function this:map4(
  $f as function(xs:double, xs:double, xs:double, xs:double) as xs:double,
  $p1 as xs:double*,
  $p2 as xs:double*,
  $p3 as xs:double*,
  $p4 as xs:double*
) as xs:double*
{
  util:zip(
    function ($d as xs:double, $fd as function(xs:double) as xs:double) as xs:double {$fd($d)},
    $p4,
    util:zip(
      function ($c as xs:double, $fc as function(xs:double, xs:double) as xs:double) as function(*) {$fc($c, ?)},
      $p3,
      util:zip(
        function ($a as xs:double, $b as xs:double) as function(*) { $f($a, $b, ?, ?) },
        $p1, $p2
      )
    )
  )
};

(:~
 : map4()
 : Map a function over the coordinates of four vectors, coordinate by
 : coordinate, returning the new vector. That is, apply function to the x
 : coordinates, then the y coordinates, and so on. Treat the vector as having a 
 : particular dimension.
 : 
 : @param $f: function from coordinate pairs to coordinate
 : @param $p1: one input vector
 : @param $p2: another input vector
 : @param $p3: third input vector
 : @param $p4: fourth input vector
 : @param $d: the effective dimension of the vectors
 : @return application of function to each quad
 :)
declare function this:map4(
  $f as function(xs:double, xs:double, xs:double, xs:double) as xs:double,
  $p1 as xs:double*,
  $p2 as xs:double*,
  $p3 as xs:double*,
  $p4 as xs:double*,
  $dim as xs:integer
) as xs:double*
{
  util:zip(
    function ($d as xs:double, $fd as function(xs:double) as xs:double) as xs:double {$fd($d)},
    this:as-dimension($p4, $dim),
    util:zip(
      function ($c as xs:double, $fc as function(xs:double, xs:double) as xs:double) as function(*) {$fc($c, ?)},
      this:as-dimension($p3, $dim),
      util:zip(
        function ($a as xs:double, $b as xs:double) as function(*) { $f($a, $b, ?, ?) },
        this:as-dimension($p1, $dim), this:as-dimension($p2, $dim)
      )
    )
  )
};

(:~ 
 : px()
 : Accessor for the raw precision x coordinate
 :
 : @param $vector: the vector
 : @return raw x coordinate
 :)
declare function this:px($vector as xs:double*) as xs:double
{
  head(($vector[1],0E0))
};

(:~ 
 : py()
 : Accessor for the raw precision y coordinate
 :
 : @param $vector: the vector
 : @return raw y coordinate
 :)
declare function this:py($vector as xs:double*) as xs:double
{
  head(($vector[2],0E0))
};


(:~ 
 : pz()
 : Accessor for the raw precision z coordinate; returns 0 for 2D vector
 :
 : @param $vector: the vector
 : @return raw z coordinate
 :)
declare function this:pz($vector as xs:double*) as xs:double
{
  head(($vector[3],0E0))
};

(:~ 
 : pw()
 : Accessor for the raw precision w coordinate; returns 0 for 2D or 3D vector
 :
 : @param $vector: the vector
 : @return raw w coordinate
 :)
declare function this:pw($vector as xs:double*) as xs:double
{
  head(($vector[4],0E0))
};

(:~ 
 : pxy()
 : Accessor for the raw precision xy vector.
 :
 : @param $vector: the vector
 : @return new vector of just the raw x and y coordinates
 :)
declare function this:pxy($vector as xs:double*) as xs:double*
{
  head(($vector[1],0E0)), head(($vector[1],0E0))
};

(:~ 
 : pxz()
 : Accessor for the raw precision xz vector.
 :
 : @param $vector: the vector
 : @return new vector of just the raw x and z coordinates
 :)
declare function this:pxz($vector as xs:double*) as xs:double*
{
  head(($vector[1],0E0)), head(($vector[3],0E0))
};

(:~ 
 : x()
 : Accessor for the snapped x coordinate
 :
 : @param $vector: the vector
 : @return x coordinate, rounded
 :)
declare function this:x($vector as xs:double*) as xs:integer
{
  let $x := head(($vector[1],0))
  return (
    if ($x = xs:double("INF")) then $util:UINT64_MAX 
    else if ($x = xs:double("-INF")) then -$util:UINT64_MAX 
    else round-half-to-even($x) cast as xs:integer
  )
};

(:~ 
 : y()
 : Accessor for the snapped y coordinate
 :
 : @param $vector: the vector
 : @return y coordinate, rounded
 :)
declare function this:y($vector as xs:double*) as xs:integer
{
  let $y := head(($vector[2],0))
  return (
    if ($y = xs:double("INF")) then $util:UINT64_MAX 
    else if ($y = xs:double("-INF")) then -$util:UINT64_MAX 
    else round-half-to-even($y) cast as xs:integer
  )
};

(:~ 
 : z()
 : Accessor for the snapped z coordinate
 :
 : @param $vector: the vector
 : @return z coordinate, rounded
 :)
declare function this:z($vector as xs:double*) as xs:integer
{
  let $z := head(($vector[3],0))
  return (
    if ($z = xs:double("INF")) then $util:UINT64_MAX 
    else if ($z = xs:double("-INF")) then -$util:UINT64_MAX 
    else round-half-to-even($z) cast as xs:integer
  )
};

(:~ 
 : w()
 : Accessor for the snapped w coordinate
 :
 : @param $vector: the vector
 : @return w coordinate, rounded
 :)
declare function this:w($vector as xs:double*) as xs:integer
{
  let $w := head(($vector[4],0))
  return (
    if ($w = xs:double("INF")) then $util:UINT64_MAX 
    else if ($w = xs:double("-INF")) then -$util:UINT64_MAX 
    else round-half-to-even($w) cast as xs:integer
  )
};

(:~
 : magnitude()
 : Magnitude of the vector from origin.
 : 
 : @param $v: the vector
 : @return Euclidean length of vector
 :)
declare function this:magnitude($v as xs:double*) as xs:double
{
  math:sqrt(
    fn:sum(for $c in $v return $c*$c)
  )
};

(:~
 : dot()
 : Compute the dot product of two vectors
 : 
 : @param $p1: one vector
 : @param $p2: another vector
 : @return p1·p2
 :)
declare function this:dot($p1 as xs:double*, $p2 as xs:double*) as xs:double
{
  fn:sum(
    this:map2(function ($a as xs:double, $b as xs:double) as xs:double {$a * $b}, $p1, $p2)
  )
};

(:~
 : dot2()
 : Compute the dot product of a vector with itself
 : 
 : @param $p: the vector
 : @return p·p
 :)
declare function this:dot2($p1 as xs:double*) as xs:double
{
  fn:sum(
    this:map(function ($a as xs:double) as xs:double {$a * $a}, $p1)
  )
};

(:~
 : Negated dot: useful for some of the SDF functions.
 : 
 : @param $p1: one vector
 : @param $p2: another vector
 : @return p1·-p2
 :)
declare function this:ndot($p1 as xs:double*, $p2 as xs:double*) as xs:double
{
  fn:sum(
    this:map2(function ($a as xs:double, $b as xs:double) as xs:double {$a * -$b}, $p1, $p2)
  )
};

(:~
 : Linear combination of two vectors, coordinate by coordinate.
 : Generally $f in in [0,1], but you can
 : get extrapolations with numbers outside that range.
 :
 : @param $u: one vector
 : @param $v: other vector
 : @param $f: combination fraction
 : @return vector of linear combination of each coordinate pair
 :)
declare function this:mix(
  $u as xs:double*,
  $v as xs:double*,
  $f as xs:double
) as xs:double*
{
  (: Logically $a + ($b - $a) * $f but better endvector behaviour :)
  this:map2(function ($a as xs:double, $b as xs:double) as xs:double {$a * (1 - $f) + $b * $f}, $u, $v)
};

(:~
 : determinant()
 : Compute the determinant of two 2D vectors.
 :
 : @param $u: one vector
 : @param $v: another vector
 : @param det(u,v)
 :)
declare function this:determinant($u as xs:double*, $v as xs:double*) as xs:double
{
  this:px($u)*this:py($v) - this:py($u)*this:px($v)
};

(:~
 : cross()
 : Compute the cross product of two 3D vectors.
 :
 : @param $u: one vector
 : @param $v: another vector
 : @return uXv
 :)
declare function this:cross($u as xs:double*, $v as xs:double*) as xs:double*
{
  this:py($u)*this:pz($v) - this:pz($u)*this:py($v),
  this:pz($u)*this:px($v) - this:px($u)*this:pz($v),
  this:px($u)*this:py($v) - this:py($u)*this:px($v)
};

(:~
 : round()
 : Round the coordinates of the vector.
 :
 : @param $v: vector
 : @return rounded vector
 :)
declare function this:round($v as xs:double*) as xs:double*
{
  this:map(function ($c as xs:double) as xs:double {fn:round($c)}, $v)
};

(:~
 : times()
 : Scale every coordinate by a constant, returning the new vector.
 :
 : @param $u: vector to scale
 : @param $k: constant multiplier
 : @return scaled vector
 :)
declare function this:times($v as xs:double*, $k as xs:double) as xs:double*
{
  this:map(function ($c as xs:double) as xs:double {$k * $c}, $v)
};

(:~
 : multiply()
 : Coordinate-by-coordinate multiplication.
 :
 : @param $u: one vector
 : @param $v: another vector
 : @return new vector
 :)
declare function this:multiply($u as xs:double*, $v as xs:double*) as xs:double*
{
  this:map2(function ($a as xs:double, $b as xs:double) as xs:double {$a * $b}, $u, $v)
};

(:~
 : div()
 : Divide every coordinate by a constant, returning the new vector.
 :
 : @param $u: vector to scale
 : @param $k: constant multiplier
 : @return scaled vector
 :)
declare function this:div($v as xs:double*, $k as xs:double) as xs:double*
{
  this:map(function ($c as xs:double) as xs:double {($c div $k) cast as xs:double}, $v)
};

(:~
 : divide()
 : Coordinate-by-coordinate division
 :
 : @param $u: one vector
 : @param $v: another vector
 : @return new vector
 :)
declare function this:divide($u as xs:double*, $v as xs:double*) as xs:double*
{
  this:map2(function ($a as xs:double, $b as xs:double) as xs:double {$a div $b}, $u, $v)
};

(:~
 : mod()
 : Compute the mod of every coordinate by a constant, returning the new vector.
 :
 : @param $u: vector
 : @param $k: constant modulus
 : @return new vector
 :)
declare function this:mod($v as xs:double*, $k as xs:double) as xs:double*
{
  this:map(function ($c as xs:double) as xs:double {$c mod $k}, $v)
};

(:~
 : divide()
 : Coordinate-by-coordinate modulo.
 :
 : @param $u: one vector
 : @param $v: another vector
 : @return new vector
 :)
declare function this:modulo($u as xs:double*, $v as xs:double*) as xs:double*
{
  this:map2(function ($a as xs:double, $b as xs:double) as xs:double {$a mod $b}, $u, $v)
};

(:~
 : add()
 : Add two vectors, returning the new vector.
 : 
 : @param $u: one vector
 : @param $v: the other vector
 : @return u+v
 :)
declare function this:add($u as xs:double*, $v as xs:double*) as xs:double*
{
  this:map2(function ($a as xs:double, $b as xs:double) as xs:double {$a + $b}, $u, $v)
};

(:~
 : plus()
 : Add a constant to every coordinate in a vector, returning the new vector.
 : 
 : @param $v: the vector
 : @param $k: constant to add
 : @return new vector
 :)
declare function this:plus($v as xs:double*, $k as xs:double) as xs:double*
{
  this:map(function ($a as xs:double) as xs:double {$a + $k}, $v)
};

(:~
 : minus()
 : Negate the coordinates of the vector, returning the new vector.
 : 
 : @param $v: the vector
 : @return -v
 :)
declare function this:minus($v as xs:double*) as xs:double*
{
  this:map(function ($a as xs:double) as xs:double {-$a}, $v)
};

(:~
 : minus()
 : Subtract a constant to every coordinate in a vector, returning the new vector.
 : 
 : @param $v: the vector
 : @param $k: constant to subtract
 : @return new vector
 :)
declare function this:minus($v as xs:double*, $k as xs:double) as xs:double*
{
  this:map(function ($a as xs:double) as xs:double {$a - $k}, $v)
};

(:~
 : sub()
 : Subtract one vector from another, returning the new vector.
 : 
 : @param $u: one vector
 : @param $v: the other vector
 : @return u-v
 :)
declare function this:sub($u as xs:double*, $v as xs:double*) as xs:double*
{
  this:map2(function ($a as xs:double, $b as xs:double) as xs:double {$a - $b}, $u, $v)
};

(:~
 : abs()
 : Coordinate-by-coordinate absolute value.
 :
 : @param $v: vector
 : @return new vector
 :)
declare function this:abs($v as xs:double*) as xs:double*
{
  this:map(function ($a as xs:double) as xs:double {fn:abs($a)}, $v)
};

(:~
 : sign()
 : Coordinate-by-coordinate sign.
 :
 : @param $v: vector
 : @return new vector
 :)
declare function this:sign($v as xs:double*) as xs:double*
{
  this:map(
    function ($a as xs:double) as xs:double {
      if ($a = 0) then 0E0
      else if ($a < 0) then -1E0
      else 1E0
    },
    $v
  )
};

(:~
 : min()
 : Coordinate-by-coordinate minimum: minimum of each coordinate pair.
 :
 : @param $u: one vector
 : @param $v: another vector
 : @return new vector
 :)
declare function this:min($u as xs:double*, $v as xs:double*) as xs:double*
{
  this:map2(function ($a as xs:double, $b as xs:double) as xs:double {fn:min(($a,$b))}, $u, $v)
};

(:~
 : max()
 : Coordinate-by-coordinate maximum: maximum of each coordinate pair.
 :
 : @param $u: one vector
 : @param $v: another vector
 : @return new vector
 :)
declare function this:max($u as xs:double*, $v as xs:double*) as xs:double*
{
  this:map2(function ($a as xs:double, $b as xs:double) as xs:double {fn:max(($a,$b))}, $u, $v)
};

(:~
 : at-min()
 : Coordinate-by-coordinate minimum: minimum of each coordinate with value.
 :
 : @param $v: vector
 : @param $val: comparison value
 : @return new vector
 :)
declare function this:at-min($v as xs:double*, $val as xs:double) as xs:double*
{
  this:map(function ($a as xs:double) as xs:double {fn:min(($a, $val))}, $v)
};

(:~
 : at-max()
 : Coordinate-by-coordinate maximum: maximum of each coordinate with value.
 :
 : @param $v: vector
 : @param $val: comparison value
 : @return new vector
 :)
declare function this:at-max($v as xs:double*, $val as xs:double) as xs:double*
{
  this:map(function ($a as xs:double) as xs:double {fn:max(($a, $val))}, $v)
};

(:~
 : normalize()
 : Normalize a vector represented as a vector (scale to unit vector), returning
 : the new vector.
 :
 : @param $v: the vector
 : @return normalized vector
 :)
declare function this:normalize($v as xs:double*) as xs:double*
{
  let $l := this:magnitude($v)
  return
    if ($l = 0) then $v
    else this:map(function ($c as xs:double) as xs:double {$c div $l}, $v)
};

(:~
 : clamp()
 : Coordinate-by-coordinate clamping.
 :
 : @param $v: vector
 : @param $min: minimum value
 : @param $max: maximum value
 : @return new vector
 :)
declare function this:clamp($v as xs:double*, $min as xs:double, $max as xs:double) as xs:double*
{
  this:map(function ($a as xs:double) as xs:double {util:clamp($a, $min, $max)}, $v)
};

(:~
 : clampv()
 : Coordinate-by-coordinate clamping: clamp with corresponding coordinates of
 : min/max vectors.
 :
 : @param $v: vector
 : @param $minv: minimum values
 : @param $maxv: maximum values
 : @return new vector
 :)
declare function this:clampv($v as xs:double*, $minv as xs:double*, $maxv as xs:double*) as xs:double*
{
  this:map3(function ($a as xs:double, $min as xs:double?, $max as xs:double?) as xs:double {util:clamp($a, $min, $max)}, $v, $minv, $maxv)
};

(:~
 : perpendicular()
 : Compute the perpendicular vector for a 2D vector represented as a vector.
 : (Rotated by 90 degrees.)
 : 
 : @param $v: the vector
 : @return perpendicular vector
 :)
declare function this:perpendicular($v as xs:double*) as xs:double*
{
  (this:py($v), -this:px($v))
};

(:~
 : snap()
 : Snap the coordinates of the vector, returning the vector with snapped
 : (i.e. integer) coordinates. NaN snaps to UINT64_MAX.
 :
 : @param $vector: the vector
 :)
declare function this:snap($vector as xs:double*) as xs:integer*
{
  for-each($vector,
    function ($c as xs:double) as xs:integer {
      if ($c = xs:double("INF") or not($c = $c)) then $util:UINT64_MAX 
      else if ($c = xs:double("-INF")) then -$util:UINT64_MAX
      else round-half-to-even($c) cast as xs:integer
    }
  )
};

(:~
 : floor()
 : Return vector where all the coordinates have been set to the floor of
 : the original vector's values. Differs from snap() for negative coordinates.
 :
 : @param $vector: the vector
 :)
declare function this:floor($vector as xs:double*) as xs:integer*
{
  for-each($vector, function ($c as xs:double) as xs:integer {fn:floor($c) cast as xs:integer})
};

(:~
 : decimal()
 : Perform decimal rounding on all the coordinates (see util:decimal).
 :
 : @param $vector: the vector to round
 : @param $digits: how many digits after the decimal vector to keep
 :)
declare function this:decimal($vector as xs:double*, $digits as xs:integer) as xs:double*
{
  for-each($vector, function ($c as xs:double) as xs:double {util:decimal($c, $digits)})
};

(:~
 : quote()
 : Return a string value for the vector, suitable for debugging.
 : 
 : @param $vector: the vector to quote
 :)
declare function this:quote($vector as xs:double*) as xs:string
{
  string-join($vector!string(.),",")
};

(:~
 : valid()
 : False if a coordinate is INF or NaN
 :)
declare function this:valid($point as xs:double*) as xs:boolean
{
  every $c in $point
  satisfies $c=$c and not($c=(xs:double("INF"),xs:double("-INF")))
};

(:~
 : same()
 : Equality comparison for vectors, ignoring annotation properties.
 : Return true() if they have equal coordinates.
 : 
 : @param $this: one vector
 : @param $other: the vector to compare it to
 : @return this=other
 :)
declare function this:same($this as xs:double*, $other as xs:double*) as xs:boolean
{
  let $d := max((count($this), count($other)))
  return (
    deep-equal(
      this:as-dimension($this, $d),
      this:as-dimension($other, $d)
    )
  )
};

(:~
 : distance()
 : Euclidean distance between vectors
 :
 : @param $v1: one vector
 : @param $v2: another vector
 : @return distance(v1,v2)
 :)
declare function this:distance($v1 as xs:double*, $v2 as xs:double*) as xs:double
{
  let $d := fn:max((this:dimension($v1), this:dimension($v2)))
  return (
    math:sqrt(
      fn:sum(
        util:zip(
          function ($c1 as xs:double, $c2 as xs:double) as xs:double {
            ($c1 - $c2)*($c1 - $c2)
          },
          this:as-dimension($v1, $d),
          this:as-dimension($v2, $d)
        )
      )
    )
  )
};

(:~
 : polar-distance()
 : Angular distance between points taken as vectors.
 :)
declare function this:polar-distance(
  $p as xs:double*,
  $q as xs:double*
) as xs:double
{
  abs(this:angle((0,0), $p) - this:angle((0,0), $q))
};

(:~
 : taxi-distance()
 : Taxi-cab distance between two points. That is, right angle distance, such as
 : a taxi going 2 blocks North plus 3 blocks East = distance 5 blocks.
 :)
declare function this:taxi-distance(
  $p as xs:double*,
  $q as xs:double*
) as xs:double
{
  let $d := max((count($p), count($q)))
  return (
    sum(
      util:zip(
        function ($cp as xs:double, $cq as xs:double) as xs:double {
          abs($cp - $cq)
        },
        this:as-dimension($p, $d),
        this:as-dimension($q, $d)
      )
    )
  )
};

(:~
 : avg-distance()
 : Average of distances along each dimension.
 :)
declare function this:avg-distance(
  $p as xs:double*,
  $q as xs:double*
) as xs:double
{
  let $d := max((count($p), count($q)))
  return (
    avg(
      util:zip(
        function ($cp as xs:double, $cq as xs:double) as xs:double {
          abs($cp - $cq)
        },
        this:as-dimension($p, $d),
        this:as-dimension($q, $d)
      )
    )
  )
};

(:~
 : rail-distance()
 : Railway distance, e.g distance to the central hub and back out.
 : The hub is the origin.
 :)
declare function this:rail-distance(
  $p as xs:double*,
  $q as xs:double*
) as xs:double
{
  this:distance($p, (0,0)) + this:distance($q, (0,0))
};

(:~
 : rail-distance()
 : Railway distance, e.g distance to the central hub and back out where the hub
 : is specified.
 :)
declare function this:rail-distance(
  $p as xs:double*,
  $q as xs:double*,
  $origin as xs:double*
) as xs:double
{
  this:distance($p, $origin) + this:distance($q, $origin)
};

(:~
 : chebyshev-distance()
 : Chebyshev distance metric. The maximum of the dimensional distances.
 :)
declare function this:chebyshev-distance(
  $p as xs:double*,
  $q as xs:double*
) as xs:double
{
  let $d := max((count($p), count($q)))
  return (
    max(
      util:zip(
        function ($cp as xs:double, $cq as xs:double) as xs:double {
          abs($cp - $cq)
        },
        this:as-dimension($p, $d),
        this:as-dimension($q, $d)
      )
    )
  )
};

(:~
 : minwokski-distance()
 : Minkowski distance metric of the given order. Certain orders give rise to
 : other metrics:
 : order=1=>taxi; order=2=>Euclidean; as order=>∞=>Chebyshev;
 : order<1 is not a metric unless undo the final pow. i.e. X is a metric but
 :   X^(1/order) is not.
 :)
declare function this:minkowski-distance(
  $p as xs:double*,
  $q as xs:double*,
  $order as xs:double
) as xs:double
{
  let $d := max((count($p), count($q)))
  return (
    math:pow(
      sum(
        util:zip(
          function ($cp as xs:double, $cq as xs:double) as xs:double {
            math:pow(abs($cp - $cq), $order)
          },
          this:as-dimension($p, $d),
          this:as-dimension($q, $d)
        )
      ),
      1 div $order
    )
  )
};

(:~
 : cosine-similarity()
 : Cosine similarity: (A·B)/(∥A∥∥B∥) = ΣAiBi/(√Ai²√Bi²)
 :)
declare function this:cosine-similarity(
  $p as xs:double*,
  $q as xs:double*
) as xs:double
{
  this:dot($p, $q) div (this:magnitude($p)*this:magnitude($q))
};

(:~
 : cosine-distance()
 : Cosine similarity as a metric: acos(similarity)/π => [0,1]
 :)
declare function this:cosine-distance(
  $p as xs:double*,
  $q as xs:double*
) as xs:double
{
  math:acos(this:cosine-similarity($p, $q)) div math:pi()
};

(:~
 : angle()
 : Compute the angle (azimuth) from one point to the next, in degrees
 : Return 0 if points are the same
 :
 : @param $last: previous point; use (0,0) if no previous
 : @param $curr: current point
 :)
declare function this:angle($last as xs:double*, $curr as xs:double*) as xs:double
{
  let $this := $curr=>this:as-dimension(2)
  let $prev := if (empty($last)) then (0,0) else this:as-dimension($last,2)
  return
    if (this:same($prev, $this) or (this:distance($prev,$this) < $config:ε)) then 0
    else if (this:px($this)=this:px($prev)) then (
      if (this:py($prev) > this:py($this))
      then 270 (: remapped -90 :)
      else 90
    ) else (
      util:remap-degrees(util:degrees(
        (math:pi() div 2) - math:atan2(this:px($this) - this:px($prev), this:py($this) - this:py($prev))
      ))
    )
};

(:~
 : inclination()
 : Compute the inclination angle from one point in the next, in degrees
 : Return 90 if the points are the same
 :
 : @param $last: previous point; use (0,0,0) if no previous
 : @param $curr: current point
 :)
declare function this:inclination($last as xs:double*, $curr as xs:double*) as xs:double
{
  let $curr := $curr=>this:as-dimension(3)
  let $prev := if (empty($last)) then (0,0,0) else $last=>this:as-dimension(3)
  let $d := this:distance($prev,$curr)
  return
    if (this:same($prev,$curr) or ($d < $config:ε)) then 90
    else (
      util:remap-degrees(util:degrees(
        math:acos( (this:pz($curr) - this:pz($prev)) div $d )
      ))
    )
};

(:~
 : destination()
 : Point at a particular distance and direction.
 : 
 : @param $point: starting point
 : @param $degrees: bearing from point
 : @param $length: how far along bearing to travel
 :) 
declare function this:destination(
  $point as xs:double*,
  $degrees as xs:double,
  $length as xs:double
) as xs:double*
{
  let $angle := util:radians($degrees)
  return
    (
      this:px($point) + math:cos($angle)*$length,
      this:py($point) + math:sin($angle)*$length
    )
};

(:~ 
 : mutate() 
 : Mutate a sequence of 2D vectors using a mutation function.
 : 
 : @param $vectors: the vectors
 : @param $mutate: function that takes a vector as an argument and returns a new vector
 :)
declare function this:mutate(
  $vectors as xs:double*,
  $mutate as function(xs:double*) as xs:double*
) as xs:double*
{
  for $i in 1 to count($vectors) idiv 2 return (
    $mutate(($vectors[2*$i - 1], $vectors[2*$i]))
  )
};